好代碼 ,壞代碼 Good Code, Bad Code: Think Like a Software Engineer

[英]湯姆·朗(Tom Long)

  • 好代碼 ,壞代碼-preview-1
  • 好代碼 ,壞代碼-preview-2
好代碼 ,壞代碼-preview-1

商品描述

本書分享的實用技巧可以幫助你編寫魯棒、可靠且易於團隊成員理解和適應不斷變化需求的代碼。內容涉及如何像高效的軟件工程師一樣思考代碼,如何編寫讀起來像一個結構良好的句子的函數,如何確保代碼可靠且無錯誤,如何進行有效的單元測試,如何識別可能導致問題的代碼並對其進行改進,如何編寫可重用並適應新需求的代碼,如何提高讀者的中長期生產力,同時還介紹瞭如何節省開發人員及團隊的寶貴時間,等等。

作者簡介

Tom Long,拥有剑桥大学信息工程专业硕士学位,目前担任Google公司高级开发工程师,领导一支针对移动设备广告的自动化及优化的技术团队。目前重点关注软件工程、Java开发、团队管理、数据分析、移动广告、技术创新等方向。

目錄大綱

第 一部分 理論

第 1章 代碼質量 3

1.1 代碼如何變成軟件 4

1.2 代碼質量目標 6

1.2.1 代碼應該正常工作 7

1.2.2 代碼應該持續正常工作 7

1.2.3 代碼應該適應不斷變化的需求 8

1.2.4 代碼不應該重復別人做過的工作 9

1.3 代碼質量的支柱 10

1.3.1 編寫易於理解的代碼 10

1.3.2 避免意外 11

1.3.3 編寫難以誤用的代碼 13

1.3.4 編寫模塊化的代碼 14

1.3.5 編寫可重用、可推廣的代碼 15

1.3.6 編寫可測試的代碼並適當測試 16

1.4 編寫高質量代碼是否會拖慢進度 17

1.5 小結 19

 

第 2章 抽象層次 20

2.1 空值和本書中的偽代碼慣例 20

2.2 為什麽要創建抽象層次 22

2.3 代碼層次 24

2.3.1 API和實現細節 25

2.3.2 函數 26

2.3.3 類 28

2.3.4 接口 36

2.3.5 當層次太薄的時候 39

2.4 微服務簡介 40

2.5 小結 41

 

第3章 其他工程師與代碼契約 42

3.1 你的代碼和其他工程師的代碼 42

3.1.1 對你來說顯而易見,但對其他人並不清晰的事情 44

3.1.2 其他工程師無意間試圖破壞你的代碼 44

3.1.3 過段時間,你會忘記自己的代碼的相關情況 44

3.2 其他人如何領會你的代碼的使用方法 45

3.2.1 查看代碼元素的名稱 45

3.2.2 查看代碼元素的數據類型 45

3.2.3 閱讀文檔 46

3.2.4 親自詢問 46

3.2.5 查看你的代碼 46

3.3 代碼契約 47

3.3.1 契約的附屬細則 47

3.3.2 不要過分依賴附屬細則 49

3.4 檢查和斷言 53

3.4.1 檢查 54

3.4.2 斷言 55

3.5 小結 56

第4章 錯誤 57

4.1 可恢復性 57

4.1.1 可以從中恢復的錯誤 57

4.1.2 無法從中恢復的錯誤 58

4.1.3 只有調用者知道能否從某種錯誤中恢復 58

4.1.4 讓調用者意識到他們可能想從中恢復的錯誤 60

4.2 魯棒性與故障 60

4.2.1 快速失敗 61

4.2.2 大聲失敗 62

4.2.3 可恢復性的範圍 62

4.2.4 不要隱藏錯誤 64

4.3 錯誤報告方式 67

4.3.1 回顧:異常 68

4.3.2 顯式:受檢異常 68

4.3.3 隱式:非受檢異常 70

4.3.4 顯式:允許為空的返回類型 71

4.3.5 顯式:結果返回類型 72

4.3.6 顯式:操作結果返回類型 74

4.3.7 隱式:承諾/未來 76

4.3.8 隱式:返回“魔法值” 78

4.4 報告不可恢復的錯誤 79

4.5 報告調用者可能想要從中恢復的錯誤 79

4.5.1 使用非受檢異常的論據 79

4.5.2 使用顯式報錯技術的論據 82

4.5.3 我的觀點:使用顯式報錯技術 84

4.6 不要忽視編譯器警告 85

4.7 小結 86

 

第二部分 實踐

第5章 編寫易於理解的代碼 91

5.1 使用描述性名稱 91

5.1.1 非描述性名稱使代碼難以理解 91

5.1.2 用註釋代替描述性名稱是很不好的做法 92

