代碼精進之路 核心知識 規範篇 命名篇

命名篇

1、有意思的命名

1.1、變量名

變量名應該是名詞,能夠正確地描述業務,有表達力。如果一個變量名需要註釋來補充說明,那麼很可能說明命名就有問題。

int d ; //標識過去的天數
int elapsedTimeInDays;

# 魔術數--見名知意
SECONDS_PER_DAY   -> 86400
PAGE_SIZE  -> 10

1.2、函數名

函數命名要具體,空泛的命名沒有意義。

processData()   差於  validateUserCredentials() 或 eliminateDuplicateRequests()

getLatestEmployee()  好於 popRecord()

1.3、類名

類是面向對象中最重要的概念之一,是一組數據和操作的封裝。對於一個應用系統,我們可以將類分爲兩大類:實體類輔助類

實體類承載了核心業務數據和核心業務邏輯,其命名要充分體現業務語義,並在團隊內達成共識,如CustomerBankEmployee等。

輔助類是輔佐實體類一起完成業務邏輯的,其命名要能夠通過後綴來體現功能。例如,用來爲Customer做控制路由的控制類CustomerController、提供Customer服務的服務類CustomerService、獲取數據存儲的倉儲類CustomerRepository

對於輔助類,儘量不要用HelperUtil之類的後綴,因爲其含義太過籠統,容易破壞SRP(單一職責原則)。比如對於處理CSV,可以這樣寫:

CSVHelper.parse(String)
CSVHelper.create(int[])

但是我更建議將CSVHelper拆開:

CSVParser.parse(String)
CSVBuilder.create(int[])

1.4、包名

包(Package)代表了一組有關係的類的集合,起到分類組合和命名空間的作用。

包名應該能夠反映一組類在更高抽象層次上的聯繫。例如,有一組類ApplePearOrange,我們可以將它們放在一個包中,命名爲fruit

包的命名要適中,不能太抽象,也不能太具體。此處以上面提到的水果作爲例子,如果包名過於具體,比如Apple,那麼Pear和Orange放進該包中就不恰當了;如果報名太抽象,稱爲Object,而Object無所不包,這就失去了包用來限定範圍的作用。

1.5、模塊名

相對於包來說,模塊的粒度更大,通常一個模塊中包含了多個包。

名稱要反映模塊在系統中的職責。因此,對任何應該遵循COLA規範的應用都有着xxx-controllerxxx-appxxx-domainxxx-Infrastructure這4個標準模塊。

2、保持一致性

保持命名的一致性,可以提高代碼的可讀性,從而簡化複雜度。因此,我們要小心選擇命名,一旦選中,就要持續遵循,保證名稱始終一致。

2.1、每個概念一個詞

每個概念對應一個詞,並且一以貫之。

例如,fetch、retrieve、get、find和query都可以表示查詢的意思,如果不加約定地給多個類中的同種查詢方法命名,你怎麼記得是哪個類中的哪個方法呢?同樣,在一段代碼中,同時存在manager、controller和handler,會令人感到困惑。

在實際項目中,按照以下約定,保持命名的一致性:

CURD操作 方法名約定
新增 create
添加 add
刪除 remove
修改 update
查詢(單個結果) get
查詢(多個結果) list
分頁查詢 page
統計 count

2.2 使用對仗詞

遵守對仗詞的命名規則有助於保持一致性,從而提高代碼的可讀性。
下面列出一些常見的對仗詞組:

  • add/remove
  • increment/decrement
  • open/close
  • begin/end
  • insert/delete
  • show/hide
  • create/destroy
  • lock/unlock
  • source/target
  • first/last
  • min/max
  • start/stop
  • get/set
  • next/previous
  • up/down
  • old/new

2.3 後置限定詞

如果你要用類似TotalSumAverageMaxMin這樣的限定詞來修改某個命名,那麼記住把限定詞加到名字的最後,並在項目中貫徹執行,保持命名風格的一致性。

這種方法有很多優點。首先,變量名中最重要的部分,即爲這一變量賦予主要含義的部分應位於最前面,這樣可以突出顯示,並會被首先閱讀到。其次,可以避免同時在程序中使用totalRevenuerevenueTotal而產生的歧義。如果貫徹限定詞後置的原則,我們就能收穫一組非常優雅、具有對稱性的變量命名,例如revenueTotal(總收入)、expenseTotal(總支出)、revenueAverage(平均收入)和expenseAverage(平均支出)。

需要注意的一點是Num這個限定詞,Num放在變量名的結束位置表示一個下標,customerNum表示的是當前客戶的序號。爲了避免Num帶來的麻煩,我建議用Count或者Total來表示總數,用Id表示序號。這樣,customerCount表示客戶的總數,customerId表示客戶的編號。

