CleanCodeNotes

忘我楊發表於2019-01-02

一、有意義的命名及函式

  1. 類名:使用名詞,方法名:動詞
  2. 別用雙關語:避免將同一單詞用於不同目的。eg. add
  3. 使用解決方案領域名稱。eg. JobQueue;如果不能使用程式設計師熟悉的術語來給手頭的工作命名,就採用從所涉問題而來的名稱。
  4. 函式應短小(每行150字元,不超過100行,20行封頂最佳)。
  5. 函式只做一件事,同設計模式一樣,應儘量遵循單一權責原則及開閉原則。
  6. 每個函式中的語句,理應都要在同一抽象層級上。
  7. 沃德原則:如果每個例程都讓你感到深合己意,那就是整潔程式碼(並且不要怕長名稱方法名)
  8. 函式的引數:最理想的引數數量是零,其次是一,再次是二,儘量避免三。有足夠特殊的理由才能用三個以上引數(阿里巴巴Java開發手冊:相同引數型別,相同業務含義,才可以使用 Java 的可變引數,可變引數放在最後,儘量不用可變引數,避免使用 Object)
  9. 標識引數:醜陋不堪,render(Boolean isSuite)代表了該函式在標識為true會這樣做,為false會那樣做。應該一分為二——renderForSuite()和renderForSingleTest()
  10. 二元及三元函式:在當單個值的有序組成部分不確定時,多元函式並不是好的選擇
  11. 引數物件:如果函式看來需要兩個、三個或三個以上引數,就說明其中一些引數應該封裝為類了。
    Circle makeCircle(double x,double y,double radius)
    Circle makeCircle(Point center,double radius)

後者優於前者

  1. 函式與引數:應是一種非常良好的動詞/名詞對形式
    write(name)
  2. 無副作用:在執行函式時,應不該造成其他部分的更改
  3. 分隔指令與詢問:函式要麼做什麼事,要麼回答什麼事
  4. 使用異常代替返回錯誤程式碼,抽離Try/Catch程式碼塊。Try/Catch程式碼塊醜陋不堪,搞亂了程式碼結構,最好把其中內容單獨寫成一個函式。

二、註釋

  1. 註釋並不能美化糟糕的程式碼,最好用程式碼來闡述
  2. TODO 是一種程式設計師認為應該做,但由於某些原因目前還沒做的工作,它可能是要提醒刪除某個不必要的特性,或者要求他人注意某個問題等。
  3. 壞註釋:
    喃喃自語、多餘的註釋、誤導性註釋、循規式註釋、日誌式註釋、廢話註釋。(p55-p61)
  4. 不應當有的註釋:位置標記、括號後面的註釋、歸屬與署名、註釋掉的程式碼、HTML註釋、非本地資訊、資訊過多、不明顯的聯絡(p62-p65)
  5. 非公共程式碼中的Javadoc

三、格式

  1. 垂直格式
    單個檔案程式碼行數fitnese多在200行到500行之間

(作者認為儘量精簡程式碼在單個檔案中,並且檔案之間程式碼行數最小與最大值差距不要過大)

  1. 向報紙學習(自上而下,內容逐次展開)。實際程式設計中注意很難注意這個
  2. 適當的換行

    1. 方法與方法之間
    2. 變數與方法之間
    3. import 與類之間
    4. import 系統類和匯入包中的類或者自定義類之間
  3. 垂直距離

    1. 變數宣告應儘可能靠近其使用位置
    2. 實體變數應放在類的頂部宣告
    3. 相關函式應儘量放在一起,一個方法呼叫了該類中的另一個方法,應儘量放在一塊
    4. 概念相關的程式碼應該放在一起,過載的方法也算
  4. 橫向距離
    (一行程式碼所用字元 [阿里巴巴java開發手冊中有定義],水品對齊,水平方向上的取個與靠近-不認同)

    1. 同一層級的程式碼應該縮排至同一個層級(Python中採用強制縮排)

