1. 程式人生 > >《代碼整潔之道》

《代碼整潔之道》

事情 控制 ati 語言 面向 code 消費者 名詞 兩個

代碼整潔之道

  • 代碼猴子(Code Monkey): 低水平編碼者.
  • 童子軍規.
  • 技藝(craftsmanship):
    • 知和行. 學寫整潔代碼, 掌握原則和模式, 並付出行動.

整潔代碼

  • 代碼呈現了需求的細節. 這些細節無法被忽略或抽象, 必須要嚴謹, 精確, 規範和詳細.

糟糕的代碼

  • 糟糕的代碼可能毀掉一家公司.
  • 稍後等於永不(Later equals never).
  • 隨著混亂的增加, 團隊生產力也持續下降,最後將趨向於零.
  • 花時間保持代碼整潔不但有關效率, 還有關生存.
  • 代碼整潔的程序員就像是藝術家, 它能用一系列變換把一塊白板變作由優秀代碼構成的系統.
  • 在寫整潔代碼時, 要遵循大量的小技巧, 貫徹刻苦學習整潔感.
  • 敷衍了事的錯誤處理大面積知識的程序員會忽視細節的一種表現:
    • 內存泄漏;
    • 靜態條件代碼;
    • 前後不一致的命名方式.
    • 整潔的代碼從不隱藏設計者的意圖. --- 幹凈利落的抽象和直截了當的控制語句.
  • 簡單代碼, 依其重要性:
    • 能通過所有測試.
    • 沒有重復代碼.
    • 體現系統中的全部設計理念.
    • 包括盡量少的實體, 比如類, 方法和函數等.
  • 有意義的命名是體現表達力的一種方式, 需要修改好幾次才能確定下名字.
  • 如果一個對象功能太多, 最好是切分為兩個或多個對象.
  • 如果方法功能太多, 使用抽象功能重構.

  • 消除重復,提高表達力, 提早構建簡單抽象, 在代碼整潔方面很有用.
  • 讀與寫花費的時間的比例超過10:1.
  • 單一權責原則, 開放閉合原則和依賴倒置原則.

有意義的命名

  • 變量, 函數, 參數, 類和封包命名.
  • 選一個好名字花時間, 但省下來的比花掉的更多.
  • 如果名稱需要註釋來補充, 就不算是名副其實了.
  • 上下文在代碼中要明確的體現.

避免誤導

  • 避免使用與本意相悖的詞.
  • 提防使用不同之處較小的名稱. 誤導性的名稱真的很可怕.
  • 做有意義的區分:
    • Info和Data就像a, an, the一樣, 是意義含混的廢話.
  • 使用讀得出來的名稱:
    • 盡量用英語單詞(長點也沒有關系).
  • 使用可搜索的名稱:
    • 名稱長短應該與其作用域大小相對應. 變量或常量存在代碼中多處使用, 應賦予其便於搜索的名稱.
  • 避免使用編碼:
    • 帶編碼的名稱通常不便於發音.
    • 不必用m_前綴來標明成員變量, 應當把類和函數做得足夠小, 消除對成員前綴的需要.
    • 代碼閱讀得越多, 眼中就越沒有前綴.
    • 接口和實現, 盡量區分.
  • 避免思維映射:
    • 除了循環計數可用i, j, k, 其他地方盡量不用.
    • 明確就是王道. 編寫其他人能理解的代碼.
  • 類名:
    • 類名和對象名應該是名詞或名詞短語.
  • 方法名:
    • 方法名應該是動詞或動詞短語.
  • 命名應該言簡意賅, 不要使用俚語.
  • 給每個概念對應一個詞:
    • 給每個抽象概念選一個詞, 並一以貫之.
  • 別用雙關語:
    • 避免將同一單詞用於不同目的, 同一術語用於不同概念. 一詞一意.
  • 使用源自所涉問題領域的名稱.
    • 優秀的程序員和設計師, 其工作之一就是分離解決方案領域和問題領域的概念.
  • 添加有意義的語境:
    • 用良好命名的類, 函數或名稱空間來放置名稱, 給讀者提供語境.
    • 將隸屬一個對象的名稱封裝為一個類.
  • 不要添加沒用的語境:
    • 會混淆理解.
  • 取好名字最難的地方在於需要良好的描述技巧和共有的文化背景.

函數

  • 短小: 函數的第一規則就是短小. 第二規則是還要更短小.
  • 函數20行封頂最佳.
  • 每個函數只說明一件事情, 而且依序帶到下一個函數.
  • if語句, else語句, while語句等, 其中的代碼塊應該只有一行. 這行應該是函數調用語句.
    • 塊內調用的函數擁有較具說明性的名稱.
    • 函數的縮進層次不應該多於一層或兩層.
  • 函數應該做一件事, 做好這件事, 只做一件事.
    • 如果函數只做了該函數名下同一抽象層上的步驟, 則函數還是只做了一件事.
    • 編寫函數的目的是為了把大一些的概念拆分為另一抽象層上的一系列步驟.
    • 只做一件事的函數無法合理地切分為多個區段.
  • 每個函數一個抽象層級:
    • 確保函數只做一件事, 函數中的語句都要在同一抽象層級上.
    • 函數中混雜不同抽象層級, 往往讓人迷惑, 讀者無法判斷是基礎概念還是細節. 基礎概念不能和細節混雜.
  • 自頂向下讀代碼: 向下規則.
    • 每一段都描述當前抽象層級, 並向下後續.
  • 確保每個switch語句都埋藏在較低的抽象層級, 而且永遠不重復. --- 利用多態來實現.
    • 將switch語句埋在抽象工廠的底下, 不讓任何人看到. 抽象工廠使用switch語句創建適當的實體.
  • 使用描述性的名稱:
    • 長而具有描述性的名稱, 要比短而令人費解的名稱好.
    • 命名方式要保持一致, 使用與模塊名一脈相承的短語, 名詞和動詞給函數命名.
  • 函數參數:
    • 最理想的參數是0, 其次是1, 應盡量避免三參數的函數.
    • 參數與函數名處在不同的抽象層, 參數越多, 測試用例也會很難寫.
    • 輸出參數比輸入參數更加難理解.
    • 一元函數的普遍形式.
    • 如果函數有三個或三個以上參數, 就說明其中一些參數應該封裝為類了.
    • 函數的關鍵字形式, 把參數的名稱編寫成函數名.
    • 應避免使用輸出參數, 如果函數必須要修改某種狀態, 那就應該修改所屬對象的狀態.
  • 分割指令與詢問:
    • 函數要麽做什麽事, 要麽回答什麽事, 但二者不可兼得.
  • 使用異常代替返回錯誤碼:
    • 如果使用異常代替返回錯誤碼, 錯誤處理代碼就能從主路徑代碼中分離出來.
    • try/catch代碼塊醜陋不堪. 會搞亂代碼結構.
    • 處理錯誤就是一件事.
  • 別重復自己:
    • 重復會呆滯代碼臃腫.
    • 重復是軟件中一切邪惡的根源.
    • 許多原則和實踐規則都是為控制與消除重復而創建.
      • 面向對象編程, 將代碼集中到基類, 為了避免冗余.
      • 面向方面編程, 面向組件編程也都是消除重復的一種策略.
  • 結構化編程:
    • 每個函數, 函數中的每個代碼塊都應該有一個入口, 一個出口.
    • 在每個函數中只有一個return語句, 循環中不能break或continue, 永遠不能goto.
    • 只要函數保持短小, 偶爾return, break, continue是沒有壞處的. 甚至比單入單出更具有表現力.
  • 先寫初稿, 然後不斷打磨這些代碼, 分解函數, 修改名稱, 消除重復.

註釋

  • 如果長於用語言來表達意圖, 那麽就不需要註釋.
  • 註釋的恰當用法是彌補在用代碼表達意圖時遭遇的失敗.
  • 如果發現需要寫註釋, 就再想想有沒有更好的辦法用代碼來表達.
  • 註釋不能美化糟糕的代碼. --- 應該用代碼來闡述.
  • 好註釋:
    • 有的註釋是必須的, 法律信息, 提供信息的註釋, 對意圖的解釋, 闡釋, 警告, TODO註釋, 放大不合理性.
    • TODO形式在源碼中放置要做的工作列表.
    • TODO是一種程序員認為應該做, 但由於某些原因目前還沒有做的工作.
  • 壞註釋:
    • 喃喃自語, 多余的註釋, 誤導性註釋, 循規式註釋, 日誌式註釋, 廢話註釋, 可怕的廢話, 能用函數和變量時就別用註釋, 位置標記, 括號後面的註釋, 歸屬與署名, 註釋掉的代碼, HTML註釋, 非本地信息, 信息過多, 不明顯的聯系, 函數頭.

格式

  • 代碼格式和重要, 代碼格式不可忽略, 必須嚴肅對待.
  • 保證代碼的可讀性.
  • 垂直格式:
    • 源文件名稱要簡單且一目了然.
    • 代碼從上至下, 從左到右, 每組代碼展示一條完整的思路, 這些思路用空白行區隔開來.
    • 垂直方向上的靠近: 精密相關的代碼應該相互靠近.
    • 垂直距離: 變量聲明應該盡可能靠近其使用位置.
      • 本地變量應該在函數的頂部出現(函數很短的情況下).
      • 實體變量應該在類的頂部聲明. C++采用剪刀原則.
    • 相關函數: 若某個函數調用另外一個函數, 應該把它們放在一起, 而且調用者應該盡可能放在被調用者上面.
    • 概念相關的代碼應該放在一起, 相關性越強, 彼此之間的距離就應該越短.
  • 橫向格式:
    • 一般為80字符, 需遵循無需滾動條到右邊的原則. 最大上限為120個字符.
    • 水平方向的區隔與靠近:
      • 賦值操作符周圍加上空格字符, 以表強調.
      • 不在函數名和左圓括號之間加空格. --- 函數與其參數密切相關.
      • 按運算符優先級加空格.
      • 盡力不對齊一組聲明中的變量, 和賦值語句中的右值.
      • 源文件是一種繼承結構, 而不是一種大綱結構. --- 縮進讓代碼的可讀性更好.
  • 團隊規則:
    • 每個程序員都有自己喜歡的格式規範, 但在一個團隊中工作, 就是團隊說了算.
    • 好的軟件系統是由一系列讀起來不錯的代碼文件組成的. --- 需要擁有一致和順暢的風格.

