Unity 遊戲框架:命名的力量–變量

  • 2020 年 3 月 29 日
  • 筆記

變量的命名入門

大家先來試着理解一下這段代碼:

var todoList = new TodoList();  todoList.Todos = new List<Todo>();    var todo = new Todo() { Id = 0, Finished = false, Content = "測試" };    todoList.Todos.Add(todo)    todo.Finished = true;  

代碼本身很簡單,就算不用去看 TodoList 類和 Todo 類的定義,也是可以讀懂以上代碼的。

那這是如何做到的呢?

答案就是:進行合適的命名。

而達到以上代碼的效果的主要是變量的命名。

因為以上出現的類都是 Model 類,而 Model 則是用來描述業務是什麼,而 Model 類中大部分代碼則是變量。
比如 這是一個 TodoList App (待辦事項),其中:

  • 有待辦事項列表
  • 待辦事項列表有待辦事項
  • 待辦事項可以完成和未完成。
  • 待辦事項可以編輯文本。

以上幾點一般是我們接收到任務並理解業務之後梳理出來的。如果設計成代碼結構,結構大致如下:

  • 待辦事項列表
    • 待辦事項
      • 完成/未完成
      • 文本

很自然 Model 的代碼就寫出來了,代碼如下。

TodoList.cs

public class TodoList  {  	public List<Todo> Todos;  }  

Todo.cs

public class Todo  {  	public int Id;    	public bool Finished;    	public string Content;    }  

為了讓以上類更容易地在其他文件中理解,筆者在命名上做了哪些工作呢?

TodoList 類中的 Todos 變量:

  • Todos 是複數,說明其類型可能是數組、List 或者其他的集合。
  • Todos 首字母大寫,說明訪問權限是 public 類型的。
  • Todos 名字本身是一個名詞,解釋成中文則是:要做的事(待辦事項),本身準確地傳達了變量的意思。

Todo 類

  • Id,和 Todos 一樣,傳達了訪問權限,和意思(標識),但是沒有傳達其類型。不過,不管是 int 還是 string 都無所謂。
  • Finished,也是一樣的,public 權限、意思(完成了)。同樣也沒有傳達其類型,不過這裡可以這樣理解,Finish 是動詞,加上 ed 則是過去式,有時態說明有狀態,這個狀態只有兩個,完成和未完成,所以是 bool 類型,這是筆者的一個習慣,當然也可以命名為 IsFinish,等等。
  • Content,中文意思是內容,也就是文本內容,一般為 string 類型,因為首字母大寫,所以也是 public 權限。

簡單幾行代碼,就可以傳達這麼多信息,所以 命名是一門藝術

小結

在對一個變量進行命名的時候,無非要思考以下三點:

  • 描述
  • 權限
  • 類型

權限和類型的表達很容易,因為關於這方面的方法論(套路)是固定的,可以很容易掌握的。

比較考驗功底的是變量的描述,是需要長期進行訓練的。

描述、權限、類型

接下來以這三點為主,分別進行方法論的介紹。

先從最簡單的來,最簡單的則是 權限 的表達

權限

權限指的是訪問權限,在 C# 中有 public、private、protected、internal 類型。

這裡在進行變量命名時分為兩種。

  • 可外部訪問的 public 和 internal。
  • 和不可被外部訪問的 private 和 protected。

權限的表達,筆者自己的習慣是,可被外部訪問的變量採用首字母大寫。

比如:

public string Name;  internal static int ReferenceCount;  

而不可被外部訪問的變量採用 m 前綴。

比如:

private string mShowedText;  protected int mMgrId;  

這是筆者長期的一個習慣,也是筆者自己維護的 QFramework 的命名規範。

所以關於權限的表達非常簡單,依靠代碼規範就能搞定了,規範怎麼定就怎麼用。

這一點是最最最容易達到的。

如果各位還沒有開始在意命名這件事情,從權限開始上手是最容易的。

類型

變量是有類型的,最好呢是把類型能夠表達出來。

類型的表達,當然這也可以依照某些代碼規範來解決的。

比如匈牙利式的代碼規範:

private int m_nAge;   // n 代表數量  private string m_strName // str 代表 string  

不過這種規範需要適應一段時間,這裡筆者不太推薦新手用這種規範解決類型表達問題。

而是採用一種比較簡潔、優雅的方式,來表達變量的類型。

比如:

public int Age;  

Age 年齡,很容易想到是 int 類型。

public string Name;  

Name 名字很容易想到是 string 類型。

public int PetCount;  public int PetNumber;  

寵物數量,很容易想到是 int 類型。

public bool Completed;  

Completed 完成了,是 bool 類型。

而這部分的詳細內容會在接下來的一個小節中詳細介紹。

描述

關於變量的描述的內容就比較多了,但是原則很簡單,就兩個字:準確。

為變量命名時最重要的考慮事項是,該名字要完全、準確地描述出該變量所代表的事物。——《代碼大全》

如何達到準確呢?這部分也會在接下來的一個小節中介紹。

在本小節,先給大家對變量所用的詞類型進行簡單的分類:

  • 事物
    • string
      • Name
      • NickName
    • int
      • Age
      • XXXCount
      • Times
  • 狀態
    • 過去式
      • Finished
    • 進行時
      • Waiting
    • 一些不推薦使用的(IsXXX):
      • IsFinished
      • IsFinish
      • IsWaiting
    • 形容詞
      • active
  • 其他
    • C# 中特有的 委託、事件 等。
      • OnXXXFinished
      • OnBeforeDestroy
      • OnLoadDone
      • OnLoadStart

所以在之後詳細講述變量的描述表達部分會根據這三點來進行介紹。

小結

讀者讀到這裡,權限的表達是最容易做到的,如果沒有意識到的童鞋們,可以在工作中開始權限的表達訓練了。

權限的表達比較適合作為變量名訓練的的第一步。

關於權限部分的內容就到此完結了,下文中不再贅述。

而剩下的類型和描述用兩個大章節進行詳細介紹。

我們先搞定相對較容易的類型的表達,這樣好讓大家掌握並快速在工作中實踐。

類型的表達

關於集合(List、Array、Dictionary 等等)

List、Array 可以使用名詞的複數來搞定。

比如:

var todos = new List<Todo>();  var todos = new Todo[]{};  

也可以將集合類型作為後綴表達

var todoList = new List<Todo>();  var todoArray = new List<Todo>();  

Dictionary

因為是 key-value 類型的集合,可以使用 xxx4yyy方式命名(4 = for),這是筆者個人習慣,不強制。

var todo4Id = new Dictionary<string,Todo>();  

也可以使用類型作為後綴。

var todoDict = new Dictionary<string,Todo>();  

但是 key-value 類型使用 類型作為後綴表達效果較差,所以推薦 xxx4yyy 方式。

其他的集合類型,比如 Queue、Stack 等,也同樣可以採用類型後綴的方式。

var panelStack = new Stack<UIPanel>();  var msgQueue = new Queue<Msg>();  

Stack 和 Queue 是技術概念,所以一般會在框架/插件/工具層使用,在業務層使用得較少。

集合類型部分介紹到這裡。

數值/數據類型、string/char 等 (事物)

數值類型(int/flout/double):

int 類型常見的後綴詞(限定詞):

Number

var todoNumber = todos.Length;  

Index

var todoIndex = todos.IndexOf(todo);  

Count

var todoCount = todos.Count;  

int 類型常見的前綴詞:

num

var numTodos = todos.Length;  // number of todos 縮寫,不太常用。  

float/double 類型常見的後綴詞
Average、Sum 等。

var ageAverage = ageSum / peopleCount;  

這種類型的後綴詞和前綴詞在計算時經常會用到。

這裡叫做計算限定詞。
下面列出常用的計算限定詞:

Count、Number、Num、Index、Total、Sum、Average、Max、Min、Record 等。  

《代碼大全》建議,除了 Num 之外其他的都建議放到後綴

還有一些數值類型的名字本身就表達了其類型。

age(int)、times(int) 等。  

數據類型(string/char):

數據類型沒什麼好說的,只能盡量選取名字本身就表達其類型的這種名字,而這部分的內容算是描述的表達部分,這裡先列出一部分:

username(string)、password(string)、name(string)、content(string)、title(string)、description(string)、id(int/string) 等。  

bool、枚舉(狀態)

bool 類型:

  • 使用動詞 + 時態。
    • done
    • finished
    • completed
    • updating
    • enabled
  • 使用 Is + 名詞。
    • isEnemy
  • 形容詞
    • active

給 bool 類型取名的原則很簡單,只要表示其狀態只有兩種就好。
比如 GameObject 要麼為 激活狀態 (active 為 true) ,要麼為 未激活狀態 (active 為 false)。符合這樣的就是一個很好的 bool 變量名,不可能有第三個狀態的。

