1. 程式人生 > 其它 >第三部分:理論五

第三部分:理論五

第三部分:理論五

命名

1、命名多長最合適?

  • 長的命名可以包含更多的資訊,更能準確直觀地表達意圖,但最好不要長到兩行的程度,影響程式碼的可讀性。
  • 在足夠表達其含義的情況下,命名當然是越短越好。對於一些預設的、大家都比較熟知的詞,我比較推薦用縮寫。
  • 對於作用域比較小的變數,我們可以使用相對短的命名,比如一些函式內的臨時變數。
  • 對於類名這種作用域比較大的,我更推薦用長的命名方式。

2、利用上下文簡化命名

  • 文中舉例,在 User 類這樣一個上下文中,我們沒有在成員變數的命名中重複新增“user”這樣一個字首單詞,而是直接命名為 name、password、avatarUrl。比如:user.getName()。這個問題還挺常見的。
  • 函式引數也可以藉助函式這個上下文來簡化命名。

未簡化命名:

public class User {
	private String userName;
	private String userPassword;
	private String userAvatarUrl;
	//...
}

藉助物件上下文簡化命名:

User user = new User();
user.getName(); // 藉助 user 物件這個上下文

藉助函式上下文簡化命名:

public void uploadUserAvatarImageToAliyun(String userAvatarImageUri);
// 利用上下文簡化為:
public void uploadUserAvatarImageToAliyun(String imageUri);

3、命名要可讀、可搜尋

  • 我這裡所說的“可讀”,指的是不要用一些特別生僻、難發音的英文單詞賴命名。
  • 我們在 IDE 中編寫程式碼的時候,經常會用“關鍵詞聯想”的方法來自動補全和搜尋。我們在命名的時候,最好能符合整個專案的命名習慣。大家都用“selectXXX”表示查詢,你就不要用“queryXXX”。這點也挺好挺有用的。

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

  • 對於介面的命名,一般有兩種比較常見的方式:
    • 一種是加字首“I”,表示一個 Interface。比如 IUserService,對應的實現類命名為 UserService。
    • 另一種是不加字首,比如 UserService,對應的實現類加字尾“Impl”,比如 UserServiceImpl。
  • 對於抽象類的命名,也有兩種方式:
    • 一種是帶上字首“Abstract”,比如 AbstractConfiguration。
    • 另一種是不帶字首“Abstract”。
  • 對於介面和抽象類,選擇哪種命名方式都是可以的,只要專案裡能夠統一就行。

註釋

  • 命名很重要,註釋跟命名同等重要。
  • 很多人說,好的命名完全可以替代註釋。如果需要註釋,那說明命名不夠好,需要在命名上下功夫,而不是添加註釋。
  • 作者認為,這樣的觀點有點太過極端。命名再好,畢竟有長度限制,不可能足夠詳盡,而這個時候,註釋就是一個很好的補充。
  • 簡直完全同意,專案通篇不寫一個字註釋的,不是裝逼就是懶。

1、註釋到底該寫什麼?

  • 註釋的目的就是讓程式碼更容易看懂。
  • 註釋的內容主要包含這樣三個方面:做什麼、為什麼、怎麼做。
  • 有些人認為,“做什麼、怎麼做”在程式碼中都可以體現出來,只需要寫清楚“為什麼”,表明程式碼的設計意圖即可。我個人不是特別認可這樣的觀點:
    • 註釋比程式碼承載的資訊更多。對於類來說,包含的資訊比較多,一個簡單的命名就不夠全面詳盡了。這個時候,在註釋中寫明“做什麼”就合情合理了。
    • 註釋起到總結性作用、文件的作用。在註釋中,關於具體的程式碼實現思路,我們可以寫一些總結性的說明、特殊情況的說明。這樣能夠讓閱讀程式碼的人通過註釋就能大概瞭解程式碼的實現思路,閱讀起來就會更加容易。我們可能還需要在註釋中寫清楚“如何用”,舉一些簡單的 quick start 的例子,讓使用者在不閱讀程式碼的情況下,快速地知道該如何使用。
    • 一些總結性註釋能讓程式碼結構更清晰。對於邏輯比較複雜的程式碼或者比較長的函式,如果不好提煉、不好拆分成小的函式呼叫,那我們可以藉助總結性的註釋來讓程式碼結構更清晰、更有條理。