5.1.3 解決方案:使名稱具有描述性 93

5.2 適當使用註釋 94

5.2.1 多餘的註釋可能有害 94

5.2.2 註釋不是可讀代碼的合格替代品 95

5.2.3 註釋可能很適合於解釋代碼存在的理由 96

5.2.4 註釋可以提供有用的高層概述 96

5.3 不要執著於代碼行數 97

5.3.1 避免簡短但難以理解的代碼 98

5.3.2 解決方案:編寫易於理解的代碼,即便需要更多行代碼 99

5.4 堅持一致的編程風格 99

5.4.1 不一致的編程風格可能引發混亂 100

5.4.2 解決方案:採納和遵循風格指南 100

5.5 避免深嵌套代碼 101

5.5.1 嵌套很深的代碼可能難以理解 102

5.5.2 解決方案:改變結構,最大限度地減少嵌套 103

5.5.3 嵌套往往是功能過多的結果 103

5.5.4 解決方案:將代碼分解為更小的函數 104

5.6 使函數調用易於理解 105

5.6.1 參數可能難以理解 105

5.6.2 解決方案:使用命名參數 105

5.6.3 解決方案:使用描述性類型 106

5.6.4 有時沒有很好的解決方案 107

5.6.5 IDE又怎麽樣呢 108

5.7 避免使用未做解釋的值 108

5.7.1 未做解釋的值可能令人困惑 109

5.7.2 解決方案:使用恰當命名的常量 110

5.7.3 解決方案:使用恰當命名的函數 110

5.8 正確使用匿名函數 111

5.8.1 匿名函數適合於小的事物 112

5.8.2 匿名函數可能導致代碼難以理解 113

5.8.3 解決方案:用命名函數代替 113

5.8.4 大的匿名函數可能造成問題 114

5.8.5 解決方案:將大的匿名函數分解為命名函數 115

5.9 正確使用新奇的編程語言特性 116

5.9.1 新特性可能改善代碼 117

5.9.2 不為人知的特性可能引起混亂 117

5.9.3 使用適合於工作的工具 118

5.10 小結 118

 

第6章 避免意外 119

6.1 避免返回魔法值 119

6.1.1 魔法值可能造成缺陷 120

6.1.2 解決方案:返回空值、可選值或者錯誤 121

6.1.3 魔法值可能偶然出現 122

6.2 正確使用空對象模式 124

6.2.1 返回空集可能改進代碼 125

6.2.2 返回空字符串有時可能造成問題 126

6.2.3 較復雜的空對象可能造成意外 128

6.2.4 空對象實現可能造成意外 129

6.3 避免造成意料之外的副作用 130

6.3.1 明顯、有意的副作用沒有問題 131

6.3.2 意料之外的副作用可能造成問題 131

6.3.3 解決方案:避免副作用或者使其顯而易見 134

6.4 謹防輸入參數突變 135

6.4.1 輸入參數突變可能導致程序缺陷 136

6.4.2 解決方案:在突變之前復制 137

6.5 避免編寫誤導性的函數 137

6.5.1 在關鍵輸入缺失時什麽都不做可能造成意外 138

6.5.2 解決方案:將關鍵輸入變成必要的輸入 140

6.6 永不過時的枚舉處理 141

6.6.1 隱式處理未來的枚舉值可能造成問題 141

6.6.2 解決方案:使用全面的switch語句 143

6.6.3 註意默認情況 144

6.6.4 註意事項:依賴另一個項目的枚舉類型 146

6.7 我們不能只用測試解決所有此類問題嗎 146

6.8 小結 147

 

第7章 編寫難以被誤用的代碼 148

7.1 考慮不可變對象 149

7.1.1 可變類可能很容易被誤用 149

7.1.2 解決方案:只在構建時設值 151

7.1.3 解決方案:使用不可變性設計模式 152

7.2 考慮實現深度不可變性 157

7.2.1 深度可變性可能導致誤用 157

7.2.2 解決方案:防禦性復制 159

7.2.3 解決方案:使用不可變數據結構 160

7.3 避免過於通用的類型 161

7.3.1 過於通用的類型可能被誤用 162

7.3.2 配對類型很容易被誤用 164

7.3.3 解決方案:使用專用類型 166

7.4 處理時間 167

7.4.1 用整數表示時間可能帶來問題 168

7.4.2 解決方案:使用合適的數據結構表示時間 170

7.5 擁有單一可信數據源 172

7.5.1 第二個可信數據源可能導致無效狀態 172

7.5.2 解決方案:使用原始數據作為單一可信數據源 173

7.6 擁有單一可信邏輯來源 175

7.6.1 多個可信邏輯來源可能導致程序缺陷 175

