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