本書詳細闡述如何在設計、規(guī)劃和實現(xiàn)軟件時做出更好的決策,通過真實的案例,以抽絲剝繭的方式分析那些失誤的決策,探討還有哪些可能的解決方案,并對比各種方案的優(yōu)缺點,摸索軟件設計的常青模式。本書通過實例來說明某些決策的后果,例如代碼重復如何影響系統(tǒng)的耦合與演進速度,以及如何在日期和時間信息方面隱藏細微差別。本書還介紹如何根據(jù)帕累托法則有效地縮小優(yōu)化范圍,確保分布式系統(tǒng)的一致性。
通過閱讀本書,讀者很快就可以將作者來之不易的經(jīng)驗應用到自己的項目中,以預防錯誤并采取更合適的編程決策。
深刻剖析軟件設計決策中的權(quán)衡與取舍,涵蓋單體系統(tǒng)、微服務、大數(shù)據(jù)處理等多領(lǐng)域。
通過真實案例與代碼片段,展示軟件設計模式的實際應用與錯誤決策的教訓。
深入分析軟件設計中的潛在問題與局限,提前預防未來可能出現(xiàn)的陷阱。
通過預識別設計問題,減少后期修改與重構(gòu)的成本。
闡釋如何平衡靈活性與復雜性、性能與優(yōu)化等關(guān)鍵設計要素。
提供一套系統(tǒng)化的方法,幫助軟件工程師在有限資源下做出更明智的決策。
通過增進對軟件設計取舍的理解,提升代碼質(zhì)量與項目成功率。
托馬斯·萊萊克(Tomasz Lelek) 托馬斯在他的軟件開發(fā)職業(yè)生涯里,設計并開發(fā)過各種各樣的生產(chǎn)服務、軟件架構(gòu),他精通多種編程語言(大多數(shù)是基于 JVM 的)。他既實現(xiàn)過單體系統(tǒng),也曾做過與微服務架構(gòu)相關(guān)的工作。他設計的有些系統(tǒng)可服務數(shù)千萬用戶,每秒處理數(shù)十萬的操作量。他的工作方向如下: ? 設計采用 CQRS 架構(gòu)的微服務(基于 Apache Kafka); ? 市場自動化及事件流處理; ? 基于 Apache Spark 和 Scala 的大數(shù)據(jù)處理。 托馬斯現(xiàn)在就職于 Dremio,負責創(chuàng)建現(xiàn)代大數(shù)據(jù)處理的數(shù)據(jù)湖解決方案。在此之前,他在DataStax 負責與 Cassandra 數(shù)據(jù)庫相關(guān)的一些產(chǎn)品。他設計的工具幫助成千上萬的開發(fā)者設計出性能優(yōu)異、用戶友好的 API,發(fā)揮了重要的作用。他為 Java-Driver、Cassandra Quarkus、Cassandra-Kafka Connector 以及 Stargate 都貢獻過代碼。 喬恩·斯基特(Jon Skeet) 喬恩是谷歌公司的資深開發(fā)工程師,目前的工作方向是谷歌云的.NET 客戶端庫。他向開源社區(qū)貢獻了.NET 版本的 Noda 時間庫,然而他最讓人稱道的是他在 Stack Overflow 開發(fā)者社區(qū)的貢獻。喬恩是 Manning 出版社出版的 C# in Depth 一書的作者,此外,他還對 Groovy in Action 以及 Real-World Functional Programming 兩書有所貢獻。喬恩對日期時間 API 以及 API版本非常感興趣,這些通常是無人問津的冷門話題。
第 1 章 引言 1
1.1 決策的后果與模式 2
1.1.1 單元測試 2
1.1.2 單元測試與集成測試的比例 3
1.2 設計模式及其失效分析 5
1.3 架構(gòu)設計模式及其失效分析 10
1.3.1 可擴展性與彈性 11
1.3.2 開發(fā)速度 12
1.3.3 微服務的復雜性 12
小結(jié) 14
第 2 章 代碼重復不一定是壞事:代碼重復與靈活性的權(quán)衡 15
2.1 代碼庫間的通用代碼及重復代碼 16
2.1.1 添加新需求導致的代碼重復 17
2.1.2 實現(xiàn)新的業(yè)務需求 17
2.1.3 結(jié)果評估 19
2.2 通過庫在代碼庫之間共享代碼 19
2.2.1 共享庫的取舍與不足 20
2.2.2 創(chuàng)建共享庫 21
2.3 抽取代碼為一個獨立的微服務 22
2.3.1 采用獨立微服務方式的取舍與弊端 24
2.3.2 關(guān)于獨立微服務的總結(jié) 27
2.4 通過代碼重復改善松耦合 28
2.5 利用繼承減少 API 設計中的重復 31
2.5.1 抽取出一個請求處理器作為基類 33
2.5.2 繼承與緊耦合的取舍 35
2.5.3 繼承與組合的取舍 36
2.5.4 一貫性的重復與偶然性的重復 37
小結(jié) 38
第 3 章 異常及其他代碼錯誤的處理模式 39
3.1 異常的層次結(jié)構(gòu) 40
4
3.2 代碼異常處理的最佳模式 44
3.2.1 公共 API 的已檢測異常處理 45
3.2.2 公共 API 的未檢測異常處理 46
3.3 異常處理的反模式 47
3.3.1 異常時,關(guān)閉資源 49
3.3.2 反模式:利用異?刂茟昧 51
3.4 源自第三方庫的異常 51
3.5 多線程環(huán)境中的異常 54
3.6 使用 Try 以函數(shù)式的途徑處理異常 59
3.6.1 在生產(chǎn)代碼中使用 Try 62
3.6.2 混合使用 Try 與拋出異常的代碼 64
3.7 異常處理策略的性能對比 65
小結(jié) 68
第 4 章 靈活性與復雜性的權(quán)衡 70
4.1 一個健壯但無法擴展的API 71
4.1.1 設計一個新組件 71
4.1.2 從最簡單的代碼開始 72
4.2 允許客戶使用自己的指標框架 75
4.3 通過鉤子為你的 API提供可擴展性 77
4.3.1 防范鉤子 API 的過度使用 79
4.3.2 鉤子 API 的性能影響 81
4.4 通過偵聽器為你的 API提供可擴展性 83
4.4.1 使用偵聽器與鉤子的取舍 84
4.4.2 設計的不可修改性 85
4.5 API 的靈活性分析及維護開銷的權(quán)衡 87
小結(jié) 88
第 5章 過早優(yōu)化 vs 熱路徑優(yōu)化:影響代碼性能的決策 89
5.1 過早優(yōu)化是萬惡之源 90
5.1.1 構(gòu)建賬戶處理管道 90
5.1.2 依據(jù)錯誤的假設進行優(yōu)化處理 91
5.1.3 對性能優(yōu)化進行基準測試 92
5.2 代碼中的熱路徑 94
5.2.1 從軟件系統(tǒng)的角度理解帕累托法則 96
5.2.2 依據(jù) SLA 配置線程(并發(fā)用戶)數(shù) 97
5.3 具有潛在熱路徑的 word服務 97
5.3.1 獲取每日一詞 98
5.3.2 驗證單詞是否存在 100
5.3.3 使用 HTTP 服務,向外提供WordsService 100
5.4 檢測你代碼中的熱路徑 102
5.4.1 使用 Gatling 創(chuàng)建 API 的性能測試 102
5.4.2 使用 MetricRegistry 度量代碼路徑 105
5.5 改進熱路徑的性能 107
5.5.1 為現(xiàn)有代碼創(chuàng)建 JMH 微基準測試 107
5.5.2 利用緩存優(yōu)化 word-exists程序 109
5.5.3 調(diào)整性能測試,使用更多的輸入單詞 113
小結(jié) 115
第 6 章 API 的簡潔性 vs 維護成本 116
6.1 一個為其他工具服務的基礎庫 117
6.1.1 創(chuàng)建云服務客戶端 117
6.1.2 漫談認證策略 119
6.1.3 理解配置的機制 120
6.2 直接暴露依賴庫的配置 123
6.3 一個將依賴庫的配置抽象化的工具 127
6.4 為云服務客戶端庫添加新的配置 129
6.4.1 為批處理工具添加新配置 130
6.4.2 為流處理工具添加新配置 132
6.4.3 方案對比:用戶體驗的友好性 vs 維護成本 132
6.5 棄用/刪除云服務客戶端庫的某個配置 133
6.5.1 刪除批處理工具的某個配置 135
6.5.2 刪除流服務中某個配置 137
6.5.3 兩種方案用戶體驗與維護成本的比較 138
小結(jié) 139
第 7 章 高效使用日期和時間數(shù)據(jù) 140
7.1 日期和時間信息的概念 141
7.1.1 機器時間:時間戳、紀元以及持續(xù)時間 141
7.1.2 民用時間:日歷系統(tǒng)、日期時間以及期間 145
7.1.3 時區(qū)、UTC 以及 UTC偏移量 149
7.1.4 讓人頭疼的日期和時間概念 154
7.2 準備處理日期和時間信息 155
7.2.1 對范疇做限定 155
7.2.2 澄清日期和時間的需求 157
7.2.3 使用恰當?shù)膸旎蛘甙?161
7.3 實現(xiàn)日期和時間代碼 162
7.3.1 保持概念的一致性 162
7.3.2 通過避免使用默認值提升可測試性 164
7.3.3 以文本方式表示日期和時間 170
7.3.4 通過注釋解釋代碼 175
7.4 有必要單獨指出并測試的極端情況 178
7.4.1 日歷計算 178
7.4.2 發(fā)生在午夜時分的時區(qū)轉(zhuǎn)換 178
7.4.3 處理不明確或者跳過的時間 179
7.4.4 處理不斷變化的時區(qū)數(shù)據(jù) 179
小結(jié) 183
第 8 章 利用機器的數(shù)據(jù)本地性和內(nèi)存 184
8.1 數(shù)據(jù)本地性是什么 184
8.1.1 將計算移動到數(shù)據(jù)處 185
8.1.2 用數(shù)據(jù)本地性擴展數(shù)據(jù)處理 186
8.2 數(shù)據(jù)的分區(qū) 188
8.2.1 線下大數(shù)據(jù)分區(qū) 188
8.2.2 分區(qū)和分片的區(qū)別 190
8.2.3 分區(qū)算法 191
8.3 連接多個分區(qū)上的大數(shù)據(jù)集 193
8.3.1 在同一臺物理機上連接數(shù)據(jù) 194
8.3.2 需要數(shù)據(jù)移動的連接 195
8.3.3 利用廣播優(yōu)化連接 196
8.4 在內(nèi)存還是磁盤中進行數(shù)據(jù)處理的權(quán)衡 198
8.4.1 基于磁盤的處理 198
8.4.2 我們?yōu)槭裁葱枰成?化簡? 198
8.4.3 計算訪問時間 201
8.4.4 基于內(nèi)存的處理 202
8.5 用 Apache Spark 實現(xiàn)連接 203
8.5.1 不使用廣播的連接 204
8.5.2 使用廣播的連接 206
小結(jié) 208
第 9 章 第三方庫:你用的庫成為你的代碼 209
9.1 引用一個庫就要對它的配置選項負責:小心那些默認配置 210
9.2 并發(fā)模型和可擴展性 213
9.2.1 使用異步和同步 API 215
9.2.2 分布式的可擴展性 217
9.3 可測試性 218
9.3.1 測試庫 219
9.3.2 用偽造值和模擬函數(shù)來進行測試 221
9.3.3 集成測試工具包 224
9.4 第三方庫的依賴 225
9.4.1 避免版本沖突 226
9.4.2 太多的依賴 227
9.5 選擇和維護第三方依賴 228
9.5.1 第 一印象 228
9.5.2 復用代碼的不同方式 229
9.5.3 鎖定供應商 229
9.5.4 軟件許可證 230
9.5.5 庫和框架 230
9.5.6 安全和更新 230
9.5.7 選擇第三方庫的檢查列表 231
小結(jié) 232
第 10 章 分布式系統(tǒng)的一致性和原子性 233
10.1 數(shù)據(jù)源的至少一次傳輸語義 234
10.1.1 單節(jié)點服務之間的網(wǎng)絡訪問 234
10.1.2 應用程序重試請求 235
10.1.3 生成數(shù)據(jù)和冪等性 236
10.1.4 理解 CQRS 238
10.2 去重庫的簡單實現(xiàn) 240
10.3 在分布式系統(tǒng)里實現(xiàn)去重會遇到的常見錯誤242
10.3.1 單節(jié)點環(huán)境 242
10.3.2 多節(jié)點環(huán)境 244
10.4 用原子性的邏輯避免競爭條件 246
小結(jié) 250
第 11 章 分布式系統(tǒng)的傳輸語義 251
11.1 事件驅(qū)動應用程序的架構(gòu) 252
11.2 基于 Apache Kafka 的生產(chǎn)者和消費者應用程序 254
11.2.1 Kafka 消費者 255
11.2.2 理解 Kafka brokers設置 257
11.3 生產(chǎn)者的邏輯 258
11.4 在消費者端實現(xiàn)不同的傳輸語義 262
11.4.1 消費者手動提交 264
11.4.2 從最早或最晚的偏移量開始重啟 266
11.4.3 (最終)恰好一次傳輸語義 268
11.5 用傳輸保證提供容錯能力 270
小結(jié) 271
第 12 章 版本管理和兼容性 272
12.1 版本管理的抽象思考 273
12.1.1 版本的屬性 273
12.1.2 向后兼容性和向前兼容性 274
12.1.3 語義版本規(guī)范 275
12.1.4 營銷版本 277
12.2 庫的版本管理 277
12.2.1 源碼、二進制和語義兼容性 278
12.2.2 依賴圖和菱形依賴 285
12.2.3 處理破壞性改動的技術(shù)手段 288
12.2.4 管理內(nèi)部庫 292
12.3 網(wǎng)絡 API 的版本管理 293
12.3.1 網(wǎng)絡 API 調(diào)用的環(huán)境 293
12.3.2 用戶喜歡公開透明的版本策略 295
12.3.3 常見的版本策略 295
12.3.4 版本管理額外的考慮因素 300
12.4 數(shù)據(jù)存儲的版本管理 303
12.4.1 簡要介紹 Protocol Buffers 303
12.4.2 哪些是破壞性改動 305
12.4.3 在存儲系統(tǒng)內(nèi)部遷移數(shù)據(jù) 306
12.4.4 準備好面對未知 309
12.4.5 分離網(wǎng)絡 API 和存儲的數(shù)據(jù)格式 310
12.4.6 評估存儲格式 312
小結(jié) 313
第 13 章 緊跟最新技術(shù)趨勢和維護舊代碼之間的權(quán)衡 315
13.1 什么時候應該使用依賴注入框架 316
13.1.1 DIY 依賴注入 317
13.1.2 使用依賴注入框架 319
13.2 什么時候應該使用響應式編程 321
13.2.1 創(chuàng)建一個單線程阻塞式處理模型 322
13.2.2 使用CompletableFuture 324
13.2.3 實現(xiàn)一個響應式方案 326
13.3 什么時候應該使用函數(shù)式編程 328
13.3.1 用非函數(shù)式語言寫函數(shù)式代碼 328
13.3.2 尾部遞歸優(yōu)化 331
13.3.3 利用不可變性 332
13.4 對比延遲和急切初始化 333
小結(jié) 335