重构格言:"优秀系统不是设计出来的,而是通过持续重构演进而来的。"
—— 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());
- else
- return 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,使底层逻辑专注于业务处理(生成标准化结果),而控制器层仅负责“按版本格式化响应”,减少了控制器中的条件判断逻辑,符合“单一职责原则”。
- 隔离版本差异逻辑:版本相关的响应转换(如旧版字符串拼接、新版 Result 装配)集中在控制器层,底层服务(如 sendSms)和 Result 模型保持无版本依赖,便于后续扩展更多版本(如 v3、v4)而不影响核心逻辑。
四、总结
小重构,大收益!
本次小重构通过版本化路径设计和响应格式分层兼容,在不破坏现有调用的前提下实现了接口的标准化升级,显著提升了接口的可维护性、调用方体验和错误处理能力。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |