[讀書筆記]《代碼整潔之道》

Bjarne Stroustrup,C++之父對好代碼的定義是這樣的:

  • 邏輯應該是清晰的,bug難以隱藏;
  • 依賴最少,易於維護;
  • 錯誤處理完全根據一個明確的策略;
  • 性能接近最佳化,避免代碼混亂和無原則的優化;
  • 整潔的代碼只做一件事。

代碼規範佔據了十分重要的地位,好代碼具有不可估量的意義:

  • 在團隊合作中,規範的代碼能夠促進團隊合作;
  • 規範的代碼可以減少bug的處理;
  • 規範的代碼可以降低維護成本;
  • 規範的代碼有助於代碼審查;
  • 養成寫好代碼的習慣,有助於自身的成長。

我在看完《代碼整潔之道》之後,對它的一部分內容進行的了歸納總結:
(我歸納得比較籠統,想要更深入瞭解的還是建議去看原書,我的另一篇博客裏面有英文版和中文版的下載連接)

一、有意義的命名
二、函數
三、註釋
四、格式
五、對象和數據結構
六、錯誤處理
七、邊界
八、類
九、系統
十、併發編程

 
 


 

一、有意義的命名

1、指明計量名稱和計量單位

2、不使用魔術數(指未經定義直接在代碼中出現的數值

3、注意作爲變量名時的字母I和o(很像數字1和0

4、做有意義的區分

5、使用讀得出來的名稱(正確的單詞

6、使用可搜索的名稱

  單字母的名稱和數字變量很難被找出來
  名字長短應與其作用域大小相對應

7、不使用成員後綴/前綴

8、避免使用編碼,但若接口和實現必須選一個編碼,則選實現;

  ShapeFactoryImpl,甚至CShapeFactory都比IShapeFactory好得多

9、避免思維映射

10、類名和對象名應該是名詞或名詞短語

11、方法名應當是動詞或動詞短語

12、不使用俗語或俚語命名

13、每個概念對應一個詞

  (Get、fetch、retrieve)、(controler、manager、driver)等同義或近義的詞在同一個項目中只使用一個

14、別用雙關語,即避免將同一單詞用於不同目的

15、可以使用解決方案領域名稱以及所涉及問題領域的名稱(專業術語

16、添加有意義的語境

  可以添加前綴以提供語境,但更好的方案是創建類,然後把變量當作該類的成員字段
 
 

二、函數

1、函數應該短小,20行封頂最佳

  if、else、while語句中的代碼塊應該只有一行,寫函數調用語句是一個不錯的選擇

2、一個函數只做一件事

3、每個函數一個抽象層級,自頂向下讀代碼:向下規則

public void B(){
    C();
}
public void C(){
    D();
}
public void D(){
    ...
}

4、將switch語句埋到抽象工廠底下

5、使用描述性語句,別害怕長名稱

  例如:SetupTeardownIncluder

6、函數參數

  • 儘量減少函數參數,沒有最好
  • 如果函數要對輸入參數轉換,則結果體現在返回值上;
  • 不使用表示參數(即不傳入布爾值);
  • 函數二個及以上可考慮封裝成類;
  • 對於一元函數,函數與參數應形成 動詞/名詞對 形式,如:write(name)

7、避免做其他被藏起來的事情

8、分隔指令與詢問,例如將設置方法set和判斷是否存在attributeExits分成兩個方法

9、使用異常代替返回錯誤碼

  • 使用異常能把錯誤處理代碼從主路徑代碼中分離出來
  • 抽離try/catch代碼塊,另外形成函數
  • 錯誤處理函數只做錯誤處理這一件事情

10、消除重複

11、結構化編程

  每個函數、函數中的每個代碼塊都應只有一個入口、一個出口,即只有一個return,循環中不能有break或continue,且不能有goto語句,但在小函數中,偶爾出現return、break、continue沒有壞處,而goto只要在大函數中才有道理,應避免使用。

小結:一開始可以想寫什麼就寫什麼,最後再按規則慢慢修改,沒有必要強制自己一開始就寫出非常完美的代碼,這幾乎沒有能做到

三、註釋

1、註釋不能美化糟糕的代碼

2、用代碼來闡述自己的意圖

3、好註釋的類型:

  1. 法律信息
  2. 提供信息的註釋
  3. 對意圖的解釋
  4. 闡釋:把晦澀難懂的參數或返回值的意義翻譯成可讀形式
  5. 警示:警告會出現某種後果
  6. TODO註釋:表示該功能代碼待編寫或修改
  7. 放大某種不合理之物的重要性
  8. 公共API的javadoc

4、壞註釋的類型

  1. 讀者看不懂的喃喃自語
  2. 多餘的註釋(不能比代碼本身提供更多的信息,沒有證明代碼的意義,也沒有給出代碼的意圖或邏輯)
  3. 誤導式註釋
  4. 循規式註釋(例如:每個變量都要有註釋的規矩)
  5. 日誌式註釋
  6. 廢話註釋(例如:/ 默認的構造器 /)
  7. 能用函數或變量時就別用註釋
  8. 位置標記儘量少用
  9. 大括號後的註釋(如;try{……} //try)
  10. 歸屬與署名:源代碼控制系統是這類信息最好的歸屬地
  11. 註釋掉的代碼
  12. Html註釋
  13. 非本地信息
  14. 信息過多
  15. 不明顯的聯繫
  16. 函數頭
  17. 非公共代碼的javadoc
  18. 範例

四、格式

1、垂直格式

  1. 名稱應當一目瞭然,函數短小而精悍
  2. 封包聲明、導入聲明和每個函數之間都有空白行隔開
  3. 緊密相關的代碼應相互靠近
  4. 變量聲明應儘可能靠近其使用位置:
    1)本地變量(局部)應在函數內頂部出現
    2)循環中的控制變量在循環語句中聲明
    3)實體變量(成員變量)應在類的頂部聲明
    4)調用函數應儘可能放在被調用函數上面;
    5)概念相關的代碼應該放在一起;

     5.自上向下展示函數調用依賴順序

