高效能團隊的Java研發規範(進階版)
目前大部分團隊是使用的阿里巴巴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研發
更多精彩文章: