重构这件事,最难的从来不是“把代码改对”,而是“改完之后行为一模一样”。我见过太多团队在重构时信心满满,结果上线后一堆边界场景炸出来,回头一看,问题几乎都出在“特性搬错了位置”或者“搬的时候顺手改了逻辑”。这篇就围绕在对象之间搬移特性这个主题,把我这些年踩过的坑、总结的判断标准和操作手法摊开讲清楚。
先说清楚这篇适合谁看:如果你已经能写业务代码,但对“什么时候该把字段挪到另一个类”“方法该不该搬”“搬完之后怎么保证不出事”这些问题心里没底,那这篇就是写给你的。如果你是有一定经验的开发者,正在做遗留系统改造,也能从里面的判断逻辑和验证手法里拿到可直接复用的东西。核心关键词是重构、搬移特性、字段搬移、方法搬移、行为保持,整篇内容都围绕这几个词展开。
1. 先搞清楚“搬移特性”到底在搬什么
很多人对“搬移特性”的理解停留在“把代码从一个文件剪切到另一个文件”,这个理解太浅了。搬移特性的本质是重新分配职责,代码位置的变动只是结果,不是目的。你搬的是一个“特性”——它可能是一个字段、一个方法、一组相关的行为,甚至是一份隐含的职责归属。搬对了,类的边界更清晰;搬错了,只是把混乱换了个地方。
1.1 特性搬移的三种基本形态
在实际项目里,对象之间的特性搬移大致可以归成三类,每一类的判断标准和操作手法都不一样。
第一类是字段搬移。典型场景是某个字段被一个类声明了,但真正频繁读写它的是另一个类。这时候字段待错了地方,每次访问都要绕一层,或者被迫暴露不该暴露的 getter/setter。字段搬移的目标是让数据和操作它的行为待在一起,也就是常说的“数据和行为就近原则”。
第二类是方法搬移。一个方法放在 A 类里,但它用到的数据几乎全来自 B 类,或者它做的事情本质上是 B 类的职责。这种方法的特征是“身在曹营心在汉”,它在这个类里显得格格不入,读代码的人会疑惑“这方法为什么在这儿”。
第三类是一组特性的整体搬移,也就是把若干字段和若干方法打包挪到新家。这类操作风险最高,因为涉及面广,牵一发动全身,必须配合更严格的验证手段。
提示:判断要不要搬,先问一句“这个特性最常和谁一起变化”。变化方向一致的东西应该待在一起,这是搬移决策最朴素也最有效的依据。
1.2 为什么“行为保持”是搬移的生命线
重构的定义里有一条铁律:不改变外部可观察行为。搬移特性尤其容易违反这条,因为你在动结构的同时,很容易“顺手优化”一下逻辑。我见过一个真实案例,某开发者搬一个计算方法时,觉得原来的空值判断写得啰嗦,就简化了一下,结果原来对空值返回默认值的逻辑变成了抛异常,线上直接报错。
所以搬移特性的第一原则不是“搬得漂亮”,而是“搬得一样”。在动手之前,你必须先有一套能验证“搬前搬后行为一致”的手段,否则就是在盲改。这套手段可以是单元测试、可以是特征测试(characterization test,即把现有行为用测试固化下来),也可以是可对比的日志输出。没有验证手段的搬移,本质上是在赌。
1.3 搬移前的三个前置检查
动手之前,我习惯做三个检查,能挡掉大部分低级错误。
- 检查访问边界:这个字段或方法被哪些地方引用?公开的还是包内可见的?搬走之后这些引用点怎么改?引用点越多,搬移成本越高,越要谨慎。
- 检查隐式依赖:方法里有没有用到所在类的其他私有成员?如果有,搬方法时这些依赖要么一起搬,要么通过参数传入,要么就得重新考虑搬移方案。
- 检查副作用:这个方法有没有修改状态、写文件、发请求?有副作用的方法搬移后,调用时序可能变化,必须重点验证。
这三项检查做完,你对这次搬移的“爆炸半径”就有了基本判断,后面的操作心里才有底。
2. 字段搬移:让数据回到该在的类里
字段搬移是三种形态里最基础的一种,但基础不代表简单。字段搬错位置,轻则多一层无谓的访问,重则破坏封装、引入循环依赖。这一节把字段搬移的完整链路拆开讲。
2.1 识别“字段待错地方”的信号
字段该不该搬,有几个很直观的信号。第一个信号是访问频率倒挂:声明字段的类自己几乎不用它,反而是另一个类频繁读写。这说明字段的“家”和“工作地点”分离了,每天通勤成本很高。
第二个信号是封装被迫妥协:为了让另一个类能访问这个字段,你不得不把 private 改成包内可见,或者加一个只为了外部读写而存在的 getter/setter。封装被打开一个口子,往往就是字段该搬的信号。
第三个信号是字段和某个类的其他成员强相关:比如一个“折扣率”字段,总是和“计算折后价”的方法一起被使用,而这两个东西分属两个类。这时候把字段搬到方法所在的类,数据和行为的凝聚力立刻提升。
我一般会用一个简单的判断:如果这个字段的读写代码里,有超过一半出现在另一个类中,就该认真考虑搬移了。这个比例不是硬指标,但是一个很好的触发点。
2.2 字段搬移的标准操作步骤
字段搬移有一套相对固定的动作序列,按顺序做能最大程度降低风险。
- 先加新字段:在目标类里新增一个同类型字段,先不删旧字段。这一步只是“复制”,不改任何逻辑。
- 建立同步机制:在过渡期,让新旧字段保持同步。如果旧字段是公开的,可以在旧字段的 setter 里同步写新字段;如果直接暴露字段,就先改成通过方法访问,再在方法里做双向同步。
- 逐个替换读取点:把所有读取旧字段的地方改成读新字段,每改一处就跑一次测试。这一步要小步走,不要一次性全改。
- 逐个替换写入点:读取点替换完、验证通过后,再替换写入点,同样小步验证。
- 删除旧字段:确认没有任何引用后,删掉旧字段和临时的同步代码。
这个“先加后删、中间同步”的手法,核心思想是任何时刻系统都是可运行的。你不需要一次性完成所有改动,可以分多次提交,每次提交都是绿的。这对团队协作尤其重要,因为你的改动不会阻塞别人。
2.3 搬移过程中最容易忽略的初始化顺序
字段搬移有一个隐蔽的坑:初始化顺序。如果旧字段在构造函数里被赋值,而新字段的初始化依赖某些运行时状态,搬移后可能出现“新字段还没初始化就被读取”的情况。
举个典型场景:旧字段在声明时直接赋了默认值,搬移后你在构造函数末尾才给它赋值,但中间某个方法已经读了它,读到的是默认值而不是预期值。这类问题在单测里不一定暴露,因为单测往往直接构造对象后立即调用目标方法,跳过了中间路径。
我的做法是:搬移字段时,把初始化逻辑一起搬,并且明确新字段的初始化时机。如果新字段依赖构造参数,就在构造函数里赋值;如果依赖运行时状态,就考虑用惰性初始化,但要确保惰性初始化的线程安全性。初始化顺序这件事,宁可多写两行注释说明清楚,也不要留给后来人猜。
2.4 一个字段搬移的完整示例
假设有一个订单类和一个客户类,订单类里有个customerLevel字段,但实际使用中,客户等级是客户自身的属性,订单只是读取它来做折扣计算。这就是典型的字段待错地方。
搬移前,订单类持有customerLevel,每次客户等级变化都要同步更新所有相关订单,非常容易漏。搬移后,customerLevel回到客户类,订单通过持有的客户引用去读取。改动之后,客户等级只有一个真实来源,同步问题自然消失。
这个例子里,搬移带来的不只是代码位置变化,而是消除了一个数据一致性的隐患。这也是判断搬移价值的一个角度:好的搬移往往顺带消灭了一类bug。
3. 方法搬移:把行为安放到职责归属地
方法搬移比字段搬移复杂,因为方法带着逻辑、带着依赖、带着副作用。搬一个方法,你要同时考虑它的入参、它的内部引用、它的调用方,以及它和其他方法的关系。这一节把方法搬移的判断、手法和验证讲透。
3.1 方法“该不该搬”的四个判断维度
判断一个方法该不该搬,我通常从四个维度看。
维度一:数据依赖。方法体里用到的字段,大部分来自哪个类?如果大部分来自另一个类,说明这个方法在“借用”别人的数据干活,它更应该待在那个类里。
维度二:职责归属。这个方法做的事情,从业务语义上属于谁的职责?比如“计算税费”放在订单类里合理,放在用户类里就莫名其妙,哪怕它用到的数据恰好都在用户类里。
维度三:变化原因。这个方法会因为什么原因被修改?如果它总是和另一个类的其他方法一起被改,说明它们属于同一个变化方向,应该聚在一起。
维度四:调用关系。这个方法主要被谁调用?如果它几乎只被另一个类调用,那它待在当前类里就是让调用方多绕一层,搬过去能简化调用链。
四个维度综合看,权重最高的是职责归属,其次是数据依赖。职责对了,数据依赖稍微别扭一点可以接受;职责错了,数据再顺手也是错的。
3.2 方法搬移的两种手法:整体搬与拆解搬
方法搬移有两种基本手法,选哪种取决于方法的“纯度”。
整体搬适用于方法逻辑内聚、依赖清晰的情况。做法是把整个方法体原样挪到目标类,然后把原来用到当前类成员的地方,改成通过参数传入或者通过目标类已有的引用访问。整体搬的优点是改动小、易验证,缺点是如果方法里混杂了多个类的职责,搬过去也只是把混杂换了个地方。
拆解搬适用于方法内部其实混了多个职责的情况。做法是先把方法按职责拆成几个小方法,每个小方法只做一件事,然后把属于目标类的那部分搬过去,剩下的留在原类或者继续拆。拆解搬更彻底,但风险也更高,因为拆分本身就可能改变行为。
我的经验是:第一次搬移优先用整体搬,把行为保持住;如果搬完发现目标类变得臃肿或者职责不清,再考虑拆解搬。不要一上来就追求“搬得完美”,先保证“搬得正确”。
3.3 搬移方法时参数列表的处理
方法搬移几乎必然涉及参数调整。原来方法可以直接访问所在类的字段,搬走后这些字段要么通过参数传入,要么通过目标类持有的引用访问。这里有个取舍。
如果字段是目标类本来就有的,直接通过this访问,不用加参数,这是最干净的。如果字段是原类特有的、目标类没有,那就得作为参数传入。参数一多,方法签名就难看,调用方也麻烦。
这时候可以考虑引入参数对象:把一组总是同时出现的参数打包成一个对象。但要注意,引入参数对象本身也是一次重构,最好单独做、单独验证,不要和方法搬移混在一次提交里。混在一起改,出问题时你分不清是搬移引入的还是参数对象引入的。
注意:参数列表的调整是方法搬移里最容易引入 bug 的环节,尤其是参数顺序。原来
(a, b)搬完变成(b, a),编译器不一定报错(如果类型相同),运行时就是灾难。改签名后务必逐个核对调用点。
3.4 副作用方法的搬移要格外小心
纯计算方法搬移相对安全,有副作用的方法搬移就要打起十二分精神。副作用包括修改成员状态、写日志、发消息、调外部接口、改全局变量等。
搬移副作用方法时,重点验证两件事:调用时序有没有变,副作用发生的次数有没有变。举个常见问题:原来方法在 A 类里被调用一次,副作用执行一次;搬到 B 类后,如果 B 类的某个流程里方法被调用了两次,副作用就执行了两次,行为就变了。
我的做法是给副作用方法搬移单独加验证:在副作用发生的地方打日志或者埋点,搬移前后对比日志序列。日志序列一致,基本可以认为时序和次数没变。这个方法土,但非常有效,尤其是在没有完善单测覆盖的老代码里。
4. 一组特性的整体搬移:处理牵一发动全身的场景
单个字段或单个方法的搬移,影响面相对可控。真正让人头疼的是“一组特性整体搬移”——若干字段加若干方法,可能还牵扯到它们之间的相互调用。这种搬移做不好,系统会进入一个“半搬半不搬”的尴尬状态,比搬之前还乱。
4.1 整体搬移前必须先画依赖图
整体搬移的第一步不是写代码,是画依赖图。把要搬的字段和方法列出来,标出它们之间的引用关系,再标出它们和外部类的引用关系。这张图不用多正式,纸上画一画或者用文本列一列都行,关键是让依赖关系可视化。
依赖图能帮你回答几个关键问题:这组特性内部是不是自洽的?有没有哪个字段被组外的方法大量使用?有没有哪个方法依赖组外的私有成员?这些问题的答案直接决定搬移方案。
我踩过的一个坑是:搬一组方法时没画依赖图,搬完才发现其中一个方法依赖原类的一个私有字段,而这个字段不在搬移范围内。结果要么把那个字段也搬走(扩大了搬移范围),要么给方法加参数(改了签名),两条路都得返工。如果提前画图,这个依赖一眼就能看到。
4.2 用“绞杀者”思路分阶段搬移
整体搬移不要追求一步到位。我推荐用“绞杀者”思路:新建一个目标类,先把要搬的特性在新类里实现一份,然后逐步把调用方从旧类切到新类,旧类的对应特性在确认无人使用后删除。
这个思路的好处是新旧并存、逐步切换。每一步都可以验证,每一步都可以回滚。具体分几个阶段:
第一阶段,新建目标类,把要搬的字段和方法复制过去,先不删旧的。这时候两份代码并存,但只有旧的被调用。
第二阶段,挑选一个调用方,改成调用新类,验证通过后提交。然后逐个调用方切换,每切一个验证一次。
第三阶段,所有调用方切换完毕后,删除旧类里的对应特性,清理临时的兼容代码。
这个过程中,新旧两份代码会短暂并存,看起来“重复”了,但这是有意为之的过渡态。重复是暂时的,混乱才是永久的,用短暂的重复换可控的迁移,这笔账划算。
4.3 搬移范围蔓延时怎么踩刹车
整体搬移最常见的失控方式是范围蔓延:搬着搬着发现“这个也得搬”“那个不搬不行”,范围越滚越大,最后变成一次大爆炸式改造。
踩刹车的办法是设定搬移边界并严格执行。动手前明确列出这次要搬的字段和方法清单,搬移过程中如果发现清单外的东西也需要动,先记下来,评估后再决定是纳入本次还是留到下次。不要边搬边扩,那等于没有边界。
如果发现清单外的东西确实非搬不可,那就停下来,重新评估范围,重新画依赖图,重新定计划。停下来重新规划的成本,远小于范围失控后返工的成本。这个判断我吃过亏才学会:有一次觉得“就多搬一个字段,很快”,结果那个字段牵出一串依赖,最后整次搬移拖了两周,还不如一开始就重新规划。
4.4 整体搬移后的回归验证清单
整体搬移完成后,验证不能只看单测绿不绿。我一般会过一遍下面这个清单:
| 验证项 | 具体做法 | 关注点 |
|---|---|---|
| 单元测试 | 跑全量单测 | 是否有用例因签名变化而失效 |
| 集成路径 | 跑核心业务流程 | 跨类调用是否正常 |
| 副作用时序 | 对比搬移前后日志序列 | 副作用次数和顺序是否一致 |
| 边界场景 | 空值、极值、异常路径 | 搬移是否改变了边界处理 |
| 性能敏感路径 | 对比关键接口耗时 | 是否因多一层调用引入明显开销 |
这个清单不是走形式,每一项都对应过真实事故。尤其是副作用时序和边界场景这两项,单测覆盖不到的地方,往往就是问题藏身的地方。
5. 搬移特性的验证体系:怎么证明“行为没变”
前面反复提到验证,这一节专门讲验证体系。搬移特性如果没有可靠的验证手段,就等于闭着眼睛改代码。验证体系不是“跑一下测试”这么简单,它需要覆盖到搬移可能影响的所有层面。
5.1 特征测试:给老代码上保险
很多遗留代码没有单测,或者单测覆盖极低。这种情况下搬移特性,第一步是补特征测试。特征测试不关心“代码应该怎样”,只关心“代码现在怎样”——把现有行为用测试固化下来,作为搬移后的对比基准。
写特征测试的技巧是:从外部可观察行为入手,不要测内部实现。比如测一个计算方法,就给定输入、断言输出,不要去 mock 内部依赖、断言内部调用。内部实现正是你要改的东西,测它等于给自己挖坑。
特征测试不需要覆盖所有分支,优先覆盖核心路径和高频边界。时间有限的情况下,把最重要的十条路径固化成测试,比追求覆盖率数字更有价值。
5.2 搬移前后的对比验证手法
有了基准测试,搬移前后的对比就有了依据。但光跑测试还不够,我还会做两类对比。
第一类是输出对比:对同一组输入,搬移前后各跑一遍,对比输出是否完全一致。这个方法适合纯计算逻辑,简单直接。
第二类是轨迹对比:在关键节点打日志,搬移前后对比日志序列。这个方法适合有副作用、有状态变化的逻辑,能发现时序和次数层面的差异。
两类对比的共同点是用机器代替人眼。人眼审查代码找差异,在几十行范围内还行,上百行就不可靠了。让程序对比输出和轨迹,人只需要看对比结果,效率和可靠性都高得多。
5.3 灰度发布作为最后一道防线
即使测试和对比都过了,搬移特性上线我仍然建议走灰度。原因很简单:测试环境再全,也模拟不出真实流量的复杂性。灰度发布让新代码先承接一小部分流量,观察一段时间没问题再全量。
灰度期间重点看几个指标:错误率有没有上升、关键接口耗时有没有异常、业务指标有没有波动。这几个指标平稳,基本可以认为搬移是安全的。如果指标异常,快速回滚,回到搬移前的状态,损失可控。
灰度不是搬移特性独有的,但搬移特性尤其需要它,因为搬移改的是结构,结构问题往往在特定流量组合下才暴露。全量上线再发现问题,回滚成本和影响面都比灰度大得多。
6. 那些年我在搬移特性上踩过的坑
理论和步骤讲完了,这一节讲点更“血泪”的东西。下面这些坑,有的是我自己踩的,有的是看别人踩的,共同点是:看文档看不出来,只有实际动手才会遇到。
6.1 坑一:搬方法时顺手“优化”了逻辑
这是最高频的坑,没有之一。搬一个方法,看着里面的代码觉得“这段写得真烂”,顺手就改了。改完测试也过了,上线后某个边界场景炸了,回头一查,就是那处“顺手优化”引入的。
这个坑的本质是混淆了重构和功能修改。重构的定义是不改变行为,任何改变行为的改动都不是重构。搬移特性时,你的唯一目标是“搬过去且行为不变”,优化逻辑是另一件事,应该单独做、单独验证。
我的规矩是:搬移的提交里,除了位置变动和必要的引用调整,不允许有任何逻辑改动。看到想优化的地方,记下来,搬移完成并验证通过后,再单独开一个提交去优化。这样出问题时,你能快速定位是搬移引入的还是优化引入的。
6.2 坑二:忽略了可见性变化带来的连锁反应
字段或方法搬移后,可见性往往需要调整。原来包内可见的字段搬到另一个包,就得改成 public 或者加访问方法。这个调整看似小事,实则可能引发连锁反应。
最典型的问题是封装被破坏:为了让搬移后的代码能访问,把本该 private 的东西改成了 public,结果外部代码开始依赖这个公开成员,封装名存实亡。更隐蔽的问题是循环依赖:A 类的方法搬到 B 类后,B 类需要访问 A 类的某个成员,而 A 类又依赖 B 类,循环依赖就形成了。
避免这类问题的办法是:搬移前就想清楚可见性方案,优先考虑通过参数传递、通过已有引用访问,实在不行再调整可见性。调整可见性时,评估一下会不会引入循环依赖,会不会让封装进一步恶化。
6.3 坑三:搬移后忘了清理“僵尸代码”
搬移完成后,旧位置的代码如果没删干净,就会变成“僵尸代码”——没人调用,但还在编译,还在被静态检查扫描,还在误导后来读代码的人。更糟的是,有时候僵尸代码里还留着旧的逻辑,后来人以为它还在生效,基于它做判断,结果判断错了。
清理僵尸代码的关键是确认零引用。删之前用 IDE 的“查找引用”功能扫一遍,确认没有任何地方还在用。如果项目里有静态分析工具,跑一遍确认没有未使用警告。删完之后再跑一遍全量测试,确保没有反射或动态调用在偷偷使用它。
提示:有些框架通过反射或配置文件引用类和方法,IDE 的查找引用扫不到。删之前搜一下配置文件、注解、字符串常量里有没有相关名字,这一步能挡掉大部分反射相关的误删。
6.4 坑四:团队协作时搬移和别人的改动撞车
搬移特性往往涉及多个文件,改动面大,和团队其他人的改动撞车的概率也高。我遇到过最难受的情况是:我搬一个方法搬了一半,同事在旧位置改了逻辑,合并时冲突一大堆,还得判断哪些改动该跟着搬、哪些该保留。
减少撞车的办法有两个。一是搬移前同步:在团队里说一声“我要搬某某特性,涉及这几个文件,大家这段时间尽量别动”,提前打招呼比事后解决冲突省事得多。二是小步提交:把搬移拆成多个小提交,每个提交改动面小,冲突也小,解决起来快。
如果撞车已经发生,处理原则是:先理解对方的改动意图,再决定怎么合并。不要机械地选“接受我的”或“接受对方的”,要搞清楚对方改的逻辑在新结构下应该放在哪里。这一步偷懒,后面就是线上问题。
6.5 坑五:过度搬移,把简单问题复杂化
最后一个坑比较反直觉:不是所有特性都值得搬。有些代码虽然位置不完美,但稳定、没人动、也没引发实际问题,这种代码搬它干嘛?搬移是有成本的,测试、验证、灰度、协作,每一项都要投入。如果搬移带来的收益(可维护性提升、bug 减少)抵不上成本,那就不该搬。
我判断要不要搬的标准是:这个特性是否正在持续制造麻烦。如果它经常导致改一处漏一处、经常有人问“这代码为什么在这儿”、经常在排查问题时成为障碍,那值得搬。如果它安安静静待着,几年没人碰,那让它待着也挺好。重构是为了解决问题,不是为了追求代码的“理想形态”。
7. 搬移特性的决策框架与实操建议
讲了这么多具体手法和坑,最后收拢成一个可操作的决策框架。这个框架不是让你机械套用,而是帮你在动手前把该想的问题想清楚。
7.1 一张表判断“搬还是不搬”
| 信号 | 倾向搬移 | 倾向不搬 |
|---|---|---|
| 数据访问 | 字段被外部类高频读写 | 字段基本只在声明类内使用 |
| 职责归属 | 方法语义明显属于另一个类 | 方法职责与当前类一致 |
| 变化方向 | 总和其他类的方法一起改 | 独立变化,很少被改 |
| 问题频率 | 经常引发 bug 或理解障碍 | 稳定运行,无人抱怨 |
| 搬移成本 | 引用点少,依赖清晰 | 引用点多,依赖复杂 |
| 验证条件 | 有测试或可补特征测试 | 完全无法验证行为 |
这张表的使用方法是:逐行对照,看自己面对的情况落在哪一侧。如果大部分落在“倾向搬移”且验证条件具备,那就搬;如果搬移成本高但验证条件不具备,那就先补验证再搬,或者暂时不搬。
7.2 搬移操作的通用节奏
不管搬什么,节奏上我建议遵循“小步、验证、提交”的循环。每一步只做一件事,做完立即验证,验证通过立即提交。不要攒一堆改动一起提交,那样出问题时定位成本高,回滚也麻烦。
具体到一次搬移,节奏大概是:先加新代码(不改旧代码),验证系统仍正常;再切一个调用点,验证;再切下一个,验证;全部切完,删旧代码,验证。这个节奏看起来慢,实际上因为每步都小、每步都可验证,总体效率反而高,返工少。
7.3 给不同经验读者的上手建议
如果你是刚接触重构的开发者,建议从单个字段搬移开始练手。找一个明确的“字段待错地方”的场景,按第 2 节的步骤走一遍,重点体会“先加后删、中间同步”的手法,以及验证的重要性。单个字段搬移影响面小,出问题容易发现,是很好的练习素材。
如果你有一定经验,可以挑战方法搬移,重点练参数列表处理和副作用验证。方法搬移的复杂度上一个台阶,但也是最能体现重构功力的地方。
如果你在带团队,建议把整体搬移的分阶段策略和验证清单沉淀成团队规范。整体搬移最容易失控,有规范约束能大幅降低事故率。同时,把“搬移提交不允许夹带逻辑改动”这条写进代码评审清单,能挡掉最高频的坑。
7.4 关于工具的一点实话
IDE 的重构功能(比如“搬移方法”“搬移字段”)能自动处理引用更新,确实省事。但工具不是万能的,它处理不了语义层面的问题:工具不知道这个方法搬过去职责对不对,不知道副作用时序会不会变,不知道可见性调整会不会引入循环依赖。工具能帮你改引用,但判断和验证还得靠人。
我的用法是:用工具做机械的引用更新,用人工做语义判断和验证。工具改完,我一定自己过一遍 diff,确认没有意外改动,再跑验证。完全信任工具、不看 diff,是另一个隐形的坑。
搬移特性这件事,说到底是在跟“变化”打交道。代码会变、需求会变、团队会变,把特性放到它该在的地方,是为了让未来的变化成本更低。这个过程没有一劳永逸的方案,只有一次次小步验证、一次次踩坑总结。我自己到现在搬一个稍大的特性,仍然会先画依赖图、先补测试、先想好回滚方案,这些笨功夫省不得。真正省事的,从来不是跳过步骤,而是把每一步做扎实,让返工和事故少发生。