public void B(){
    C();
}
public void C(){
    D();
}
public void D(){
    ...
}

2、橫向格式

  1. 盡力保持代碼行短小
  2. 在賦值操作符周圍加上空格,如示例A;
        不在函數名和左圓括號之間加空格,函數調用括號中的參數一一隔開,如示例B;
//示例A
int i = 3//示例B
determinant(a, b, c);

//示例C
int temp = b*b - 4*a*c;

/* 乘法間不加空格,因爲乘法的優先級比減法高 */

3、縮進

4、while、for、if等語句的語句體用“{” “}”括起來

五、對象和數據結構

1、對象:暴露行爲,隱藏數據。便於添加新對象類型而無需修改既有行爲,但難以在既有對象中添加新行爲;

public class Square{
    public Point topLeft;
    public double side;
}

public class Circle{
    public Point center;
    public double radius;
}

public class Geometry{
    public double are(Object Shape){
        //求面積的行爲
    }
}

/** 1\若在Geometry類中添加一個求周長的方法,不會影響Square類和Circle類
    2\但添加一個Rectangle類有可能需要修改方法are(Object Shape)
*/

2、數據結構:暴露數據,沒有明顯行爲。便於向既有數據結構添加新行爲,同時也難以向既有函數添加數據結構;

public class Shape implements Shape{
    private Point topLeft;
    private double side;
    
    public double area(){
        //計算面積的方法
    }
}

public class Circle implements Shape{
    private Poit center;
    private double radius;
    private final double PI = 3.14159;

    public double area(){
        //計算面積的方法
    }
}

/** 添加一個Rectangle類,不會影響現有方法area();
    但在Shape中添加一個求周長的方法時,所有類都要加上該方法
*/


六、錯誤處理

1、使用異常代替錯誤返回碼

2、先寫try-catch-finally語句

3、使用不可控異常(運行時異常),可控異常違反開放/閉合原則

4、給出異常發生的環境說明

5、依調用者的需求定義異常

6、定義常規流程

7、避免返回null值,可以改爲返回空列表、空數組……,避免NullPointerException;

8、別傳遞null值


七、邊界

1、不要將類似Map這樣的邊界接口在系統中傳遞(即避免從公共API中返回邊界接口,或將邊界接口作爲參數傳遞給公共API)


八、類

1、類的組織

    類應該從一組變量列表開始,之後是公共函數
    公共靜態常量 -> 私有靜態變量 -> 私有實體變量

2、類應該短小

  1. 單一權責原則(SRP)

  2. 保持內聚性
        若一個類中的每一個變量都被每個方法所使用,則該類具有最大的內聚性
    應當嘗試將這些變量和方法拆分到兩個或多個類中,讓新的類更爲內聚;

3、爲了修改而組織

  • 修改現存類有可能會破環其他代碼,應在編碼代碼的時候,將有可能修改的類(如:sql類)的每個接口方法都重構到從sql類派生出來的類中。

  • 開放-閉合原則(OCP):類應當對外擴展開放,對修改封閉;


九、系統

1、將系統的構造與使用分離

  • 分解main;將全部構造過程搬遷到main或被稱之爲main的模塊
  • 使用抽象工廠模式
  • 依賴注入(控制反轉)

十、併發編程

1、爲什麼要併發?

併發是一種解耦策略。它幫助我們把做什麼(目的)和何時做(時間)分解開。併發能明顯地改進應用程序的吞吐量和結構

2、併發防禦原則

  1. 單一權責原則,建議:分離併發代碼與其他代碼
  2. 限制數據作用域
  3. 謹記封裝數據;嚴格限制對可能被共享的數據的訪問。可以採用synchronized關鍵字在代碼中保護一塊使用共享對象的臨界區。
  4. 使用數據複本
  5. 線程應儘可能獨立:嘗試將數據分解到可被獨立線程操作的子集

3、警惕同步方法之間的依賴

  • 避免使用一個共享對象的多個方法

4、保持同步區微小

  • synchronized製造了鎖,帶來了延遲和額外開銷。

5、測試線程代碼

  1. 將僞失敗看作可能的線程問題
  2. 先使非線程代碼可工作,不要同時追蹤非線程缺陷和線程缺陷
  3. 編寫可調整的線程代碼
  4. 運行多於處理器數量的線程
  5. 在不同的平臺上運行
  6. 編寫可插拔的線程代碼
  7. 裝置試錯代碼
      有兩種裝置代碼的方法:
        1) 硬編碼:手工向代碼中插入wait()、sleep()、yield()、priority()的調用
        2) 自動化:可以使用Aspect-Oriented Framework、CGLIB或ASM之類的工具通過編碼來裝置代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章