你花了一个下午优化监控面板的算法,测完很满意。几天后用户反馈"另一个页面数据不对"——你才发现,同一个逻辑在代码里有第二份实现,而你只改了第一份。
现象
改了一个监控面板的某段逻辑,另一个同源的监控面板却没有跟着改,两边显示的数据开始不一致。
更糟的一次是合并版本时引入了空指针 bug,还丢掉了两个渲染函数——因为两个面板各有一份实现,合并冲突解决时没有做完整性对照,一方的新代码覆盖了另一方。
根因
同一套逻辑存在第二份并行实现。
这在"复制一份改改就成了新页面"的开发习惯下极易发生:一开始是两个页面长得像,于是直接复制;后来各自演化,但核心算法还是一模一样的。当你需要改这个核心算法时,改动只落在你正在看的那个文件上——因为文件系统层面它们没有任何关联,你甚至不会意识到还有另一份。
另一个加重问题的因素:版本合并时没有做完整性对照。 两份实现是不同文件,git 不会提示"这两个文件实现的是同一个东西",所以合并时很容易用一方的版本整体覆盖,丢函数、丢逻辑,而且编译不报错。
解决
短期:这次怎么修。
第一步,先用搜索找出所有实现。用逻辑里最有辨识度的变量名、函数名或者魔法数字做关键词全局搜索:
grep -rn "calculateScore\|renderChart\|0.618" src/
第二步,逐份对照修改,并写一条对照清单(哪个文件、哪一行、改成什么),改完勾掉。清单很重要,因为它能防止"改了一半去喝水,回来忘了另一处"。
第三步,如果这次是合并引起的,把两个版本的关键函数逐个比对,确认没有丢函数:
# 分别列出两边的函数名清单再 diff
grep -n "^function \|^const .* = (" 版本A.js | sort > a.txt
grep -n "^function \|^const .* = (" 版本B.js | sort > b.txt
diff a.txt b.txt
长期:怎么根治。
把重复实现抽成一份公共模块,两边都调用它:
// shared/score.js
export function calculateScore(input) { /* 唯一实现 */ }
// panel-a.js
import { calculateScore } from './shared/score.js';
// panel-b.js
import { calculateScore } from './shared/score.js';
这是唯一的根治办法——只要还有两份实现,每次改动成本就永远是两倍,而且迟早会漏掉一处。
延伸与预防
在改动清单里加一条固定项:"这段逻辑有没有第二份实现?" 尤其在动手前搜索一下同名函数、同类变量、相同的魔法数字。
另外,代码评审时如果发现"两个文件里有一模一样的函数",就应该当场提出抽取,而不是等下次踩坑。评审是发现重复实现的最后一道关口——因为写代码的人往往"只看得见自己改的那份"。
同理心地说一句:这条坑的代价不是"多花一倍时间",而是"你以为改完了,其实没有"。那种自信地告诉别人"已经修好了"、结果几天后被打脸的感觉,才是真正的成本。