敲代码就是一把梭_2020必看!开发五年的大佬日常工作中所使用的java代码技巧...
前言
羅列工作中實(shí)際使用的一些代碼技巧或者叫工具類;知識(shí)無(wú)大小,希望大家都有收獲
實(shí)用技巧
rpc服務(wù)出參統(tǒng)一化
什么,出參統(tǒng)一化有什么好說(shuō)的????? 我不知道你們有沒(méi)有遇到過(guò)多少五花八門(mén)的外部服務(wù)提供的返回對(duì)象,可能別人沒(méi)有規(guī)范約束,我們管不了,但是從我們這里出去的,我們可以強(qiáng)制約束一下,不然發(fā)生新老交替,這代碼還能看嗎
首先出參都叫xxDTO的,阿里java開(kāi)發(fā)手冊(cè)提到過(guò);再者我們是提供服務(wù)的一方,錯(cuò)誤碼code,錯(cuò)誤信息msg,以及返回結(jié)果data都是要明確體現(xiàn)出來(lái)的,像下面這樣
1public class TradeResultDTO implements Serializable {
2 /**
3 * 默認(rèn)失敗編碼
4 */
5 private static final String DEFAULT_FAIL_CODE = "500";
6 private boolean success;
7 private String code;
8 private String msg; 9 private T data;
10 public static TradeResultDTO success(T data) {
11 return base("200", null, true, data);
12 }
13
14 public static TradeResultDTO fail(String code, String msg) {
15 return base(code, msg, false, null);
16 }
17
18 public static TradeResultDTO fail(String msg) {
19 return base(DEFAULT_FAIL_CODE, msg, false, null);
20 }
21
22 public static TradeResultDTO success() {
23 return base("200", null, true, null);
24 }
25
26 public static TradeResultDTO fail(IError iError) {
27 return base(iError.getErrorCode(), iError.getErrorMsg(), false, null);
28 }
29}
統(tǒng)一對(duì)象返回的結(jié)構(gòu)就是上面這樣
接著這個(gè)我想說(shuō)的是,作為服務(wù)提供方,如果這個(gè)接口提供了返回值,我拿創(chuàng)建訂單接口舉例
1/**
2 * 創(chuàng)建交易單,業(yè)務(wù)系統(tǒng)發(fā)起
3 *
4 * @param req 創(chuàng)建入?yún)?/p>
5 * @return 返回創(chuàng)建信息6 */
7TradeResultDTO createOrder(TradeCreateOrderRequestDTO req)
8
比如這個(gè)TradeCreateOrderResponseDTO 返回了訂單號(hào)之類的基本信息,這個(gè)接口對(duì)于具體業(yè)務(wù)場(chǎng)景只能產(chǎn)生一筆訂單號(hào),我之前遇到過(guò)對(duì)方只是提示什么的錯(cuò)誤信息(訂單已存在),是的沒(méi)錯(cuò),他做了冪等,但是他沒(méi)有返回原值,那對(duì)應(yīng)的調(diào)用方進(jìn)入了死循環(huán),可能對(duì)應(yīng)的業(yè)務(wù)系統(tǒng),需要返回的訂單號(hào)落到自己的數(shù)據(jù)庫(kù),一直異常一直回滾重試,沒(méi)有任何意義;所以作為一個(gè)負(fù)責(zé)人的服務(wù)提供方,類似這種情況,如果你的方法有冪等,那么請(qǐng)一定返回存在的那個(gè)對(duì)象;
異常統(tǒng)一化
統(tǒng)一化使用,杜絕項(xiàng)目出現(xiàn)各種各樣的自定義異常
對(duì)外統(tǒng)一拋出異常
我使用的統(tǒng)一化有兩個(gè)方面:
- 拋出的自定義異常不要五花八門(mén),一個(gè)就夠了;很多人喜歡寫(xiě)各種各樣的異常,初衷其實(shí)沒(méi)錯(cuò),但是人多手雜,自定義異常可能越寫(xiě)越亂;
- 異常信息最好盡可能的具體化,描述出業(yè)務(wù)產(chǎn)生異常原因就可以了,比如入?yún)⑿r?yàn)的用戶信息不存在之類的;或者在調(diào)用用戶中心的時(shí)候,捕獲了該異常,此時(shí)你只需定義調(diào)用用戶中心異常就可以了
然后看下工作中比較推薦的:
首先,需要搞一個(gè)統(tǒng)一拋出異常的工具 ExceptionUtil(這里Exceptions是公司內(nèi)部統(tǒng)一前后端交互的,基于這個(gè)包裝一個(gè)基礎(chǔ)util,統(tǒng)一整個(gè)組拋異常的入口)
1public class ExceptionUtil {
2 public static OptimusExceptionBase fail(IError error) throws OptimusExceptionBase {
3 return Exceptions.fail(errorMessage(error));
4 }
5
6 public static OptimusExceptionBase fail(IError error, String... msg) throws OptimusExceptionBase {
7 return Exceptions.fail(errorMessage(error, msg));
8 }
9
10 public static OptimusExceptionBase fault(IError error) throws OptimusExceptionBase {
11 return Exceptions.fault(errorMessage(error));
12 }
13
14 public static OptimusExceptionBase fault(IError error, String... msg) throws OptimusExceptionBase {
15 return Exceptions.fault(errorMessage(error, msg));
16 }
17
18
19 private static ErrorMessage errorMessage(IError error) {
20 if (error == null) {
21 error = CommonErrorEnum.DEFAULT_ERROR;
22 }
23 return ErrorMessage.errorMessage("500", "[" + error.getErrorCode() + "]" + error.getErrorMsg());
24 }
25
26 private static ErrorMessage errorMessage(IError error, String... msg) {
27 if (error == null) {
28 error = CommonErrorEnum.DEFAULT_ERROR;
29 }
30 return ErrorMessage.errorMessage("500", "[" + error.getErrorCode() + "]" + MessageFormat.format(error.getErrorMsg(), msg));
31 }
32}
其實(shí)上面代碼里也體現(xiàn)出來(lái)IError這個(gè)接口了,我們的錯(cuò)誤枚舉都需要實(shí)現(xiàn)這個(gè)異常接口,方便統(tǒng)一獲取對(duì)應(yīng)的錯(cuò)誤碼和錯(cuò)誤信息,這里列舉一下通用異常的定義
1public interface IError {
2 String getErrorCode();
3
4 String getErrorMsg();
5}
6@AllArgsConstructor
7public enum CommonErrorEnum implements IError {
8 /**
9 *
10 */
11 DEFAULT_ERROR("00000000", "系統(tǒng)異常"),
12 REQUEST_OBJECT_IS_NULL_ERROR("00000001", "入?yún)?duì)象為空"),
13 PARAMS_CANNOT_BE_NULL_ERROR("00000002", "參數(shù)不能為空"),
14 BUILD_LOCK_KEY_ERROR("00000003", "系統(tǒng)異常:lock key異常"),
15 REPEAT_COMMIT_ERROR("00000004", "正在提交中,請(qǐng)稍候");
16
17 private String code;
18 private String msg;
19
20 @Override
21 public String getErrorCode() {
22 return code;
23 }
24
25 @Override
26 public String getErrorMsg() {
27 return msg;
28 }
29}
類似上面CommonErrorEnum的方式我們可以按照具體業(yè)務(wù)定義相應(yīng)的枚舉,比如OrderErrorEnum、PayErrorEnum之類,不僅具有區(qū)分度而且,也能瞬速定位問(wèn)題;
所以對(duì)外拋出異常統(tǒng)一化就一把梭:統(tǒng)一util和業(yè)務(wù)錯(cuò)誤枚舉分類
對(duì)內(nèi)統(tǒng)一捕獲外部異常
很多時(shí)候我們需要調(diào)用別人的服務(wù)然后寫(xiě)了一些adapter適配類,然后在里面trycatch一把;其實(shí)這時(shí)候可以利用aop好好搞一把就完事了,并且統(tǒng)一輸出adapter層里面的日志
1 public Object transferException(ProceedingJoinPoint joinPoint) {
2 try {
3 Object result = joinPoint.proceed();
4 log.info("adapter result:{}", JSON.toJSONString(result));
5 return result;
6 } catch (Throwable exception) {
7 MethodSignature signature = (MethodSignature) joinPoint.getSignature();
8 Method method = signature.getMethod();
9 log.error("{}.{} throw exception", method.getDeclaringClass().getName(), method.getName(), exception);
10 throw ExceptionUtils.fault(CommonErrorEnum.ADAPTER_SERVICE_ERROR);
11 return null;
12 }
13 }
上面這段統(tǒng)一捕獲了外部服務(wù),記錄異常日志,避免了每個(gè)adapter類重復(fù)捕獲的問(wèn)題
參數(shù)校驗(yàn)
用過(guò)swagger的應(yīng)該了解api方法里有對(duì)應(yīng)的注解屬性約束是否必填項(xiàng),但是如果判斷不是在api入口又或者沒(méi)有類似的注解,你們一般怎么做的,下面給出我自己的一種簡(jiǎn)單工具;有更好大佬的可以推薦一下
ParamCheckUtil.java
1@Slf4j
2public class ParamCheckUtil {
3
4 /**
5 * 校驗(yàn)請(qǐng)求參數(shù)是否為空
6 *
7 * @param requestParams 請(qǐng)求入?yún)?/p>
8 * @param keys 屬性值數(shù)組 9 */
10 public static void checkParams(Object requestParams, String... keys) {
11 if (null == requestParams) {
12 throw ExceptionUtil.fault(CommonErrorEnum.REQUEST_OBJECT_IS_NULL_ERROR);
13 }
14 StringBuilder sb = new StringBuilder();
15 for (String fieldName : keys) {
16 Object value = null;
17 Type type = null;
18 try {
19 String firstLetter = fieldName.substring(0, 1).toUpperCase();
20 String getter = "get" + firstLetter + fieldName.substring(1);
21 Method method = requestParams.getClass().getMethod(getter);
22 value = method.invoke(requestParams);
23 type = method.getReturnType();
24 } catch (Exception e) {
25 log.error("獲取屬性值出錯(cuò),requestParams={}, fieldName={}", requestParams, fieldName);
26 } finally {
27 // 判空標(biāo)志 String/Collection/Map特殊處理
28 boolean isEmpty =
29 (String.class == type && StringUtil.isEmpty((String) value))
30 || (Collection.class == type && CollectionUtils.isEmpty((Collection extends Object>) value))
31 || (Map.class == type && CollectionUtils.isEmpty((Collection extends Object>) value))
32 || (null == value);
33 if (isEmpty) {
34 if (sb.length() != 0) {
35 sb.append(",");
36 }
37 sb.append(fieldName);
38 }
39 }
40 }
41
42 if (sb.length() > 0) {
43 log.error(sb.toString() + CommonErrorEnum.PARAMS_CANNOT_BE_NULL_ERROR.getErrorMsg());
44 throw ExceptionUtil.fault(CommonErrorEnum.PARAMS_CANNOT_BE_NULL_ERROR, sb.toString() + CommonErrorEnum.PARAMS_CANNOT_BE_NULL_ERROR.getErrorMsg());
45 }
46 }
47
48 // test
49 public static void main(String[] args) {
50 TradeCreateOrderRequestDTO tradeCreateOrderRequestDTO = new TradeCreateOrderRequestDTO();
51 tradeCreateOrderRequestDTO.setBusinessNo("");
52 ParamCheckUtil.checkParams(tradeCreateOrderRequestDTO, "businessNo", "tradeType", "tradeItemDTOS");
53 }
54
55}
基于了上面統(tǒng)一異常的形式,只要參數(shù)校驗(yàn)出空我就拋出異常中斷程序,并且告知其缺什么參數(shù)
我在業(yè)務(wù)代碼需要判斷字段非空的地方只需要一行就夠了,就行下面這樣
1ParamCheckUtil.checkParams(tradeCreateOrderRequestDTO, "businessNo", "tradeType", "tradeItemDTOS");
而不是我們常用的一堆判斷,像下面這樣;看到這些我人都暈了,一次兩次就算了,一大段全是這種
1if (null == tradeCreateOrderRequestDTO) {
2// 提示tradeCreateOrderRequestDTO為空
3}
4if (StringUtil.isEmpty(tradeCreateOrderRequestDTO.getBusinessNo())) {
5// 提示businessNo為空
6}
7if (StringUtil.isEmpty(tradeCreateOrderRequestDTO.getTradeType())) {
8// 提示tradeType為空
9}
10if (CollectionUtils.isEmpty(tradeCreateOrderRequestDTO.getTradeItemDTOS())) {
11// 提示tradeItemDTOS列表為空
12}
如果你是上面說(shuō)的這種形式,不妨試試我提供的這種
bean相關(guān)
對(duì)象的構(gòu)造
關(guān)于對(duì)象的構(gòu)造,我想提兩點(diǎn),構(gòu)造變的對(duì)象和不變的對(duì)象
- 構(gòu)造不變對(duì)象,使用builder,不提供set方法,推薦使用lombok @Builder
1@Builder
2public class UserInfo {
3 private String id;
4 private String name;
5
6 public static void main(String[] args) {
7 UserInfo userInfo = UserInfo.builder().id("a").name("name").build();
8 }
9}
構(gòu)造可變對(duì)象,推薦提供鏈?zhǔn)秸{(diào)用形式 使用lombok @Accessors(chain = true)注解1@Data
2@Accessors(chain = true)
3public class CardInfo {
4 private String id;
5 private String name;
6 public static void main(String[] args) {
7 CardInfo cardInfo = new CardInfo().setId("c").setName("name");
8 }
9}
對(duì)象轉(zhuǎn)換
就一把梭:lambda工具類+mapstruct進(jìn)行轉(zhuǎn)換
BeanConvertUtil.java 通用的對(duì)象、list、Page轉(zhuǎn)換
1public class BeanConvertUtil {
2 /**
3 * 對(duì)象轉(zhuǎn)換
4 *
5 * @param source 源對(duì)象
6 * @param convertFun T -> R lambda轉(zhuǎn)換表達(dá)式
7 * @param 輸入類型
8 * @param 輸出類型
9 * @return 返回轉(zhuǎn)化后輸出類型的對(duì)象
10 */
11 public static R convertObject(T source, Function convertFun) {
12 if (null == source) {
13 return null;
14 }
15 return convertFun.apply(source);
16 }
17
18 /**
19 * Page轉(zhuǎn)換
20 *
21 * @param page 源對(duì)象
22 * @param convertFun T -> R lambda轉(zhuǎn)換表達(dá)式
23 * @param 輸入類型
24 * @param 輸出類型
25 * @return 返回轉(zhuǎn)化后輸出類型的對(duì)象
26 */
27 public static Page convertPage(Page page, Function convertFun) {
28 if (Objects.isNull(page)) {
29 return new Page<>(0, 1, 10, Collections.emptyList());
30 }
31 List pageList = convertList(page.getItems(), convertFun);
32 return new Page<>(page.getTotalNumber(), page.getCurrentIndex(), page.getPageSize(), pageList);
33 }
34
35 /**
36 * ListData轉(zhuǎn)換
37 *
38 * @param inputList 數(shù)據(jù)源
39 * @param convertFun T -> R lambda轉(zhuǎn)換表達(dá)式
40 * @param 輸入類型
41 * @param 輸出類型
42 * @return 輸出
43 */
44 public static List convertList(List inputList, Function convertFun) {
45 if (org.springframework.util.CollectionUtils.isEmpty(inputList)) {
46 return Lists.newArrayList();
47 }
48 return inputList
49 .stream()
50 .map(convertFun)
51 .collect(Collectors.toList());
52 }
53}
實(shí)戰(zhàn)使用,在lambda方法進(jìn)行轉(zhuǎn)換: 先轉(zhuǎn)換相同屬性,再進(jìn)行剩余屬性賦值
1?public?interface?OrderConverter?{ 2????OrderConverter?INSTANCE?=?Mappers.getMapper(OrderConverter.class); 3????????//?入?yún)⑦M(jìn)行相同屬性轉(zhuǎn)換 4????TradeOrderDO?createOrder2TradeOrderDO(TradeCreateOrderRequestDTO?req); 5} 6?TradeOrderDO?mainTradeOrder?=?BeanConvertUtil.convertObject(req,?x?->?{ 7?????TradeOrderDO?tod?=?OrderConverter.INSTANCE.createOrder2TradeOrderDO(req); 8?????tod.setOrderType(mainOrderType); 9?????tod.setOrderCode(snowflakeIdAdapterService.getId());10?????tod.setOrderStatus(TradeStateEnum.ORDER_CREATED.getValue());11?????tod.setDateCreate(new?Date());12?????tod.setDateUpdate(new?Date());13?????return?tod;14});其實(shí)對(duì)象轉(zhuǎn)換也可以完全通過(guò)mapstruct提供的一些表達(dá)式進(jìn)行轉(zhuǎn)換,但是有時(shí)候?qū)懩莻€(gè)感覺(jué)不是很直觀,其實(shí)都可以,我比較喜歡我這種形式,大家有建議也可以提出
NPE解決指南
1.null值手動(dòng)判斷[強(qiáng)制]
嵌套取值<3 推薦 null值判斷(PS:強(qiáng)制null寫(xiě)在前面,別問(wèn)為什么,問(wèn)就是這樣寫(xiě)你會(huì)意識(shí)到這里要NPE)
學(xué)會(huì)這點(diǎn) 基本意識(shí)有了
1null!=obj&&null!=obj.getXX()
2.Optional
2.1 Optional嵌套取值[強(qiáng)制]
參數(shù)>=3的取值操作學(xué)會(huì)這點(diǎn) 基本告別NPE
這里以O(shè)rderInfo對(duì)象為例 獲取purchaseType
1Optional?optional?=?Optional.ofNullable(dto);2Integer?purchaseType?=?optional.map(OrderInfo::getOrderCarDTO)3?????????????????????????????????.map(OrderCarDTO::getPurchaseType)4?????????????????????????????????.orElse(null);如果對(duì)取出的值如需再次進(jìn)行判斷操作 參考第1點(diǎn)
2.2 中斷拋出異常[按需]
還是以上面的例子
1{
2 // ...
3 optional.map(OrderInfo::getOrderDTO).map(OrderDTO::getOrderBusinessType)
4 .orElseThrow(() -> new Exception("獲取cityCode失敗"));
5}
如果依賴某些值,可盡早fail-fast
3.對(duì)象判空[強(qiáng)制]
1Objects.equals(obj1,obj2);4.Boolean值判斷[強(qiáng)制]
棄用以下方式謝謝(PS:很多時(shí)候null判斷你會(huì)丟的)
1null!=a&&a;正確食用
1Boolean.TRUE.equals(a);5.list正確判空姿勢(shì)[強(qiáng)制]
1if?(CollectionUtils.isEmpty(list))?{2????//?fail?fast3????//?return?xxObj?or?return;4}5List?safeList?=?list.stream().filter(Objects::nonNull).collect(Collectors.toList());6if?(CollectionUtils.isEmpty(safeList))?{7????//?fail?fast8????//?return?xxObj?or?return;9}6.String正確判空姿勢(shì)[強(qiáng)制]
1//?不為空2if?(StringUtil.isNotEmpty(s))?{3????//?...4}7.包裝類型轉(zhuǎn)換判空[強(qiáng)制]
特別是遍歷過(guò)程中使用,需要判斷是否為空。
1int?i?=?0;2list.forEach(item?->?{3????if(Objects.nonNull(item.getType)){4?????i?+=?item.getType;?//item.getType?返回?Integer???5????}6});小結(jié)
融會(huì)貫通以上幾招絕對(duì)告別NPE
END
未完待續(xù),大家如果有好的建議,希望在留言中提出
喜歡的可以點(diǎn)贊+關(guān)注,感謝支持
總結(jié)
以上是生活随笔為你收集整理的敲代码就是一把梭_2020必看!开发五年的大佬日常工作中所使用的java代码技巧...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 页面转发后文本显示???_使用Divi的
- 下一篇: python输入名字、输出欢迎你_pyt