重构格言:"优秀系统不是设计出来的,而是通过持续重构演进而来的。"
—— 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)而不影响核心逻辑。
四、总结
小重构,大收益!
本次小重构通过版本化路径设计和响应格式分层兼容,在不破坏现有调用的前提下实现了接口的标准化升级,显著提升了接口的可维护性、调用方体验和错误处理能力。