高效能團隊的Java研發規範(進階版)

2022-08-12 18:05:39

目前大部分團隊是使用的阿里巴巴Java開發規範,不過在日常開發中難免遇到覆蓋不到的場景,本文在阿里巴巴Java開發規範基礎上,補充一些常用的規範,用於提升程式碼質量及增強程式碼可讀性。

程式設計規約

1、基礎型別及操作

(1)轉換

基本型別轉換

String型別轉數位:使用apache common-lang3包中的工具類NumberUtils,優勢:可設定預設值,轉換出錯時返回預設值

NumberUtils.toInt("1");

拆箱:包裝類轉化為基本型別的時候,需要判定null,比如:

Integer numObject = param.get(0);
int num = numObject != null ? numObject : 0;
物件型別轉換

使用MapStruct工具,轉換類字尾Convertor,所有轉換操作都在轉換類中操作,禁止在業務程式碼中編寫大量set程式碼。

(2)判斷

列舉判定

使用列舉判等,而非列舉對應的數位。因為列舉更直觀,方便檢視程式碼及偵錯,數位容易出錯。

判空

各種物件的判空:

//物件判空&非空
Objects.isNull()
Objects.nonNull()

//String判空&非空
StringUtils.isEmpty()   //可匹配null和空字串
StringUtils.isNotEmpty()
StringUtils.isBlank()   //可匹配null、空字串、多個空白字元
StringUtils.isNotBlank()

//集合判空&非空
CollectionUtils.isEmpty()
CollectionUtils.isNotEmpty()

//Map判空&非空
MapUtils.isEmpty()
MapUtils.isNotEmpty()
斷言

使用Guava裡的Preconditions工具類,比如:

//如果是空則拋異常
Preconditions.checkNotNull()
//通用判斷
Preconditions.checkArgument()

2、集合處理

(1)Map快捷操作

推薦:

//如果值不存在則計算
map.computeIfAbsent("key",k-> execValue(k));
//預設值
map.getOrDefault("key", DEFAULT_VALUE)

反例:

//如果值不存在則計算
String v = map.get("key");
if(v == null){
    v = execValue("key");
    map.put("key", v);
}
//預設值
map.containsKey("key") ? map.get("key") : DEFAULT_VALUE

(2)建立物件

構造方法或Builder模式,超過3個引數物件建立使用Builder模式

//Java11+:
List.of(1, 2, 3)  
Set.of(1, 2, 3)
Map.of("a", 1)

//Java8中不可變集合(需引入Guava)
ImmutableList.of(1,2,3)
ImmutableSet.of(1,2,3)
ImmutableMap.of("key","value")
//多值情況
ImmutableMap.builder()
    .put("key", "value")
    .put("key2", "value2")
    .build()

//Java8中可變集合(需引入Guava)
Lists.newArrayList(1, 2, 3)
Sets.newHashSet(1, 2, 3)
Maps.newHashMap("key", "value")

反例:

new ArrayList<>(){{
   add(1);
   add(2);
}};

(3)集合巢狀

集合裡的值如果是基礎型別必須加上註釋,說明集合裡存的是什麼,比如:

//返回值: Map(key: 姓名, value: List(商品))
Map<String, List<String>> res;

超過2層集合物件封裝必須封裝成自定義類:

//推薦
Map<String, List<Node>> res;

@Value
public static class Node {
    /**
    * 備註說明欄位
    */
    String name;
    /**
    * 備註說明欄位2
    */
    List<Integer> subjectIds;
}

//反例
Map<String, List<Pair<String, List<Integer>>>> res;

異常及紀錄檔

1、異常

關於異常及錯誤碼的思考,請參考筆者的另一篇文章:錯誤碼設計思考

異常除了拋異常還有一種場景,即:上層發起多個必要呼叫,某些可能失敗,需要上層自行決定處理策略,推薦使用vavr中的Either類,Either使用建議:通常我們使用左值表示異常,而右值表示正常呼叫後的返回結果,即: Either<Throwable, Data>

2、紀錄檔

(1)紀錄檔檔案

根據紀錄檔等級一般分為4個紀錄檔檔案即可:debug.log、info.log、warn.log、error.log;