再比如,一個任務要麼完成(finished 為 true),要麼為未完成(finished 為 false)。

枚舉

枚舉部分要分兩個部分講,一個是定義部分,一個是變量部分。
枚舉的定義:

// 事件  public enum UIMainPanelEvent  {  	// 事件監聽  	OnModelDataChanged,    	// 指令 (動賓短語、動詞)  	UpdateView  }    // 狀態 (與 bool 類型一樣)  public enum ResLoadState  {  	LoadBegan,  	Loading,  	LoadEnded,  }    // 結果 (名詞、形容詞)  public enum ResLoadResult  {  	Success,  	Fail,  }  

定義部分和 bool 類型很像,比如 ModelDataChanged 就是 動詞 + 時態的方式。

Unity 特有的 GameObject、Transform、Button、Image 等

GameObject heroObj;  GameObject heroGameObj;  Transform  heroTrans;  Button btnEnterMainPanel;  Image logoImage;  

以上為常見的前綴、和後綴。

關於類型的表達介紹就到這裡。

小結

到這裡我們對類型的表達進行了詳盡的介紹。
筆者關於類型表達的方法論也都全部介紹完了,大家可以筆者的方法論為準進行練習,在此基礎上進行不斷思考改進稱為自己的方法論。

思考什麼呢?
思考如何讓別人(或自己)在別的文件中使用變量就能猜到其類型呢?
所以筆者介紹的方法論不是重點也不是權威,重點是不斷進行思考。

描述的表達

終於到了重頭戲了。
描述的表達是能夠考驗一個開發者功底的工作。

對於 Model 類的變量命名。

  • 要想有一個好的描述,必須非常了解業務的。
  • 因為只有非常了解業務了,才能找到非常準確的描述。

就像本 Chat 的開頭的入門那樣:

  • 業務最起碼應該是梳理過一次的,梳理成自己能理解。
  • 梳理過一次之後要再進行一次結構化的梳理,這樣比較好轉化為代碼。
  • 結構化梳理之後才能定義 Model 類和其變量。

以上對各位的要求會比較高,所以筆者這裡建議,大家熟練掌握了了權限和類型的表達後在進行描述的練習。

而在這之前,本 Chat 也只是介紹了 Model 的變量命名。
這是因為 Model 的變量定義是相對容易的。但是我們的工作不只有定義 Model,還有好多其他的類需要我們定義,而定義類多少要定義一些變量。而本 Chat 不能覆蓋所有的變量命名場景,所以會給出一些準則和練習步驟來給大家參考。

練習步驟一: 代碼部分全部為英文。

在 Unity 中少數的需要顯示在編輯器上提供參數調整的變量可以用一些中文漢字。而剩餘的最好是全部使用英文。
這裡最好是在編碼時隨手備着一個查詞軟件,可以通過鍵盤快捷鍵快速查詢的。要養成這個習慣。到最好發現,在某一個領域或某種業務常用的詞彙就那些,自己都記住了,很少會再編碼時使用查詞軟件。

練習步驟二: 業務命名而不是技術命名

這個標題是筆者自己理解的,《代碼大全》的描述是變量名應該描述的是 what 而不是 how。也就是變量是什麼而不是怎麼做。
因為業務概念是現實的模擬,而技術概念只是手段。所以筆者理解為為了業務命名而不是為技術命名。

要理解此準則非常簡單。摘抄《代碼大全》的一段話給大家理解:

一個好記的名字反映的通常都是問題,而不是解決方案。一個好名字通常表達的是「什麼」(what),而不是「如何」(how)。一般而言,如果一個名字反映了計算的某些方面而不是問題本身,那麼它反映的就是「how」而非「what」了。請避免選取這樣的名字,而應該在名字中反映出問題本身。

一條員工數據記錄可以稱作 inputRecord 或者 employeeData。inputRecord 是一個反映輸入、記錄這些計算概念的計算機術語。employeeData 則指的是問題領域,與計算世界無關。與此類似,對一個用於表示打印狀態的位域來說,bitFlag 就要比 printerReady 更具計算機特徵。在財務軟件里,calculateValue 的計算痕迹(how) 也要比 sum (what) 明顯。

書中原文解釋的很清楚了,不再多說了。

練習步驟三:分清楚何時用技術命名