註釋示例:

/**
* (what) Bean factory to create beans.
*
* (why) The class likes Spring IOC framework, but is more lightweight.
*
* (how) Create objects from different sources sequentially:
* user specified object > SPI > configuration > default object.
*/
public class BeansFactory {
	// ...
}

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

  • 註釋太多和太少都有問題。太多,有可能意味著程式碼寫得不夠可讀,需要寫很多註釋來補充。
  • 註釋太多也會對程式碼本身的閱讀起到干擾。而且,後期的維護成本也比較高,有時候程式碼改了,註釋忘了同步修改,就會讓程式碼閱讀者更加迷惑。
  • 類和函式一定要寫註釋,而且要寫得儘可能全面、詳細,而函式內部的註釋要相對少一些,一般都是靠好的命名、提煉函式、解釋性變數、總結性註釋來提高程式碼的可讀性。

程式碼風格

類、函式多大才合適?

  • 類或函式的程式碼行數太多,一個類上千行,一個函式幾百行,邏輯過於繁雜,閱讀程式碼的時候,很容易就會看了後面忘了前面。
  • 類或函式的程式碼行數太少,在程式碼總量相同的情況下,被分割成的類和函式就會相應增多,呼叫關係就會變得更復雜,閱讀某個程式碼邏輯的時候,需要頻繁地在 n 多類或者 n 多函式之間跳來跳去,閱讀體驗也不好。
  • 對於函式程式碼行數的最大限制,不要超過一個顯示屏的垂直高度,讓一個函式的程式碼完整地顯示在 IDE 中,大概是50行左右。
  • 對於類的程式碼行數的最大限制,這個就更難給出一個確切的值了。當一個類的程式碼讀起來讓你感覺頭大了,實現某個功能時不知道該用哪個函數了,想用哪個函式翻半天都找不到了,只用到一個小功能要引入整個類(類中包含很多無關此功能實現的函式)的時候,這就說明類的行數過多了。

一行程式碼多長最合適?

  • 一行程式碼最長限制為 100 個字元左右。
  • 一行程式碼最長不能超過 IDE 顯示的寬度。

善用空行分割單元塊

  • 對於比較長的函式,如果邏輯上可以分為幾個獨立的程式碼塊,可以使用空行來分割各個程式碼塊。
  • 在類的成員變數與函式之間、靜態成員變數與普通成員變數之間、各函式之間、甚至各成員變數之間,我們都可以通過新增空行的方式,讓這些不同模組的程式碼之間,界限更加明確。

四格縮排還是兩格縮排?

  • 到底應該是兩格縮排還是四格縮排,只要專案內部能夠統一就行了。
  • 作者個人推薦兩格縮排,節省空間。在程式碼巢狀層次比較深的情況下,累計縮排較多會導致一個語句折成兩行。這塊說的有一定道理,不過我們團隊推行的是四格縮排,PHP用四格縮排的比較多。
  • 另外,作者反對用tab鍵縮排。這裡不能認同,只要大家約定好縮排格數,也是可以統一風格的。我這裡甚至推薦用tab鍵縮排,減少鍵盤敲擊次數。

大括號是否要另起一行?

  • 首先還是隻要團隊統一、業內統一、跟開源專案看齊就好了,沒有絕對的優劣之分。
  • 作者推薦將括號放到跟語句同一行的風格。我平時喜歡另起一行的風格,用vim折行方便整齊,還可以露出函式名方便檢視。