7.6.2 解決方案:使用單一可信來源 177

7.7 小結 179

 

第8章 實現代碼模塊化 180

8.1 考慮使用依賴註入 180

8.1.1 硬編程的依賴項可能造成問題 181

8.1.2 解決方案:使用依賴註入 182

8.1.3 在設計代碼時考慮依賴註入 184

8.2 傾向於依賴接口 185

8.2.1 依賴於具體實現將限制適應性 186

8.2.2 解決方案:盡可能依賴於接口 186

8.3 註意類的繼承 187

8.3.1 類繼承可能造成問題 188

8.3.2 解決方案:使用組合 192

8.3.3 真正的“is-a”關系該怎麽辦 194

8.4 類應該只關心自身 196

8.4.1 過於關心其他類可能造成問題 196

8.4.2 解決方案:使類僅關心自身 197

8.5 將相關聯的數據封裝在一起 198

8.5.1 未封裝的數據可能難以處理 199

8.5.2 解決方案:將相關數據組合為對象或類 200

8.6 防止在返回類型中泄露實現細節 201

8.6.1 在返回類型中泄露實現細節可能造成問題 202

8.6.2 解決方案:返回對應於抽象層次的類型 203

8.7 防止在異常中泄露實現細節 204

8.7.1 在異常中泄露實現細節可能造成問題 204

8.7.2 解決方案:使異常適合抽象層次 206

8.8 小結 208

 

第9章 編寫可重用、可推廣的代碼 209

9.1 註意各種假設 209

9.1.1 代碼重用時假設將導致缺陷 210

9.1.2 解決方案:避免不必要的假設 210

9.1.3 解決方案:如果假設是必要的,則強制實施 211

9.2 註意全局狀態 213

9.2.1 全局狀態可能使重用變得不安全 215

9.2.2 解決方案:依賴註入共享狀態 217

9.3 恰當地使用默認返回值 219

9.3.1 低層次代碼中的默認返回值可能損害可重用性 220

9.3.2 解決方案:在較高層次代碼中使用默認值 221

9.4 保持函數參數的集中度 223

9.4.1 如果函數參數超出需要,可能難以重用 224

9.4.2 解決方案:讓函數只取得需要的參數 225

9.5 考慮使用泛型 226

9.5.1 依賴於特定類型將限制可推廣性 226

9.5.2 解決方案:使用泛型 227

9.6 小結 228

 

第三部分 單元測試

第 10章 單元測試原則 231

10.1 單元測試入門 232

 

10.2 是什麽造就好的單元測試 233

10.2.1 準確檢測破壞 234

10.2.2 與實現細節無關 235

10.2.3 充分解釋失敗 236

10.2.4 易於理解的測試代碼 237

10.2.5 便捷運行 237

10.3 專註於公共API,但不要忽略重要的行為 238

10.4 測試替身 242

10.4.1 使用測試替身的理由 242

10.4.2 模擬對象 246

10.4.3 樁 248

10.4.4 模擬對象和樁可能有問題 250

10.4.5 偽造對象 253

10.4.6 關於模擬對象的不同學派 256

10.5 挑選測試思想 257

10.6 小結 258

 

第 11章 單元測試實踐 259

11.1 測試行為,而不僅僅是函數 259

11.1.1 每個函數一個測試用例往往是不夠的 260

11.1.2 解決方案:專註於測試每個行為 261

11.2 避免僅為了測試而使所有細節可見 263

11.2.1 測試私有函數往往是個壞主意 264

11.2.2 解決方案:首選通過公共API測試 265

11.2.3 解決方案:將代碼分解為較小的單元 266

11.3 一次測試一個行為 270

11.3.1 一次測試多個行為可能導致降低測試質量 270

11.3.2 解決方案:以單獨的測試用例測試每個行為 272

11.3.3 參數化測試 273

11.4 恰當地使用共享測試配置 274

11.4.1 共享狀態可能帶來問題 275

11.4.2 解決方案:避免共享狀態或者重置狀態 277

11.4.3 共享配置可能帶來問題 278

11.4.4 解決方案:在測試用例中定義重要配置 281

11.4.5 何時適用共享配置 283

11.5 使用合適的斷言匹配器 284

11.5.1 不合適的匹配器可能導致無法充分解釋失敗 284

11.5.2 解決方案:使用合適的匹配器 286

11.6 使用依賴註入來提高可測試性 287

11.6.1 硬編程的依賴項可能導致代碼無法測試 287

11.6.2 解決方案:使用依賴註入 288

11.7 關於測試的一些結論 289

11.8 小結 290

 

附錄A 巧克力糕餅食譜 291

附錄B 空值安全與可選類型 292

附錄C 額外的代碼示例 295