構建
目錄
理想設計重要性
理想設計
代碼技巧
設計流程
整潔代碼
參考
1、理想設計重要性
一個項目開始編寫乾淨的代碼是一個軟件產品的整個生命週期中使變化成本保持儘可能恆定的一項投資。因此,當寫乾淨的代碼(灰線)時最初的變化成本比快速和骯髒的編程(黑線)稍高,但支付卻相當快。尤其要記住的在軟件維護過程中大部分費用需要支付。隨著時間的推移,如果不重構乾淨的代碼,不潔代碼會導致技術債務增加。還有其他原因導致技術債務,如壞流程和缺少文件,但不潔的代碼是一個主要驅動因素。造成結果的是,響應變化的能力在下降(紅線)
所以,當有好的設計,會減少代碼成本、增加響應速度。簡單的說就是成本降低。 若有一個好的代碼設計,成本降低,相對的可以完成的事情就更多了,強者恆強弱者恆若。
2、理想設計
最小複雜度:分解多個子系統降低問題的複雜度、且設計短小的子程序。
易於維護:寫代碼也要替你的讀者想想,賦予可讀性。讀代碼時間要比寫代碼的時間要多,且不同維護人來看,所以此見事情很重要。
鬆散耦合:低耦合,儘量與其他不相關,獨立運作。相互依賴少。水管概念,連接越少裝修就越容易裝修。
高扇入:大量的類使用,如utility classes。把相關操作包到一個 utility class
低扇出:一個類裡少量用其他的類。最好低於七個。不然過於複雜。(應用越多錯誤率越高)
可擴展:改動某個地方而不會影響。
可重用性:最好供給大家一起共用,小少代碼重複。
可移植性
精簡性:去除不必要的代碼與註解。
層次性:中間層,負責新舊交替與封裝概念。
局不改變:保持局部變化意味著有設計變化不交叉的邊界。
容易刪除:當一個塊變得太複雜,它可以移除並替換一個或多個簡單的塊
3、代碼技巧
設計階段
多利用畫圖解釋
TFD(test-first Development)。
創建中央控制點,儘量在同一個地方找到某樣事務,相對安全容易。
信息統一,100 -> 定義為 MAX_EMPLOYEES 。改一個地方全部都改。沒有重複的代碼。
達到信息隱藏,不必要的不顯示於外部。分為 public、private。不要因為一個類需要用到就當成公開接口;若是private設定錯為public,有可能會誤導以為外面可以設置,增加維護成本。
類內的數據,不應該定義為全局。
避免循環依賴(retain cycle)
對象參數耦合:ObjectA -> ObjectB 若很多參數都需要設定,考慮 ObjectC 設計。傳遞 Object
高內聚:一箇中心目逼上的緊密合作。(高內聚 50%沒有錯誤,低內聚 18%沒有錯誤,功能內聚力來說: sin())
method 有 switch case,可能需要用到繼承來實作。看情況而定。
包的設計
a、包裝凝聚力
發佈重用等效原理(RREP)
重用的顆粒釋放該顆粒。
共同封閉原則(CCP)
一起改變的類被打包在一起。
共同重用原則(CRP)
一起使用的類被打包在一起
b、包裝耦合
無環依賴原則(ADP)
包的依賴關係圖,必須沒有周期。
穩定依賴原則(SDP)
取決於穩定的方向。
穩定抽象原則(SAP)
抽象穩定增加
子程序
命名要有意義、不要數字part1 part2..NO
縮小代碼規模 50~150行,便於查找問題、優化。(子程序長度與錯誤率成反比)
子程序不能修改請定義 const or final。
正確定義宏 macro #dfine Cub(a) ((a)(a)(a))
多定義枚舉,比 switch(type) typea: 比 switch(type) 0: 更清楚。
參數:少於7個;傳入與定義的參數就一定要用,避免閱讀障礙
輸入值,必須清楚知道其範圍。
防禦式子程序:比如除以零...等等。或有可能造成crash的可能。
錯誤處理方式:返回錯誤代碼(錯誤代碼以怎樣顯示給用戶?)、或error寫於日誌傳於Server(測試時候不應該出現的條件,斷言直接crash)、嚴重的問題以關閉程序如交易。
變量
命名,有意義 9-16 字符
存活時間
變成
如下
作用域。越小越好。減少用全局數據。
不要有 magic number。129.1000...等等的 hard code。
一個變量單一用途,有時候會 temp 從上面用到下面。不好的作法
類第一個字符大寫,變量都小寫,如 Widget widget;
變量不要有數字,width1 width2
if or (switch case)
if( 'a' >= inputCharacter && 'z' <= inputCharacter) -> if(IsLetter(inputCharacter)) 更容易讀。
正常情況放前面,if(status == sucessful)
switch case 頻率越高放於上面 或 按照字母
其他
之前看人開發,有把 doc 文件放於 eclipse 上面。編譯上不編譯。這樣會達到不錯的效益嗎?
設計實踐遇到問題:愛迪生 1000次失敗浪費,已經發現1000種材料不能用。
4、設計流程
階段性分解
類的設計流程
創建類
創建子程序
審查測試類
子程序的設計流程
設計子程序。定義子程序解決之問題。
決定如何測試子程序。
-
檢查設計
編寫子程序代碼
審查測試子程序
寫偽代碼 -> 變成註釋 -> 填充代碼
檢查代碼
收尾 -> 完成。
5、整潔代碼
可以用於自我代碼審查,或幫別人代碼審查用;或重構依據
註釋
不恰當的信息、廢棄的註釋、冗餘註釋、糟糕的註釋、註釋掉的代碼,都應該刪除殆盡。
環境
需要多步才能實現的構建、需要多步才能做到的測試。(應該需要將多步驟簡約簡單步驟)
函數
過多的參數:三個參數需要質疑。
輸出參數
標識參數:參數大聲宣告,令人疑惑就該刪除。
死函數:不調用的函數,delete。
一般性問題
1、一個源文件中存在多種語言:儘量減少語言數量。
2、明顯的行為未被實現:函數名稱必須和實作相同,否則會在閱讀者來看會越來越不信任。
3、不正確的邊界行為
4、忽視安全:別忽略僅告信息。
5、重複:避免重複代碼,若使用switch
6、在錯誤的抽象層級上的代碼
7、基類依賴於派生類:基類不應該提到派生類的名稱。
8、信息過多:外部調用的方法越少越好。
9、死代碼、混淆視聽:不會執行的代碼或沒有用到的參數需要刪除,如 try catch ...等等。(所以需要有審查軟件, 檢查代碼是否有用到,XCode的潛在問題檢查)
10、垂直分割:宣告變量與使用變量儘量靠近,幫助閱讀。
11、前後不一致:假設 response = HTTPServletResponse ,則不會有其他 response1 = HTTPServletResponse, 必須一致。
12、人為耦合:宣告的變量和長量必須判斷是否一般使用?一般使用則定義共用的文檔中,特定使用的則定義於 類中。若是一般使用的定義於特定類中,就會造成必須要import,而造成代碼耦合的狀況。
13、特性依戀
14、選擇算子參數:選擇用多個函數 取代 一個函數里面需要很多 if else
15、晦澀的意圖:代碼儘量可以表達。
16、位置錯誤的權責
17、不恰當的靜態方法:Math.max(double a ,double b) 是個良好的靜態變量
18、使用解釋性變量
19、函數名稱應該表達其行為
20、理解算法:獲取知識和理解通常是重構。
21、把邏輯依賴改為物理依賴
22、用多態代替IF/ELSE或者SWITHC/CASE:當遇到很多判斷時,就需要判斷是否需要再用其他的子類(派生類) 實現
23、遵循標準約定:約定、並且代碼需要完全遵守。
24、用命名常量代替魔術師
25、準確:必須要準確input,來確保執行的正確性,如貨幣處理數據,使用整數,並恰當處理四捨五入。
26、結構甚於約定:好的結構>好的命名,如 switch case就要用繼承等等方式實作。這更為重要。
27、封裝條件:if(this.ShouldBeDeleted(定時器))優選於if(&& timer.HasExpired!timer.IsRecurrent)
28、避免否定性條件:儘量用肯定 if(true)
29、函數只該做一件事
30、掩蔽時序耦合:不應該掩飾時序耦合。儘量刻意寫成如下,必須要透過前一個值來完成下一次的工作。
public void dive(String reason){
Gradient gradient = saturateGradient();List<spline> splines = reticulateSplines(gradient);
diveForMoog(splines, reason);
}/<spline>31、別隨意:結構隨意會造成別人想修改它,若是都保持一致其他人就會使用它。
32、封裝邊界條件
33、函數應該只在一個抽象層級上
34、在較高層級放置可配置數據
35、避免傳遞瀏覽:避免寫 a.getB().getC().doSomething();
Java
1、通過使用通配符避免過長的導入清單:通過使用 import package.* ,避免很攏長的定義。
2、不要繼承常量
3、常量VS枚舉:枚舉提供更多的資訊。
名稱
1、採用描述性名稱
2、名稱應該與抽象層級相符:Class name 和其 Method 應該要符合,如 Moden 必須搭配 connect method ,而非 dial
3、儘可能使用標準命名法
4、無歧義的名稱:有些解釋會造成名稱過長,但重要性 可以解釋 > 名稱過長。
5、為較大作用範圍選用較長的名稱:以 i 或 j 命名,只是用於 5 行代碼以內。若代碼超過 5 行就顯得過了。
6、避免編碼:m_ 或 f 之類的名稱儘量避免。
7、名稱應該說明副作用:createOrReturnOos 取代 getOos ,因為當 Oos 空值則以 create 並且 return。
測試
1、測試不足:需要全部條件驗證才算足夠。
2、使用覆蓋率工具
3、別略過小測試
4、被忽略的測試就是對不確定事物的疑問
5、測試邊界條件:需要定義精準度、範圍,邊界時常發現缺陷。
6、全面測試相近的缺陷
7、測試失敗的模式有啟發性:儘量寫完的測試用例。
8、測試覆蓋率的模式有啟發性
9、測試應該快速:快速測試有機會測出更完整的測試。
現在你開始審視你的代碼,看看有哪些地方是可以改進的,甚至於有問題的。雖然不能一定做到百分之百,也不一定要全面遵守上面的約定,但是讓自己代碼更好,是程序員的職責。不要把它當工作,當成喜歡的,一件藝術品來完成。
從遺留的代碼到乾淨的代碼
總是有一個正在運行的系統,在小步驟裡更改系統,從一個運行狀態到另一個運行狀態。
識別功能
在您的代碼裡確定現有的功能,,並根據有關它們未來的發展(變化的可能性和風險)再來優先考慮他們。
引入邊界接口可測性
重構的界限,你的系統接口,以便可以模擬環境測試的雙精度(假貨,嘲笑,存根,模擬器)。
寫功能驗收測試
覆蓋功能驗收測試,以建立一個安全網重構。
識別組件
在一個特徵中,確定所使用的組件提供的功能。根據未來發展的相關性(可能性和風險的變化)的優先次序來排列組件。
重構組件之間的接口
重構(或引進)之間的接口組件,以便每個組件都可以在隔離的環境中測試。
寫組件的驗收測試
覆蓋功能由組件的功能與驗收測試提供。
決定每個組件:重構,流程再造,保留
決定是否要重構,再造或保留它的每個組件。
重構
a、重構組件
內重新設計類組件和重構一步步驟(見重構的格局)。添加單元測試的每一個新設計的一類。
b、重新設計組件
使用ATDD和TDD(見清潔ATDD/ TDD小抄)的重新實現組件。
c、保留組件
如果你預計未來只有少數的變化到一個組件,並且該組件在過去很少有缺陷,可以考慮保留它。
重構模式
調解分歧——統一類似的代碼
更改的代碼逐級兩條,直到它們是相同的。
隔離變更
首先,隔離的代碼從剩餘部分進行重構。然後重構。最後,撤消隔離。
遷移數據
移動從一個表示到另一個暫時性重複數據結構。
臨時並行實現
“重構”通過引入臨時並行的算法實現。後一個調用者切換等。當不再需要時,刪除舊的解決方案。
非軍事區的組件
介紹了內部組件邊界和一切不必要的內部邊界外推到組件接口和內部邊界之間的非軍事區。重構組件接口與內部邊界相匹配並消除非軍事區。
如何瞭解乾淨的結對編程
一個是驅動器(負責編寫代碼),另一個是導航器。(負責按照編碼準則來維持解決方案與架構)
提交評論:同行開發人員針對乾淨的代碼的指引和設計對代碼進行檢查。
編碼道場:
一組開發人員在編碼道場走到一起,鍛鍊他們的技能。兩個開發人員解決問題(形比賽)的結對編程。 其餘的人員一起觀察。 10分鐘後,該組旋轉建立一個新的對。觀察員可以批判當前的解決方案,但只有當所有的測試都是綠色的。 (所以在結隊邊程需要不斷交換,觀察員可做評論,畢竟每個人的想法都不一樣,可以互相學習,成長更快!!)
6、參考
代碼大全(第2版)
代碼整潔之道
閱讀更多 潘江 的文章