這裡要說一點注意的,要分清楚在哪個層。舉一個筆者框架中的 SerializeHelper (序列化器),序列化是技術概念,是「怎麼做?」 (how),但是它可以做遊戲數據的存儲等操作。那麼它為什麼不叫做 DataSaver/DataLoader 這些名字呢?

這是因為 SerializeHelper 這個類不在業務層,而是在框架層。框架本身提供的更多的是工具。

小結

練習步驟給出了 1、2、3,很容易上手,命名這件事本身比較容易掌握,難得是初學者非常不在意。老手呢一般趟坑躺多了自然就會意識到。而初學者再去趟一下這些坑有點浪費時間,不如一開始就意識到命名這件事的重要性。

到此呢,權限、類型、描述的練習步驟和原則(準則)都給了。接下來簡單聊聊,為什麼要命名和如何更容易地落實命名這件事到工作中呢?

為什麼要命名?

代碼的本質是信息,其承載着信息,這個信息可以被編譯器理解,也要被人理解。

首先你,代碼要被編譯器理解,這是最基本的要求,也代表着開發者能和計算機溝通,讓計算機幫忙處理任務。

其次呢,是要被人理解。

這些人可能是:

  • 自己(日常)
    • 與自己朝夕相處時間最長的就是這些代碼了,如果代碼非常容易讀懂並且整潔,每天的工作狀態都會不一樣。
    • 在 debug 時候,還是要去讀代碼的,好的代碼是更容易被閱讀的,容易被閱讀 debug 所花費的時間就會減少。
    • 增加功能,也還是一樣需要閱讀代碼的,以上同理。
    • 使自己對項目的理解更深刻、清晰。
  • 同事(協作)
    • 同樣代碼被容易閱讀,同事的效率也有所提升,也會得到同事的認可,
  • leader(晉陞)
    • 很多團隊有代碼 review 的文化,好的代碼更容易獲得上級和同事的認可。
    • 得到同事和上級的認可,晉陞就是很自然的事情了。
  • 來接手項目的人(離職)
    • 離職的時間也會被節省,不會被拖着問這是什麼那是什麼。
  • 所有開發者(開源)
    • 節省非常多人的時間,時間就是生命。
  • 讀者(教學,正在讀本 chat 的各位則是這個範圍的)

最終受益最多的還是自己,而投入的只不過每個變量多花費了幾秒的思考時間而已。

總之協作這個詞有一個協字,可以理解成妥協的協,也可以理解成協助的協。

給變量合適的命名既是一種妥協也是一種協助。

這就是為什麼筆者單開一個 chat 來寫一個大家認為如此簡單的事情。

如何落地變量的命名

單寫這一小節是希望大家不是只讀一讀這篇 chat 就完事了,而是要把變量的命名這件事情落實到自己產出的代碼上去。

在工作中養成一個習慣的阻礙有很多。
最常見的原因就是項目時間緊。

這個問題很好解決,只要訓練的時機根據實際情況進行調整就好了。

對於初學者或者項目非常緊急的開發者,在第一遍寫邏輯的時候,變量可以先隨便命名(按照自己的習慣),但是只要當前的測試點驗證通過或者功能點完成,就應該回過頭把變量的名字好好想下,進行重新命名。一般 IDE 都帶重構功能,只要改一個地方,其他代碼部分會全部跟着更改。

這樣做的目的是為了防止同一時間做兩件事情。

  • 邏輯編寫
  • 變量命名

對於命名新手來說,同一時間做兩件事情會很吃力。
本身邏輯編寫就很吃力了,再加上需要思考的變量命名,會更加吃力。
所以對於命名新手要求同一時間只思考一件事情。

對於時間非常緊急的開發者,如果進行命名的思考會影響項目的進度,那還是等項目不急的時候再落實就好了。

只要能做到,在進行邏輯編寫時,只考慮如何完成或者如何實現就好了。
在變量命名時,只專註於如何取個合適的名字,只考慮變量的描述、類型、權限就好。
如下:

  • 變量的命名是否準確描述?
  • 變量的命名是否表達了類型?
  • 變量的命名是否表示了權限?

這樣堅持一段時間後,就能做到同一時間就可以做兩件事情。

初學者們只要記住一點就夠了:

  • 合適的命名可以不在創建變量的時候完成。

但是最終目標還是將進行合適的命名這件事情養成習慣,在創建變量的同時就已經為變量取了一個非常合適的名字。

最後,非常感謝 QFramework 的 邊上海 邊前輩對此專欄的勘誤和提供的一些非常有價值的建議。

更多內容