上周帮一家做智能巡检系统的客户排查响应延迟,接口平均耗时从 80ms 突然涨到 450ms。查日志没报错,CPU 和内存也平稳,最后发现是新上线的设备状态校验模块里,同一段 JSON 解析逻辑在单次请求中被重复调用了 7 次——不是业务需要,只是三个不同方法各自 copy-paste 了一模一样的解析代码。
冗余不是“多写几行”,是运行时的真实开销
很多人觉得“代码多点而已,反正机器快”,但冗余代码在运行期会实实在在吃掉资源:重复计算、反复序列化、无意义的对象创建、冗余条件判断……这些不会在编译时报错,却会让 CPU 在同一任务上打转,GC 更频繁,线程等待时间拉长。
比如一个高频调用的订单处理服务,有段校验逻辑:
String orderId = request.getOrderId();
if (orderId == null || orderId.trim().length() == 0) {
throw new IllegalArgumentException("订单号不能为空");
}
if (!orderId.matches("^[A-Z]{2}\d{8}$")) {
throw new IllegalArgumentException("订单号格式错误");
}这段代码在 controller、service、mapper 入参前被复制了三次。每次请求都执行三遍正则匹配和 trim —— 正则引擎要编译模式、分配栈帧、回溯匹配;trim 要新建字符串对象。看似毫秒级操作,乘以每秒上千次调用,就成了可观的延迟来源。
别只盯着“删代码”,先定位真冗余
不是所有重复都是坏味道。工具类里的 StringUtils.isEmpty() 被多处调用不算冗余;但同一业务流程中连续三次 new Date()、三次 parse 时间字符串、三次查同一个配置项,就是典型可优化点。
推荐两个轻量手段:
• 用 JVM 自带的 -XX:+PrintGCDetails + GC 日志观察对象创建速率,暴增的 short-lived 对象往往来自冗余 new;
• 在关键路径加简单纳秒计时(System.nanoTime()),对比调用前后差值,比看整体接口耗时更容易揪出“藏得深”的重复块。
调优不是炫技,是让代码回归职责
把重复逻辑拎出来,不等于硬塞进一个“Utils”类就完事。重点看它属于谁的职责:
- 如果是入参校验 → 放到 DTO 的 @Valid 注解或自定义 ConstraintValidator 中;
- 如果是数据转换 → 交给 MapStruct 或 Jackson 的 @JsonCreator 封装;
- 如果是配置读取 → 用 Spring @Value("${xxx}") + @ConfigurationProperties 统一加载,避免每次调用都去 Environment.getProperty()。
再举个例子:某后台导出报表功能,每次生成 Excel 前都要根据用户角色查一遍权限码,而这个权限码在登录后已存入 ThreadLocal。原代码在 4 个 service 方法里各自 getRoleCode(),改成统一从上下文获取,导出耗时直接降了 35%。
冗余代码就像办公室里三台打印机共用一个插线板还各自配了独立开关——看着方便,其实只要一个总闸,再加条分支线就够了。