工程實踐:讓變數命名做到"自解釋"

Matrix海子發表於2019-04-01

在上一篇文章中跟大家分享了關於函式命名的一些實踐心得,今天我們繼續命名這個話題,來講一講如何對變數命名。

  以下是本文的目錄大綱:

  一. 變數命名風格

  二. 變數命名最高境界

  三. 變數命名最佳實踐

  若有不正之處請多多諒解,並歡迎批評指正。

  請尊重作者勞動成果,轉載請標明原文連結:

 https://www.cnblogs.com/dolphin0520/p/10639167.html

一.變數命名風格

  變數命名風格通常會根據不同的變數型別來區分,以Java語言為例,根據變數型別不同有兩種命名風格:

1)類成員變數、區域性變數

  類成員變數、區域性變數通常採用駝峰命名風格,如下:

String userName;
複製程式碼

2)靜態成員變數、列舉值、常量

  靜態成員變數、列舉值、常量通常採用所有字母大寫、多個單詞以英文下劃線連線,如:

public static final int MAX_YEARS = 25;
​
// 建議列舉類都以Enum結尾
enum ColorEnum {
    RED(0, "紅色"),
    YELLOW(1, "黃色"),
    GREEN(2, "綠色"),
    WHITE(3, "白色"),
    BLACK(4, "黑色");
    private int code;
    private String name;
​
    Color(int code, String name) {
        this.code = code;
        this.name = name;
    }
}複製程式碼

二.變數命名最高境界

  在函式命名那篇中我們說的函式命名最高境界是見字如面,那麼對於變數命名來說,最高境界是什麼呢? 我認為是:自解釋,即"程式碼即註釋"。

  為什麼這麼說呢,因為通常來說一個函式是會有函式註釋的,即使函式名字取的不好,如果註釋寫的比較清楚,對於後續維護人員來說也是瞭解函式具體功能的一種方式。

  而變數則不同,在一個工程裡面,變數的數量遠遠大於函式的數量,所以不太可能對於每個變數都去寫註釋,所以如果一個工程的變數命名很糟糕,那麼對於後續維護人員來說將是毀滅性的打擊,因為每讀到一個變數,可能就需要去猜測變數的含義,我想沒有哪個人願意讀到這樣的程式碼,永遠記住一點:"程式碼是寫給人看的,不是寫給機器看的"。

  譬如下面這段程式碼的命名就非常糟糕:

ppn = (cpn > 1) ? (cpn - 1) : cpn;
npn = (cpn < tpn) ? (cpn + 1) : tpn;
​
p = new Page(ppn, cpn, npn, tpn);複製程式碼

  上面這段程式碼估計只有原作者清楚地知道各個變數的含義是啥了,

  如果修改為下面這種寫法,可讀性會好很多,並且一目瞭然,很容易知道其大概意圖是計算分頁資訊:

prePageNum = (curPageNum > 1) ? (curPageNum - 1) : curPageNum;
nextPageNum = (curPageNum < totalPageNum) ? (curPageNum + 1) : totalPageNum;
​
page = new Page(prePageNum, curPageNum, nextPageNum, totalPageNum);複製程式碼

三.變數命名最佳實踐

1)採用名詞或者形容詞來命名變數

  變數一般情況下建議使用名詞、名字組合或者形容詞,因為變數一般形容的是一種事物或者事物的屬性,所以用名詞或者名片語合更容易讓人理解,而形容詞一般用於bool型別的變數。

2)避免使用單字母變數,儘量細化變數含義

  在程式中,儘量避免使用單字母變數,唯一可以接受使用單字母變數的場景只有for迴圈,不過還是不太推薦在for迴圈中使用單字母變數(用pos、index比for迴圈的i、j、k要好很多)。

  舉個例子,比如下面這行程式碼:

