【Java分享客棧】我有一個朋友,和前端工程師聯調介面被狠狠鄙視了一番。

福隆苑居士發表於2022-03-03

前言

我有一個朋友,昨天和前端工程師聯調一個介面,然後被狠狠鄙視了一番。

大家知道,自從前後端分離以後,像我一樣一直以Java工程師為傲而自居的碼聖們就砍掉了一半脊樑,從此被貼上了“Java服務端工程師”、“Java後端工程師”等等這樣的標籤。

同時,前端爸比越來越多,也讓我們寫個介面都如履薄冰。


那麼到底發生了審麼事情咧?


經過

梳理出來,大體經過是這樣滴:

1)、我朋友是Java工程師,入職公司四個月,剛轉正一個月,目前正在參與一個緊急的專案開發;

2)、他寫完了介面,自測沒問題,然後釋出到測試環境,再測沒問題,歐克,輸出文件給前端,準備聯調咯;

3)、前端工程師是個爸比,三十出頭,追求細節,人狠話不多,入職三年多,公司大半前端頁面和資料繫結都由他完成,是前端扛把子,看完了文件,調了下介面,歐克,沒問題,開幹;

4)、一上午過去了,很簡單的介面並沒有聯調完,甚至兩人發生了些許不愉快;

5)、前端爸比認為介面正常情況下可以,但異常情況下狀態給的不明確,沒辦法根據狀態值給使用者友好提示;

6)、我朋友來的時間短,敢怒不敢言,畏畏縮縮指出了自己介面自定義了返回物件,正常時狀態返回200,異常時會觸發全域性異常處理,返回狀態500,很明確並沒有什麼問題嚶嚶嬰;

7)、前端爸比一聲嗤笑,哼小夥子,你一看就道行尚淺,和我有一腿……有交集的後端如過江之鯽,我聯調過的介面比你拉的SHI還多,你快坐回去好好看看程式碼,是不是介面加了trycatch,然後捕獲異常時直接返回了自定義響應物件;

8)、我朋友心中一慌,這老銀幣有點東西,一個前端連我Java程式碼怎麼寫的都知道,趕忙跑回去重新審視程式碼,來回審視和自測了好幾遍,終於發現了不算問題的問題;

9)、原來介面返回的業務狀態有很多,但HTTP狀態永遠是200成功,但這對你前端有毛的影響?

10)、前端爸比說確實沒啥影響,但我就是要判斷HTTP狀態碼,我有強迫症;

11)、沒辦法,畢竟是爸比,我朋友之後參考了我所負責的專案裡面的介面程式碼,順利完成了之後的聯調工作,但從此在前端爸比心裡打上了菜鳥的標籤。


問題重現

為了節省時間,我直接以renren-fast作為腳手架來重現這個問題。
首先,我們定義一個簡單的介面,使用自定義響應物件R返回,對介面進行try..catch,成功時返回R.ok(),異常時在catch中返回R.error()及錯誤資訊。

(PS:題外話,工作這些年換過幾個公司,其實看到不少同事喜歡這麼寫,我想其他公司也不在少數。)

/**
 * 自定義響應物件返回
 * @return 結果
 */
@GetMapping("getUserInfo")
@ApiOperation("獲取使用者資訊")
public R getUserInfo() {
   Map<String, Object> map = new HashMap<>();
   try {
      map.put("id", "1001");
      map.put("name", "張三");
      map.put("age", "33");
      map.put("address", "湖北省神農架野人洞");
   } catch (Exception ex) {
      return R.error("異常:" + ex.getMessage());
   }
   return R.ok().put("data", map);
}

使用Postman除錯一下介面,嗯可以正常返回。

1.png

接下來,模擬介面發生一個異常。

/**
 * 自定義響應物件返回
 * @return 結果
 */
@GetMapping("getUserInfo")
@ApiOperation("獲取使用者資訊")
public R getUserInfo() {
   Map<String, Object> map = new HashMap<>();
   try {
      map.put("id", "1001");
      map.put("name", "張三");
      map.put("age", "33");
      map.put("address", "湖北省神農架野人洞");
      // 模擬異常
      int i = 1/0;
   } catch (Exception ex) {
      return R.error("異常:" + ex.getMessage());
   }
   return R.ok().put("data", map);
}

再使用Postman呼叫下看看,是返回500異常了,HTTP狀態是200,沒啥問題啊。

2.png

如果丟擲一個異常呢

/**
    * 自定義響應物件返回
    * @return 結果
    */
   @GetMapping("getUserInfo")
   @ApiOperation("獲取使用者資訊")
   public R getUserInfo() {
      Map<String, Object> map = new HashMap<>();
      try {
         map.put("id", "1001");
         map.put("name", "張三");
         map.put("age", "33");
         map.put("address", "湖北省神農架野人洞");
         // 模擬異常
         int i = 1/0;
      } catch (Exception ex) {
         // 丟擲一個執行時異常
         throw new RuntimeException(ex.getMessage());
      }
      return R.ok().put("data", map);
   }