3、自明的代碼

3.1、中間變量

我們可以通過添加中間變量讓代碼變得更加自明,即將計算過程打散成多個步驟,並用有意義的變量名來命名中間變量,從而把隱藏的計算過程以顯性化的方式表達出來。

例如,我們要通過Regex來獲得字符串中的值,並放到map中。

Marcher matcher = headerPattern.matcher(line);
if(matcher.find()) {
  headers.put(matcher.group(1), matcher.group(2));
}

用中間變量,可以寫成如下形式:

Marcher matcher = headerPattern.matcher(line);
if(matcher.find()) {
  String key = matcher.group(1);
  String value = matcher.group(2);
  headers.put(key, value);
}

中間變量的這種簡單用法,顯性地表達了第一個匹配組是key,第二個匹配組是value。只要把計算過程打散成一系列良好命名的中間值,不透明的語義自然會變得透明。

3.2、設計模式語言

使用設計模式語言也是代碼自明的重要手段之一,在技術人員之間共享和使用設計模式語言,可以極大地提升溝通的效率。當然,前提是大家都要理解和熟悉這些模式,否則就會變成“雞同鴨講”。因此,我們有必要在命名上就將設計模式顯性化出來,這樣閱讀代碼的人能很快領會到設計者的意圖。

例如,Spring裏面的ApplicationListener就充分體現了它的設計和用處。通過這個命名,我們知道它使用了觀察者模式,每一個被註冊的ApplicationListenerApplication狀態發生變化時,都會接收到一個notify。這樣我們就可以在容器初始化完成之後進行一些業務操作,比如數據加載、初始化緩存等。

3.3、小心註釋

  • 不要複述功能
  • 要解釋背後意圖

4、規範

4.1、命名規範

在Java中,我們通常使用如下命名約定。

  • 類名採用“大駝峯”形式,即首字母大寫的駝峯,例如ObjectStringBufferFileInputStream
  • 方法名採用“小駝峯”形式,即首字母小寫的駝峯,方法名一般爲動詞,與參數組成動賓結構,例如Threadsleep(long millis)StringBufferappend(String str)
  • 常量命名的字母全部大寫,單詞之間用下劃線連接,例如TOTAL_COUNTPAGE_SIZE等。
  • 枚舉類以EnumType結尾,枚舉類成員名稱需要全大寫,單詞間用下劃線連接,例如SexEnum.MALESexEnum.FEMALE
  • 抽象類名使用Abstract開頭;異常類使用Exception結尾;實現類以Impl結尾;測試類以它要測試的類名開始,以Test結尾。

4.2、日誌規範

詳細的日誌輸出級別分爲OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者自定義的級別。我認爲比較有用的4個級別依次是ERROR、WARN、INFO和DEBUG。通常這4個級別就能夠很好地滿足我們的需求了。

  • 1.ERROR級別
    ERROR表示不能自己恢復的錯誤,需要立即被關注和解決。例如,數據庫操作錯誤、I/O錯誤(網絡調用超時、文件讀取錯誤等)、未知的系統錯誤(NullPointerException、OutOfMemoryError等)。
    對於ERROR,我們不僅要打印線程堆棧,最好打印出一定的上下文(鏈路TraceId、用戶Id、訂單Id、外部傳來的關鍵數據),以便於排查問題。
    ERROR要接入監控和報警系統。ERROR需要人工介入處理,及時止損,否則會影響系統的可用性。當然也不能濫用ERROR,否則就會出現“狼來了”的情況。我在實際工作中曾碰到過系統每天會發出上千條錯誤報警的情況,導致根本沒有人看報警內容,在真正出現問題時,也沒有人關注,從而引發線上故障。因此,一定要做好ERROR輸出的場景定義和規範,再配合監控治理,雙管齊下,確保線上系統的穩定。
  • 2.WARN級別
    對於可預知的業務問題,最好不要用ERROR輸出日誌,以免污染報警系統。例如,參數校驗不通過、沒有訪問權限等業務異常,就不應該用ERROR輸出。
    需要注意的是,在短時間內產生過多的WARN日誌,也是一種系統不健康的表現。因此,我們有必要爲WARN配置一個適當閾值的報警,比如訪問受限WARN超過100次/分,則發出報警。這樣在WARN日誌過於頻繁時,我們能及時收到系統報警,去跟進用戶問題。例如,如果是產品設計上有缺陷導致用戶頻繁出現操作卡點,可以考慮做一下流程或者產品上的優化。
  • 3.INFO級別
    INFO用於記錄系統的基本運行過程和運行狀態。
    通常來說,優先根據INFO日誌可初步定位,主要包括系統狀態變化日誌、業務流程的核心處理、關鍵動作和業務流程的狀態變化。適當的INFO可以協助我們排查問題,但是切忌把INFO當成DEBUG使用,這樣會導致記錄的數據過多,一方面影響系統性能,日誌文件增長過快,消耗不必要的存儲資源;另一方面也不利於閱讀日誌文件。
  • 4.DEBUG級別
    DEBUG是輸出調試信息,如request/response的對象內容。在輸出對象內容時,要覆蓋Object的toString方法,否則輸出的是對象的內存地址,就起不到調試的作用了。通常在開發和預發環境下,DEBUG日誌會打開,以方便開發和調試。而在線上環境,DEBUG開關需要關閉,因爲在生產環境下開啓DEBUG會導致日誌量非常大,其損耗是難以接受的。只有當線上出現bug或者棘手的問題時,纔可以動態地開啓DEBUG。爲了防止日誌量過大,我們可以採用分佈式配置工具來實現基於requestId判斷的日誌過濾,從而只打印我們所需請求的DEBUG日誌。

