Refresh Clean Code

這是一本被前輩稱讚, 另一個馬丁的知名著作, 被賦予學習如何寫出漂亮代碼的教皇級手冊, 最近(再)瀏覽, 有諸多感受.

總結一下就是如果你是新手, 可能看優秀的open source學習更好; 如果你是老手, 可能他說的東西要麼你會覺得太囉嗦, 要麼你會覺得例子太細節, 太教條, 無法舉一反三.

不過一些General的要點思想我摘錄了出來, 有些還包括個人的觀點, 記錄在此, 以備查閱.

  • 勒布朗(LeBlanc)法則, Later equals never
  • 代碼如果爛了就會越來越爛, 所謂破窗原理, 以前老是也提過叫代碼腐化
  • 命名
    • 命名應該合理, 成員變量不用加前綴, 靠高亮就很好
    • 接口加前綴I也不太好, 不如實現加Imp
    • 方法命名最好使用動詞或動詞短語
    • 多使用計算機領域的詞彙
    • 有時候命名在語境裏纔有意義, 有時候變量命名也不需要重複語境
  • 函數
    • 函數短小一些比較易懂, 20行內最佳
    • if/else/while代碼塊封裝函數, 只有一行最好 (有點激進?)
    • 一個函數只幹一件事情
    • 複雜的switch試圖用多態取代, 然後封裝在抽象工廠裏
    • 同一類型函數命名風格應該一致, 使用描述性的語句描述比難懂的短詞更好
    • 一元函數最普遍, 標識函數(參數爲boolean)的不如分成兩個函數, 二元函數儘可能轉換成一元函數
    • 參數太多可能需要封裝類了
    • 好名字的函數一般是動詞, 或者動詞+關鍵字
    • 函數一般要麼做事, 要麼回答事, 不可兼得 (其實也不一定, 比如很多返回boolean狀態的函數)
    • 推薦使用異常代替錯誤代碼
    • 最好將try/catch單獨抽入一個函數 (如Android代碼會對RemoteService調用進行類似封裝, 喫掉exception)
    • 使用枚舉表示錯誤碼會讓改動變得繁瑣 (重新導入或者編譯部署), 大家都依賴這個枚舉, 應該用異常與繼承取代
    • 不要重複, 偶爾可以考慮AOP這類的方法解決
  • 註釋
    • 註釋少要比註釋多有用, 註釋多說明代碼糟糕 (其實分情況吧)
    • 有目的性的註釋還是有用的, 比如闡述, 放大, 警告, 定期維護的TODO等
    • 如果代碼不用, 不要註釋掉, 直接刪掉
  • 格式
    • 橫向, 豎向對齊, 間隔, 縮進等. 其實目前formater已經很強大了, 養成良好的format習慣, 漸漸的寫出來的代碼就會像直接format後一樣
  • 對象與數據結構
    • 面向過程與面向對象對立, 前者容易加函數, 不容易加對象, 後者容易加對象, 而不容易加函數
    • 理想情況下, Law of Demeter認爲, 類不應該操作對象內部的東西, 如不應該操作函數返回對象的方法. 目的只是爲了降低複雜度, 認爲這樣將私有變量公開化, 增加了重構的難度, 如添加新函數, 方法等, 將數據結構與對象邏輯耦合在一起.
    • 針對於上面的問題, 原則應該是對象暴露行爲, 隱藏數據, 數據結構暴露數據
  • 錯誤處理
    • Java特色的受控異常(Checked Exception), 必須得被catch, 對受控異常的修改會引起上層所有調用方法的改動, 儘量不要使用, 其他語言只有RuntimeException
    • 可以包裝第三方邏輯, 封裝自己的異常類, 簡化爲只catch一種異常
    • 簡化使用也可以將異常處理完全封裝進去, 返回特例即可
    • 輕易不要返回null, 傳入null
    • 代碼的堅固與乾淨不衝突, 所以添加一些check, throw相應的Exception也合理
  • 邊界
    • 所謂邊界就是自己可以控制到的程序與第三方的邊界, 通常需要通過封裝的辦法來劃清邊界, 限制那些無法控制的第三方, 如直接Wrapper或者Adapter模式等
  • 單元測試
    • 測試需要整潔, 需要跟隨代碼一起更新, 測試的最大用途是保證你後續的修改有信心
    • 測試的整潔主要講的是可讀性, 即分爲三個環節, build->operate->check, 可以把繁雜的準備封裝起來
    • 測試API是漸漸重構演進過來的, 也不可能是起初就設計出來
    • 有人建議每個測試一個assert, 但是這樣會有很多重複的代碼, 不過可以利用Template Method來解決. 不過也不一定必須一個, 做到最小化就可以了
    • 整潔還有五條規則, Fast, Independent, Repeatable, Self-Validation, Timely
    • 這裏講到TDD的好處, 主要是幫助你覆蓋更多的測試, 如果寫完再測, 發現測不了, 就不寫了.
    • 類應該短小, 權責應該足夠單一, 內聚性應該高
    • 如果期望抽走一部分邏輯, 最好連相關函數參數也抽走
    • 簡化類的過程應該小步, 每一步都運行測試一下
    • 類的精簡是爲了更好的體現開閉原則, 整體結構爲修改而設計
    • 類應依賴於接口, 不依賴實現, 可以隔離修改, 符合依賴倒置原則, 類似策略模式
  • 系統
    • 構造與使用分開, 通過依賴注入等
    • 通過AOP, Proxy的方式, 無侵入性的插入邏輯
    • 系統如果充分模塊化, 領域之間相互直接松耦合, 最爲理想, 就可以通過測試來驅動
    • DSL的使用可以平衡領域與技術
  • 迭代
    • 代碼是在不斷迭代中進步的, 比如通過抽函數, 運用設計模式等
  • 併發
    • 應該儘可能分離併發代碼與其他代碼
    • 儘可能讓線程之間獨立, 不要有共享
    • 線程模型有典型的生產者與消費者, 讀者與作者, 宴席哲學家模型, 分別用來說明互斥, 讀寫, 競爭死鎖等特例
    • 鎖定代碼塊應該儘可能小
    • 線程最好可插拔, 遵循單一權責, 分離線程與其他代碼, 測試先保證除線程之外的邏輯
    • 最後一張通過實例來介紹併發代碼如何重構的比較清晰
  • 逐步改進
    • 這片使用了一個例子, 先採用蠻幹進行重構發現越來越難, 後來採用逐步的辦法, 還寫的很細節, 但是比較Tricky的地方是, 這裏提出的每步改動都通過測試來印證, 然後保證測試通過, 或者補充一個測試, 讓代碼通過, 但是所謂TDD部分只有思想, 老代碼既然寫的不好, 測試是如何出來的, 如何做到Cover全的. 如果假設在一個有比較好測試覆蓋的基礎上重構, 我感覺即便蠻力也不會太差...
  • JUnit
    • 比較神奇的一章, 感覺更像是用一個算法類的重構來說明如何把代碼改簡單, 但是開頭先介紹了100%覆蓋的Junit測試長什麼樣子... 感覺作者是想說以前的人做的還不錯, 都用JUnit給覆蓋全了, 還是可以重構的更漂亮的, 所以感覺標題不太好
  • 重構SerialDate
    • 跟上一個對比, 這個測試覆蓋不全, 所以在補測試的過程中進行重構, 還發現了缺陷, 又最終把代碼改清晰了. 不過同樣由於太過細節, 一些重構理論也很教條, 如果想通過看別人重構的例子來學習如果寫簡潔的代碼, 不如直接看優秀的代碼是怎麼寫的
  • 味道
    • 裏面類比了很多Bad Smell, 同樣是很細節, 比如太多, 太死, 太複雜, 重複, 不一致, 耦合, 測試不足等, 提供的Tips大多的中心思想就是抽, 封裝, 單一職責, 命名清晰
    • 提到使用*來避免過長import, 不過這個與現在流行的lint檢測違背, 可以因爲以前都是手動, 通配符簡單吧.
    • 不要通過繼承來使用常量, 還提到不要用靜態常量, 用枚舉, 這個也與當前的思想有出入, 現在經常將Java的enum太冗餘, 不必要時可以用常量
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章