如何讓你的代碼更乾淨
已發表: 2020-06-24開發人員大部分時間都在閱讀和維護現有代碼。 我們試圖了解它的作用以及如何更改或擴展它。 那麼為什麼只關注性能呢?
在構建持久的代碼時,可讀性應該同樣重要。
在開髮長壽命軟件時,具有出色的代碼可讀性至關重要。 無論是維護舊代碼、添加新功能還是更改舊代碼,可讀的代碼都讓工作變得更加容易。
另一個好處是當您處於新開發人員的入職位置時。 成功的入職依賴於現有的代碼可讀性。
現實情況是,當您開始開發一個已經開發多年的產品時,您將不得不在某個時候處理遺留代碼。 當您被成千上萬行並非您編寫的不可讀代碼所淹沒時,會積累很多挫敗感。 維護它並以此為基礎只會讓情況變得更糟。
在 Mediatoolkit 中,我們正在開發我們的產品 long-term 。 一方面,這意味著我們避免短期修復和冒險的業務決策。 另一方面,它要求工程團隊在代碼可讀性方面付出一些額外的努力。
事實是,直到幾年前,我們才如此強調代碼的可讀性。 您猜對了,那時出現了一些與新開發人員入職和升級功能有關的嚴重問題。
希望其他人不會重複我們的錯誤,而是會按時開始清潔。 以下是我們在 Mediatoolkit 堅持的一些關於代碼可讀性的指南。
可讀性很重要
如果在現有產品上工作,開發人員大部分時間都在閱讀和維護現有代碼。 如果某件事需要數年時間才能開發出來,那麼它肯定需要一分鐘多的時間才能弄清楚它的作用以及如何改變它。
儘管開發人員花費了大量時間閱讀現有代碼,但更多的注意力放在了編寫新的、性能更好的代碼上。 很少考慮維護它所需的時間。 那麼,應該關注什麼?
從基礎開始。 這些是您可以採取的第一步來改進您的代碼並使其更清潔:
- 使用有意義的名稱
- 改進功能
- 更喜歡不變性
- 更喜歡聲明式編程而不是命令式編程
- 保持簡單愚蠢 - KISS
- 不要重複自己 - DRY
- 你不需要它——YAGNI
使用有意義的名稱
您在代碼中命名所有內容,因此請確保將其做好。 有意義的名稱提供了正在發生的事情的上下文。 名稱應該告訴代碼的意圖——某物存在的原因、它的作用以及它是如何使用的。
花點時間想出好名字。 試圖用隨機的胡言亂語記住你想表達的東西會佔用你更多的時間。
沒有有意義名稱的代碼
具有有意義名稱的代碼
如果可能,名稱應該來自問題域。 如果域中有更好的名稱,請不要使用技術名稱。
類和對象的名稱應包含名詞或名詞短語,例如customer 、 emailSender或htmlParser 。
但是,在遵循該規則的同時,請盡量避免包含一般名詞的名稱,如data 、 info 、 manager 、 processor 、 controller ...,因為這些通常沒有太多意義。
通用術語不會告訴您它的域是什麼。 它只在最廣泛的意義上告訴你它包含的內容。
例如,名為info的對像比userInfo更難閱讀。 此外,您可以在整個代碼中擁有多個infos 、 controllers或managers ,因此當這些名稱具有域特徵時,更容易遵循代碼的邏輯。
方法應該使用動詞或動詞短語來命名,例如postPayment 、 deletePage或insert 。 最好避免使用縮寫名稱,因為它們通常只會在最初的開發過程中造成混淆和直觀。
相反,使用可發音的名稱。 在閱讀和理解代碼時,這些更容易理解和記憶。
沒有評論的壞名字沒有任何意義:
一個更好的自我解釋名稱:
每個概念選擇一個術語並堅持下去
將fetch 、 retrieve和get作為不同類的等效方法令人困惑。
為相同或相似的事物使用不同的名稱會使讀者感到困惑,並引發諸如這些命名事物如何不同的問題,因為它們的名稱不同。
改進功能
改進功能的第一件事就是使它們變小。 當我說小時,我的意思是 50 行以下,最好是 10-15 行以下。 通過保持較小的功能,您可以減少閱讀時必須考慮的上下文。
函數中的縮進也增加了上下文保持。 您應該很好地解釋為什麼在一個函數中有 2 個(或更多)級別的縮進。
如果可能,用函數調用替換嵌套的縮進塊。
上面的 `calculateEmployeeSalary` 的縮進較少的版本:
為了提高可讀性,一個函數應該只做一件事。
長函數通常會做很多事情,這使得它們很長。 為了做多件事,同時保持代碼的可讀性,你應該把長函數拆分成多個更小的函數。
調用其他函數的函數應該仍然只做一件事,只是更抽像一點。
引入多層次的抽象
如果您可以用更高的抽象級別描述行為,則將代碼片段提取到函數中。
與一些低級別的功能相比,許多更高抽象級別的功能更容易理解。 抽象應該基於域行為。