如有特殊需求可根據場景單獨建檔案,比如請求紀錄檔:request.log、gc紀錄檔:gc.log等。

(2)所有使用者紀錄檔都要有追蹤欄位

追蹤欄位包括:traceId、userId等,推薦使用MDC,常用的紀錄檔框架:Log4j、Logback都支援。

(3)紀錄檔清理及持久化

本地紀錄檔根據磁碟大小,必須設定紀錄檔儲存天數,否則有硬碟滿風險;

分散式環境為了方便查詢,需要將紀錄檔採集到ES中查詢;

重要紀錄檔:比如審計紀錄檔、B端操作紀錄檔需要持久儲存,一般是儲存到Hive中;

工具篇

1、JSON

推薦:使用Gson或Jackson;

不推薦:Fastjson。Fastjson爆出的漏洞多。

2、物件轉換

推薦:MapStruct,根據註解編譯成Java程式碼,沒有反射,速度快;行為可預測,可檢視編譯後的Java程式碼檢視轉換邏輯;

不推薦:BeanUtils、Dozer等。需要反射,行為不可預測,需要測試;

不推薦:超過3個欄位手動轉換;

3、模板程式碼

推薦:Lombok,減少程式碼行數,提升開發效率,自動生成Java程式碼,沒有效能損耗;

不推薦:手動生成大量set、get方法;

4、引數校驗

推薦:hibernate Validation、spring-boot-starter-validation,可通過註解自動實現引數攔截;

不推薦:每個入口(比如Controller)都copy大量重複的校驗邏輯;

5、快取

推薦:Spring Cache,通過註解控制快取邏輯,適合常用的加快取場景。

設計篇

1、正向語意

正向語意的好處在於使程式碼容易理解。 比如:if(judge()){....},很容易理解,即:判定成功則執行程式碼塊。

相反,如果是負向語意,思維還要轉換一下,一般用於方法前置的引數校驗。

正向語意的應用場景有:

  • 方法定義:方法名推薦:canPass、checkParam,返回true代表成功。 不推薦:比如isInvalidParam返回true代表失敗,增加理解成本;
  • Lambda表示式:filter 操作符中返回true是可以通過的元素;
  • if和三目運運算元:condition ? doSomething() : doSomething2() , 條件判定後緊跟的是判定成功後執行的操作。

反例:

if (!judge()) {
   doSomething2()
} else {
   doSomething()
}

2、防禦式程式設計

(1)外部資料校驗

外部傳過來資料都需要校驗,一般分為兩類:

  • 資料流入:使用者Http請求、RPC請求、MQ消費者等
  • 資料依賴:依賴的第三方RPC、資料庫等

如果是資料流入,一定要首先校驗資料合法性再往下執行,推薦hibernate Validation這類工具,可以很方便的做資料校驗

資料是資料依賴,一定要考慮各種網路、限流、背壓等場景,做好熔斷、降級保障。推薦建立防腐層,將第三方的限界上下文語意轉換為當前上下文語意,避免理解上的歧義;

(2)Null處理

  • 對於強依賴,沒有返回值不行(比如查詢資料庫):直接拋異常;

  • 需要反饋給上層處理:

    (1)可能返回null的場景:使用Optional;

    (2)上層需要感知資訊異常資訊:使用vavr中的Either;

  • 可降級:

    (1)返回值是預設值:集合類返回,數位返回0或-1,字串返回空字串,其他場景自定義

    集合預設值:

Collections.emptyList()  //空List
Collections.emptySet()   //空Set
Collections.emptyMap()   //空Map

總結

本文總結了Java開發常用的高階規範,暫時想到這麼多,對文章中觀點感興趣,歡迎留言或加微信交流。

作者部落格連結:Java研發規範(進階版)

作者簡介:木小豐,美團Java技術專家,專注分享軟體研發實踐、架構思考。歡迎關注公共號:Java研發

更多精彩文章:

錯誤碼設計思考

Java執行緒池進階

從MVC到DDD的架構演進

平臺化建設思路淺談

構建可回滾的應用及上線checklist實踐