天天看點

程式員應該如何寫優雅代碼,整潔代碼,疊代優化 任重道遠

如何讓自己的代碼更加優雅,如何讓自己的代碼越來越健壯,如何讓自己跳出天天都在處理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:測試應該快速。