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

重构格言:"优秀系统不是设计出来的,而是通过持续重构演进而来的。"
—— 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  // 成功示例
短信账户密码错误        // 失败示例

这种设计在早期快速迭代阶段勉强可用,但随着系统复杂度提升,其弊端日益凸显:

  1. 客户端解析困难:需通过字符串前缀匹配判断成功与否,易因格式微调引发故障
  2. 可观测性差:缺乏唯一请求标识,排查问题如大海捞针
  3. 扩展性受限:无法携带额外数据(如运营商回执、计费信息)

为此,我决定做一个小小的升级,同时要兼容当前响应值。

我们计划使用 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}

二、重构目标:鱼与熊掌兼得

怎么进行这项代码重构呢?

  1. 为该接口增加版本号参数,不同版本响应值不同。
  2. 改造现有WEB控制器方法的返回值。原先返回 String, 变更这个返回值。
  3. 这个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 中的 codesuccess 字段使调用方能够通过统一逻辑处理成功与失败场景(如 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)而不影响核心逻辑。

四、总结

小重构,大收益!

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/903111.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OSPF中DR/BDR的选举

OSPF 开放式最短路径优先协议-CSDN博客 选举原因&#xff1a;广播网络中使路由信息交换更加高速有序&#xff0c;可以降低需要维护的邻接关系数量 基本概念&#xff1a; DR (Designated Router, 指定路由器)&#xff1a;负责在广播网络&#xff08;以太网&#xff09;或NBMA网…

[特殊字符]‍[特殊字符]Linux驱动开发入门 | 并发与互斥机制详解

文章目录 &#x1f468;‍&#x1f4bb;Linux驱动开发入门 | 并发与互斥机制详解&#x1f4cc;为什么驱动中需要并发和互斥控制&#xff1f;&#x1f4a1;常见的并发控制机制&#x1f510;自旋锁和信号量通俗理解&#x1f300;自旋锁&#xff08;Spinlock&#xff09;——“厕所…

Kafka 架构设计和组件介绍

什么是Apache Kafka&#xff1f; Apache Kafka 是一个强大的开源分布式事件流平台。它最初由 LinkedIn 开发&#xff0c;最初是一个消息队列&#xff0c;后来发展成为处理各种场景数据流的工具。 Kafka 的分布式系统架构支持水平扩展&#xff0c;使消费者能够按照自己的节奏检…

elk中kibana一直处于可用和降级之间且es群集状态并没有问题的解决方法

前言 在公司部elk的时候发现kibana的web界面一直很卡&#xff0c;数据量为0也会很卡&#xff0c;es群集状态正常&#xff0c;资源足够。 报错信息 [2025-03-17T09:54:50.19400:00][INFO ][status] Kibana is now available (was degraded) [2025-03-17T09:55:03.28000:00][I…

什么是视频上墙

视频联动上墙是指当监控系统中出现报警或其他特定事件时&#xff0c;相关的视频画面能够自动切换并显示在指定的监控大屏或显示设备上&#xff0c;以便监控人员能够快速、直观地查看事件现场的情况&#xff0c;及时做出响应和处理。 具体介绍• 系统组成 &#xff1a;一般由前端…

26考研——存储系统(3)

408答疑 文章目录 一、存储器概述二、主存储器三、主存储器与 CPU 的连接四、外部存储器五、高速缓冲存储器六、虚拟存储器七、参考资料鲍鱼科技课件26王道考研书 八、总结复习提示思考题常见问题和易混淆知识点 一、存储器概述 文章链接: 点击跳转 二、主存储器 文章链接: …

.NET 6 + Dapper + User-Defined Table Type

大家都知道&#xff0c;对于SQL Server IN是有限制条件的&#xff0c;如果IN里面的内容过多&#xff0c;在执行的时候会被自动截断&#xff0c;因而导致查询到的结果不是实际需要的结果。 select * from Payments where Id in (1,2,3,4,...) 为了解决上面的限制&#xff0c;可以…

MySQL 8(Ubuntu 18.04.6 LTS)安装笔记

一、前言 其实之前已经写过一篇笔记【MySQL 8.0.34&#xff08;x64&#xff09;安装笔记】。机缘巧合&#xff0c;这次遇到的环境是Ubuntu 18.04 LTS&#xff0c;相比Windows平台的安装&#xff0c;对mysql的版本以及依赖的选择&#xff0c;稍微要窄一些。特作笔记。 二、准备…

学习 Apache Kafka

