當前位置: 妍妍網 > 碼農

這是我見過寫得最爛的Controller層程式碼,沒有之一!

2024-05-13碼農

工作中,少不了要定義各種介面,系統整合要定義介面,前後台掉呼叫也要定義介面。介面定義一定程度上能反應程式設計師的編程功底。列舉一下工作中我發現大家容易出現的問題:

1. 返回格式不統一

同一個介面,有時候返回陣列,有時候返回單個;成功的時候返回物件,失敗的時候返回錯誤資訊字串。工作中有個系統整合就是這樣定義的介面,真是辣眼睛。這個對應程式碼上,返回的型別是map,json,object,都是不應該的。實際工作中,我們會定義一個統一的格式,就是ResultBean,分頁的有另外一個PageResultBean。

錯誤範例:

//返回map可讀性不好,盡量不要
 @PostMapping("/delete")
public Map<StringObjectdelete(long id, String lang) {
 }
// 成功返回boolean,失敗返回string,大忌
 @PostMapping("/delete")
publicObjectdelete(long id, String lang) {
try {
boolean result = configService.delete(id, local);
return result;
catch (Exception e) {
log.error(e);
return e.toString();
}
 }

2. 沒有考慮失敗情況

一開始只考慮成功場景,等後面測試發現有錯誤情況,怎麽辦,改介面唄,前後台都改,勞民傷財無用功。

錯誤範例:

//不返回任何數據,沒有考慮失敗場景,容易返工
 @PostMapping("/update")
publicvoidupdate(long id, xxx){
 }

3. 出現和業務無關的輸入參數

如lang語言,當前使用者資訊 都不應該出現參數裏面,應該從當前會話裏面獲取。後面講ThreadLocal會說到怎麽樣去掉。除了程式碼可讀性不好問題外,尤其是參數出現當前使用者資訊的,這是個嚴重問題。

錯誤範例:

// (當前使用者刪除數據)參數出現lang和userid,尤其是userid,大忌
 @PostMapping("/delete")
public Map<StringObjectdelete(long id, String lang, String userId) {
 }

4. 出現復雜的輸入參數

一般情況下,不允許出現例如json字串這樣的參數,這種參數可讀性極差。應該定義對應的bean。

錯誤範例:

// 參數出現json格式,可讀性不好,程式碼也難看
 @PostMapping("/update")
public Map<StringObject> update(long id, String jsonStr) {
 }

5. 沒有返回應該返回的數據

例如,新增介面一般情況下應該返回新物件的id標識,這需要編程經驗。新手定義的時候因為前台沒有用就不返回數據或者只返回true,這都是不恰當的。別人要不要是別人的事情,你該返回的還是應該返回。

錯誤範例:

// 約定俗成,新建應該返回新物件的資訊,只返回boolean容易導致返工
 @PostMapping("/add")
public boolean add(xxx{
//xxx
return configService.add();
 }

很多人都覺得技術也很簡單,沒有什麽特別的地方,但是,實作這個程式碼框架之前,就是要你的介面的統一的格式ResultBean,aop才好做。有些人誤解了,上周末那篇文章說的都不是技術,重點說的是編碼習慣工作方式,如果你重點還是放在什麽技術上,那我也幫不了你了。同樣,如果我後面的關於習慣和規範的貼文,你重點還是放在技術上的話,那是丟了西瓜撿芝麻,有很多貼還是沒有任何技術點呢。

附上ResultBean,沒有任何技術含量:

@Data
public classResultBean<TimplementsSerializable{
privatestaticfinallong serialVersionUID = 1L;
publicstaticfinalint SUCCESS = 0;
publicstaticfinalint FAIL = 1;
publicstaticfinalint NO_PERMISSION = 2;
private String msg = "success";
privateint code = SUCCESS;
private T data;
publicResultBean(){
super();
 }
publicResultBean(T data){
super();
this.data = data;
 }
publicResultBean(Throwable e){
super();
this.msg = e.toString();
this.code = FAIL ;
 }
}









統一的介面規範,能幫忙規避很多無用的返工修改和可能出現的問題。能使程式碼可讀性更加好,利於進行aop和自動化測試這些額外工作。大家一定要重視。

上面2段程式碼,第一個是原生態的,第2段是我指定了介面定義規範,使用AOP技術之後最終交付的程式碼,從15行到1行,自己感受一下。接下來說說大家關註的AOP如何實作。

先說說Controller規範,主要的內容是就是介面定義裏面的內容,你只要遵循裏面的規範,controller就問題不大,除了這些,還有另外的幾點:

1.所有函式返回統一的ResultBean/PageResultBean格式

原因見我的介面定義這個貼。沒有統一格式,AOP無法玩。

2.ResultBean/PageResultBean是controller專用的,不允許往後傳!

3.Controller做參數格式的轉換,不允許把json,map這類物件傳到services去,也不允許services返回json、map。

一般情況下!寫過程式碼都知道,map,json這種格式靈活,但是可讀性差,如果放業務數據,每次閱讀起來都比較困難。定義一個bean看著工作量多了,但程式碼清晰多了。

4.參數中一般情況不允許出現Request,Response這些物件

主要是可讀性問題。一般情況下。

5.不需要打印日誌

日誌在AOP裏面會打印,而且我的建議是大部份日誌在Services這層打印。

規範裏面大部份是 不要做的項多,要做的比較少,落地比較容易。

ResultBean定義帶泛型,使用了lombok。公眾 號:Java精選,回復面試,獲取面試資料。

@Data
public classResultBean<TimplementsSerializable{
privatestaticfinallong serialVersionUID = 1L;
publicstaticfinalint NO_LOGIN = -1;
publicstaticfinalint SUCCESS = 0;
publicstaticfinalint FAIL = 1;
publicstaticfinalint NO_PERMISSION = 2;
private String msg = "success";
privateint code = SUCCESS;
private T data;
publicResultBean(){
super();
 }
publicResultBean(T data){
super();
this.data = data;
 }
publicResultBean(Throwable e){
super();
this.msg = e.toString();
this.code = FAIL;
 }
}










AOP程式碼,主要就是打印日誌和捕獲異常,異常要區分已知異常和未知異常,其中未知的異常是我們重點關註的,可以做一些信件通知啥的,已知異常可以再細分一下,可以不同的異常返回不同的返回碼:

/**
* 處理和包裝異常
*/

public classControllerAOP{
privatestaticfinal Logger logger = LoggerFactory.getLogger(ControllerAOP. class);
public Object handlerControllerMethod(ProceedingJoinPoint pjp) {
long startTime = System.currentTimeMillis();
ResultBean<?> result;
try {
result = (ResultBean<?>) pjp.proceed();
logger.info(pjp.getSignature() + "use time:" + (System.currentTimeMillis() - startTime));
catch (Throwable e) {
result = handlerException(pjp, e);
}
return result;
 }
private ResultBean<?> handlerException(ProceedingJoinPoint pjp, Throwable e) {
ResultBean<?> result = new ResultBean();
// 已知異常
if (e instanceof CheckException) {
result.setMsg(e.getLocalizedMessage());
result.setCode(ResultBean.FAIL);
elseif (e instanceof UnloginException) {
result.setMsg("Unlogin");
result.setCode(ResultBean.NO_LOGIN);
else {
logger.error(pjp.getSignature() + " error ", e);
//TODO 未知的異常,應該格外註意,可以發送信件通知等
result.setMsg(e.toString());
result.setCode(ResultBean.FAIL);
}
return result;
 }
}






AOP配置:(關於用java程式碼還是xml配置,這裏我傾向於xml配置,因為這個會非週期性改動)

<aop:aspectj-autoproxy />
<beans:beanid="controllerAop" class="xxx.common.aop.ControllerAOP" />
<aop:config>
<aop:aspectid="myAop"ref="controllerAop">
<aop:pointcutid="target"
expression="execution(public xxx.common.beans.ResultBean *(..))" />

<aop:aroundmethod="handlerControllerMethod"pointcut-ref="target" />
</aop:aspect>
</aop:config>

現在知道為什麽要返回統一的一個ResultBean了:1.為了統一格式 ;2.為了套用AOP ;3.為了包裝異常資訊。

分頁的PageResultBean大同小異,大家自己依葫蘆畫瓢自己完成就好了。

貼一個簡單的controller(左邊的箭頭表示AOP攔截了)。請對比 吐槽我見過的最爛的java程式碼裏面原來的程式碼檢視,沒有對比就沒有傷害。

最後說一句,先有統一的介面定義規範,然後有AOP實作。先有思想再有技術。技術不是關鍵,AOP技術也很簡單,這個貼文的關鍵點不是技術,而是習慣和思想,不要撿了芝麻丟了西瓜。網路上講技術的貼多,講習慣、風格的少,這些都是我工作多年的行之有效的經驗之談。

來源:https://zhuanlan.zhihu.com/p/28708259

>>

END

精品資料,超贊福利,免費領

微信掃碼/長按辨識 添加【技術交流群

群內每天分享精品學習資料

最近開發整理了一個用於速刷面試題的小程式;其中收錄了上千道常見面試題及答案(包含基礎並行JVMMySQLRedisSpringSpringMVCSpringBootSpringCloud訊息佇列等多個型別),歡迎您的使用。

👇👇

👇點選"閱讀原文",獲取更多資料(持續更新中