Like Share Discussion Bookmark Smile

J.J. Huang   2020-05-05   Java   瀏覽次數:

《阿里Java開發手冊》 | 編程規約 - 控制語句

【強制】在一個 switch 塊內,每個 case 要麼通過 continue/break/return 等來終止,要麼註解說明程序將繼續執行到哪一個 case 為止;在一個 switch 塊內,都必須包含一個 default 語句並且放在最後,即使它什麼程式碼也沒有。
說明:注意 break 是退出 switch 語句塊,而 return 是退出方法體。


【強制】當 switch 括號內的變數類型為 String 並且此變數為外部參數時,必須先進行 null 判斷。
反例:如下的程式碼輸出是什麼?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SwitchString {
public static void main(String[] args) {
method(null);
}
public static void method(String param) {
switch (param) {
// 肯定不是進入這裡
case "sth":
System.out.println("it's sth");
break;
// 也不是進入這裡
case "null":
System.out.println("it's null");
break;
// 也不是進入這裡
default:
System.out.println("default");
}
}
}

【強制】在 if/else/for/while/do 語句中必須使用大括號。
說明:即使只有一行程式碼,禁止不採用大括號的編碼方式:if (condition) statements;


【強制】三目運算符 condition? 表達式1 : 表達式2 中,高度注意表達式 1 和 2 在類型對齊時,可能拋出因自動拆箱導致的 NPE 異常。
說明:以下兩種場景會觸發類型對齊的拆箱操作:

  • 1.表達式 1 或表達式 2 的值只要有一個是原始類型。
  • 2.表達式 1 或表達式 2 的值的類型不一致,會強制拆箱升級成表示範圍更大的那個類型。

反例:

1
2
3
4
5
6
Integer a = 1;
Integer b = 2;
Integer c = null;
Boolean flag = false;
// a*b 的結果是 int 類型,那麼 c 會強制拆箱成 int 類型,拋出 NPE 異常
Integer result = (flag ? a * b : c);

【強制】在高並發場景中,避免使用”等於”判斷作為中斷或退出的條件。
說明:如果並發控制沒有處理好,容易產生等值判斷被“擊穿”的情況,使用大於或小於的區間判斷條件來代替。
反例:判斷剩餘獎品數量等於 0 時,終止發放獎品,但因為並發處理錯誤導致獎品數量瞬間變成了負數,這樣的話,活動無法終止。


【推薦】當某個方法的程式碼行數超過 10 行時,return / throw 等中斷邏輯的右大括號後加一個空行。
說明:這樣做邏輯清晰,有利於程式碼閱讀時重點關注。


【推薦】表達異常的分支時,少用 if-else 方式,這種方式可以改寫成:

1
2
3
4
5
if (condition) {
...
return obj;
}
// 接著寫 else 的業務邏輯程式碼;

說明:如果非使用 if()…else if()…else… 方式表達邏輯,避免後續程式碼維護困難,請勿超過 3 層。
正例:超過 3 層的 if-else 的邏輯判斷程式碼可以使用衛語句、策略模式、狀態模式等來實作,其中衛語句示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void findBoyfriend(Man man) {
if (man.isUgly()) {
System.out.println("本姑娘是外貌協會的資深會員");
return;
}
if (man.isPoor()) {
System.out.println("貧賤夫妻百事哀");
return;
}
if (man.isBadTemper()) {
System.out.println("銀河有多遠,你就給我滾多遠");
return;
}
System.out.println("可以先交往一段時間看看");
}

【推薦】除常用方法(如getXxx/isXxx)等外,不要在條件判斷中執行其它複雜的語句,將復雜邏輯判斷的結果賦值給一個有意義的布林變數名,以提高可讀性。
說明:很多 if 語句內的邏輯表達式相當複雜,與、或、取反混合運算,甚至各種方法縱深調用,理解成本非常高。如果賦​​值一個非常好理解的布林變數名字,則是件令人爽心悅目的事情。
正例:
// 偽程式碼如下

