軟件開發中的決策:權衡與取捨 Software Mistakes and Tradeoffs: How to Make Good Programming Decisions

[美]托馬斯·萊萊克(Tomasz Lelek) [英]喬恩·斯基特 (Jon Skeet)

  • 軟件開發中的決策:權衡與取捨-preview-1
  • 軟件開發中的決策:權衡與取捨-preview-2
軟件開發中的決策:權衡與取捨-preview-1

相關主題

商品描述

本書詳細闡述如何在設計、規劃和實現軟件時做出更好的決策,通過真實的案例,以抽絲剝繭的方式分析那些失誤的決策,探討還有哪些可能的解決方案,並對比各種方案的優缺點,摸索軟件設計的常青模式。本書通過實例來說明某些決策的後果,例如代碼重復如何影響系統的耦合與演進速度,以及如何在日期和時間信息方面隱藏細微差別。本書還介紹如何根據帕累托法則有效地縮小優化範圍,確保分佈式系統的一致性。

通過閱讀本書,讀者很快就可以將作者來之不易的經驗應用到自己的項目中,以預防錯誤並採取更合適的編程決策。

作者簡介

托马斯·莱莱克(Tomasz Lelek)

托马斯在他的软件开发职业生涯里,设计并开发过各种各样的生产服务、软件架构,他精通多种编程语言(大多数是基于 JVM 的)。他既实现过单体系统,也曾做过与微服务架构相关的工作。他设计的有些系统可服务数千万用户,每秒处理数十万的操作量。他的工作方向如下:

? 设计采用 CQRS 架构的微服务(基于 Apache Kafka);

? 市场自动化及事件流处理;

? 基于 Apache Spark 和 Scala 的大数据处理。

 

托马斯现在就职于 Dremio,负责创建现代大数据处理的数据湖解决方案。在此之前,他在DataStax 负责与 Cassandra 数据库相关的一些产品。他设计的工具帮助成千上万的开发者设计出性能优异、用户友好的 API,发挥了重要的作用。他为 Java-Driver、Cassandra Quarkus、Cassandra-Kafka Connector 以及 Stargate 都贡献过代码。

乔恩·斯基特(Jon Skeet)

乔恩是谷歌公司的资深开发工程师,目前的工作方向是谷歌云的.NET 客户端库。他向开源社区贡献了.NET 版本的 Noda 时间库,然而他最让人称道的是他在 Stack Overflow 开发者社区的贡献。乔恩是 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 架構設計模式及其失效分析 10

1.3.1 可擴展性與彈性 11

1.3.2 開發速度 12

1.3.3 微服務的復雜性 12

小結 14

第 2 章 代碼重復不一定是壞事:代碼重復與靈活性的權衡 15

2.1 代碼庫間的通用代碼及重復代碼 16

2.1.1 添加新需求導致的代碼重復 17

2.1.2 實現新的業務需求 17

2.1.3 結果評估 19

2.2 通過庫在代碼庫之間共享代碼 19

2.2.1 共享庫的取捨與不足 20

2.2.2 創建共享庫 21

2.3 抽取代碼為一個獨立的微服務 22

2.3.1 採用獨立微服務方式的取捨與弊端 24

2.3.2 關於獨立微服務的總結 27

2.4 通過代碼重復改善松耦合 28

2.5 利用繼承減少 API 設計中的重復 31

2.5.1 抽取出一個請求處理器作為基類 33

2.5.2 繼承與緊耦合的取捨 35

2.5.3 繼承與組合的取捨 36

2.5.4 一貫性的重復與偶然性的重復 37

小結 38

第 3 章 異常及其他——代碼錯誤的處理模式 39

3.1 異常的層次結構 40

4

3.2 代碼異常處理的最佳模式 44

3.2.1 公共 API 的已檢測異常處理 45

3.2.2 公共 API 的未檢測異常處理 46

3.3 異常處理的反模式 47

3.3.1 異常時,關閉資源 49

3.3.2 反模式:利用異常控制應用流 51

3.4 源自第三方庫的異常 51

3.5 多線程環境中的異常 54

3.6 使用 Try 以函數式的途徑處理異常 59

3.6.1 在生產代碼中使用 Try 62

3.6.2 混合使用 Try 與拋出異常的代碼 64

3.7 異常處理策略的性能對比 65

小結 68

第 4 章 靈活性與復雜性的權衡 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 的靈活性分析及維護開銷的權衡 87

小結 88

第 5章 過早優化 vs 熱路徑優化:影響代碼性能的決策 89

5.1 過早優化是萬惡之源 90

5.1.1 構建賬戶處理管道 90

5.1.2 依據錯誤的假設進行優化處理 91

5.1.3 對性能優化進行基準測試 92

5.2 代碼中的熱路徑 94

5.2.1 從軟件系統的角度理解帕累托法則 96

5.2.2 依據 SLA 配置線程(並發用戶)數 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 創建 API 的性能測試 102

5.4.2 使用 MetricRegistry 度量代碼路徑 105

5.5 改進熱路徑的性能 107

5.5.1 為現有代碼創建 JMH 微基準測試 107

5.5.2 利用緩存優化 word-exists程序 109

5.5.3 調整性能測試,使用更多的輸入單詞 113

小結 115

