阿里妹導讀:你有沒有遇到過這種狀況:過幾周或者幾個月以後,再看到本身寫的代碼,感受一團糟,不由懷疑人生?咱們天天都與代碼打交道,但當被問道什麼是好的代碼時,不少人可能會先愣一下,而後給出的回答要麼比較空泛,要麼比較散,沒辦法簡單明瞭地歸納出來。今天,咱們就來講什麼是好的代碼?
一句話歸納
衡量代碼質量的惟一有效標準:WTF/min —— Robert C. Martingit
Bob大叔對於好代碼的理解很是有趣,對我也有很大的啓發。咱們編寫的代碼,除了用於機器執行產生咱們預期的效果之外,更多的時候是給人讀的,這個讀代碼的多是後來的維護人員,更多時候是一段時間後的做者本人。程序員
我敢打賭每一個人都遇到過這樣的狀況:過幾周或者幾個月以後,再看到本身寫的代碼,感受一團糟,不由懷疑人生。編程
咱們本身寫的代碼,一段時間後本身看尚且如此,更別提拿給別人看了。函數
任何一個傻瓜都能寫出計算機能夠理解的代碼。惟有寫出人類容易理解的代碼,纔是優秀的程序員。 —— Martin Fowlerspa
因此,談到好代碼,首先跳入本身腦子裏的一個詞就是:整潔。好的代碼必定是整潔的,給閱讀的人一種如沐春風,賞心悅目的感受。設計
整潔的代碼如同優美的散文。 —— Grady Booch版本控制
很難給好的代碼下一個定義,相信不少人跟我同樣不會認爲整潔的代碼就必定是好代碼,但好代碼必定是整潔的,整潔是好代碼的必要條件。整潔的代碼必定是高內聚低耦合的,也必定是可讀性強、易維護的。code
高內聚低耦合對象
高內聚低耦合幾乎是每一個程序員員都會掛在嘴邊的,但這個詞太過於寬泛,太過於正確,因此聰明的編程人員們提出了若干面向對象設計原則來衡量代碼的優劣:blog
這些原則想必你們都很熟悉了,是咱們編寫代碼時的指導方針,按照這些原則開發的代碼具備高內聚低耦合的特性。換句話說,咱們能夠用這些原則來衡量代碼的優劣。
但這些原則並非死板的教條,咱們也常常會由於其餘的權衡(例如可讀性、複雜度等)違背或者放棄一些原則。好比子類擁有特性的方法時,咱們極可能打破里氏替換原則。再好比,單一職責原則跟接口隔離原則有時候是衝突的,咱們一般會捨棄接口隔離原則,保持單一職責。只要打破原則的理由足夠充分,也並不見得是壞的代碼。
可讀性
代碼只要具備了高內聚和低耦合就足夠好了嗎?並不見得,我認爲代碼還必須是易讀的。好的代碼不管是風格、結構仍是設計上都應該是可讀性很強的。能夠從如下幾個方面考慮整潔代碼,提升可讀性。
大到項目名、包名、類名,小到方法名、變量名、參數名,甚至是一個臨時變量的名稱,其命名都是很嚴肅的事,好的名字須要斟酌。
名副其實:好的名稱必定是名副其實的,不須要註釋解釋便可明白其含義的。
/** * 建立後的天數 **/ int d; int daysSinceCreation;
後者比前者的命名要好不少,閱讀者一會兒就明白了變量的意思。
容易區分:咱們很容易就會寫下很是相近的方法名,僅從名稱沒法區分二者到底有啥區別(eg. getAccount()與getAccountInfo()),這樣在調用時也很難抉擇要用哪一個,須要去看實現的代碼才能肯定。
可讀的:名稱必定是可讀的,易讀的,最好不要用自創的縮寫,或者中英文混寫。
足夠短:名稱固然不是越長越好,應該在足夠表達其含義的狀況下越短越好。
良好的代碼格式也是提升可讀性很是重要的一環,分爲垂直格式和水平格式。
垂直格式:一般一行只寫一個表達式或者子句。一組代碼表明一個完整的思路,不一樣組的代碼中間用空行間隔。
public class Demo { @Resource private List<Handler> handlerList; private Map<TypeEnum, Handler> handlerMap = new ConcurrentHashMap<>(); @PostConstruct private void init() { if (!CollectionUtils.isEmpty(handlerList)) { for (Handler handler : handlerList) { handlerMap.put(handler.getType(), handler); } } } public Result<Map<String, Object>> query(Long id, TypeEnum typeEnum) { Handler handler = handlerMap.get(typeEnum); if (null == handler) { return Result.returnFailed(ErrorCode.CAN_NOT_HANDLE); } return handler.query(id); } }
若是去掉了空行,可讀性大大下降:
public class Demo { @Resource private List<Handler> handlerList; private Map<TypeEnum, Handler> handlerMap = new ConcurrentHashMap<>(); @PostConstruct private void init() { if (!CollectionUtils.isEmpty(handlerList)) { for (Handler handler : handlerList) { handlerMap.put(handler.getType(), handler); } } } public Result<Map<String, Object>> query(Long id, TypeEnum typeEnum) { Handler handler = handlerMap.get(typeEnum); if (null == handler) { return Result.returnFailed(ErrorCode.CAN_NOT_HANDLE); } return handler.query(id); } }
類靜態變量、實體變量應定義在類的頂部。類內方法定義順序依次是:公有方法或保護方法 > 私有方法 > getter/setter方法。
水平格式:要有適當的縮進和空格。
團隊統一:一般,同一個團隊的風格儘可能保持一致。集團對於Java開發進行了很是詳細的規範。
類和函數應短小,更短小:類和函數都不該該過長(集團要求函數長度最多不能超過80行),過長的函數可讀性必定差,每每也包含了大量重複的代碼。
函數只作一件事(同一層次的事):同一個函數的每條執行語句應該是統一層次的抽象。例如,咱們常常會寫一個函數須要給某個DTO賦值,而後再調用接口,接着返回結果。那麼這個函數應該包含三步:DTO賦值,調用接口,處理結果。若是函數中還包含了DTO賦值的具體操做,那麼說明此函數的執行語句並非在同一層次的抽象。
參數越少越好:參數越多的函數,調用時越麻煩。儘可能保持參數數量足夠少,最好是沒有。
別給糟糕的代碼加註釋,重構他:註釋不能美化糟糕的代碼。當企圖使用註釋前,先考慮是否能夠經過調整結構,命名等操做,消除寫註釋的必要,每每這樣作以後註釋就多餘了。
好的註釋提供信息、表達意圖、闡釋、警告:咱們常常遇到這樣的狀況:註釋寫的代碼執行邏輯與實際代碼的邏輯並不符合。大多數時候都是由於代碼變化了,而註釋並無跟進變化。因此,註釋最好提供一些代碼沒有的額外信息,展現本身的設計意圖,而不是寫具體如何實現。
刪除掉註釋的代碼:git等版本控制已經幫咱們記錄了代碼的變動歷史,不必繼續留着過期的代碼,註釋的代碼也會對閱讀等形成干擾。
錯誤處理很重要,但他不能搞亂代碼邏輯:錯誤處理應該集中在同一層處理,而且錯誤處理的函數最好不包含其餘的業務邏輯代碼,只須要處理錯誤信息便可。
拋出異常時提供足夠多的環境和說明,方便排查問題:異常拋出時最好將執行的類名,關鍵數據,環境信息等均拋出,此時自定義的異常類就派上用場了,經過統一的一層處理異常,能夠方便快速地定位到問題。
特例模型可消除異常控制或者null判斷:大多數的異常都是來源於NPE,有時候這個能夠經過Null Object來消除掉。
儘可能不要返回null,不要傳null參數:不返回null和不傳null也是爲了儘可能下降NPE的可能性。
討論了好代碼的必要條件,咱們再來看看好代碼的否認條件:什麼不是好的代碼。
Kent Beck使用味道來形容重構的時機,我認爲當代碼有壞味道的時候,也表明了其並非好的代碼。
代碼的壞味道
重複多是軟件中一`切邪惡的根源。 —— Robert C.Martin
重複:Martin Fowler也認爲壞味道中首當其衝的就是重複代碼。不少時候,當咱們消除了重複代碼以後,發現代碼就已經比原來整潔多了。
函數過長、類過大、參數過長:過長的函數解釋能力、共享能力、選擇能力都較差,也不易維護。
過大的類表明了類作了不少事情,也經常有過多的重複代碼。
參數過長,不易理解,調用時也容易出錯。
發散式變化、霰彈式修改、依戀情結:若是一個類不是單一職責的,則不一樣的變化可能都須要修改這個類,說明存在發散式變化,應考慮將不一樣的變化分離開。
若是某個變化須要修改多個類的方法,則說明存在霰彈式修改,應考慮將這些須要修改的方法放入同一個類。
若是函數對於某個類的興趣高於了本身所處的類,說明存在依戀情結,應考慮將函數轉移到他應有的類中。
數據泥團:有時候會發現三四個相同的字段,在多個類和函數中均出現,這時候說明有必要給這一組字段創建一個類,將其封裝起來。
過多的if...else 或者使用switch:過多的if...else或者switch,都應該考慮用多態來替換掉。甚至有些人認爲除個別狀況外,代碼中就不該該存在if...else。
本文首先一句話歸納了我認爲的好代碼的必要條件:整潔,接着具體分析了整潔代碼的特色,又分析了好代碼的否認條件:什麼樣的代碼不是好的代碼。僅是本人的一些看法,但願對各位之後的編程有些許的幫助。
我認爲僅僅編寫出可運行的代碼是遠遠不夠的,還要時刻注意代碼的整潔度,留下一些漂亮的代碼。
參考:
《重構——改善既有代碼的設計》Martin Fowler
《代碼整潔之道》 Robert C. Martin
《Java開發規約中文版 》阿里巴巴集團約碼項目組
本文來自雲棲社區合做夥伴「阿里技術」,如需轉載請聯繫原做者。