ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

改两行代码,不动原有结构——最小侵入式改造的一次实践

改两行代码,不动原有结构——最小侵入式改造的一次实践 改两行代码不动原有结构——最小侵入式改造的一次实践电子面单对接实录 · 技术深潜 前情《Token刷新与缓存失效一对被忽视的上下游》先说一个场景。系统里有两个Token刷新方法一个后台定时跑一个前台手动触发。两个方法的核心逻辑完全一样跑了三年稳。现在要加一个功能刷新成功后主动清除缓存里的旧Token配置让下次取号立刻用新值。如果按“理想做法”应该趁机把两个方法的重复代码抽取一下、把吞异常的逻辑优化一下、把日志级别修正一下。但我选择了一个“不理想”的做法只加不改最小侵入。这篇文章记录这次改造的完整思考过程——不是技术有多难而是在遗留系统里决定“不动什么”比决定“改什么”更需要判断力。01 两个方法同一个逻辑截然不同的异常策略先看两个方法各自干什么。后台定时刷新定时任务触发遍历所有组织逐个调用平台API刷新Token。一个组织失败不能影响后续吞掉异常继续跑。前台手动刷新用户点按钮触发逻辑和后台一模一样。但失败必须抛异常——用户需要知道刷没刷成功。核心代码的差异只有一处后台定时刷新if(!isGetData){this.logger.error(更新org.getName()授权失败:responseVal);// 不抛异常继续下一个组织}前台手动刷新if(!isGetData){thrownewBusinessException(更新org.getName()授权失败:responseVal);}同一个业务逻辑吞异常和抛异常都是对的——取决于谁在调用。这不是代码重复是场景驱动的工程设计。AI会建议“抽取公共方法异常策略参数化”但在这个阶段保持两个方法的独立性比消除重复更重要——因为改动一处就要回归两处而它们跑了三年没出过问题。02 缓存失效的时机问题后台刷新更新了数据库里的Token值但缓存组件里还存着旧值。当前设计是缓存定时全量清空默认1小时一次。这意味着Token刷新后最多等1小时缓存才能感知到新值。更理想的方式是刷新成功后主动清除对应缓存。但改造方式有讲究。如果趁机重构——抽取公共方法、优化异常处理、修正日志级别——改动范围会迅速膨胀。定时任务和手动触发都要回归测试四个平台、两个方法、八个改造点任何一个出错都会影响线上Token的有效性。我的原则是只加不改最小侵入。03 改造方案在if (isGetData)里加六行代码两个方法的异常处理结构是一样的// 解析API响应更新数据库BooleanisGetDataBoolean.FALSE;// ... 解析逻辑 ...// 原来只处理失败情况if(!isGetData){// 后台打日志 前台抛异常}改造思路很简单把if (!isGetData)改成if-else成功分支加缓存清除失败分支保持原样。后台定时刷新if(isGetData){// 新增清除缓存 try{tokenCache.evictByOrgId(PlatFormType.PLAT_A_CODE,org.getId(),normal);this.logger.info(已清除平台A普通Token缓存: org.getCode());}catch(ExceptioncacheException){this.logger.warn(清除缓存失败: cacheException.getMessage());}}else{// 原代码一字不动 this.logger.error(更新org.getName()授权失败:responseVal);}前台手动刷新if(isGetData){// 新增清除缓存 try{tokenCache.evictByOrgId(PlatFormType.PLAT_A_CODE,org.getId(),normal);this.logger.info(已清除平台A普通Token缓存: org.getCode());}catch(ExceptioncacheException){this.logger.warn(清除缓存失败: cacheException.getMessage());}}else{// 原代码一字不动 thrownewBusinessException(更新org.getName()授权失败:responseVal);}关键设计缓存清除包裹在try-catch中失败只打warn日志绝不中断主流程缓存清除在if块的最前面和业务逻辑完全隔离原有的else分支一字不动——后台继续打日志前台继续抛异常04 八个改造点统一模式四个平台通道平台A普通、平台A代发、平台B、平台C× 两个方法自动手动 八处改造点。每处的改造模式完全一致改造点成功分支失败分支自动-平台A普通清除缓存 info日志原有error日志自动-平台A代发清除缓存 info日志原有error日志自动-平台B清除缓存 info日志原有error日志自动-平台C清除缓存 info日志原有error日志手动-平台A普通清除缓存 info日志原有throw异常手动-平台A代发清除缓存 info日志原有throw异常手动-平台B清除缓存 info日志原有throw异常手动-平台C清除缓存 info日志原有throw异常每处改动不超过10行代码原有代码一行不动。平台A的普通和代发用不同的模式后缀区分缓存Keynormalvsdaifa平台B和平台C不需要区分模式传null即可。05 还有一个意外收获在梳理缓存Key时发现平台A的普通和代发模式共用同一个缓存Key——两者的Token存在不同字段但Key一样先刷后刷会互相覆盖。这个问题当前没暴露因为两种模式在同一个定时任务里几乎同时刷新。但如果未来刷新任务分离就会出现缓存加载到过期Token的隐患。给缓存Key加一个模式后缀普通模式PLAT_A_123_normal 代发模式PLAT_A_123_daifa一次改造同时解决了“缓存失效不及时”和“缓存Key冲突”两个问题。这种“顺便发现”的隐患在遗留系统里比比皆是——它们不是一眼能看出来的而是在动手梳理时才会浮现。06 为什么不动原有结构可能有人会问两个方法重复率这么高为什么不趁机抽取公共方法我的判断是现在不是时候。抽取公共方法意味着两个方法的改动捆绑在一起回归范围翻倍异常策略的差异吞异常 vs 抛异常需要参数化增加复杂度定时任务和手动触发是两条独立的业务路径合并后一条出问题会影响另一条当前阶段的首要目标是让缓存失效机制尽快上线而不是优化代码结构。等系统稳定运行一段时间确认缓存清除逻辑没有问题再考虑代码层面的重构。原则不变先上线、跑稳、观察再迭代。07 核心收获1. 最小侵入是遗留系统改造的安全网。八个改造点每处只改一个if结构新增代码全部包裹在try-catch中。上线风险为零——最坏的情况是缓存清除失败打一行warn日志不影响Token刷新本身。2. 保持两个方法的独立性现阶段比消除重复更重要。它们跑了三年没出问题各自独立意味着改动互不影响、回归各自进行、出问题互不牵连。3. 改造过程中“顺便发现”的隐患往往比原问题更有价值。缓存Key冲突这个问题如果不在这次改造中梳理清楚未来某天就会变成生产故障。4. 不是所有“能优化”的地方都要立刻优化。日志级别还在用error打正常流程代码重复率依然很高——这些都在优化清单上但优先级排后。知道“什么时候不该做什么”是遗留系统改造中最难的一课。讨论话题你在改老系统时有没有过“想顺手重构但忍住了”的经历后来回头看那个决定是对是错评论区聊聊。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进