痨砖 发表于 2025-6-1 00:00:27

小重构,大收益!技术重构实践:如何优雅升级老旧接口

重构格言:"优秀系统不是设计出来的,而是通过持续重构演进而来的。"
—— 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)而不影响核心逻辑。
四、总结

小重构,大收益!
本次小重构通过版本化路径设计和响应格式分层兼容,在不破坏现有调用的前提下实现了接口的标准化升级,显著提升了接口的可维护性、调用方体验和错误处理能力。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 小重构,大收益!技术重构实践:如何优雅升级老旧接口