一般會交由專案的全域性異常進行處理,實際返回的還是自定義的響應物件R.error()。

@ExceptionHandler(Exception.class)
public R handleException(Exception e){
   logger.error(e.getMessage(), e);
   return R.error();
}

然後Postman再除錯,可以看到,HTTP狀態不變,介面業務狀態返回500並提示異常,和前面一樣,確實牟悶提啊。

3.png

好,這裡說下,程式實際上是發生了異常,由程式碼自行捕獲並返回了自定義響應結果,HTTP狀態是200表示介面連通性正常,業務狀態是500表示業務程式發生了異常。

其實大部分專案都這麼做的,本身沒什麼問題,但有時會給前端工程師對介面狀態的邏輯判斷產生誤解,再有,如果你是給另一個廠家寫介面,你是提供方,對方是消費方,這麼寫會給對方製造麻煩。



正常來講,有經驗的前端工程師一般會這麼判斷:

1)、先判斷HTTP狀態,不是200表示失敗則給出友好提示,成功則繼續判斷介面業務狀態;

2)、判斷介面業務狀態,若返回200表示成功,則繫結資料,若不是200,給出友好提示,若有特殊業務狀態,另行判斷並處理。



那麼,當後端工程師返回的是如示例所示的自定義響應物件,且全域性異常處理中返回的也是示例中的自定義響應物件時,就意味著我們的介面HTTP狀態永遠都是200成功,前端對這一塊的判斷完全是失效的,一旦線上的專案出現特殊情景,可能造成意外假象。



再者,如前面所說,你是給其他公司廠家甚至第三方元件提供介面,這麼寫的話HTTP狀態永遠是200,也存在隱患,比如本人第一家公司用的XXLJOB,我們需要寫介面給XXLJOB進行任務排程,這個介面就是上面那樣返回的,一開始是好的,後來有同事改程式碼改出點問題,線上剛好也出現了該異常,而XXLJOB就是判斷HTTP_STATUS的,結果它怎麼識別我們介面都是返回200成功,它就沒有反饋任何異常警告,導致這個重要的排程任務雖然正常執行卻是無效的,我們也沒留意,直到一堆待退費訂單沒有處理才發現問題。


優化處理

上面展示的實際上本身不是問題,大部分專案這麼寫也能正常線上上執行,只是存在小概率的風險,當專案規模較大時,存在很多不確定性,介面的返回狀態是消費方進行邏輯處理的唯一依賴,因此,我的建議是最好同時返回更準確的HTTP狀態和介面業務狀態。


處理方式十分簡單,使用spring-web自帶的ResponseEntity包裝一下即可。

/**
* 自定義響應物件返回(外層包裝ResponseEntity)
* @return 結果
*/
@GetMapping("getUserInfo2")
@ApiOperation("獲取使用者資訊")
public ResponseEntity<R> getUserInfo2() {
  Map<String, Object> map = new HashMap<>();
  try {
     map.put("id", "1001");
     map.put("name", "張三");
     map.put("age", "33");
     map.put("address", "湖北省神農架野人洞");
     // 模擬異常
     int i = 1/0;
  } catch (Exception ex) {
     // return ResponseEntity.badRequest().body(R.error("異常:" + ex.getMessage()));
     return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(R.error("異常:" + ex.getMessage()));
  }
  return ResponseEntity.ok().body(R.ok().put("data", map));
}

效果:

4.png

ResponseEntity封裝了幾乎所有的HTTP狀態,上面示例程式碼包含註釋掉的那行,一共兩種方式,都可以自行返回具體的HTTP狀態給前端.

如果選擇自定義響應物件作為返回,那麼就放到body裡面即可,相當於ResponseEntity做了一層外包裝,這樣就能保證返回的介面既有具體HTTP狀態,也有具體的業務狀態,前後端工程師從此成為相親相愛一家人。


總結

我給大家的最終建議是這樣的:

1)、整個專案都規範好以ResponseEntity作為響應物件;

2)、如果有使用自定義響應物件,最好用ResponseEntity進行一層外包裝;

3)、如果嫌棄這種寫法,還可以這樣,介面依然返回自定義響應物件,但全域性異常處理中返回物件進行ResponseEntity包裝,最後在出問題的地方throw自定義異常即可。


現在各種新技術層出不窮且內卷的狀況下,不要過分追求強大流行的技術,反而要多關注基本功和編碼小細節。

尤其是對尚未工作及工作年限不久的同行們而言,不要小看寫介面的能力,否則也會被公司的爸比所鄙視哦。



本人專注於分享各種技術、工作中的趣事及經驗,喜歡或有收穫的朋友們,不要吝嗇您的一個小小推薦哦~~
也可以檢視個人主頁關注一下里面的資訊哦~~


相關文章