double calConeVolume(double b, double d) {
  return Math.PI * b * b * d / 3;
}複製程式碼

  咋一看這個函式引數感覺挺清晰,但是一細看,b是什麼?d又是什麼?如果我要用這個函式,該怎麼傳參?估計大部人是一臉懵逼狀,只能進去看實際的函式實現才知道b是圓錐體半徑,d是圓錐體高度;

  那麼怎麼優化這段程式碼命名呢?其實很簡單,稍微細化一下變數含義,讓變數名自己去表達實際意圖:

double calConeVolume(double radius, double height) {
  return Math.PI * radius * radius * height / 3;
}複製程式碼

3)變數命名前後用詞需統一

  在同一個工程或者一個場景下,變數命名風格需前後統一,比如total和sum都能表示總計的意思,那麼所有需要用到"總計"含義的地方要麼全部使用total、要麼全部使用sum。

  保持前後命名風格統一是保證工程程式碼良好可讀性的關鍵保證。

4)集合變數用型別或者複數s作為字尾

  在java中,有很多集合,比如List、Map、Set等,那麼集合變數該怎麼命名呢?

  一般可採取兩種方式:

  • 使用複數s結尾

List<Student> students = new ArrayList<>();
複製程式碼

  • 用集合型別作為字尾

List<Student> studentList = new ArrayList<>();
複製程式碼

  上面兩種方式均可,沒有比較明顯的偏好,根據實際場景決定。第一種方式相對更簡潔,第二種在區域性作用域裡面有多種相關的集合變數時區分度更大,比如:

List<Student> studentList = new ArrayList<>();
Map<Long, Student> studentMap = Maps.newHashMap();
​
for (Student stu : studentList) {
  studentMap.put(stu.getId, stu);
}複製程式碼

  我的建議是如果區域性作用域只有一種型別的集合,那麼推薦使用複數形式;如果區域性作用域有多個相關的集合型別,那麼推薦用型別結尾。

5)禁止使用is作為bool型別的類成員變數前置

  在java中,禁止用is作為bool型別的類成員變數的字首,因為is作為字首會導致序列化/反序列出現問題,阿里的java程式碼規範中也明確提到了這一點,所以在寫程式碼的時候最好還是遵守公認的規範,不然哪天說不定就踩坑了。

6)儘量避免使用縮寫進行命名

  有些時候,變數名可能有點長,不利於程式碼可讀性,因此很多時候在寫程式碼的時候喜歡用縮寫來命名,但這個不是一個好的習慣,除非使用的縮寫是大家都會使用的約定俗稱的縮寫。

  比如下面這個命名:

int avgStudentAge;
複製程式碼

  因為avg大家都知道是average的縮寫,所以這麼寫問題不大,不會引起歧義;

  但是下面這種縮寫命名:

res
tmp
cnt
dep複製程式碼

  就不是好的縮寫命名,因為不同的人閱讀可能會有不同的理解:

res => response、resource、result
tmp => temporary、template
cnt => count、content、context複製程式碼

  附上一些約定俗稱的縮寫:

全稱縮寫
identificationid
averageavg
maximummax
minimummin
bufferbuf
errorerr
messagemsg
imageimg
lengthlen
librarylib
passwordpwd
positionpos
data transfer objectdto
view objectvo

7)拋棄掉flag變數

  國內一些早期的教材上,到處充斥著各種flag風格的變數,這種命名方式對於大型工程簡直就是噩夢,比如:

int flag = getDoctorFlag(doctorId);
if (flag == 1) {
  //....
}複製程式碼

  看到這段程式碼,讀者會有疑問flag變數的含義是什麼?flag值為1的時候又代表什麼含義?是醫生的值班/在崗狀態、還是醫生的身體狀態?估計讀者的內心是崩潰的。

  如果優化成下面這種形式:

DutyStatus doctorDutyStatus = getDoctorDutyStatus(doctorId);
if (doctorDutyStatus == DutyStatus.ONLINE) {
  // ...
}複製程式碼

  就比上面的形式清晰多了,很容易看出來判斷的是醫生的值班/在崗狀態。


部落格園連結:https://www.cnblogs.com/dolphin0520/

公眾號:

工程實踐:讓變數命名做到"自解釋"


相關文章