第 6 章 API 的簡潔性 vs 維護成本 116

6.1 一個為其他工具服務的基礎庫 117

6.1.1 創建雲服務客戶端 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

小結 139

第 7 章 高效使用日期和時間數據 140

7.1 日期和時間信息的概念 141

7.1.1 機器時間:時間戳、紀元以及持續時間 141

7.1.2 民用時間:日歷系統、日期時間以及期間 145

7.1.3 時區、UTC 以及 UTC偏移量 149

7.1.4 讓人頭疼的日期和時間概念 154

7.2 準備處理日期和時間信息 155

7.2.1 對範疇做限定 155

7.2.2 澄清日期和時間的需求 157

7.2.3 使用恰當的庫或者包 161

7.3 實現日期和時間代碼 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 發生在午夜時分的時區轉換 178

7.4.3 處理不明確或者跳過的時間 179

7.4.4 處理不斷變化的時區數據 179

小結 183

第 8 章 利用機器的數據本地性和內存 184

8.1 數據本地性是什麽 184

8.1.1 將計算移動到數據處 185

8.1.2 用數據本地性擴展數據處理 186

8.2 數據的分區 188

8.2.1 線下大數據分區 188

8.2.2 分區和分片的區別 190

8.2.3 分區算法 191

8.3 連接多個分區上的大數據集 193

8.3.1 在同一臺物理機上連接數據 194

8.3.2 需要數據移動的連接 195

8.3.3 利用廣播優化連接 196

8.4 在內存還是磁盤中進行數據處理的權衡 198

8.4.1 基於磁盤的處理 198

8.4.2 我們為什麽需要映射-化簡? 198

8.4.3 計算訪問時間 201

8.4.4 基於內存的處理 202

8.5 用 Apache Spark 實現連接 203

8.5.1 不使用廣播的連接 204

8.5.2 使用廣播的連接 206

小結 208

第 9 章 第三方庫:你用的庫成為你的代碼 209

9.1 引用一個庫就要對它的配置選項負責:小心那些默認配置 210

9.2 並發模型和可擴展性 213

9.2.1 使用異步和同步 API 215

9.2.2 分佈式的可擴展性 217

9.3 可測試性 218

9.3.1 測試庫 219

9.3.2 用偽造值和模擬函數來進行測試 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

小結 232

第 10 章 分佈式系統的一致性和原子性 233

10.1 數據源的至少一次傳輸語義 234

10.1.1 單節點服務之間的網絡訪問 234

10.1.2 應用程序重試請求 235

10.1.3 生成數據和冪等性 236

10.1.4 理解 CQRS 238

10.2 去重庫的簡單實現 240

10.3 在分佈式系統里實現去重會遇到的常見錯誤242

10.3.1 單節點環境 242

10.3.2 多節點環境 244

10.4 用原子性的邏輯避免競爭條件 246

小結 250

第 11 章 分佈式系統的傳輸語義 251

11.1 事件驅動應用程序的架構 252

11.2 基於 Apache Kafka 的生產者和消費者應用程序 254

11.2.1 Kafka 消費者 255

11.2.2 理解 Kafka brokers設置 257

11.3 生產者的邏輯 258

11.4 在消費者端實現不同的傳輸語義 262

11.4.1 消費者手動提交 264

11.4.2 從最早或最晚的偏移量開始重啟 266

11.4.3 (最終)恰好一次傳輸語義 268

11.5 用傳輸保證提供容錯能力 270

小結 271

第 12 章 版本管理和兼容性 272

12.1 版本管理的抽象思考 273

12.1.1 版本的屬性 273

12.1.2 向後兼容性和向前兼容性 274

12.1.3 語義版本規範 275

12.1.4 營銷版本 277

12.2 庫的版本管理 277

12.2.1 源碼、二進制和語義兼容性 278

12.2.2 依賴圖和菱形依賴 285

12.2.3 處理破壞性改動的技術手段 288

12.2.4 管理內部庫 292

12.3 網絡 API 的版本管理 293

12.3.1 網絡 API 調用的環境 293

12.3.2 用戶喜歡公開透明的版本策略 295

12.3.3 常見的版本策略 295

12.3.4 版本管理額外的考慮因素 300

12.4 數據存儲的版本管理 303

12.4.1 簡要介紹 Protocol Buffers 303

12.4.2 哪些是破壞性改動 305

12.4.3 在存儲系統內部遷移數據 306

12.4.4 準備好面對未知 309

12.4.5 分離網絡 API 和存儲的數據格式 310

12.4.6 評估存儲格式 312

小結 313

第 13 章 緊跟最新技術趨勢和維護舊代碼之間的權衡 315

13.1 什麽時候應該使用依賴註入框架 316

13.1.1 DIY 依賴註入 317

13.1.2 使用依賴註入框架 319

13.2 什麽時候應該使用響應式編程 321

13.2.1 創建一個單線程阻塞式處理模型 322

13.2.2 使用CompletableFuture 324

13.2.3 實現一個響應式方案 326

13.3 什麽時候應該使用函數式編程 328

13.3.1 用非函數式語言寫函數式代碼 328

13.3.2 尾部遞歸優化 331

13.3.3 利用不可變性 332

13.4 對比延遲和急切初始化 333

小結 335