關於改善程式碼質量的程式設計規範

語言: CN / TW / HK

根據《設計模式之美》中部分章節,整理記錄下的改善程式碼質量的程式設計規範。內容主要分為三個部分:命名與註釋、程式碼風格、程式設計技巧。文中程式碼示例使用了Java作為示例,但並不限於Java,這些規範對於其他語言也是可以通用作為參考。

書寫程式碼的質量非常依賴個人的開發經驗,而下面總結出的這些規範,大部分簡單明瞭,在閱讀並掌握後,即使對於經驗略少的開發者,在編寫程式碼時對質量的提升也能夠起到立竿見影的效果。

命名

1. 命名長度

實際開發中會遇到兩種相對極端的命名方式,一種為了準確達意,命名儘可能的長,導致專案裡類名、函式名都很長。另一種命名儘可能的短,能用縮寫就用縮寫,甚至字母。那麼究竟那種方式更為合理?

我們要知道命名的原則是準確達意。理論上在足夠表達其含義的基礎上,命名越短越好。而在實際開發中,在遇到作用域較大的變數,為了減少可能產生的影響,更推薦用長的命名方式。

縮短命名的小建議,對於一些預設的、大家都比較熟悉的詞可以使用縮寫,可以讓命名更短又不影響閱讀理解。比如,sec 表示 second、str 表示 string、num 表示 number。

對於自己來說,對程式碼的邏輯很清楚,總感覺用什麼樣的命名都可以達意,實際上,對於不熟悉你程式碼的同事來講,可能就不這麼認為了。所以,命名的時候,一定要學會換位思考,假設自己不熟悉這塊程式碼,從程式碼閱讀者的角度去考量命名是否足夠直觀。

2. 利用上下文簡化命名

