這是我見過寫得最爛的Controller層程式碼...

語言: CN / TW / HK

文章來源:https://c1n.cn/XAu85

目錄

  • 介面定義

  • Controller 規範

  • 實現程式碼

介面定義

工作中,少不了要定義各種介面,系統整合要定義介面,前後臺掉呼叫也要定義介面。介面定義一定程度上能反應程式設計師的程式設計功底。

列舉一下工作中我發現大家容易出現的問題:

| 返回格式不統一

同一個介面,有時候返回陣列,有時候返回單個;成功的時候返回物件,失敗的時候返回錯誤資訊字串。

工作中有個系統整合就是這樣定義的介面,真是辣眼睛。這個對應程式碼上,返回的型別是 map,json,object,都是不應該的。

實際工作中,我們會定義一個統一的格式,就是 ResultBean,分頁的有另外一個 PageResultBean。

錯誤範例:

//返回map可讀性不好,儘量不要
 @PostMapping("/delete")
 public Map<String, Object> delete(long id, String lang) {

 }

 // 成功返回boolean,失敗返回string,大忌
 @PostMapping("/delete")
 public Object delete(long id, String lang) {
   try {
     boolean result = configService.delete(id, local);
     return result;
   } catch (Exception e) {
     log.error(e);
     return e.toString();
   }
 }

| 沒有考慮失敗情況

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

錯誤範例:

//不返回任何資料,沒有考慮失敗場景,容易返工
 @PostMapping("/update")
 public void update(long id, xxx) {

 }

| 出現和業務無關的輸入引數

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

錯誤範例:

// (當前使用者刪除資料)引數出現lang和userid,尤其是userid,大忌
 @PostMapping("/delete")
 public Map<String, Object> delete(long id, String lang, String userId) {

 }

| 出現複雜的輸入引數

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

錯誤範例:

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

 }

| 沒有返回應該返回的資料

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

錯誤範例:

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

很多人都覺得技術也很簡單,沒有什麼特別的地方,但是,實現這個程式碼框架之前,就是要你的介面的統一的格式 ResultBean,aop 才好做。

有些人誤解了,之前那篇文章說的都不是技術,重點說的是編碼習慣工作方式,如果你重點還是放在什麼技術上,那我也幫不了你了。

同樣,如果我後面的關於習慣和規範的帖子,你重點還是放在技術上的話,那是丟了西瓜撿芝麻,有很多貼還是沒有任何技術點呢。

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

@Data
public class ResultBean<T> implements Serializable {

 private static final long serialVersionUID = 1L;

 public static final int SUCCESS = 0;

 public static final int FAIL = 1;

 public static final int NO_PERMISSION = 2;

 private String msg = "success";

 private int code = SUCCESS;

 private T data;

 public ResultBean() {
   super();
 }

 public ResultBean(T data) {
   super();
   this.data = data;
 }

 public ResultBean(Throwable e) {
   super();
   this.msg = e.toString();
   this.code = FAIL ;
 }
}

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

Controller 規範

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

先說說 Controller 規範,主要的內容是就是介面定義裡面的內容,你只要遵循裡面的規範,controller 就問題不大。

除了這些,還有另外的幾點:

  • 所有函式返回統一的 ResultBean/PageResultBean 格式,原因見我的介面定義這個貼。沒有統一格式,AOP 無法玩。

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

  • Controller 做引數格式的轉換,不允許把 json,map 這類物件傳到 services 去,也不允許 services 返回 json、map。一般情況下!寫過程式碼都知道,map,json 這種格式靈活,但是可讀性差,如果放業務資料,每次閱讀起來都比較困難。定義一個 bean 看著工作量多了,但程式碼清晰多了。

  • 引數中一般情況不允許出現 Request,Response 這些物件,主要是可讀性問題。一般情況下。

  • 不需要列印日誌,日誌在 AOP 裡面會列印,而且我的建議是大部分日誌在 Services 這層列印。規範裡面大部分是不要做的項多,要做的比較少,落地比較容易。

ResultBean 定義帶泛型,使用了 lombok。

@Data
public class ResultBean<T> implements Serializable {

 private static final long serialVersionUID = 1L;

 public static final int NO_LOGIN = -1;

 public static final int SUCCESS = 0;

 public static final int FAIL = 1;

 public static final int NO_PERMISSION = 2;

 private String msg = "success";

 private int code = SUCCESS;

 private T data;

 public ResultBean() {
   super();
 }

 public ResultBean(T data) {
   super();
   this.data = data;
 }

 public ResultBean(Throwable e) {
   super();
   this.msg = e.toString();
   this.code = FAIL;
 }
}

AOP 程式碼: 主要就是列印日誌和捕獲異常,異常要區分已知異常和未知異常,其中未知的異常是我們重點關注的。

可以做一些郵件通知啥的,已知異常可以再細分一下,可以不同的異常返回不同的返回碼:

/**
* 處理和包裝異常
*/
public class ControllerAOP {
 private static final 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);
   } else if (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 -->
 <aop:aspectj-autoproxy />
 <beans:bean id="controllerAop" class="xxx.common.aop.ControllerAOP" />
 <aop:config>
   <aop:aspect id="myAop" ref="controllerAop">
     <aop:pointcut id="target"
       expression="execution(public xxx.common.beans.ResultBean *(..))" />
     <aop:around method="handlerControllerMethod" pointcut-ref="target" />
   </aop:aspect>
 </aop:config>

現在知道為什麼要返回統一的一個 ResultBean 了:

  • 為了統一格式

  • 為了應用 AOP

  • 為了包裝異常資訊

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

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

最後說一句,先有統一的介面定義規範,然後有 AOP 實現,先有思想再有技術。技術不是關鍵,AOP 技術也很簡單,這個帖子的關鍵點不是技術,而是習慣和思想,不要撿了芝麻丟了西瓜。

網路上講技術的貼多,講習慣、風格的少,這些都是我工作多年的行之有效的經驗之談。

-------------  END  -------------

掃碼 免費 獲取 600+頁 石杉老師原創精品文章彙總PDF

原創技術文章彙總

點個 在看 你最好看