本文來自 https://ergou.vip/2020/10/clean-code/原創。未經允許不得轉載
## 1 有意義的命名
1. 名副其實
1. 變量函數類的名稱已經答覆了所有的大問題。如果名稱需要注釋來補充,那就不算名副其實
2. 一旦發現更好的名稱,就換掉舊的
2. 避免誤導
1. 不應該使用已存在的專有名稱用作變量名
2. 別用 accountList 來指一組帳號,除非它真的是 List 類型
3. 避免使用不同之處較小的名稱
4. 小寫字母 1 大寫字母 O 誤導性變量名稱
3. 做有意義的區分
1. 如 Product 與 ProductInfo
2. 廢話都是冗餘,如 NameString 中的 String
3. 要區分名稱,就要以讀者能鑑別不同之處的方式來區分
4. 使用讀的出來的名稱
1. 使用恰當的英語詞,不要使用自造縮寫詞。如 genymdhms 與 genTimestamp
5. 使用可搜索的名稱
1. 單字母名稱僅用於短方法中的本地變量,名稱長短應與其作用域大小相對應
6. 避免使用編碼 (我覺得有點極端,遵守開發規範是必要的。看看就好)
1. 匈牙利標記法。增加了於都代碼的難度,增加了誤導的可能性
2. 把類和函數做的足夠小,不必用前綴來標明成員變量
3. 接口與實現。抽象工廠和具體類的命名方式,IShapeFactory 和 ShapeFactory,作者認為 I 被濫用廢話。他覺得如果接口和實現必須選一個編碼他選實現,ShapeFactoryImp 甚至 CShapeFactory 都比接口名稱編碼來的好
7. 避免思維映射
1. 不應該讓讀者把你名稱翻譯成他們熟知的名稱。問題經常出現在選擇是使用問題領域術語還是解決方案領域術語時
2. 明確是王道
8. 類名與方法名
1. 類型應該是名詞或者名詞短語,而不應當是動詞
2. 方法名應當是動詞或者動詞短語
9. 別扮可愛
1. 避免使用俗語與俚語
2. 言到意到,意到言到
10. 每個概念對應一個詞
1. 同一堆代碼中有 controller,又有 manager,還有 driver 就會令人困惑
11. 別用雙關語
1. 如 add本來表示加法,insert append 也使用 add,add 就是雙關語了
12. 使用解決方案領域名稱
1. 取個技術性名稱
13. 使用源自所涉問題領域的名稱
1. 如果不能取個技術性名稱,那就採用從所涉問題領域的名稱。至少維護代碼的程式設計師可以請教領域專家
14. 添加有意義的語境
1. 有良好命名的類、函數或者名稱空間來放置名稱,提供語境。
2. 如果沒有,給名稱添加前綴就是最後一招了
15. 不要添加沒用的語境
1. 不要給每個類添加相同前綴。
2. 只要短名稱足夠清楚,就要比長名稱好
## 2 函數
1. 短小
2. 只做一件事
3. 每個函數一個抽象層次
1. 函數中混雜不同抽象層級,往往讓人迷惑。讀者無法判斷某個表達式是基礎概念還是細節
2. 自頂向下讀代碼:向下規則。每個函數後面都跟著位於下一抽象層級的函數
4. switch 語句
1. 無法避免,確保每個 switch 都埋藏在較低的抽象層級,用不重複。可以利用多態實現這一點
2. 就事論事,難免有違反
5. 使用描述性的名稱
1. 別害怕長名字
2. 別害怕花時間取名字
3. 命名方式要保持一致
6. 函數參數
1. 參數越少越理想
2. 測試複雜性增加
3. 參數包含輸入參數輸出參數更難以理解
4. 如果需要三個或者三個以上的參數,就說明一些參數應該封裝為類了
7. 無副作用
1. 比如違反製作一件事,藏起來做了其他的事
2. 避免使用輸出參數,可以使用類調用,this 也有輸出函數的意味
8. 分割指令與詢問
1. 要做什麼事和要回答什麼事要分開。還是只做一件事
9. 使用異常代替返回錯誤碼
1. 返回錯誤碼會導致更深層次的嵌套結構。當返回錯誤嗎時候,就是在要求調用者立刻處理錯誤
2. 錯誤處理代碼就能從主路徑代碼中分離出來
3. 抽離 try catch 代碼塊。把 try catch 代碼塊的主體部分抽離出來,另外形成函數
10. 別重複自己
1. 消除重複
11. 結構化編程
1. Edsger Dijkstra 結構化編程規則:每個函數、函數中的每個代碼塊都應該有一個入口一個出口。遵循這些規則,意味著在每個函數中只該有一個 return 語句,循環中不能有 break 或者 continue 語句,永遠不能有任何goto 語句
2. 小函數中這些規則助益不大,保持函數短小偶爾出現 return break 或 continue 語句沒有壞處。大函數中這些規則才有明顯的好處。goto 避免使用。
12. 如何寫出這樣的函數
1. 想寫什麼些什麼
2. 打磨:分解函數,修改名稱、消除重複。縮短和重新安置方法
3. 遵循本章規則,組裝好這些函數
## 3 注釋
1. 注釋不能美化糟糕的代碼
1. 不如清潔代碼
2. 用代碼來闡述
3. 好注釋
1. 法律信息
2. 提供信息的注釋
3. 對意圖的解釋
4. 闡釋,把晦澀難明的參數或者返回值的意義翻譯成某種可讀形式
5. 警示
6. TODO
7. 放大。注釋可以用來放大某種看似不合理之物的重要性
8. 公共 API 注釋
4. 壞注釋
1. 喃喃自語。應該過程需要就添加注釋,是無畏之舉
2. 多餘的注釋。代碼清晰明了,讀注釋比讀代碼花的時間長
3. 誤導性注釋
4. 循軌式注釋。每個函數每個變量全注釋是愚蠢可笑的
5. 日誌式注釋,歸屬與署名。svn git 不會更好?
6. 廢話式注釋
7. 能用函數或變量時就別用注釋
8. 位置標記
9. 括號後面的注釋。( while/try/catch { ... } // while/try/catch end )應該做的是縮短函數。
10. 注釋掉的代碼
11. 原始碼注釋中的 HTML 注釋。注釋生成網頁的時候使用的吧,應該是工具而非程式設計師來負責給注釋加上合適的 HTML 標籤
12. 非本地信息。注釋一定要確保描述了離他最近的代碼。別在本地注釋的上下文環境中給出系統級的信息
13. 信息過多
14. 不明顯的聯繫
15. 函數頭。起個好的函數名
16. 非公共 API 代碼中的注釋。
## 4 格式
1. 格式的目的
1. 關乎溝通
2. 代碼風格會影響到可維護性和擴展性
2. 垂直格式
1. 像報紙學習。名稱簡單一目了然,源文件頂部給出高層次概念和算法。細節向下漸次展開。
2. 概念間垂直方向上的區隔。用空白行區分新的思路
3. 垂直方向的漸進。緊密聯繫的代碼應該相互靠近
4. 垂直距離
1. 變量聲明。變量聲明儘可能靠近其使用位置
2. 實體變量應該在類的頂部位置
3. 相關函數。調用者在被調用者的上面,放到一起
4. 概念相關的代碼應該放到一起。相關性越強,距離應該越短
5. 垂直順序。被調用的函數應該放在執行調用的函數下面
3. 橫向格式
1. 水平方向上的區隔與靠近。用空格字符將緊密相關的事務聯繫到一起,相關性較弱的事務分隔開
2. 並不需要水平對齊。不需要多行變量水平對齊,容易忽略類型,只讀變量名
3. 縮進。我覺得短小的額 if while 或者小函數中違反縮進規則沒有問題
4. 空範圍。儘量不適用 while 或 for 語句的語句體為空。要不分號另開一行,或者加上中括號
4. 團隊規則
1. 遵循團隊規則的風格
## 5 對象和數據結構
1. 數據抽象
1. 隱藏實現關乎抽象。類並不簡單的用取值器和賦值器將其變量推向外檢,而是暴露抽象接口,一遍用戶無需了解數據的實現就能操作數據本體
2. 數據、對象的反對稱性
1. 過程式代碼(使用數據結構的代碼)便於在不改動既有數據結構的前提下添加新函數,面向對象代碼便於在不該懂既有函數的前提下添加新類
2. 反過來講:過程式代碼難以添加新的數據結構,因為必須修改所有函數,面向對象代碼難以添加新的函數,應為必須修改所有類
3. 德墨忒耳律
1. 德墨忒耳律認為,模塊不應該了解它所操作對象的內部情形
2. 避免火車失事。如: string a = ctxt.a().b().c();對數據結構來說 string a = ctxt.a.b.c 無所謂。
3. 避免創造一半是對象一半是數據結構的混雜結構
4. 對象的內部結構應該做到隱藏而不暴露
4. 數據傳送對象(Data Transfer Objects)
1. 最為精簡的數據結構,只有公共變量、沒有函數的類。多用在與資料庫通信,解析套接字信息之類的場景中。例如 JavaBean 結構,會有用複製器和取值器操作的私有變量。其實這些封裝並無其他好處
2. Active Record,特殊 DTO 形式。擁有公共變量的數據結構,通常也會擁有類似 save 和 find 這樣可瀏覽方法。一般是對資料庫表或者其他數據源的直接翻譯。不要在這類數據結構裡面塞進業務規則,應該創建包含業務規則隱藏內部數據的獨立對象。
## 6 錯誤處理
1. 使用異常而非你返回碼
1. 代碼整潔,不容易被錯誤處理搞亂
2. 顯示 try catch finally 語句
1. 可以幫你定義代碼的用戶應該期待什麼,無論 try 代碼塊中執行的代碼出什麼錯都一樣
2. 使用 try catch 結構定義一個範圍,繼續用測試驅動(TDD)的方法構建剩餘的代碼邏輯
3. 使用不可控異常
1. 可控異常的代價就是違反開放/閉合原則。如果你的方案中拋出可控異常,舊的在 catch 語句和拋出異常處之間的每個方法籤名中聲明該異常
2. 可控異常有時也會有用:你必須捕獲異常。對於一般應用開發,其依賴成本要高於收益
4. 給出異常發生的環境說明
1. 異常提供足夠的環境說明,一遍判斷錯誤的來源和處所。堆棧蹤跡以及充分的錯誤消息都應該記錄下來
5. 依調用者需要定義異常類
1. 打包類翻譯處理調用 API 並處理 API 所拋出的異常。降低對 API 的依賴
2. 如果你想要捕獲某個異常,並且放過其他異常,就使用不同的異常類
6. 定義常規流程
1. 使用特例模式(SPECIAL CASE PATTERN)。創建一個類或者配置一個對象,來處理特例。客戶代碼就不用應付異常行為了,異常行為被封裝到特例對象中
7. 別返回 null 值
1. 可以使用特例模式創建空對象,減少 null 值檢查或者異常處理
8. 別傳遞 null 值
## 7 邊界
1. 使用第三方代碼
1. 三方代碼封裝到對象中,對象處理三方代碼的事宜。不建議在邊界上的其它接口在系統中傳遞
2. 瀏覽和學習邊界
1. 閱讀文檔,決定如何使用。
2. 編寫使用三方代碼,確定是否如願工作。
3. 為三方代碼編寫測試,找出在我們或他們代碼中的缺陷
3. 學習性測試的好處不只是免費
1. 幫助我們增進對 API 的理解
4. 使用上不存在的代碼
1. 定義與三方接口的 ADAPTER,等接入三方代碼時再完成
5. 整潔的邊界
1. 包裝一層去使用
2. 使用 ADAPTER 模式將我們的接口轉換為第三方提供的接口
3. 上面兩種方法都可以使三方代碼有改動時候修改點更少
## 8 單元測試
1. TDD 三定律
1. 在編寫不能通過的單元測試前,不可編寫生產代碼
2. 只可編寫剛好無法通過的單元測試,不能編譯也算不通過
3. 只可編寫剛好足以通過當前失敗測試的生產代碼
2. 保持測試整潔
1. 測試代碼和生產代碼一樣重要。它需要被思考、被設計和被照料。它該像生產代碼一般保持整潔,避免無法維護測試代碼
2. 測試越髒,代碼就會變得越髒。最終丟掉了測試,代碼頁開始腐壞
3. 整潔的測試
1. 可讀性。明確簡潔還有足夠的表達力
2. 打造一套包裝這些需要測試的 API 的函數和工具代碼,更方便編寫測試,更便於閱讀
3. 雙重標準。不需要在乎內存或 CPU 效率
4. 每個測試一個斷言
1. 每個只測試一個概念
5. F.I.R.S.T.
1. 快速(Fast) 測試應該夠快
2. 獨立(Independent) 測試應該相互獨立
3. 可重複(Repeatable) 測試應當可以在任何環境中重複通過
4. 自足驗證(Self-Validating) 測試應該有布爾值輸出
5. 及時(Timely) 測試應該及時編寫(編寫生產代碼之前)
## 9 類
1. 類的組織
1. 變量開始。(標準 Java 約定)公共靜態變量,私有靜態變量,私有試題變量,公共變量
2. 自頂向下原則
3. 放鬆封裝總是下策。但不執著與變量和工具函數的私有性。有時可以使用 protected 變量便於測試訪問
2. 類應該短小
1. 權責來衡量類的大小。如果無法為某個類名以精確的名稱,這個類就太長了。類名越含混,該類越有可能用有過多的權責
2. 單一權責原則(SRP)認為,類或模塊應有且只有一條加以修改的理由。系統應該由許多短小的類而不是少量巨大的類組成。每個小類封裝一個權責,只有一個修改的原因,並與少數其他類一起協同達成期望的系統行為
3. 保持函數和參數列表短小的策略,有時會導致為一組子集方法所用的實體變量數量增加。出現這種情況時,往往意味著至少有一個類要從大類中掙扎出來。你應當嘗試將這些變量和方法分拆到兩個或多個類中,讓新的類更為內聚。
4. 將大函數拆為許多小函數,往往也是將類拆分為多個小類的時機。程序會更加有組織,也會擁有更為透明的結構。
3. 為了修改而組織
1. 如果系統解耦到足以隔離修改,也就更加靈活,更加可復用。部件之間的解耦代表著系統中的元素互相隔離得很好。隔離也讓對系統每個元素的理解變得更加容易
2. 通過降低連接度,我們的類就遵循了另一條類設計原則,依賴倒置原則(Dependency Inversion Principle,DIP)。本質而言,DIP 認為類應當依賴於抽象而不是依賴於具體細節
## 10 系統
1. 將系統的構造與使用分開
1. 軟體系統應將啟始過程和啟始過程之後的運行時邏輯分離開,在啟始過程中構建應用對象,也會存在互相纏結的依賴關係。
2. 將構造與使用分開的方法之一是將全部構造過程搬遷到 main 或被稱之為 main 的模塊中,設計系統的其餘部分時,假設所有對象都已正確構造和設置
3. 有一種強大的機制可以實現分離構造與使用,那就是依賴注入(Dependency Injection,DI),控制反轉(Inversion of Control,IoC)在依賴管理中的一種應用手段。控制反轉將第二權責從對象中拿出來,轉移到另一個專注於此的對象中,從而遵循了單一權責原則。在依賴管理情景中,對象不應負責實體化對自身的依賴。反之,它應當將這份權責移交給其他「有權力」的機制,從而實現控制的反轉。因為初始設置是一種全局問題,這種授權機制通常要麼是 main 例程,要麼是有特定目的的容器。
2. 擴容
1. 我們應該只去實現今天的用戶故事,然後重構,明天再擴展系統、實現新的用戶故事。這就是迭代和增量敏捷的精髓所在。測試驅動開發、重構以及它們打造出的整潔代碼,在代碼層面保證了這個過程的實現。
2. 軟體系統與物理系統可以類比。它們的架構都可以遞增式地增長,只要我們持續將關注面恰當地切分。
3. Java 代理(略)
4. 純 Java AOP 框架(略)
5. AspectJ 的方面(略)
6. 測試驅動系統架構
1. 最佳的系統架構由模塊化的關注面領域組成,每個關注面均用純 Java(或其他語言)對象實現。不同的領域之間用最不具有侵害性的方面或類方面工具整合起來。這種架構能測試驅動,就像代碼一樣。
7. 優化決策
1. 擁有模塊化關注面的 POJO 系統提供的敏捷能力,允許我們基於最新的知識做出優化的、時機剛好的決策。決策的複雜性也降低了。
2. POJO(Plain Ordinary Java Object)簡單的 Java 對象,實際就是普通 JavaBeans,是為了避免和EJB混淆所創造的簡稱。
3. EJB (Enterprise Java Beans) 企業 Java Beans 是用於開發和部署多層結構的、分布式的、面向對象的Java應用系統的跨平臺的構件體系結構。
8. 明智使用添加了可論證價值的標準
1. 有了標準,就更易復用想法和組件、僱用擁有相關經驗的人才、封裝好點子,以及將組件連接起來。不過,創立標準的過程有時卻漫長到行業等不及的程度,有些標準沒能與它要服務的採用者的真實需求相結合。
9. 系統需要領域特定語言
1. 在軟體領域,領域特定語言(Domain-Specific Language,DSL)是一種單獨的小型腳本語言或以標準語言寫就的 API,領域專家可以用它編寫讀起來像是組織嚴謹的散文一般的代碼。
2. 優秀的 DSL 填平了領域概念和實現領域概念的代碼之間的「壕溝」,就像敏捷實踐優化了開發團隊和甲方之間的溝通一樣。如果你用與領域專家使用的同一種語言來實現領域邏輯,就會降低不正確地將領域翻譯為實現的風險。
3. DSL 在有效使用時能提升代碼慣用法和設計模式之上的抽象層次。它允許開發者在恰當的抽象層級上直指代碼的初衷。
4. 領域特定語言允許所有抽象層級和應用程式中的所有領域,從高級策略到底層細節,使用 POJO 來表達。
## 11 迭進
1. 通過迭進設計達到整潔目的
1. Kent Beck 關於簡單設計的四條規則
2. 以下規則按其重要程度排列
2. 簡單的設計規則 1:運行所有測試
1. 緊耦合的代碼難以編寫測試。同樣,編寫測試越多,就越會遵循 DIP 之類規則,使用依賴注入、接口和抽象等工具儘可能減少耦合。如此一來,設計就有長足進步。
2. 遵循有關編寫測試並持續運行測試的簡單、明確的規則,系統就會更貼近 OO 低耦合度、高內聚度的目標。編寫測試引致更好的設計。
3. 簡單的設計規則 2~4:重構
1. 有了測試,就能保持代碼和類的整潔,方法就是遞增式地重構代碼。添加了幾行代碼後,就要暫停,琢磨一下變化了的設計。設計退步了嗎?如果是,就要清理它,並且運行測試,保證沒有破壞任何東西。測試消除了對清理代碼就會破壞代碼的恐懼。
2. 在重構過程中,可以應用有關優秀軟體設計的一切知識。提升內聚性,降低耦合度,切分關注面,模塊化系統性關注面,縮小函數和類的尺寸,選用更好的名稱,如此等等。這也是應用簡單設計後三條規則的地方:消除重複,保證表達力,儘可能減少類和方法的數量。
4. 不可重複
1. 重複是擁有良好設計系統的大敵。它代表著額外的工作、額外的風險和額外且不必要的複雜度。重複有多種表現。極其雷同的代碼行當然是重複。類似的代碼往往可以調整得更相似,這樣就能更容易地進行重構。
5. 表達力
1. 軟體項目的主要成本在於長期維護。為了在修改時儘量降低出現缺陷的可能性,很有必要理解系統是做什麼的。當系統變得越來越複雜,開發者就需要越來越多的時間來理解它,而且也極有可能誤解。所以,代碼應當清晰地表達其作者的意圖。作者把代碼寫得越清晰,其他人花在理解代碼上的時間也就越少,從而減少缺陷,縮減維護成本。
2. 選用好名稱
3. 保持函數和類尺寸短小
4. 採用標準命名法
5. 編寫良好的單元測試
6. 最重要方式卻是嘗試。下足功夫調整代碼,讓後來者易於閱讀
6. 儘可能少的類和方法
1. 即便是消除重複、代碼表達力和 SRP 等最基礎的概念也會被過度使用。為了保持類和函數短小,我們可能會造出太多的細小類和方法。
2. 我們的目標是在保持函數和類短小的同時,保持整個系統短小精悍。不過要記住,這在關於簡單設計的四條規則裡面是優先級最低的一條。所以,儘管使類和函數的數量儘量少是很重要的,但更重要的卻是測試、消除重複和表達力。
## 12 並發編程
1. 並發目的
1. 並發是一種解耦策略。它幫助我們把做什麼(目的)和何時(時機)做分解開。
2. 有關並發的中肯說法
1. 並發會在性能和編寫額外代碼上增加一些開銷;
2. 正確的並發是複雜的,即便對於簡單的問題也是如此;
3. 並發缺陷並非總能重現,所以常被看做偶發事件而忽略,未被當做真的缺陷看待;
4. 並發常常需要對設計策略的根本性修改。
2. 挑戰(線程相互影響例子。略)
3. 並發防禦原則
1. 單一權責原則
1. 單一權責原則(SRP)認為,方法/類/組件應當只有一個修改的理由。並發設計自身足夠複雜到成為修改的理由,所以也該從其他代碼中分離出來。不幸的是,並發實現細節常常直接嵌入到其他生產代碼中。
2. 建議:分離並發相關代碼與其他代碼。
2. 推論:限制數據作用域
1. 謹記數據封裝;嚴格限制對可能被共享的數據的訪問。
3. 推論:使用數據複本
1. 避免共享數據的好方法之一就是一開始就避免共享數據。
2. 如果有避免共享數據的簡易手段,結果代碼就會大大減少導致錯誤的可能。你可能會關心創建額外對象的成本。值得試驗一下看看那是否真是個問題。然而,假使使用對象複本能避免代碼同步執行,則因避免了鎖定而省下的價值有可能補償得上額外的創建成本和垃圾收集開銷。
4. 推論:線程應儘可能地獨立
1. 嘗試將數據分解到可被獨立線程(可能在不同處理器上)操作的獨立子集
4. 了解 Java 庫(略)
5. 了解執行模型
1. Producer-Consumer 生產者-消費者模型
2. Readers-Writers 讀者-作者模型
3. Dining Philosophers 宴席哲學家
6. 警惕同步方法之間的依賴
1. 避免使用一個共享對象的多個方法。必須使用一個共享對象的多個方法的時候有三種寫對代碼的手段,如下:
2. 基於客戶端的鎖定——客戶端代碼在調用第一個方法前鎖定服務端,確保鎖的範圍覆蓋了調用最後一個方法的代碼;
3. 基於服務端的鎖定——在服務端內創建鎖定服務端的方法,調用所有方法,然後解鎖。讓客戶端代碼調用新方法;
3. 適配服務端——創建執行鎖定的中間層。這是一種基於服務端的鎖定的例子,但不修改原始服務端代碼
7. 保持同步區域微小
1. 鎖是昂貴的,因為它們帶來了延遲和額外開銷
2. 臨界區應該被保護起來。
8. 很難編寫正確的關閉代碼
1. 儘早考慮關閉問題,儘早令其工作正常。這會花費比你預期更多的時間。檢視既有算法,因為這可能會比想像中難得多。
2. (weifei) 通常遊戲伺服器等待存檔之後殺掉也可以
9. 測試線程代碼
1. 編寫有潛力曝露問題的測試,在不同的編程配置、系統配置和負載條件下頻繁運行。如果測試失敗,跟蹤錯誤。別因為後來測試通過了後來的運行就忽略失敗。
2. 測試建議:
1. 將偽失敗看作可能的線程問題;
2. 先使非線程代碼可工作;
3. 編寫可插拔的線程代碼;
1. 單線程與多個線程在執行時不同的情況;
2. 線程代碼與實物或測試替身互動;
3. 用運行快速、緩慢和有變動的測試替身執行;
4. 將測試配置為能運行一定數量的迭代。
4. 編寫可調整的線程代碼;
5. 運行多於處理器數量的線程;任務交換越頻繁,越有可能找到錯過臨界區或導致死鎖的代碼
6. 在不同平臺上運行;應該在所有可能部署的環境中運行測試
7. 調整代碼並強迫錯誤發生;硬編碼
8. 自動化;可以使用 Aspect-Oriented Framework、CGLIB 或 ASM 之類工具通過編程來裝置代碼。
## 13 逐漸改進 (對一個命令行參數解析程序的案例研究)
1. Args 的實現 (略)
2. Args: 草稿(略)
3. 字符串參數(略)
## 14 JUnit 內幕(略)
1. JUnit 框架(略)
## 15 重構 SerialDate(略)
1. 首先讓它能工作(略)
2. 讓它作對(略)
## 16 味道和啟發
1. 注釋
1. 不恰當的信息
2. 廢棄的注釋
3. 冗餘注釋
4. 糟糕的注釋
5. 注釋掉的代碼
2. 環境
1. 需要多步才能實現的構建
2. 需要多步才能做到的測試
3. 函數
1. 過多的參數
2. 輸出參數
3. 標識參數
4. 死函數
4. 一般性問題
1. 一個源文件中存在多種語言
2. 明顯的行為未被實現
3. 不確定的邊界行為
4. 忽視安全
5. 重複
6. 在錯誤的抽象層級上的代碼
7. 基類依賴於派生類
1. 將概念分解到基類和派生類的最普遍的原因是較高層級基類概念可以不依賴於較低層級派生類概念。這樣,如果看到基類提到派生類名稱,就可能發現了問題。通常來說,基類對派生類應該一無所知。
2. 當然也有例外。有時,派生類數量嚴格固定,而基類中擁有在派生類之間選擇的代碼。在有限狀態機的實現中這種情形很多見。然而,在那種情況下,派生類和基類緊密耦合,總是在同一個 jar 文件中部署。一般情況下,我們會想要把派生類和基類部署到不同的 jar 文件中。
3. 將派生類和基類部署到不同的 jar 文件中,確保基類 jar 文件對派生類 jar 文件的內容一無所知,我們就能把系統部署為分散和獨立的組件。修改了這些組件時,不必重新部署基組件就能部署它們。這意味著修改產生的影響極大地降低了,而維護系統也變得更加簡單。
8. 信息過多
9. 死代碼
10. 垂直分隔
11. 前後不一致。從一而終。這可以追溯到最小驚異原則。小心選擇約定,一旦選中,就小心持續遵循。
12. 混淆視聽
1. 沒有實現的默認構造器有何用處呢?它只會用無意義的雜碎搞亂對代碼的理解。沒有用到的變量,從不調用的函數,沒有信息量的注釋,等等,這些都是應該移除的廢物。保持源文件整潔,良好地組織,不被搞亂。
13. 人為耦合。不互相依賴的東西不該耦合。
14. 特性依賴
1. 類的方法只應對其所屬類中的變量和函數感興趣,不該垂青其他類中的變量和函數。當方法通過某個其他對象的訪問器和修改器來操作該對象內部數據,則它就依戀於該對象所屬類的範圍。它期望自己在那個類裡面,這樣就能直接訪問它操作的變量。
15. 選擇算子參數。不要在參數中使用 true false flag
16. 晦澀的意圖。代碼要儘可能具有表達力。聯排表達式、匈牙利語標記法和魔術數都遮蔽了作者的意圖。
17. 未知錯誤的權責。最小驚異原則在這裡起作用了。代碼應該放在讀者自然而然期待它所在的地方。
18. 不恰當的靜態方法
1. 通常應該傾向於選用非靜態方法。如果有疑問,就是用非靜態函數。如果的確需要靜態函數,確保沒機會打算讓它有多態行為。
19. 使用解釋性的變量。讓程序可讀的最有力方法之一就是將計算過程打散成在用有意義的單詞命名的變量中放置的中間值。
20. 函數名稱應該表達其行為
21. 理解算法
22. 把邏輯依賴改為物理依賴
1. 如果某個模塊依賴於另一個模塊,依賴就該是物理上的而不是邏輯上的。依賴者模塊不應對被依賴者模塊有假定(換言之,邏輯依賴)。它應當明確地詢問後者全部信息。
23. 用多態替代 if/else 或 switch/case
24. 遵循標準約定
25. 用命名常量代替魔術數
26. 準確
1. 在代碼中做決定時,確認自己足夠準確。明確自己為何要這麼做,如果遇到異常情況如何處理。別懶得理會決定的準確性。如果你打算調用可能返回 null 的函數,確認自己檢查了 null 值。如果查詢你認為是資料庫中唯一的記錄,確保代碼檢查不存在其他記錄。如果要處理貨幣數據,使用整數(定點數?),並恰當地處理四捨五入。如果可能有並發更新,確認你實現了某種鎖定機制。代碼中的含糊和不準確要麼是意見不同的結果,要麼源於懶惰。無論原因是什麼,都要消除。
27. 結構甚於約定
1. 堅守結構甚於約定的設計決策。命名約定很好,但卻次於強制性的結構。例如,用到良好命名的枚舉的 switch/case 要弱於擁有抽象方法的基類。沒人會被強迫每次都以同樣方式實現 switch/case 語句,但基類卻讓具體類必須實現所有抽象方法。
28. 封裝條件。如果沒有 if 或 while 語句的上下文,布爾邏輯就難以理解。應該把解釋了條件意圖的函數抽離出來。
29. 避免否定性條件。否定式要比肯定式難明白一些。所以,儘可能將條件表示為肯定形式
30. 函數隻該做一件事
31. 掩蔽時序耦合。常常有必要使用時序耦合,但你不應該掩蔽它。排列函數參數,好讓它們被調用的次序顯而易見。
1. 這樣就通過創建順序隊列暴露了時序耦合。每個函數都產生出下一個函數所需的結果,這樣一來就沒理由不按順序調用了。
```java
public class MoogDiver {
Gradient gradient;
List<Spline> splines;
public void dive(String reason) {
saturateGradient();
reticulateSplines();
diveForMoog(reason);
}
…
}
//暴露時序耦合的做法:
public class MoogDiver {
Gradient gradient;
List<Spline> splines;
public void dive(String reason) {
Gradient gradient = saturateGradient();
List<Spline> splines = reticulateSplines(gradient);
diveForMoog(splines, reason);
}
…
}
```
32. 別隨意。構建代碼需要理由,而且理由應與代碼結構相契合。如果結構顯得太隨意,其他人就會想修改它。如果結構自始至終保持一致,其他人就會使用它,並且遵循其約定。
33. 封裝邊界條件
34. 函數應該只在一個抽象層級上
35. 在較高層級放置可配置程序
36. 避免傳遞瀏覽。德墨忒爾定律,不寫 a.getb().getc().dosomething()。 正確的做法是讓直接協作者提供所需的全部服務。不必逛遍系統的對象全圖,搜尋我們要調用的方法。
5. Java
1. 通過使用通配符避免過長的導入清單
2. 不要繼承常量
3. 使用枚舉而不是常量
6. 名稱
1. 採用描述性名稱
2. 名稱應與抽象層級相符
3. 儘可能使用標準命名法
4. 無歧義名稱
5. 為較大作用範圍選用較長名稱
6. 避免編碼
7. 名稱應該說明副作用
7. 測試
1. 測試不足
2. 使用覆蓋率工具
3. 別略過小測試
4. 被忽略的測試就是對不確定事物的疑問
5. 測試表姐條件
6. 全面測試相近的缺陷
7. 測試失敗的模式有啟發性
8. 測試覆蓋率的模式有啟發性
9. 測試應該快速