java public class User { private String name; //not userName private String password; //not userPassword private String avatarUrl; //not userAvatarUrl //... }

類似情況下,藉助上下文即可表意明確。同時也便於日後做一些抽象的操作。

3. 命名要可讀、可搜尋

命名可讀指的是不要用一些特別生僻、難發音的英文單詞或者不常見的縮寫來命名。雖然我們不排斥一些獨特的命名方式,但是起碼得讓大部分人看一眼就能知道怎麼讀。不影響溝通交流,這就算是一個比較好的命名。

命名可搜尋指的是命名時最好能符合專案整體的命名習慣,便於在IDE中編寫程式碼時聯想補全以及搜尋。比如大家都用“selectXXX”表示查詢,你就不要用“queryXXX”或者“searchXXX”;大家都用“insertXXX”表示插入一條資料,你就不要用“addXXX”。統一規約是很重要的,能減少很多不必要的麻煩。

4. 如何命名介面和抽象類

介面有兩種命名方式:一種是在介面中帶字首“l”;另一種是在介面的實現類中帶字尾“lmpl”。對於抽象類的命名,也有兩種方式,一種是帶上字首“abstract”,一種是不帶字首。這兩種命名方式都可以,關鍵是要在專案中統一。

註釋

註釋跟命名同等重要。很多書籍認為,好的命名完全可以替代註釋,如果需要註釋,那說明命名不夠好,需要在命名上下功夫,而不是添加註釋。實際上個人覺得,這樣的觀點有點太過極端。命名再好,畢竟有長度限制,不可能足夠詳盡,這個時候,註釋就是一個很好的補充。

1. 註釋到底該寫什麼?

註釋的目的就是讓程式碼更容易看懂。只要符合這個要求的內容,你就可以將它寫到註釋裡。

註釋的內容主要包含這樣三個方面:做什麼、為什麼、怎麼做。

有些人認為,註釋是要提供一些程式碼沒有的額外資訊,所以不要寫“做什麼、怎麼做”,這兩方面在程式碼中都可以體現出來,只需要寫清楚“為什麼”,表明程式碼的設計意圖即可。

對於以上觀點並不是特別認可,還是要分情況來處理,理由主要有以下3點:

  • 註釋比程式碼承載的資訊更多

命名主要目的是解釋“做什麼”,對於函式和變數來說如果命名的好,確實可以不用在註釋中再解釋它是做什麼的。但是對於類來說,包含的資訊比較多,一個簡單的命名就不夠全面詳盡了。這個時候,在註釋中寫明“做什麼”就合情合理了。

  • 註釋起到總結性作用、文件的作用

通過閱讀程式碼可以明確知道程式碼是“怎麼做”的,也就是知道程式碼是如何實現的,那註釋中是不是就不用寫“怎麼做”了?實際上也可以寫。在註釋中,關於具體的程式碼實現思路,我們可以寫一些總結性的說明、特殊情況的說明。這樣能夠讓閱讀程式碼的人通過註釋就能大概瞭解程式碼的實現思路,閱讀起來就會更加容易。

實際開發中,對於有些比較複雜的類或者介面,我們可能還需要在註釋中寫清楚“如何用”,舉一些簡單的 quick start 的例子,讓使用者在不閱讀程式碼的情況下,快速知道該如何使用。

  • 一些總結性註釋能讓程式碼結構更清晰

對於邏輯比較複雜的程式碼或者比較長的函式,如果不好提煉、拆分成小的函式呼叫,那我們可以藉助總結性註釋來讓程式碼結構更清晰、更有調理。

```java public boolean isValidPasword (String password) { // check if password is null or empty if (StringUtils.isBLank (password)) { return false; }

// check if the Length of password is between 4 and 64 
int Length = password.Length();
if (Length < 4 || Length > 64) { 
  return false;
}

// check if password contains only Lowercase characters 
if (!StringUtils.isAllLowerCase(password)) { 
  return false;
)

// check if password contains only a-z,d-9, dot
for (int i = 0; i < length; ++i) 
  char c = password.charAt(i);
  if (!(c >= 'a' && c <= "z")||(c >='0'&& c<= '9')||c=='.'){
     return false;
  }
}
return true;

} ```

2. 註釋是不是越多越好?

註釋太多和太少都有問題。

註釋太多,有可能意味著程式碼寫的不夠可讀,需要寫很多註釋來補充。除此之外,註釋太多也會對程式碼本身的閱讀起到干擾。而且,後期的維護成本也比較高,有時候程式碼改了,註釋忘了同步修改,就會讓程式碼閱讀者更加迷惑。

如果程式碼中一行註釋都沒有,那隻能說明這個程式設計師有點偷懶,我們要適當督促一下,讓他注意新增一些必要的註釋。

註釋本身有一定的維護成本,所以並非越多越好,類和函式一定要寫註釋,而且要寫的儘可能全面、詳細,而函式內部的註釋要相對少一些,一般都是靠好的命名、提煉函式、解釋性變數、總結性註釋來提高程式碼的可讀性。

其他

關於註釋,應該使用英文還是中文來書寫呢?工作中還是要根據團隊情況,交付要求等因素來決定。個人開發的話根據自身英文掌握情況,畢竟註釋的目的是要讓程式碼更容易看懂,英文足夠熟練,那直接英文即可。如果不夠熟練可以試著中文註釋+英文註釋,本著提升英文水平目的同時兼顧註釋的可讀性。

程式碼風格

1. 類、函式多大才合適?

總體上來講,類或函式的程式碼行數不能太多,但也不能太少。類活函式的程式碼行數太多,一個類上千行,一個函式幾百行,邏輯過於繁雜,閱讀程式碼的時候,很容易就會看了後面忘了前面。相反,類或者函式的程式碼行數太少,在程式碼總量相同的情況下,被分隔成的類和函式就會相應增多,呼叫關係就會變得更復雜,閱讀某個程式碼邏輯的時候,需要頻繁地在n多類或者函式之間跳來跳去,閱讀體驗也不好。

要給出一個精確的量化值很難。比如做飯,對於放少許鹽,即便是大廚也很難告訴你一個特別具體的量。

對於函式程式碼行數的最大限制,網上有一種說法,就是不要超過一個顯示屏的垂直高度,讓一個函式的程式碼完整的顯示在 IDE 中。這個說法還是比較有道理的。因為超過一屏之後,閱讀程式碼時為了串聯前後的程式碼邏輯,就可能需要頻繁的上下滾動螢幕,閱讀體驗不好喝不說,還容易出錯。

對於類的程式碼行數的最大限制,這個就更難給出一個確切的值了。類當中往往包含了若干功能,高度大部分情況下都過超出一屏的顯示範圍。這裡只能給一個間接的判斷標準。那就是當一個類的程式碼讀起來讓你感覺吃力了,實現某個功能不知道改用哪個函數了,想用哪個函式翻半天都找不到,只用到一個小功能要引入整個類(類中包含很多無關此功能實現的函式)的時候,就說明類的行數過多了。

2. 一行程式碼多長合適?

不同程式語言、不同規範、不同團隊,對此的限制可能都不同。不管這個限制是多少,總體上我們儘量遵循一個原則:一行程式碼的長度不超過 IDE 顯示的寬度。不論是需要滑鼠滾動才能檢視完整程式碼還是 IDE 自動折行,都或多或少會不利於程式碼的閱讀。當然,這個限制也不能太小,太小會導致很多稍長點的語句被折成兩行,影響程式碼整潔以至於影響程式碼閱讀。

3. 善用空行分割單元塊

對於比較長的函式,如果邏輯上可以分為幾個獨立的程式碼塊,在不方便將這些獨立的程式碼塊抽取成小函式的情況下,為了讓邏輯更加清晰,除了前面提到的註釋,我們還可以用空行來分割各個程式碼塊。

除此之外,在類的成員變數與函式之間、靜態成員變數與普通成員變數之間、各函式之間、甚至各成員變數之間,我們都可以通過新增空行的方式,讓這些不同模組的程式碼之間,界限更加明確。類似寫文章,善於應用空行,可以讓程式碼的整體結構看起來更加清晰有條理。

4. 四格縮排還是兩格縮排?

這個還是取決於個人喜好,保證專案內部能夠統一就行了。

還有一個選擇的標準,就是跟業內推薦的風格統一、跟著名開源專案統一。

需要強調的是,不管是用幾格縮排,一定不要用 tab 鍵縮排。因為在不同的 IDE 下,tab 鍵的顯示寬度有可能會不同,雖然可以通過 IDE 設定去配置,但是為了避免意外的麻煩,最好還是能夠養成不要使用 tab 縮排的習慣。

5. 大括號是否要另起一行

將大括號放到跟上一條語句同一行,可以節省程式碼行數。但是將大括號另起新的一行的方式,左右括號可以垂直對齊,哪些程式碼屬於哪一個程式碼塊,更加一目瞭然。

6. 類中成員的排列順序

在 Google Java 程式設計規範中,依賴類按照字母順序從小到大排列。類中先寫成員變數後寫函式。成員變數之間或函式之間,先寫靜態成員變數或函式,後寫普通變數或函式,並且按照作用域大小依次排列。

程式碼風格小結

以上所有程式碼風格都沒有對錯和優劣之分,只要能在團隊、專案中統一即可,不過最好能跟業內推薦的風格、開源專案的程式碼風格相一致。

程式設計技巧

1. 把程式碼分割成更小的單元塊

打不分人閱讀程式碼的習慣都是先看整體再看細節。所以,我們要有模組化和抽象思維,善於將大塊的複雜邏輯提煉成類或者函式,遮蔽掉細節,讓閱讀程式碼的人不至於迷失在細節中,這樣能極大的提高程式碼的可讀性。不過,只有程式碼邏輯比較複雜的時候,我們其實才建議提煉類或者函式。畢竟如果提煉出的函式只包含兩三行程式碼,在閱讀程式碼的時候,還得跳過去看一下,這樣反倒增加了閱讀成本。

2. 避免函式引數過多

個人覺得,函式包含3、4個引數的時候還是能接受的,大於等於5個的時候,就覺得引數有點過多了,會影響到程式碼的可讀性,使用起來也不方便。針對引數過多的情況,一般有2中處理方法。

  • 考慮函式是否職責單一,是否能通過拆分成多個函式的方式來減少引數。
  • 將函式的引數封裝成物件。

除此之外,如果函式是對外暴露的遠端介面,將引數封裝成物件,還可以提高介面的相容性。在往介面中新增新的引數時,老的遠端介面呼叫者有可能就不需要修改程式碼來相容新的介面了。

3. 不要用函式引數來控制邏輯

不要在函式中使用布林型別的標識引數來控制內容部邏輯,true 的時候走這塊邏輯,false的時候走另一塊邏輯。這明顯違背了單一職責原則和介面隔離原則。這種情況建議將其拆成兩個函式,可讀性上也要更好。

不過,如果函式是 private 私有函式,影響範圍有限,或者拆分之後的兩個函式經常同時被呼叫,我們可以酌情考慮保留標識引數。

4. 函式設計要職責單一

前面在說到單一職責原則的時候,針對的是類、模組這樣的應用物件。實際上,對於函式的設計來說,更要滿足單一職責原則。仙姑低於類和模組,函式的粒度比較小,程式碼行數少,所以在應用單一職責原則的時候,沒有像應用到類或者模組那樣模稜兩可,能多單一就多單一。

5. 移除過深的巢狀層次

程式碼巢狀層次過深往往是因為 if-else、switch-case、for迴圈過度巢狀導致的。個人建議,巢狀最好不超過兩層,超過兩層後就要思考一下是否可以減少巢狀。過深的巢狀本身理解起來就比較費勁,初次之外,巢狀過深很容易因為程式碼多次索引,導致巢狀內部的語句超過一行的長度而折行,影響程式碼的整潔。

解決巢狀過深的方法也比較成熟,有下面4種常見的思路:

  • 去掉多餘的 if 或 else 語句。
  • 使用程式語言提供的 continue、break、return 關鍵字,提前退出巢狀。
  • 調整執行順序來減少巢狀。
  • 將部分巢狀邏輯封裝成函式呼叫,以此來減少巢狀。

初次之外,常用的還有通過使用多型來替代 if-else、switch-case 條件判斷的方法。

6. 學會使用解釋性變數

常用的解釋性變數來提高程式碼的可讀性的情況有下面 2 種。

  • 常量取代魔法數字。(魔法數字即表意不明的數字。極大的影響程式碼可讀性)
  • 使用解釋性變數來解釋複雜表示式。

統一程式碼規範

通過以上說到的命名、註釋、程式碼風格、程式設計技巧這些知識點,可以發現大多數規範並沒有一個準確的要求,只是建議或者推薦。最為重要的其實是,專案、團隊乃至公司,應該制定統一的編碼規範,並通過 Code Review 督促執行,這對提高程式碼質量有立竿見影的效果。