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、好註釋的類型:
- 法律信息
- 提供信息的註釋
- 對意圖的解釋
- 闡釋:把晦澀難懂的參數或返回值的意義翻譯成可讀形式
- 警示:警告會出現某種後果
- TODO註釋:表示該功能代碼待編寫或修改
- 放大某種不合理之物的重要性
- 公共API的javadoc
4、壞註釋的類型
- 讀者看不懂的喃喃自語
- 多餘的註釋(不能比代碼本身提供更多的信息,沒有證明代碼的意義,也沒有給出代碼的意圖或邏輯)
- 誤導式註釋
- 循規式註釋(例如:每個變量都要有註釋的規矩)
- 日誌式註釋
- 廢話註釋(例如:/ 默認的構造器 /)
- 能用函數或變量時就別用註釋
- 位置標記儘量少用
- 大括號後的註釋(如;try{……} //try)
- 歸屬與署名:源代碼控制系統是這類信息最好的歸屬地
- 註釋掉的代碼
- Html註釋
- 非本地信息
- 信息過多
- 不明顯的聯繫
- 函數頭
- 非公共代碼的javadoc
- 範例
四、格式
1、垂直格式
- 名稱應當一目瞭然,函數短小而精悍
- 封包聲明、導入聲明和每個函數之間都有空白行隔開
- 緊密相關的代碼應相互靠近
- 變量聲明應儘可能靠近其使用位置:
1)本地變量(局部)應在函數內頂部出現
2)循環中的控制變量在循環語句中聲明
3)實體變量(成員變量)應在類的頂部聲明
4)調用函數應儘可能放在被調用函數上面;
5)概念相關的代碼應該放在一起;
5.自上向下展示函數調用依賴順序
public void B(){
C();
}
public void C(){
D();
}
public void D(){
...
}
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、類應該短小
-
單一權責原則(SRP)
-
保持內聚性
若一個類中的每一個變量都被每個方法所使用,則該類具有最大的內聚性
應當嘗試將這些變量和方法拆分到兩個或多個類中,讓新的類更爲內聚;
3、爲了修改而組織
-
修改現存類有可能會破環其他代碼,應在編碼代碼的時候,將有可能修改的類(如:sql類)的每個接口方法都重構到從sql類派生出來的類中。
-
開放-閉合原則(OCP):類應當對外擴展開放,對修改封閉;
九、系統
1、將系統的構造與使用分離
- 分解main;將全部構造過程搬遷到main或被稱之爲main的模塊
- 使用抽象工廠模式
- 依賴注入(控制反轉)
十、併發編程
1、爲什麼要併發?
併發是一種解耦策略。它幫助我們把做什麼(目的)和何時做(時間)分解開。併發能明顯地改進應用程序的吞吐量和結構
2、併發防禦原則
- 單一權責原則,建議:分離併發代碼與其他代碼
- 限制數據作用域
- 謹記封裝數據;嚴格限制對可能被共享的數據的訪問。可以採用synchronized關鍵字在代碼中保護一塊使用共享對象的臨界區。
- 使用數據複本
- 線程應儘可能獨立:嘗試將數據分解到可被獨立線程操作的子集
3、警惕同步方法之間的依賴
- 避免使用一個共享對象的多個方法
4、保持同步區微小
- synchronized製造了鎖,帶來了延遲和額外開銷。
5、測試線程代碼
- 將僞失敗看作可能的線程問題
- 先使非線程代碼可工作,不要同時追蹤非線程缺陷和線程缺陷
- 編寫可調整的線程代碼
- 運行多於處理器數量的線程
- 在不同的平臺上運行
- 編寫可插拔的線程代碼
- 裝置試錯代碼
有兩種裝置代碼的方法:
1) 硬編碼:手工向代碼中插入wait()、sleep()、yield()、priority()的調用
2) 自動化:可以使用Aspect-Oriented Framework、CGLIB或ASM之類的工具通過編碼來裝置代碼