對象和數據結構

  • 將變量設置為私有(private)有一個理由, 不想讓其他人依賴這些變量.
  • 數據抽象:
    • 隱藏實現關乎抽象.
    • 不願暴露數據細節, 更願意以抽象形態表述數據.
  • 數據, 對象的反對稱性:
    • 對象把數據隱藏於抽象之後, 暴露操作數據的函數.
    • 數據結構暴露其數據, 沒有提供有意義的函數.
    • 過程式代碼便於在不改動既有數據結構的前提下添加新函數; 面向對象代碼便於在不改動既有函數的前提下添加新類.
  • The Law of Demeter:
    • 模塊不應了解它操作對象的內部情形. 對象隱藏數據, 暴露操作.
    • 方法不應調用由任何函數返回的對象的方法.
    • 盡量不使用連串的調用.
  • 數據傳送對象:
    • 只有公共變量, 沒有函數的類, DTO(data transfer objects).
    • Active Record, 擁有公共變量的數據結構, 但也擁有save和find這樣的方法.

錯誤處理

  • 使用異常而非返回值, 在C++中是有些爭議的.
  • 先寫Try-catch-Finally語句.
    • try代碼塊就像是事務, catch代碼塊將程序維持在一種持續狀態.
  • 使用不可控異常:
    • 可控異常需要捕獲, 使用成本較高.
  • 異常發生的環境說明.
  • 依調用者需要定義異常類.
  • 定義常規流程. 在業務邏輯和錯誤處理代碼之間要有良好的區別.
  • 不要返回null值. --- 也不要傳遞null值.

邊界

  • 使用第三方代碼:
    • 第三方程序包和框架提供者追求普適性.
    • 避免從公共API中返回邊界接口, 或將邊界接口作為參數傳遞給公共API.
  • 學習性測試的好處.

單元測試

  • TDD 三定律:
    • 在編寫不能通過的單元測試前, 不可編寫生成代碼.
    • 只可編寫剛好無法通過的單元測試, 不能編譯也算不能通過.
    • 只可編寫剛好足以通過當前失敗測試的生產代碼.
  • 測試代碼和生產代碼一樣重要, 它需要被思考, 被設計和被照料, 應該像生產代碼一樣保持整潔.
    • 可讀性, 明確, 整潔, 還有足夠的表達裏.
  • F.I.R.S.T原則:
    • 快速(Fast), 測試應該足夠快.
    • 獨立(Independent), 測試應該相互獨立.
    • 可重復性(Repeatable), 測試應在任何環境中重復通過.
    • 自足驗證(Self-Validating), 測試應該有布爾值輸出.
    • 及時(Timely), 測試應及時編寫.

  • 類應該短小:
    • 類的短小用權責(responsibility)來衡量.
    • 單一權責原則(SRP) --- 類或模塊應有且只有一條加以修改的理由.
  • 系統應該由一些短小的類而不是巨大的類組成, 每個小類封裝一個權責, 只有一個修改的原因, 並與少數其他類一起協同達成期望的系統行為.
  • 類應該只有少量實體變量, 方法操作的變量越多, 就越粘聚到類上.
    • 內聚性高, 意味著類中的方法和變量互相依賴, 相互結合成一個邏輯整體.
    • 將一些變量和方法拆分到兩個或多個類中, 讓新的類更為內聚.
    • 保持內聚性會得到很多短小的類.
  • 為了修改而組織:
    • 需求會改變, 所以代碼也會改變, 具體類包含實現細節, 而抽象類則值程序概念.

系統

  • 軟件系統應該將啟動過程和啟動過程之後的運行邏輯分開, 在啟動過程中構建應用對象.
  • 分解main.
  • 使用抽象工廠模式讓應用程序控制何時創建對象.

跌進

  • 通過跌進設計達到整潔目的.
  • 盡可能少的類和方法, 保持整個系統的短小精悍.

並發編程

  • 編寫整潔的並發程序非常難.
  • 並發防禦原則:
    • 單一權責原則:
      • 並發相關代碼有自己的並發, 修正和調試生命周期.
      • 並發相關代碼有自己要對付的挑戰, 和非並發相關代碼不同.
    • 限制數據作用域.
    • 使用數據副本.
    • 線程應盡可能地獨立.
  • 並發編程的模型:
    • 生產者-消費者模型.
    • 讀者-消費者模型.
    • 哲學家問題.
  • 警惕同步方法之間的依賴.
  • 保持同步區域盡量小.

逐步改進

  • 徐徐漸進地改進代碼.
  • 首先讓程序能夠正常工作, 再讓它做對, 接著想辦法讓它簡單整潔.

《代碼整潔之道》