前言
畢業快三年了,前后也待過幾家公司,碰到各種各樣的同事。見識過各種各樣的代碼,優秀的、垃圾的、不堪入目的、看了想跑路的等等,所以這篇文章記錄一下一個優秀的后端 JAVA 開發應該有哪些好的開發習慣。
拆分合理的目錄結構
受傳統的 MVC 模式影響,傳統做法大多是幾個固定的文件夾 controller、service、mApper、entity,然后無限制添加,到最后你就會發現一個 service 文件夾下面有幾十上百個 Service 類,根本沒法分清業務模塊。正確的做法是在寫 service 上層新建一個 modules 文件夾,在 moudles 文件夾下根據不同業務建立不同的包,在這些包下面寫具體的 service、controller、entity、enums 包或者繼續拆分。
等以后開發版本迭代,如果某個包可以繼續拆領域就繼續往下拆,可以很清楚的一覽項目業務模塊。后續拆微服務也簡單。
封裝方法形參
當你的方法形參過多時請封裝一個對象出來...... 下面是一個反面教材,誰特么教你這樣寫代碼的!
public void updateCustomerDeviceAndInstallInfo(long customerId, String channelKey,
String AndroidId, String imei, String gaId,
String gcmPushToken, String instanceId) {}
復制代碼
寫個對象出來
public class CustomerDeviceRequest {
private Long customerId;
//省略屬性......
}
復制代碼
為什么要這么寫?比如你這方法是用來查詢的,萬一以后加個查詢條件是不是要修改方法?每次加每次都要改方法參數列表。封裝個對象,以后無論加多少查詢條件都只需要在對象里面加字段就行。而且關鍵是看起來代碼也很舒服啊!
封裝業務邏輯
如果你看過“屎山”你就會有深刻的感觸,這特么一個方法能寫幾千行代碼,還無任何規則可言......往往負責的人會說,這個業務太復雜,沒有辦法改善,實際上這都是懶的借口。不管業務再復雜,我們都能夠用合理的設計、封裝去提升代碼可讀性。下面貼兩段高級開發(假裝自己是高級開發)寫的代碼
@Transactional
public ChildOrder submit(Long orderId, OrderSubmitRequest.Shop shop) {
ChildOrder childOrder = this.generateOrder(shop);
childOrder.setOrderId(orderId);
//訂單來源 APP/微信小程序
childOrder.setSource(userService.getOrderSource());
// 校驗優惠券
orderAdjustmentService.validate(shop.getOrderAdjustments());
// 訂單商品
orderProductService.add(childOrder, shop);
// 訂單附件
orderAnnexService.add(childOrder.getId(), shop.getOrderAnnexes());
// 處理訂單地址信息
processAddress(childOrder, shop);
// 最后插入訂單
childOrderMapper.insert(childOrder);
this.updateSkuInventory(shop, childOrder);
// 發送訂單創建事件
applicationEventPublisher.publishEvent(new ChildOrderCreatedEvent(this, shop, childOrder));
return childOrder;
}
復制代碼
@Transactional
public void clearBills(Long customerId) {
// 獲取清算需要的賬單、deposit等信息
ClearContext context = getClearContext(customerId);
// 校驗金額合法
checkAmount(context);
// 判斷是否可用優惠券,返回可抵扣金額
CouponDeductibleResponse deductibleResponse = couponDeducted(context);
// 清算所有賬單
DepositClearResponse response = clearBills(context);
// 更新 l_pay_deposit
lPayDepositService.clear(context.getDeposit(), response);
// 發送還款對賬消息
repaymentService.sendVerifyBillMessage(customerId, context.getDeposit(), EventName.DEPOSIT_SUCCEED_FLOW_REMINDER);
// 更新賬戶余額
accountService.clear(context, response);
// 處理清算的優惠券,被用掉或者解綁
couponService.clear(deductibleResponse);
// 保存券抵扣記錄
clearCouponDeductService.add(context, deductibleResponse);
}
復制代碼
這段兩代碼里面其實業務很復雜,內部估計保守干了五萬件事情,但是不同水平的人寫出來就完全不同,不得不贊一下這個注釋,這個業務的拆分和方法的封裝。一個大業務里面有多個小業務,不同的業務調用不同的 service 方法即可,后續接手的人即使沒有流程圖等相關文檔也能快速理解這里的業務,而很多初級開發寫出來的業務方法就是上一行代碼是 A 業務的,下一行代碼是 B業務的,在下面一行代碼又是 A 業務的,業務調用之間還嵌套這一堆單元邏輯,顯得非常混亂,代碼還多。
判斷集合類型不為空的正確方式
很多人喜歡寫這樣的代碼去判斷集合
if (list == null || list.size() == 0) {
return null;
}
復制代碼
當然你硬要這么寫也沒什么問題......但是不覺得難受么,現在框架中隨便一個 jar 包都有集合工具類,比如
org.springframework.util.CollectionUtils、com.baomidou.mybatisplus.core.toolkit.CollectionUtils 。 以后請這么寫
if (CollectionUtils.isEmpty(list) || CollectionUtils.isNotEmpty(list)) {
return null;
}
復制代碼
集合類型返回值不要 return null
當你的業務方法返回值是集合類型時,請不要返回 null,正確的操作是返回一個空集合。你看 mybatis 的列表查詢,如果沒查詢到元素返回的就是一個空集合,而不是 null。否則調用方得去做 NULL 判斷,多數場景下對于對象也是如此。
映射數據庫的屬性盡量不要用基本類型
我們都知道 int/long 等基本數據類型作為成員變量默認值是 0。現在流行使用 mybatisplus 、mybatis 等 ORM 框架,在進行插入或者更新的時候很容易會帶著默認值插入更新到數據庫。我特么真想砍了之前的開發,重構的項目里面實體類里面全都是基本數據類型。當場裂開......
封裝判斷條件
public void method(LoanAppEntity loanAppEntity, long operatorId) {
if (LoanAppEntity.LoanAppStatus.OVERDUE != loanAppEntity.getStatus()
&& LoanAppEntity.LoanAppStatus.CURRENT != loanAppEntity.getStatus()
&& LoanAppEntity.LoanAppStatus.GRACE_PERIOD != loanAppEntity.getStatus()) {
//...
return;
}
復制代碼
這段代碼的可讀性很差,這 if 里面誰知道干啥的?我們用面向對象的思想去給 loanApp 這個對象里面封裝個方法不就行了么?
public void method(LoanAppEntity loan, long operatorId) {
if (!loan.finished()) {
//...
return;
}
復制代碼
LoanApp 這個類中封裝一個方法,簡單來說就是這個邏輯判斷細節不該出現在業務方法中。
/**
* 貸款單是否完成
*/
public boolean finished() {
return LoanAppEntity.LoanAppStatus.OVERDUE != this.getStatus()
&& LoanAppEntity.LoanAppStatus.CURRENT != this.getStatus()
&& LoanAppEntity.LoanAppStatus.GRACE_PERIOD != this.getStatus();
}
復制代碼
控制方法復雜度
推薦一款 IDEA 插件 CodeMetrics ,它能顯示出方法的復雜度,它是對方法中的表達式進行計算,布爾表達式,if/else 分支,循環等。

點擊可以查看哪些代碼增加了方法的復雜度,可以適當進行參考,畢竟我們通常寫的是業務代碼,在保證正常工作的前提下最重要的是要讓別人能夠快速看懂。當你的方法復雜度超過 10 就要考慮是否可以優化了。
使用 @ConfigurationProperties 代替 @Value
之前居然還看到有文章推薦使用 @Value 比 @ConfigurationProperties 好用的,吐了,別誤人子弟。列舉一下 @ConfigurationProperties 的好處。
- 在項目 application.yml 配置文件中按住 ctrl + 鼠標左鍵點擊配置屬性可以快速導航到配置類。寫配置時也能自動補全、聯想到注釋。需要額外引入一個依賴 org.springframework.boot:spring-boot-configuration-processor 。

- @ConfigurationProperties 支持 NACOS 配置自動刷新,使用 @Value 需要在 BEAN 上面使用 @RefreshScope 注解才能實現自動刷新
- @ConfigurationProperties 可以結合 Validation 校驗,@NotNull、@Length 等注解,如果配置校驗沒通過程序將啟動不起來,及早的發現生產丟失配置等問題。
- @ConfigurationProperties 可以注入多個屬性,@Value 只能一個一個寫
- @ConfigurationProperties 可以支持復雜類型,無論嵌套多少層,都可以正確映射成對象
相比之下我不明白為什么那么多人不愿意接受新的東西,裂開......你可以看下所有的 springboot-starter 里面用的都是 @ConfigurationProperties 來接配置屬性。
推薦使用 lombok
當然這是一個有爭議的問題,我的習慣是使用它省去 getter、setter、toString 等等。
不要在 AService 調用 BMapper
我們一定要遵循從 AService -> BService -> BMapper,如果每個 Service 都能直接調用其他的 Mapper,那特么還要其他 Service 干嘛?老項目還有從 controller 調用 mapper 的,把控制器當 service 來處理了。。。
盡量少寫工具類
為什么說要少寫工具類,因為你寫的大部分工具類,在你無形中引入的 jar 包里面就有,String 的,Assert 斷言的,IO 上傳文件,拷貝流的,Bigdecimal 的等等。自己寫容易錯還要加載多余的類。
不要包裹 OpenFeign 接口返回值
搞不懂為什么那么多人喜歡把接口的返回值用 Response 包裝起來......加個 code、message、success 字段,然后每次調用方就變成這樣
CouponCommonResult bindResult = couponApi.useCoupon(request.getCustomerId(), order.getLoanId(), coupon.getCode());
if (Objects.isNull(bindResult) || !bindResult.getResult()) {
throw new AppException(CouponErrorCode.ERR_REC_COUPON_USED_FAILED);
}
復制代碼
這樣就相當于
- 在 coupon-api 拋出異常
- 在 coupon-api 攔截異常,修改 Response.code
- 在調用方判斷 response.code 如果是 FAIELD 再把異常拋出去......
你直接在服務提供方拋異常不就行了么。。。而且這樣一包裝 HTTP 請求永遠都是 200,沒法做重試和監控。當然這個問題涉及到接口響應體該如何設計,目前網上大多是三種流派
- 接口響應狀態一律 200
- 接口響應狀態遵從HTTP真實狀態
- 佛系開發,領導怎么說就怎么做
不接受反駁,我推薦使用 HTTP 標準狀態。特定場景包括參數校驗失敗等一律使用 400 給前端彈 toast。下篇文章會闡述一律 200 的壞處。
寫有意義的方法注釋
這種注釋你寫出來是怕后面接手的人瞎么......
/**
* 請求電話驗證
*
* @param credentialNum
* @param callback
* @param param
* @return PhoneVerifyResult
*/
復制代碼
要么就別寫,要么就在后面加上描述......寫這樣的注釋被 IDEA 報一堆警告看著蛋疼
和前端交互的 DTO 對象命名
什么 VO、BO、DTO、PO 我倒真是覺得沒有那么大必要分那么詳細,至少我們在和前端交互的時候類名要起的合適,不要直接用映射數據庫的類返回給前端,這會返回很多不必要的信息,如果有敏感信息還要特殊處理。
推薦的做法是接受前端請求的類定義為 XxxRequest,響應的定義為 XxxResponse。以訂單為例:接受保存更新訂單信息的實體類可以定義為 OrderRequest,訂單查詢響應定義為 OrderResponse,訂單的查詢條件請求定義為 OrderQueryRequest。
盡量別讓 IDEA 報警
我是很反感看到 IDEA 代碼窗口一串警告的,非常難受。因為有警告就代表代碼還可以優化,或者說存在問題。 前幾天捕捉了一個團隊內部的小bug,其實本來和我沒有關系,但是同事都在一頭霧水的看外面的業務判斷為什么走的分支不對,我一眼就掃到了問題。

因為 java 中整數字面量都是 int 類型,到集合中就變成了 Integer,然后 stepId 點上去一看是 long 類型,在集合中就是 Long,那這個 contains 妥妥的返回 false,都不是一個類型。
你看如果注重到警告,鼠標移過去看一眼提示就清楚了,少了一個生產 bug。
盡可能使用新技術組件
我覺得這是一個程序員應該具備的素養......反正我是喜歡用新的技術組件,因為新的技術組件出現必定是解決舊技術組件的不足,而且作為一個技術人員我們應該要與時俱進~~ 當然前提是要做好準備工作,不能無腦升級。舉個最簡單的例子,Java 17 都出來了,新項目現在還有人用 Date 來處理日期時間...... 都什么年代了你還在用 Date
結語
本篇文章簡單介紹我日常開發的習慣,當然僅是作者自己的見解。暫時只想到這幾點,以后發現其他的會更新。
如果這篇文章對你有幫助,記得點贊加關注!你的支持就是我繼續創作的動力!
作者:暮色妖嬈丶
鏈接:
https://juejin.cn/post/7072252275002966030