背景
我們在APP上有個功能,需要獲取用戶當前定位,然后當用戶關閉了GPS后,沒有獲取到用戶定位,會觸發一個bug,彈窗內容如下。
問題分析
這個問題的直接原因就是移動端的值取不到,導致沒有給變量賦值,就將"undefined"傳給了后端,后端的這個值定義的Integer,類型轉換失敗,報錯。
深層原因是異常處理機制有問題,於是后端和前端開始撕逼了
前端觀點: 后端代碼太不健壯了, 就算前端傳錯了,也應該具備容錯性;此外APP是有版本的,就算hotfix,用戶也不一定升級,上一版本用戶還是會有問題,所以這種問題盡量是后端來修復。
后端觀點:前端沒有異常兜底機制,用戶不應該看到任何這種程序異常。對於有定制需求的異常,報特定異常,沒有應該顯示通用異常,比如彈窗"服務不可用"。另外這種屬於http請求層面的約束,前端不遵從約束,還來怪我。我后端框架層面就給你攔截了,沒到業務代碼。
雙方說的都好有道理,誰也說服不了誰。但是關於目標大家達成一致:堅決不能讓用戶看到這種類型的彈窗異常。
既然說服不了對方,就只能從更深入的分析問題,看看更合理的解法
通用異常的處理方式
http通常錯誤有
- 4開頭:客戶端參數有問題,需要后端提供debug信息。理論上應該只是聯調的時候會出現,但是實際上不一定(這不就打臉了嗎)
- 5開頭:服務器端有錯誤,客戶端有統一提供的異常處理
- 2開頭:業務異常,如果有UI要求,后端返回一個code碼,前端根據code碼,展示UI。如果沒有UI要求,前端直接展示后端返回的錯誤消息。
為了統一異常處理,一般公司的做法都是API統一返回一套數據格式,
{
"error_code": "xx", // code碼,1代表正常,其他表示異常。
"error_msg": "xx" // msg,錯誤提示消息
"data": "xx" // 正常數據
}
我們也是,並且將4開頭的都統一處理成這套統一的數據格式。
那么前端處理異常的邏輯
這次的問題就是走到2的分支了。
前后端都沒做錯,問題是后端對於異常模型的抽象有問題,客戶端參數有問題,需要后端提供debug信息
,而不是給用戶展示的錯誤信息。
其實服務端對於異常就分三種
- 客戶端參數有問題的異常(前端需要debug信息和錯誤msg信息)
- 需要用戶知道的業務異常,前端需要根據code展示的(前端需要code碼)
- 通用的服務端異常,包裝成消息給前端。(前端需要錯誤msg信息)
解法
分析清楚了問題后,就有了解法。
解法1:錯誤消息和debug消息分離,返回的API統一格式中增加一種字段。debug_msg
給開發看的,error_msg
還是給用戶看的
解法2:定義幾個枚舉code。作為開發的約定
code | error_msg | 參數錯誤含義 |
---|---|---|
010000 | 系統錯誤[010000] | 請求方法不支持 |
010001 | 系統錯誤[010001] | 缺少路徑參數 |
010002 | 系統錯誤[010002] | 缺少必須的請求參數 |
010003 | 系統錯誤[010003] | 類型不匹配 |
010004 | 系統錯誤[010004] | 請求體異常 |
010005 | 系統錯誤[010005] | // 參數校驗異常(body 或 param) |
010006 | 系統錯誤[010006] | 參數綁定異常(表單) |
解法1定義比較清晰,但是為了這種corner case增加了一個字段的開銷,網絡傳輸代價高了。另外還需要前端配合更改,改動成本比較大。
解法2兼容了現在的實現,前端不用更改,但是對於客戶端參數有問題
這種錯誤提醒不是很友好,不能向前端顯示具體的參數錯誤了。只能打日志。
和前端討論了下,確定了解法2。
總結
所以這個問題,最后的解法
- 前端獲取不到定位時,將
undefined
這個變量賦值 - 后端針對移動端這個服務,改動了異常處理機制
@RestControllerAdvice
public class ApiStandardException {
private static final String ERROR_MSG = "系統錯誤";
@ExceptionHandler(value = Exception.class)
public ResponseEntity<Result> handle(final Exception ex, final WebRequest request)
throws Exception {
try {
if (ex instanceof HttpRequestMethodNotSupportedException) {
// 請求方法不支持
LOG.warn("Request method is not supported");
throw new BusinessException(WebRequestErrorEnum.METHOD_ERR.getCode(), ERROR_MSG);
} else if (ex instanceof MissingPathVariableException) {
LOG.warn("MISSING_PATHVAR" + ex.getMessage());
// 缺少路徑參數
throw new BusinessException(WebRequestErrorEnum.MISSING_PARAM.getCode(), ERROR_MSG);
} else if (ex instanceof MissingServletRequestParameterException) {
// 缺少必須的請求參數
}
// 省略其他異常處理
關注【方丈的寺院】,第一時間收到文章的更新,與方丈一起開始技術修行之路