出於代碼重用目的而引入的任意抽象通常會使事情變得更糟。 嘗試將函數的函數調用保持在同一抽象級別。 這有時需要單行函數來達到抽像水平,這很好(性能不應該成為問題,因為現代語言通過內聯對其進行了優化)。
按層級組織功能使代碼講述行為故事,而不是一步一步的食譜。
函數應該有盡可能少的參數。 應該很少使用超過 2 個。
許多參數使代碼更難理解,尤其是在使用參數作為輸出時(在函數完成後修改的參數)。
輸出參數通常是意外且不自然的,因此請避免使用它們。
注意:前面的示例使用三個參數盡可能與相關示例保持相似,但應避免使用。
函數應該是執行操作(副作用)的命令,或者是向調用者返回數據的查詢,但不能同時是兩者。
他們的“角色”應該通過它的名字來強調。 如果函數是命令或名稱查詢,則永遠不應該產生誤解。
修改其參數狀態的命令的名稱示例:
在不修改參數狀態的情況下提取值的查詢的名稱示例:
副作用必須始終明確。 他們永遠不應該出乎意料。 如果不必要,請避免使用它們。 盡量保持函數“純”,即無狀態。
更喜歡不變性
“不變性”在編程中究竟意味著什麼?
它是一個屬性,表明對象/變量在完全創建後無法修改。 不可變代碼而不是對象的變異狀態會創建具有修改狀態的副本。
可變代碼:
不可變代碼中的相同行為:
不變性使代碼更簡單,更容易遵循。
通過知道對像是不可變的,您不必擔心傳遞對象的函數是否會修改它。 並發編程也變得更容易和更乾淨。
不可變對象本質上是線程安全的,這意味著不需要同步塊,因為您始終使用不可更改的一致狀態。
當然,有時邏輯也可能不自然、複雜或性能不佳,但仍表示為不可變。 在這種情況下,您仍然可以使用可變性,但盡量縮小其範圍。
更喜歡聲明式編程而不是命令式編程
編寫您的繼承人易於閱讀的代碼的一個重要部分是選擇聲明式編程而不是命令式編程。 什麼是命令式編程,什麼是聲明式編程?
命令式編程說明如何做事。 它的重點是逐步機制,如條件語句、循環、突變。
另一方面,聲明式編程說的是做什麼而不是怎麼做。 它的重點是必須做的事情,這意味著整個行動,而不是一步一步的解釋。 聲明式編程建立在命令式編程逐步風格之上。
嘗試將命令式代碼抽象為聲明性代碼。
使用聲明式風格,業務邏輯/意圖變得更加明顯。 第一眼看到代碼的意圖,使維護和修復代碼更容易掌握。 它使我們能夠查明有問題的部分,然後深入研究細節,而不是首先閱讀整個代碼來破譯其意圖。
行為在聲明式風格中更為明顯,如下所示:
保持簡單愚蠢 - KISS
開發人員經常為給定的問題想出“聰明”的解決方案。
這些解決方案雖然在特定時刻看起來像是天賜良機,但不應該被首選,儘管它們可能會給我們帶來自我提升。 大多數時候,它們很難理解和遵循,因此更難維護或升級。
簡單應該是設計的一個關鍵目標。 避免不必要的複雜性。
不要重複自己 - DRY
重複是不好的,尤其是知識的重複。 您的目標應該是在您的系統中擁有單一的知識表示。 如果知識發生變化,您必須在每個重複的地方都對其進行更改。
如果你錯過了一個副本,它很有可能會變成一個錯誤。
維護很容易成為具有大量重複項的噩夢。
在處理複雜問題時,在製作原型時復制粘貼是一種常規做法。 這並沒有錯,因為它可以幫助我們發現適當的知識抽象,從而更好地表達意圖。
但是,就像我們小時候被教導的那樣,在遊戲時間之後,是時候清理了。 這些複製品達到了他們的目的。 現在是時候刪除它們了。
你不需要它——YAGNI
不要實施你將來“可能”需要的東西,只實施你需要的東西。 我們“可能”需要的東西往往不是我們以後真正需要的東西。
我們“可能”需要的東西的過早實現關閉了我們的設計可以走的許多路徑。 它迫使我們採用未針對我們產品的實際需求進行優化的設計,這反過來又導致我們難以維護的糟糕(呃)和復雜(呃)的整體設計。
下一步
您可以採取的下一步改進是SOLID 原則。
它們是旨在使軟件設計更加易於理解、靈活和可維護的五項設計原則:
- 單一責任原則——SRP
- 開閉原則——OCP
- Liskov 的替換原則 – LSP
- 接口隔離原則——ISP
- 依賴倒置原理——DIP
通過遵循這些(以及其他一些)原則,我們設法使大部分代碼和域設計保持可讀、易於維護和可升級。
我們這樣做的原因是為歡迎我們團隊的新成員做準備,主要是通過使入職過程更加高效和舒適。
說到新成員,請查看我們的高級 Java 開發人員的空缺職位或我們的職業頁面上的任何其他職位空缺。