類中成員的排列順序

  • 在類中,成員變數排在函式的前面。
  • 成員變數之間或函式之間,都是按照“先靜態(靜態函式或靜態成員變數)、後普通(非靜態函式或非靜態成員變數)”的方式來排列的。
  • 成員變數之間或函式之間,還會按照作用域範圍從大到小的順序來排列,先寫 public 成員變數或函式,然後是 protected 的,最後是 private 的。也有的排序把有呼叫關係的函式放到一塊。
  • 我在排序這塊以前好像一直都是瞎寫的。。。

其餘技巧

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

  • 大部分人閱讀程式碼的習慣都是,先看整體再看細節。
  • 將大塊的複雜邏輯提煉成類或者函式,遮蔽掉細節,讓閱讀程式碼的人不至於迷失在細節中,這樣能極大地提高程式碼的可讀性。
  • 如果提煉出的函式只包含兩三行程式碼,在閱讀程式碼的時候,還得跳過去看一下,這樣反倒增加了閱讀成本。但是作者在後面一個例子就把一串ifelse分成三個只有兩三行程式碼的小函式。迷惑。。。

避免函式引數過多

  • 函式包含 3、4 個引數的時候還是能接受的,大於等於 5 個的時候,就有點過多,影響程式碼可讀性,使用起來也不方便。
  • 一般有兩種解決方法:
    • 考慮函式是否職責單一,是否能通過拆分成多個函式的方式來減少引數。
    • 將函式的引數封裝成物件。這種我比較常用,尤其是一組同類引數,封裝成物件是最好的,引數有增減也方便。

勿用函式引數來控制邏輯

  • 不要在函式中使用布林型別的標識引數來控制內部邏輯,true 的時候走這塊邏輯,false 的時候走另一塊邏輯。這明顯違背了單一職責原則和介面隔離原則。我建議將其拆成兩個函式,可讀性上也要更好。
  • 不過,如果函式是 private 私有函式,影響範圍有限,或者拆分之後的兩個函式經常同時被呼叫,我們可以酌情考慮保留標識引數。比如根據標識引數來判斷要呼叫兩個函式中的哪一個。
  • 除了布林型別作為標識引數來控制邏輯的情況外,還有一種“根據引數是否為 null”來控制邏輯的情況。針對這種情況,我們也應該將其拆分成多個函式。拆分之後的函式職責更明確,不容易用錯。

函式設計要職責單一

  • 對於函式的設計來說,更要滿足單一職責原則。相對於類和模組,函式的粒度比較小,程式碼行數少,所以在應用單一職責原則的時候,沒有像應用到類或者模組那樣模稜兩可,能多單一就多單一。
  • 作者分別把電話、使用者名稱、郵箱是否為空的檢查,分成三個函式,每個裡面就兩行程式碼,這裡是否劃分過細,是個思考點。

移除過深的巢狀層次

  • 巢狀最好不超過兩層,超過兩層之後就要思考一下是否可以減少巢狀。
  • 過深的巢狀本身理解起來就比較費勁,除此之外,巢狀過深很容易因為程式碼多次縮排,導致巢狀內部的語句超過一行的長度而折成兩行,影響程式碼的整潔。
  • 解決巢狀過深的方法也比較成熟,有下面 4 種常見的思路:
    • 去掉多餘的 if 或 else 語句。文中舉例,有些情況其實不需要else,只要if處理特殊情況,剩下的程式碼接著往下走就行。
    • 使用程式語言提供的 continue、break、return 關鍵字,提前退出巢狀。這種方案還是比較常用的。
    • 調整執行順序來減少巢狀。這個需要一點小技巧性,編碼時要多動腦,多思考。有時一下想不出來,也可以先把邏輯寫出來,看哪裡彆扭囉嗦,再調整。
    • 將部分巢狀邏輯封裝成函式呼叫,以此來減少巢狀。

學會使用解釋性變數

  • 常量取代魔法數字。如果多次使用,好處更大。
  • 使用解釋性變數來解釋複雜表示式。在PHP中沒用過,也不知道是否支援,這是用一個變數來代替一個長的表示式。