学习 Apache Kafka 是一个很好的选择&#xff0c;尤其是在实时数据流处理和大数据领域。以下是一个系统化的学习建议&#xff0c;帮助你从入门到进阶掌握 Kafka&#xff1a; 1. 先决条件 在开始 Kafka 之前&#xff0c;确保你具备以下基础&#xff1a; Java 基础&#xff1a;K…

使用 binlog2sql 闪回 MySQL8 数据

【说明】 MySQL服务器版本 8.0.26 mysql> SELECT version(); ----------- | version() | ----------- | 8.0.26 | -----------Python 版本 Python 3.8.10 [infuq ~]# python -V Python 3.8.10【安装】 binlog2sql 官方地址 1.安装 binlog2sql [infuq ~]# git clone …

JavaScript 异步编程与请求取消全指南

JavaScript 异步编程与请求取消全指南 涵盖&#xff1a;同步/异步、Promise、async/await、AbortController、前后端协作 一、同步与异步 1. 同步&#xff08;Synchronous&#xff09; 定义&#xff1a;代码按顺序执行&#xff0c;前一步完成才能执行下一步。特点&#xff1…

永久缓存 Git 凭证

永久缓存 Git 凭证 打开终端或命令行工具。 执行以下命令&#xff0c;设置 Git 使用 store 凭证帮助程序&#xff1a; bash git config --global credential.helper store第一次执行 git pull 时输入账号密码。之后&#xff0c;所有需要凭证的操作都将自动使用存储的凭证&…

力扣-48.旋转图像

题目描述 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 class Solution { public:void rotate(vector<vector<int>…

Qt ModbusSlave多线程实践总结

最近项目中用到了ModbusSlave&#xff0c;也就是Modbus从设备的功能&#xff0c;之前用的基本都是master设备&#xff0c;所以读取数据啥的用单线程就行了&#xff0c;用 void WaitHelper::WaitImplByEventloop(int msec) {QEventLoop loop;QTimer::singleShot(msec, &loop…

opencv--图像

像素(像素点) 定义&#xff1a; Pixel 是 Picture Element&#xff08;图像元素&#xff09;的缩写&#xff0c;是数字图像中最小的独立单位。每个像素代表图像中的一个点的颜色和亮度信息。 关键特性&#xff1a; 颜色&#xff1a;通过不同的色彩模型&#xff08;如RGB、CMYK…

记录学习汇编语言02+各种寄存器分类

8086cpu是十六位的 然后寄存器能存八位 所以分为高八位低八位 高八位在下面低八位在上面 从下往上读&#xff08;从地址小的地方开始读&#xff09; 8086cpu种有两个和栈相关的寄存器 栈段寄存器ss&#xff08;栈顶的段寄存器&#xff09; 栈顶指针寄存器sp&#xff08;…

OpenCV 图形API(53)颜色空间转换-----将 RGB 图像转换为灰度图像函数RGB2Gray()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 将图像从 RGB 色彩空间转换为灰度。 R、G 和 B 通道值的常规范围是 0 到 255。生成的灰度值计算方式如下&#xff1a; dst ( I ) 0.299 ∗ src…

(51单片机)LCD显示数据存储(DS1302时钟模块教学)(LCD1602教程)(独立按键教程)(延时函数教程)(I2C总线认识)(AT24C02认识)

目录 演示视频&#xff1a; 源代码 main.c LCD1602.c LCD1602.h AT24C02.c AT24C02.h Key.c Key.h I2C.c I2C.h Delay.c Delay.h 代码解析与教程&#xff1a; Dealy模块 LCD1602模块 Key模块 I2C总线模块 AT24C02模块 /E2PROM模块 main模块 演示视频&#xff1a; E2…

电子病历高质量语料库构建方法与架构项目(数据遗忘篇)

引言 在人工智能与医疗健康的深度融合时代,医疗数据的价值与风险并存。跨机构和平台的医疗数据共享对于推动医学研究、提高诊断精度和实现个性化治疗至关重要,但同时也带来了前所未有的隐私挑战。先进的AI技术可以从理论上去标识化的医疗扫描中重新识别个人身份,例如从MRI数…

CentOS创建swap内存

服务器版本为CentOS7 一、检查现有 swap 空间 sudo swapon --show如果系统中没有 swap 空间或者现有的 swap 空间不足&#xff0c;可以继续后续步骤来创建 swap 空间。 二、创建 swap 文件&#xff08;推荐 2GB 作为示例&#xff09; sudo dd if/dev/zero of/swapfile bs1M …