## Chapter 11 變數名稱的力量 參考摘要來源[軟體開發實務指南 (2) — 變數名稱命名](https://medium.com/johnliu-%E7%9A%84%E8%BB%9F%E9%AB%94%E5%B7%A5%E7%A8%8B%E6%80%9D%E7%B6%AD/%E8%BB%9F%E9%AB%94%E9%96%8B%E7%99%BC%E5%AF%A6%E5%8B%99%E6%8C%87%E5%8D%97%E9%96%B1%E8%AE%80%E7%AD%86%E8%A8%98-2-%E8%AE%8A%E6%95%B8%E5%90%8D%E7%A8%B1%E5%91%BD%E5%90%8D-ee8ebe0cde2d) ### 內容章節 - 11.1 選擇好變數名稱的注意事項 - 11.2 為特定型別的資料命名 - 11.3 命名規則的力量 (建立命名規則的好處) - 11.4 非正式命名規則 ---- - 11.5 標準前綴 - 11.6 建立具備可讀性的短名稱 (縮寫的注意事項) - 11.7 應該避免的名稱 --- ### 11.1 選擇好變數名稱的注意事項 1. <font color=#FF8064>最重要的命名注意事項</font>,命名完全又準確。通常,<font color=#FF0000>對變數的描述</font>就是<strong>++最佳++的變數名稱</strong>。 - Java 範例 : 不優秀的變數名稱 ```[Java]= x = x - xx; xxx = fido + SalesTax(fido); x = x + LateFee(x1, x) + xxx; x = x + Interest(x1, x); ``` - Java 範例 : 良好的變數名稱 ```[Java] balance = balance - lastPayment; monthyTotal = newPurchases + SalesTax(newPurchases); balance = balance + LateFee(customerID, balance) + monthlyTotal; balance = balance + Interest(customerID, balance); ``` ---- 舉例: ![image](https://hackmd.io/_uploads/SkkwREqTa.png) - 目前日期,使用『 currentdate 』,而不是『 date 』 - `var currentDate` 優於 `var date` ---- 2. 問題為導向,而不是計算方法。employeeData 比 inputRec 更好 3. 變數長度,限定 10 -16 個字元內 變數命稱長短的比較: ![image](https://hackmd.io/_uploads/HyED1B9p6.png) ---- 4. 考慮作用域,短變數意旨短使用區間 ; 長變數常用在全域變數 5. 全域變數可用 namespace 切割,例如 UserInterFace::Employee vs DatabaseInterface::Employee C# 範例 : 使用 namespace 關鍵字來劃分全域命名空間 ```[C++]= namespace UserInterFace { public interface IEmployeeService{ // 取得所有員工資料的方法 Task<IEnumerable<EmployeeModel>> GetEmployees(); // 更新員工資料的方法 Task<bool> UpdateEmployee(EmployeeModel employee); } } namespace DatabaseInterface { public interface IEmployeeService{ // 取得所有員工資料的方法 Task<IEnumerable<EmployeeDataModel>> GetEmployees(); // 更新員工資料的方法 Task<bool> UpdateEmployee(EmployeeDataModel employeeData); } } ``` ---- 6. 計算值修飾詞 ( Total, Sum, Average, Max, Min ) 應放在最後方,例如:revenueTotal 好於 totalRevenue,前者要放入真正有意義的詞 7. 變數名稱中的常用反義詞,例如:minPurchase / MaxPurchase --- ### 11.2 為特定型別的資料命名 1. 為迴圈索引 key 值命名,避免 i, j 的命名方式 - Java 範例:簡單的迴圈變數名稱 ```[Java]= for ( i = fristItem; i < lastItem; i++) { data[i] = 0; } ``` ---- - Java 範例:描述性較好的迴圈變數名稱 ```[Java]= recordCount = 0; while ( moreScores() ) { score[recordCount] = GetNextScore(); recordCount++; } // lines using recordCount ``` ---- - Java 範例:巢狀迴圈中好的迴圈變數名稱 ```[Java] for (teamIndex = 0; teamIndex < teamCount; teamIndex++) { for (evntIndex = 0; eventIndex < eventCount[teamIndex]; eventIndex++) { score[tramIndex][eventIndex] = 0; } } ``` ---- ### 2. 為狀態變數命名,避免用 flag 作為名稱 - C++ 範例:涵義模糊的標記 ```[C++]= if (flag) ... if (statusFlag & 0x0F) ... if (printFlag == 16) ... if (computeFlag == 0) ... flag = 0x1; statusFlag = 0x80; printFlag = 16; computeFlag = 0; ``` ---- - C++ 使用狀態變數較佳的範例 ```[C++]= if (dateReady) ... if (characterType & PRINTABLE_CHAR) ... if (repotType == ReportType_Annual) ... if (recalcNeeded == false) ... dateReady = true; characterType = CONTROL_CHARACTER; reportType = ReportType_Annual; recalcNeeded = false; ``` ---- C++ 中宣告狀態變數 ```[C++] // values for CharcterType const int LETTER = 0x01; const int DIGIT = 0x02; const int PUNCTUATION = 0x04; const int LINE_DRAW = 0x08; const int PRINTABLE_CHAR = (LETTER | DIDIT | PUNCTUATION | LINE_DRAW); const int CONTROL_CHARACTER = 0x80; ``` ```[C++]= // values for ReportType enum ReportType { ReportType_Daily, ReportType_Monthly, ReportType_Quarterly, ReportType_Annual, ReportType_ALL }; ``` ---- 因為是狀態變數,是為了反映某件事情是否發生變化,所以可以將變數使用 `bool` 宣告。 參考來源:網路的範例文章(https://moodle.tnfsh.tn.edu.tw/mod/page/view.php?id=153) --- ### 3. 要替臨時變數 temp 做命名 - C++ 範例:不提供訊息的「臨時」變數名稱 ```[C++] // 計算一元二次方程的根 // 假設 (b^2-4*a*c) 是 有正根的 temp = sqrt(b^2-4*a*c); root[0] = (-b + temp) / (2 * a); root[1] = (-b - temp) / (2 * a); ``` - C++ 範例:用真正的變數替代「臨時」變數 ```[C++] discriminant = sqrt(b^2-4*a*c); root[0] = (-b + discriminant) / (2 * a); root[1] = (-b - discriminant) / (2 * a); ``` ---- 4. 布林變數,found > isFound,跟可讀性有關,且避免否定命名法( isNotFound ) - 典型的布林變數名稱 - done : 預設為 false。 - error : 預設為 false。 - found : 預設為 false。 - sucess / ok : 可以用更具體的命名來代替 sucess,例如:processingComplete。 - 給布林變數賦予隱含「真/假」含意的名稱 - 取名為 statusOK 或是 statusError 會比直接取名 status 好。 - sourceFileAvailable 及 sourceFileFound 較優於 sourceFile。 ---- 5. 列舉和常數命名也要注意精準度,避免 FIVE = 6.0 這種 - Visual Basic 範例:為列舉型別使用前綴命名原則 ```[Visual Basic] Public Enum Color Color_Red Color_Green Color_Blue End Enum Public Enum Planet Planet_Earth Planet_Mars Planet_Venus End Enum ``` --- ### 11.3 命名規則的力量 (建立命名規則的好處) 1. 做全域決策而不是局部決策 2. 專案間傳遞知識更容易 3. 更快速學習程式碼 4. 避免命名增生,同意義不同名稱 5. 採用命名規則的時機點 - 多人一起協作一個專案的時候 - 交接工作給下一位繼任者時候 - Code Review 的時候 --- ### 11.4 非正式命名規則 1. 大多數專案採用非正式命名規則 ```[C#] 方案一:透過大寫字母開頭區分型別和變數 Widget widget; LongerWidget longerWidget; ``` ```[C#] 方案二:透過全部大寫區分型別和變數 WIDGET widget; LONGERWIDGET longerWidget; ``` ```[C#] 方案三:透過給型別加「t_」前綴區分型別和變數 t_Widget widget; t_LongerWidget longerWidget; ``` ```[C#] 方案四:透過給變數加「a」前綴區分型別和變數 Widget awidget; LongerWidget alongerWidget; ``` ```[C#] 方案五:透過對變數採用更明確的名稱區分型別和變數 Widget employeeWidget; LongerWidget fullEmployeeWidget; ``` ---- 2. 可以設定自己的命名規則,例如 g_ 前綴為全域變數,m_ 前綴為成員變數, const_ 前綴代表不可修改的變數 ---- 3. 使用各程式語言風格自己的命名規則 - C 語言的命名規則 | 實體 | 描述 | | ------------------ | -------- | | TypeName | 型別名稱混合使用大小寫,首字大寫 | | GlobalRoutineName() | 公用子程式名稱混合使用大小寫 | | f_FileRoutineName()| 單一模組(檔案)私用的子程式名稱用 f_前綴 | | LocalVarialbe | 局部變數混合使用大小寫。名稱與底層資料型別無關,應反映該變數所代表的事物 | ---- | 實體 | 描述 | | ------------------ | -------- | | RoutineParameter | 子程式參數的格式與局部變數相同 | | f_FileStaticVariable | 模組(檔案)變數名稱使用 f_ 前綴 | | G_GLOBAL_GlobalVariable | 全域變數名稱以 G_ 前綴和一個能反映定義該變數的模組(檔案)的、全部大寫的名稱開頭 | ---- | 實體 | 描述 | | ------------------ | -------- | | LOCAL_CONSTANT | 單一子程式或模組(檔案)私用的具名常數全部使用大寫 | | G_GLOBALCONSTANT | 全域具名常數全部大寫,並加上 G_ 前綴一個能反映定義該變數的模組(檔案)的、全部大寫的名稱開頭 | ---- | 實體 | 描述 | | ------------------ | -------- | | LOCALMACRO() | 單一子程式或模組(檔案)私用的巨集定義全部使用大寫 | | G_GLOBAL_MACRO() | 全域巨集定義全部大寫,並以 G_ 前綴和一個能反映定義該巨集的模組(檔案)的、全部大寫的名稱開頭 | ---- - C++ & Java 的命名規則 | 實體 | 描述 | | --------- | -------- | | ClassName | 類別名稱混合使用大小寫,首字大寫 | | TypeName | 型別定義,包括列舉型別和 typeof,混合使用大小寫,首字大寫 | | EnumeratedTypes | 遵循上述規則外,列舉型別總以複數形式表示 | ---- | 實體 | 描述 | | --------- | -------- | | localVariable | 局部變數混合使用大小寫,首字小寫。名稱與底層資料型別無關,應反映該變數所代表的事物 | | routineParameter | 子程式參數的格式與局部變數相同 | | RoutineName() | 子程式名稱混合使用大小寫 | ---- | 實體 | 描述 | | --------- | -------- | | m_ClassVariable | 對類別的多個子程式可見(且只對該類別可見)的成員變數名稱使用 m_ 前綴 | | g_GlobalVariable | 全域變數名稱用 g_ 前綴 | | CONSTANT | 具名常數全部大寫 | ---- | 實體 | 描述 | | --------- | -------- | | MACRO | 巨集全部大寫 | | Base_EnumeratedType | 列舉型別名稱用能夠反映其基礎型別的、單數形式的前綴,例如:Color_REd、Color_Blue | ---- - Visual Basic 的命名規則 | 實體 | 描述 | | ----------- | ---------- | | C_ClassName | 類別名稱混合使用大小寫,首字大寫,並加上 C_前綴 | | T_TypeName | 型別定義,包括列舉型別和 typeof,混合使用大小寫,首字大寫,並加上 T_ 前綴 | | T_EnumeratedTypes | 遵循上述的規則外,列舉型別總以複數形式表示 | ---- | 實體 | 描述 | | ----------- | ---------- | | localVariable | 局部變數混合使用大小寫,首字小寫,名稱與底層資料型別無關,應反映該變數所代表的事物 | | rountineParameter | 子程式參數的格式與局部變數相同 | | RoutineName() | 子程式名稱混合使用大小寫 | ---- | 實體 | 描述 | | ----------- | ---------- | | m_ClassVariable | 只在一個類別範圍內對該類別的多個子程式可見的成員變數名稱使用 m_ 前綴 | | g_GlobalVariable | 全域變數名稱以 g_ 前綴開頭 | | CONSTANT | 具名常數全部大寫 | | Base_EnumeratedType | 列舉型別名稱用能夠反映其基礎型別的、單數形式的前綴,例如:Color_REd、Color_Blue | ---- ### 【討論 1】目前自己接觸過(或是喜歡)的命名規則 ```[C#] // 非同步方法 public async Task<IResult> GetAsync() { } public async Task<IResult> UpdateAsync() { } // 私有 DI 實體 private readonly IReturn _response; // 私有方法 private IAction _CountAmount() { } // 變數名稱小寫開頭 var countNumber = 0; ``` `git命名規則 : Function/你的名字/2024/03/08/Register ` ---- ### 11.5 標準前綴 - 匈牙利命名法 參考來源 Wiki (https://zh.wikipedia.org/zh-tw/%E5%8C%88%E7%89%99%E5%88%A9%E5%91%BD%E5%90%8D%E6%B3%95 - 在匈牙利命名法中,一個變數名由一個或多個小寫字母開始,這些字母有助於記憶變數的類型和用途,緊跟著的就是程式設計師選擇的任何名稱。這個後半部分的首字母可以大寫,以區別前面的類型指示字母 ---- - 只用者自定義型別縮寫 User Defined Types (UDT) - 參考資料 (https://stackoverflow.com/questions/20596083/user-defined-types-udt-in-c) - Model 的資料 ```[C#] namespace YourNamespace.Models { public class Card { public int CardId { get; set; } // 卡片ID public string Title { get; set; } // 卡片標題 public string Description { get; set; } // 卡片描述 public DateTime CreatedTime { get; set; } // 建立時間 // UDT public int CardId { get; set; } // 卡片ID public str Title { get; set; } // 卡片標題 public str Description { get; set; } // 卡片描述 public DTime CreatedTime { get; set; } // 建立時間 } } ``` --- ### 11.6 建立具備可讀性的短名稱 (縮寫注意事項) 1. 不要從每個單字刪除一個字母來縮寫 2. 縮寫要一致,不要一下 No, 一下 Num 3. 要能用嘴巴念出來 --- ### 【討論 2】藍新金流 信用卡定期定額 Api 串接技術手冊 https://www.newebpay.com/website/Page/content/download_api --- ### 11.7 應該避免的名稱 1. 避免使用令人誤解的名稱 2. 避免使用具有相似含義的名稱,例如 input 和 inputValue 3. 避免使用具有不同含義卻相似名稱的變數,例如 clientRecs 和 clientReps 4. 避免用數字命名,例如 Button1、Button2 5. 避免發音相近的命名,例如 wrap 和 rap 6. 不要拼錯字 --- ### 小結 - 好的變數名稱是提高程式可讀性的一個關鍵要素。對特殊種類的變數,例如迴圈的索引及和狀態變數,需要加以特殊的考量。 - 名稱要盡可能具體,不要使用太通用以致於能夠用於多種目的的名稱通常不建議。 - 命名規則應能夠區分局部資料、類別資料、全域資料。且應當可區分型別名、具名常數、列舉型別名稱和變數名稱。 - 無論做哪種型別專案,我們都應該採用某種變數命名規則。我們所採用的規則的種類取決於程式的規模,以及專案參與的人數。 - 現代程式語言很少需要用到縮寫,如果有需要使用縮寫,請使用專案縮寫詞典或者標準前綴來幫助理解縮寫。 - 程式碼閱讀的次數遠遠多於編寫的次數。確保你所取的名稱更側重於閱讀方便而不是撰寫方便 --- ### 【討論 3】在開發時,不同程式語言在其 IDE 輔助檢測 Code 的套件工具 - .NET : Compiler Platform(Roslyn) Analyzers (https://learn.microsoft.com/zh-tw/visualstudio/code-quality/roslyn-analyzers-overview?view=vs-2022) - .NETCore : StyleCop.Analyzers (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/Configuration.md) - 大家可以提出討論