1
2
3
4
final boolean existed = (file.open(fileName, "w") != null) && (...) || (...);
if (existed) {
...
}

反例:

1
2
3
4
5
public final void acquire(long arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
selfInterrupt();
}

【推薦】不要在其它表達式(尤其是條件表達式)中,插入賦值語句。
說明:賦值點類似於人體的穴位,對於程式碼的理解至關重要,所以賦值語句需要清晰地單獨成為一行。
反例:

1
2
3
4
5
6
public Lock getLock(boolean fair) {
// 算術表達式中出現賦值操作,容易忽略 count 值已經被改變
threshold = (count = Integer.MAX_VALUE) - 1;
// 條件表達式中出現賦值操作,容易誤認為是 sync==fair
return (sync = fair) ? new FairSync() : new NonfairSync();
}

【推薦】循環體中的語句要考量性能,以下操作盡量移至循環體外處理,如定義對象、變數、 獲取資料庫連接,進行不必要的 try-catch 操作(這個try-catch 是否可以移至循環體外)。


【推薦】避免採用取反邏輯運算符。
說明:取反邏輯不利於快速理解,並且取反邏輯寫法必然存在對應的正向邏輯寫法。
正例:使用 if (x < 628) 來表達 x 小於 628
反例:使用 if (!(x >= 628)) 來表達 x 小於 628


【推薦】接口入參保護,這種場景常見的是用作批量操作的接口。
反例:某業務系統,提供一個用戶批量查詢的接口,API 文件上有說最多查多少個,但接口實作上沒做任何保護,導致調用方傳了一個 1000 的用戶 id 陣列過來後,查詢訊息後,內存爆了。


【參考】下列情形,需要進行參數校驗:

  • 調用頻次低的方法。
  • 執行時間開銷很大的方法。此情形中,參數校驗時間幾乎可以忽略不計,但如果因為參數錯誤導致中間執行回退,或者錯誤,那得不償失。
  • 需要極高穩定性和可用性的方法。
  • 對外提供的開放接口,不管是 RPC/API/HTTP 接口。
  • 敏感權限入口。

【參考】下列情形,不需要進行參數校驗:

  • 極有可能被循環調用的方法。但在方法說明里必須註明外部參數檢查。
  • 底層調用頻度比較高的方法。畢竟是像純淨水過濾的最後一道,參數錯誤不太可能到底層才會暴露
    問題。一般 DAO 層與 Service 層都在同一個應用中,部署在同一台伺服器中,所以 DAO 的參數校驗,可 以省略。
  • 被聲明成 private 只會被自己程式碼所調用的方法,如果能夠確定調用方法的程式碼傳入參數已經做過檢 查或者肯定不會有問題,此時可以不校驗參數。

心得

看完這篇「控制語句」後,多數在說的是與判斷有關的程式碼,還有一些程式碼上面閱讀可能造成的誤會,或是花費很多成本來做理解。整體來說主要的就是讓程式碼好閱讀好理解;當然另外有提到一些判斷的條件不可過於複雜或是多層,造成效能上的不良。

結語

文章越看越多,技術越學越多,就會發現自己的不足;技術學到後面都會想要將基礎再重新在打得更加扎實。

以前在開發覺得理所當然的事情,例如:命名規則、命名規範,照著別人怎麼說就怎麼做的想法,並沒有好好去想為什麼要這樣設計和規範。
於是乎同事們推薦《阿里巴巴Java開發手冊》來做閱讀,書中提到種種規範《正確範例》、《錯誤範例》還有解釋定義說明;我相信在閱讀完這一系列後,一定會更加扎實且實在。

如對此書有興趣,建議去購買官方認證的書籍,給予官方支持。

註:如有侵權,通知即刪。


註:以上參考了
Alibaba-Java-Coding-Guidelines Github
Alibaba-Java-Coding-Guidelines English Version