重構格言:"優秀系統不是設計出來的,而是通過持續重構演進而來的。"
—— Martin Fowler《重構:改善既有代碼的設計》
希望本文能為您的重構之旅提供指引,讓老舊系統煥發新生!

一、背景:一個“穩定”接口的隱患
下面WEB控制器方法,是我們歷史悠久的短信服務(SMS)里的短信發送接口。
@RestController
@RequestMapping({"/smsSend", "/sendSms"})
public class SmsSendController {@GetMapping@PreventDuplicateRequest(key = "SmsSendController_smsSend", spEL = "{#phones, T(com.sms.common.utils.MD5Util).getMD5(#content)}", expireSeconds = 5)public String smsSend(@RequestParam("account") String account,@RequestParam("sign") String sign,@RequestParam("mphone") String phones,// 多個用逗號分隔@RequestParam("content") String content) {return sendSms(account, sign, phones, content);//"SUCCESS" + msgIds.substring(0, msgIds.length() - 1);}
}
這個接口是在若干年前開發的,彼時,大家技術能力有限。
該接口響應格式簡單粗暴——成功時返回 SUCCESS 拼接消息ID,失敗時直接返回錯誤原因字符串。例如:
SUCCESS123456,789012 // 成功示例
短信賬戶密碼錯誤 // 失敗示例
這種設計在早期快速迭代階段勉強可用,但隨著系統復雜度提升,其弊端日益凸顯:
- 客戶端解析困難:需通過字符串前綴匹配判斷成功與否,易因格式微調引發故障
- 可觀測性差:缺乏唯一請求標識,排查問題如大海撈針
- 擴展性受限:無法攜帶額外數據(如運營商回執、計費信息)
為此,我決定做一個小小的升級,同時要兼容當前響應值。
我們計劃使用 Result
對象來實現相應結構的標準化,即 code/msg/data 的形式,符合RESTful API的最佳實踐。例如:
// 錯誤響應
{"reqId":"a369331163aba36","message":"短信賬戶錯誤","code":500,"data":null,"timestamp":1745285069433,"success":false}
//成功響應
{"reqId":"a6a0bb2f83844d9","message":"發送成功","code":200,"data":["2504223325367695"],"timestamp":1745285136909,"success":true}
二、重構目標:魚與熊掌兼得
怎么進行這項代碼重構呢?
- 為該接口增加版本號參數,不同版本響應值不同。
- 改造現有WEB控制器方法的返回值。原先返回 String, 變更這個返回值。
- 這個API方法所調用的 sendSms,變更其返回值,以明確方法職責。
@RestController
@RequestMapping({"/smsSend", "/sendSms"})
public class SmsSendController {@GetMapping({"/{version}", ""})@PreventDuplicateRequest(key = "SmsSendController_smsSend", spEL = "{#phones, T(com.sms.common.utils.MD5Util).getMD5(#content)}", expireSeconds = 5)public Object smsSend(@RequestParam("account") String account,@RequestParam("sign") String sign,@RequestParam("mphone") String phones,// 多個用逗號分隔@RequestParam("content") String content, @PathVariable(value = "version", required = false) String version) {Result<List<String>> listResult = sendSms(account, sign, phones, content);if ("v2".equals(version)) {// v2版本返回值listResult.setReqId(MDC.get("traceId"));return listResult;} else {// v1版本返回值if (listResult.isSuccess())return "SUCCESS" + String.join(",", listResult.getResult());elsereturn listResult.getMessage();}}
}
三、重構收益:從能用走向好用
1. 響應結構標準化
-
可維護性提升:統一使用 Result 結構體,符合 RESTful 設計規范
-
錯誤處理增強:
Result
中的code
和success
字段使調用方能夠通過統一邏輯處理成功與失敗場景(如if (result.isSuccess())
),避免了舊版中依賴字符串內容(如判斷是否以“SUCCESS”開頭)的脆弱邏輯。同時,精準錯誤碼也可指導用戶處理(如 code=501 提示賬戶余額不足) -
顯著降低客戶端使用成本:相比原始的“SUCCESS+ID”或“錯誤字符串”,調用方無需通過字符串解析邏輯(如前綴匹配、異常分支判斷)即可快速識別請求結果
2. 版本兼容性設計
實現方式:
@GetMapping({"/{version}", ""})
public Object smsSend(..., @PathVariable String version) {if ("v2".equals(version)) { /* 新版本邏輯 */ }else { /* 舊版本兼容 */ }
}
-
平滑升級:通過兼容新舊版本共存,舊客戶端無需立即改造,避免“一刀切”式升級帶來的兼容性風險。
-
灰度發布能力:通過 URL 路徑控制新老版本流量比例
3. 請求追蹤集成
-
日志可追溯:通過 reqId 快速關聯請求全鏈路日志
-
調試效率提升:快速定位具體請求的服務器處理線程,故障定位時間縮短 70%
4. 底層邏輯與接口解耦
- 統一內部返回類型:將
sendSms
方法的返回值從原始字符串改為Result<List<String>>
,使底層邏輯專注于業務處理(生成標準化結果),而控制器層僅負責“按版本格式化響應”,減少了控制器中的條件判斷邏輯,符合“單一職責原則”。 - 隔離版本差異邏輯:版本相關的響應轉換(如舊版字符串拼接、新版
Result
裝配)集中在控制器層,底層服務(如sendSms
)和Result
模型保持無版本依賴,便于后續擴展更多版本(如 v3、v4)而不影響核心邏輯。
四、總結
小重構,大收益!
本次小重構通過版本化路徑設計和響應格式分層兼容,在不破壞現有調用的前提下實現了接口的標準化升級,顯著提升了接口的可維護性、調用方體驗和錯誤處理能力。