四、物件和資料結構

  1. 程式導向式程式碼與物件導向式程式碼(p89-p90)
    對於物件導向較難的事,對於過程式程式碼卻較容易,反之亦然。
  2. 得墨忒耳律認為,類 C 的方法f只應該呼叫以下物件的方法:

    1. C
    2. 由 f 建立的物件
    3. 作為引數傳遞給 f 的物件
    4. 由 C 的實體變數持有的物件
      方法不應呼叫有任何函式返回的物件的方法,換言之,只跟朋友談話,不與陌生人談話。(實際生產環境用jfinal service 調 dao 是用的該實體類的靜態內部物件,與ssm有不同)
  3. DTO(Data Transfer Objects) 裡最好不要有業務規則程式碼。

五、錯誤處理

  1. 使用異常而非返回碼
  2. 先寫 try – catch – finally
  3. 某種意義上,try 程式碼塊就像是事務,catch 程式碼塊將程式維持在一中持續狀態,無論 try 程式碼庫中發生了什麼均如此。
  4. 給出異常發生的環境說明。
  5. 別傳遞 null 值:可以用斷言代替判空。 assert p1 != null: “p1 should not be null”;

六、邊界

  1. 學習 log4j
  2. 使用上尚不存在的程式碼:介面卡模式(作者採用的)

七、類

  1. 類的組織:公共靜態變數->私有靜態變數->私有實體變數。很少出現公共實體變數
  2. 類應該短小:對於類,我們採用不同的衡量方法,計算權責。
  3. 單一權責原則:類或模組應有且只有一條加以修改的理由。
  4. 內聚:類應該只有少量實體變數,類中的每個方法都應該操作一個或多個這種變數。
  5. 文中舉例說明了一個 Sql 類的重構。將一個 Sql 類中所有的 CRUD 方法抽象成一個 generate 方法,並由相應的 CRUD 類繼承。(和現在 jfinal 的使用方法不一樣,雖然違反了設計模式開閉原則。但我認為對中小型專案開發,反設計模式更為便捷)

八、系統

  1. 依賴注入(Dependency Injection DI),控制反轉(Inversion of Control IOC):可以實現分離構造與使用,控制反轉將第二權責從物件中拿出來,轉義到另一個專注於此的物件中,從而遵循了單一權責原則。因為初始設定是一種全域性問題,這種授權機制通常要麼是 main 例程,要麼是有特定的目的的容器(Spring 中採用 ConcurrentHashMap 作為該容器)。
  2. 擴容:為達到鬆耦合,採用 AOP 實現某些程式碼的行為修改。(最終的系統,系統內的模組達到高內聚低耦合的狀態,並且易擴充套件)
  3. Java代理:詳情 p148

九、併發程式設計

  1. 併發是一種解耦策略。它幫助我們把做什麼(目的)和 何時(時機)做分解開。
  2. 常見的迷思與誤解
  3. 併發總能改進效能?
    併發有時候能改進效能,單隻在多個執行緒或處理器之間能分享大量等待時間的時候管用。事情沒那麼簡單。(編排考生時,又開了一個執行緒,編排所有考生)
  4. 編寫併發程式無需修改設計
    事實上,併發演算法的設計有可能與單執行緒系統的設計極不相同。目的與時間的解耦往往對系統結構產生巨大影響。
  5. 採用Web 或 EJB 容器的時候,理解併發問題並不重要(實際開發中,面向servlet開發。。。)
  6. 併發防禦原則
  7. 單一權責原則
    建議將併發相關程式碼與其他程式碼進行分離
  8. 限制資料作用域
    產生併發問題的原因,其實就是共享變數,在 JMM 中,共享變數存在於主存中,多個執行緒從主存中拷貝共享變數到當前執行緒中,線上程內部進行對變數的操作。 CPU 給每個執行緒分配時間片,當執行緒 A 將共享變數刷回主存中前,執行緒 B 可能得到了 CPU 分配的時間片,由於 A 執行緒未及時重新整理共享變數至主存,B 執行緒取得的是未經過 A 執行緒修改的值,會造成資料與理想情況不一致。最經典的多執行緒入門,進行自增操作時,如果開了多執行緒進行該操作,結果永遠是小於等於 期望值。例如 `java

int num=0;

    for(int i=0;i<100;i++){
      num++;
  }
 }