如何讓自己的代碼更加優雅,如何讓自己的代碼越來越健壯,如何讓自己跳出天天都在處理bug的怪圈。如何讓自己過了2個月後看自己編輯的代碼依然輕輕鬆鬆就知道它是幹嘛的。
一個開發了50年的老程序員Bob大叔告訴你就得這麼幹。
內容整理自Robert C. Martin的《代碼整潔之道》
第一章 整潔代碼
1,整潔代碼力求集中,每個函數、每個類和每個模塊都全神貫注於一件事。
2,整潔代碼簡單直接,從不隱藏設計者的意圖。
3,整潔代碼應當有單元測試和驗收測試。它使用有意義的命名,代碼通過其字面表達含義。
4,消除重複代碼,提高代碼表達力。
5,時時保持代碼整潔。
第二章 有意義的命名
1,使用體現本意的命名能讓人更容易理解和修改代碼。
2,編程本來就是一種社會活動。
3,盡力寫出易於理解的代碼
第三章 函數
1,一個函數應該只做一件事(高內聚),無副作用。
2,自頂向下閱讀代碼,如同是在閱讀報刊文章。
3,長而具有描述性的函數名稱,好過描述性的長註釋。
4,少用輸出參數。
5,拒絕boolean型標識參數。
例: CopyUtil.copyToDB(isWorkDB) --> CopyUtil.copyToWorkDB(), CopyUtil.copyToLiveDB()
6,使用異常代替返回錯誤碼,錯誤處理代碼就能從主路徑代碼中分離出來得到簡化。
7,寫代碼很像是寫文章。先想怎麼寫就怎麼寫,然後再打磨:分解函數、修改名稱、消除重複。
8,編程其實是一門語言設計藝術,大師級程序員把程序系統當做故事來講。使用準確、清晰、富有表達力的代碼來幫助你講故事。
第四章 註釋
1,別給糟糕的代碼加註釋----重寫吧。
2,把力氣花在寫清楚明白的代碼上,直接保證無需編寫註釋。
3,好的註釋:
法律信息
提供信息
解釋意圖
警示
TODO註釋
第五章 格式
1,代碼格式很重要。代碼格式關乎溝通,而溝通是專業開發者的頭等大事。
2,向報紙格式學習代碼編寫。
第六章 對象和數據結構
1,對象把數據隱藏於抽象之後,只提供操作數據的函數。
數據結構暴露其數據,沒有提供有意義的函數。
2,The Law of Demeter:模塊不應去了解它所操作的對象內部細節。
第七章 錯誤處理
1, 使用異常而非返回錯誤碼.
2, try-catch-finally, log出錯信息.
3, 不要返回null,不要傳遞null。
NULL Object模式, 例:Collections.emptyList();
第十章 類
1,自頂向下原則:讓程序讀起來就像是一篇報紙文章。
2,method可以是protected,以便於單元測試。
3,SRP:類或模塊應有且僅有一個加以修改的原因。類名應準確描述其職責。高內聚。
4,開放閉合原則OCP、依賴倒置原則DIP
5,變量名、方法名、類名都是給代碼添加註釋的一種手段。
第十二章 迭代前進
1, 緊耦合的代碼難以編寫單元測試。
2,單元測試消除了對清理代碼會破壞代碼的恐懼。
3,寫出自己能理解的代碼很容易,軟件項目的主要成本在於長期維護。
4,代碼應當清晰表達其作者的意圖;測試代碼可以通過實例起到文檔作用。
第十四章 逐步改進
1,編程是一種技藝。要編寫整潔代碼,必須先容忍髒代碼,然後清理!
2,寫出好文章就是一個逐步改進的過程
第十五章 JUNIT內幕
1.條件判斷應該封裝,從而更清晰的表達代碼意圖
2.函數變量不要和成員變量同名
3.否定式比肯定式更難理解
4.建議每個函數的定義都正好在其被調用的位置後面
C1:不恰當的信息
修改歷史記錄只會用大量過時而無趣的文本搞亂源代碼文件。通常作者,最後修改時間等元數據不應該在註釋中出現。註釋只應該描述有關代碼和設計的技術性信息
C2:廢棄的註釋
過時、無關和不正確的註釋就是廢棄的註釋。如果發現廢棄的註釋,最好儘快更新或刪除掉。
C3:冗餘註釋
如果註釋描述的是某種充分自我描述了的東西,那麼註釋就是多餘
i++;//increment i
C4:糟糕的註釋
如果要編寫一條註釋,就花時間保證寫出最好的註釋
C4:註釋掉的代碼
看到註釋掉的代碼,就刪除它。放心源代碼控制系統還會記住它
環境
E1:需要多少步才能實現構建
應當單個命令簽出系統,單個命令構建它
E2:需要多少步才能做到的測試
發出單個指令就能運行全部單元測試
函數
F1:過多的參數
函數的參數量應該少。沒參數最好。三個以上的參數非常非常值得質疑
F2:輸出參數
輸出參數違反直覺。期望參數用於輸入而非輸出。如果函數非要修改什麼東西的狀態不可。就修改它所在對象的狀態就好
F3:標識參數
boolean參數大聲宣告函數做了不止一件事。他們令人迷惑應該消滅掉
F4:死函數
永遠不會調用的方法應該丟棄。保留死代碼純屬浪費。別害怕刪除函數。記住,源代碼控制系統還會記住它
17.4一般性問題。
G1:一個源文件存在多種語言
理想的源文件包括且只包括一種語言。現實上,我們可能會不得不使用多於一種語言。單應該盡力減少源文件中額外語言的數量和範圍。
G2:明顯的行爲未被實現
如果明顯的行爲未被實現,讀者和用戶就不能再依靠他們對函數名稱的直覺。他們不再信任原作者,不得不閱讀代碼細節。
G3:不正確的邊界行爲
每種邊界條件、每種極端情形、每個異常都代表了某種可能搞亂優雅而直白的算法的東西。別依賴直覺。追索每種邊界條件,並編寫測試。
G4:忽視安全。
關閉失敗測試,告訴自己過後再處理,這和假裝刷信用卡不用還錢一樣壞。
G5:重複
每次看到重複代碼,都代表遺漏了抽象。重複的代碼可能成爲子程序或乾脆是另一個類。將將重複代碼疊放進類似的抽象,增加了你的設計語言的詞彙量。其他程序員可以用到你創建的抽象。編碼越來越快,錯誤越來越少。因爲你提升了抽象層級。
1.重複最明顯的形態就是你不斷看到明顯一樣的代碼,可用單一方法來替代之。
2.較隱蔽的形態是不同模塊不斷重複出現、檢測同一組條件的switch/case或if/else鏈。可以用多態替代之。
3.更隱蔽的形態是採用類似算法但是具體代碼 行不同的模塊。這也是一種重複,可以使用模板方法或者策略模式來修正。
G6.在錯誤的抽象層級上的代碼
所有較低層級的概念放在派生類中,所有較高層級概念放在基類中。
例如:只與細節實現有關的常量、變量或工具函數不應該在基類。基類應該對這些東西一無所知。
較低層級概念和較高層級概念不應該混在一起。不能就錯誤放置的抽象模型撒謊。孤立抽象是軟件開發者最難做到的事之一,而且一旦做錯也沒快捷的修復手段。
G7:基類依賴於派生類
基類應該對派生類應該一無所知。
有例外:有時,派生類數量嚴格固定,而基類中擁有派生類之間選擇的代碼,在有限狀態機的實現中這種情形很多見。
G8:信息過多
設計良好的模塊有着非常小的接口,讓你事半功倍。設計良好的接口並不提供許多需要依靠的函數。耦合度就低。
優秀的程序員學會限制類或模塊中暴露的接口數量。類中的方法越少越好。函數知道的變量越少越好。類擁有的實體變量越少越好。
隱藏你的數據,隱藏你的工具函數,隱藏你的常量和臨時變量。不用創建大量方法或大量實體變量的類。不要爲子類創建大量受保護變量和函數。儘量保持接口緊湊。
G9:死代碼
刪掉。
G10: 垂直分隔
變量和函數應該在靠近被使用的地方定義。本地變量應該正好在其首次被使用的位置上面聲明,垂直距離要斷。本地變量不應該在被使用之處幾百行以外聲明。
私有函數應該剛好在其首次被使用的位置下面定義。
G11:前後不一致。
如果在特定函數中用名爲response的變量來持有HttpServletResponse對象,則在其他用到HttpServletResponse對象的函數中也用同樣的變量名。
前後一致,一旦堅決貫徹。就能讓代碼更加易於閱讀和修改。
G12:混淆視聽
沒有實現的默認構造器有什麼用處呢。他只會用無意義的雜碎搞亂對代碼的理解
沒有用到的變量,從不調用的函數,沒有信息量的註釋等等,這些都是應該移除的廢物。
保持源文件整潔,良好地組織,不被搞亂。
G13:人爲耦合
不互相依賴的東西不應該耦合。
對於在特殊類中聲明一般目的的static函數。普通的枚舉類不應該在特殊類中包括。
花點時間研究應該在什麼地方聲明函數、常量和變量。不要爲了方便隨手放置。然後置之不理。
G14:特性依戀
類的方法應該只對其所屬類的變量和函數感興趣,不應該垂青其他類的變量和函數。但是有時也無法避免,可以適當妥協。
G15:選擇算子參數。
沒有什麼比在函數末尾遇到一個fasle參數更爲可憎的事情。遇到整個情況,可以將這個函數切割成多個函數。
使用多個函數通常優於向單個函數傳遞某些代碼來選擇函數行爲。
G16: 晦澀的意圖。
G17:位置錯誤的權責
代碼應該放在讀者自然而然期待它所在的地方。
G18:不恰當的靜態方法
如果用靜態函數,確保沒機會打算讓它有多態行爲。
G19:使用解釋性變量
讓程序可讀的最有力方法之一就是將計算過程打散成在用有意義的單詞命名的變量中放置的中間值。
G20:函數名稱應該表達其行爲。
如果函數向日期添加5天並且修改該日期,就應該命名addDaysTo或者increaseByDays
如果函數返回一個表示5天后訂單日期,而不修改日期實體就該叫做daysLater或daysSince。
如果你必須查看函數的實現(或文檔)才知道他做什麼,就該換個更好的函數名。
G21:理解算法
在你認爲自己完成某個函數之前,確認自己理解了它是怎麼工作的。通過全部測試還不夠好,你必須知道解決方法是正確的。
獲得這種知識和理解的最好路徑,往往是重構函數。得到某種整潔而足具表達力、清楚呈示如何工作的東西。
G22: 把邏輯依賴改爲物理依賴
G23:用多態替代if/else 或則switch/case
G24:遵循標準約定
推薦《阿里巴巴java開發手冊》
G25:用命名常量替代魔術數
G26:準確
1.期望某個查詢的第一次匹配就是唯一匹配可能過於天真
2.用浮點數標識貨幣幾乎就是犯罪了。等等
在代碼中做決定時,要確認自己足夠準確。明確自己爲和要這麼做。如果遇到異常如何處理。
如果你打算調用可能返回null的函數,確認自己檢查了null值
如果查詢你認爲是數據庫唯一的記錄,確保代碼檢查不存在其他記錄。
G27:結構甚於約定
堅守結構甚於約定的設計決策。
比如用到良好命名的枚舉的switch/case要弱於用於抽象方法的基類。沒人會被強迫每次都以同樣方式實現switch/case語句,但基類卻讓具體類必須實現所有的抽象方法。
G28:封裝條件
如果沒有if或while語句的上下文,布爾邏輯就難以理解。應該把解釋了條件意圖的函數抽離出來。
G29:避免否定性條件。
否定式比肯定式難明白一些。
G30:函數只該做一件事情。
G31:掩蔽時序耦合。
每個函數都產出下一個函數所需要的結果,這樣一來就沒理由不按順序調用了。這點額外的複雜度卻暴露了該情況真正的時序複雜性
G32:別隨意
G33 :封裝邊界條件。
邊界條件難以追蹤,把處理邊界條件的代碼集中到一處,不要散落於代碼中。我們不想見到四處散落的+1 和-1字樣。
G34:函數應該只在一個抽象層上。
G35:在較高層級放置可配置數據
G36:避免傳遞瀏覽
我們不想讓摸個模塊瞭解太多其協作者的信息。
如果A與B協助,B與C協作,我們不想讓A瞭解C的信息。
java
J1:通過使用通配符避免過長的導入清單。
J2:不要繼承常量,別利用繼承欺騙編程語言的作用範圍規則,應該使用靜態導入。
J3:枚舉VS常量
名稱
N1:採用描述性名稱
N2:名稱應與抽象層級相符
N3:儘可能使用標準命名法
N4:無歧義的名稱
N5:爲較大作用範圍選用較長名稱
N6:編碼編碼:不要用匈牙利命名法玷污你的名稱
N7:名稱應該說明副作用 不要用名稱掩蔽副作用。
不要用簡單的動詞來描述做了不止一個簡單動作的函數。
createOrReturnOos
測試
T1:測試不足
T2:使用覆蓋率工具
T3:別略過小測試 小測試容易編寫,其文檔上的價值高於編寫成本。
T4:被忽略的測試就是對不確定事物的疑問。
T5:測試邊界條件
T6:全面測試相近的缺陷,可能發現缺陷不止一個
T7:測試失敗的模式有啓發性
T8:測試覆蓋率的模式有啓發性
T9:測試應該快速。