4.3、異常規範

4.3.1、異常處理

建議在業務系統中設定兩個異常,分別是BizException(業務異常)和SysException(系統異常),而且這兩個異常都應該是UncheckedException。

爲什麼不建議用Checked Exception呢?

因爲它破壞了開閉原則。如果你在一個方法中拋出了Checked Exception,而catch語句在3個層級之上,那麼你就要在catch語句和拋出異常處理之間的每個方法簽名中聲明該異常。這意味着在軟件中修改較低層級時,都將波及較高層級,修改好的模塊必須重新構建、發佈,即便它們自身所關注的任何東西都沒有被改動過。這也是C#、Python和Ruby語言都不支持Checked Exception的原因,因爲其依賴成本要高於顯式聲明帶來的收益。

最後,針對業務異常和系統異常要做統一的異常處理,類似於AOP,在應用處理請求的切面上進行異常處理收斂,其處理流程如下:

try {
  // 業務處理
Response res = process(request);
} catch(BizException e) {
  // 業務異常使用 warn 級別
  logger.warn("BizException with error code:{}, error message:{}", e.getErrorCode, e.getErrorMeg());
}catch (SysException ex){
  //系統異常使用 error 級別
  logger.error("System error" + ex.getMessage(), ex);
}catch (Exception ex) {
  //兜底
  logger.error("System error" + ex.getMessage(), ex);
}

千萬不要在業務處理內部到處使用try/catch打印錯誤日誌,這樣會使功能代碼和業務代碼纏繞在一起,讓代碼顯得很凌亂,並且影響代碼的可讀性。

4.3.2、錯誤碼

(1)編號錯誤碼

對於平臺、底層系統或軟件產品,可以採用編號式的編碼規範,好處是編碼風格固定,給人一種正式感;缺點是必須要配合文檔才能理解錯誤碼代表的意思。

例如,數據庫軟件Oracle中總共有2000多個異常,其編碼規則是ORA-00001~ORA-02149,每一個錯誤碼都有對應的錯誤解釋。
ORA-00001:違反唯一約束條件。
ORA-00017:請求會話以設置跟蹤事件。
ORA-00018:超出最大會話數。
ORA-00019:超出最大會話許可數。
ORA-00023:會話引用進程私用內存;無法分離會話。
ORA-00024:單一進程模式下不允許從多個進程註冊。

要注意,對不同的錯誤波段,一定要預留足夠的碼號。例如,淘寶開放平臺所用的3位數就顯得有些拘謹,其支撐的錯誤數最多不能超過100,超過100後,爲了向後兼容,只能通過子錯誤碼的方式進行變通處理。

(2)顯性化錯誤碼

顯性化的錯誤碼具有更強的靈活性,適合敏捷開發。例如,我們可以將錯誤碼定義成3個部分:類型+場景+自定義標識。每個部分之間用下劃線連接,內容以大駝峯的方式書寫。這裏可以打破Java的常量命名規範,駝峯方式會更方便閱讀。

對於錯誤類型,我們可以做一個約定:P代表參數異常(ParamException)、B代表業務異常(BizException)、S代表系統異常(SystemException)。一個完整的示例如表2-1所示。

錯誤類型 錯誤碼約定 舉例
參數異常 P_XX_XX P_Customer_NameNull:客戶姓名不能爲空
業務異常 B_XX_XX B_Customer_NameAlreadyExist:客戶姓名已存在
系統異常 S_XX_XX S_Unknow_Error:未知系統錯誤

如果業務應用的錯誤都用這種約定來描述和表達,那麼只要大家都遵守相同的規範,系統的可維護性和可理解性就會大大提升。

4.4、埋點規範

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章