找回密码
 立即注册
首页 业界区 业界 我在大厂做 CR——再谈如何优雅修改代码 ...

我在大厂做 CR——再谈如何优雅修改代码

唐嘉懿 2025-6-6 13:51:52
书接上回为什么需要依赖注入再做下扩展
上文谈到:“基于抽象接口编程确实是最佳实践:把易于变动的功能点通过定义抽象接口的形式暴露出来,不同的实现做到隔离和扩展,这体现了开闭原则”
  1. public class Foo {
  2.   private Bar bar ;
  3.   @Inject
  4.   public Foo(Bar bar) {
  5.     this.bar = bar;
  6.   }
  7.   public String doSomething(int key) {
  8.     //Bar#getResult 体验了代码的复杂性,通过注入不同的 Bar 实现对象,做到功能点的隔离和扩展
  9.     return bar.getResult(key);
  10.   }
  11. }
复制代码
但在真实项目里,往往是多人协作一起开发,一些历史原因导致某些代码片段的实现往往“千奇百怪”,既不能很好的单侧覆盖,同时也充斥着违反了开闭原则的“代码坏味道”;
而此时的你,作为“被选中的人”,需要对其功能迭代;
或许经过你的评估后,可以去大刀阔斧的架构演进,这是点赞的;
但有时也要全局 ROI 去评估大刀阔斧重构收益是否足够大,有时候我们只能妥协(trade-off)。即:如何在紧张的交付周期内做到比较好的重构,不让代码继续腐化;
所以这次继续介绍两种修改代码的艺术:方法新增和方法覆盖
策略 1:方法新增

通过新增方法来隔离旧逻辑,即:在旧方法里横切“缝隙”,注入新的业务逻辑被调用;
拿之前的 Case 举例,一个历史老方法,需要对返回的数据集合过滤掉空对象:
  1. public class Foo {
  2.    private Bar bar;
  3.    public Foo() {
  4.        bar = new Bar();
  5.    }
  6.    public List<Data> doSomething(int key) {
  7.        //依赖三方服务,RPC 调用结果集
  8.        List<Data> result = bar.getResult(key);
  9.        //过滤掉空对象
  10.        return result.stream().filter(Objects::nonNull).collect(Collectors.toList());
  11.    }
  12. }
复制代码
此处逻辑很简单,使用了Java Lambda 表达式做了过滤,但这样的写法无疑雪上加霜:确实原先方法已经很 Low 了,也无法单侧。本次只是在最后加了一段简单的逻辑。已经驾轻就熟了,可能不少人都会这样搞;
但作为好的程序员,眼前现状确实我们只能妥协,但后续的每一行代码,需要做到保质保量,努力做到不影响原有业务逻辑下做到可测试;
“方法新增”:通过新增方法 getDataIfNotNull 来隔离旧逻辑:
  1. public List<Data> doSomething(int key) {
  2.        //依赖三方服务,RPC 调用结果集
  3.        List<Data> result = bar.getResult(key);
  4.        return getDataIfNotNull(result);
  5. }
复制代码
如下 getDataIfNotNull 作为新增方法,很容易对其进行独立测试,同时原有的方法 doSomething 也没有继续腐化
  1. public List<Data> getDataIfNotNull(List<Data> result) {
  2.    return result.stream().filter(Objects::nonNull).collect(Collectors.toList());
  3. }
复制代码
可以看到优点很明显:新老代码清晰隔离;当然为了更加职责分明,使用新增类隔离会更好;
策略 2:方法覆盖

将待修改的方法重命名,并创建一个新方法和原方法名和签名一致,同时在新方法中调用重命名后的原方法;
假设有新需求:针对 doSomething 方法做一个消息通知操作,那么“方法覆盖”即:将原方法 doSomething 重命名为 doSomethingAndFilterData,再创建一个与原方法同名的新方法 doSomething,最后在新方法中调用更名后的原方法:
  1. //将原方法 doSomething 重命名为 doSomethingAndFilterData
  2. public List<Data> doSomethingAndFilterData(int key) {
  3.        //依赖三方服务,RPC 调用结果集
  4.        List<Data> result = bar.getResult(key);
  5.        return getDataIfNotNull(result);
  6. }
  7. //创建一个与原方法同名的新方法 doSomething
  8. public List<Data> doSomething(int key) {
  9.        //调用旧方法
  10.        List<Data> data = this.doSomethingAndFilterData(key);
  11.        //调用新方法
  12.        doNotifyMsg(data);
  13.        return data;
  14. }
  15. //新的扩展方法符合隔离扩展,不影响旧方法,也支持单侧覆盖
  16. public void doNotifyMsg(List<Data> data){
  17.       //
  18. }
复制代码
方法覆盖的另一种写法:通常是再定义一个新的方法,然后在新的方法依次调用新老业务逻辑;
一般在架构演进的时候,用于切流新老逻辑;例如:基于客户端版本,大于 3.10.x 的客户端切流使用新的逻辑——我们创建一个新的方法调用新旧两个方法。
  1. //老的历史代码,不做改造
  2. public List<Data> doSomething(int key) {
  3.        //依赖三方服务,RPC 调用结果集
  4.        List<Data> result = bar.getResult(key);
  5.        List<Data> data = getDataIfNotNull(result);
  6.        return data;
  7. }
  8. //新创建一个方法,聚合调用新老逻辑
  9. public List<Data> doSomethingWithNotifyMsg(int key) {
  10.        List<Data> data = this.doSomething(key);
  11.        //调用新方法
  12.        doNotifyMsg(data);
  13.        return data;
  14. }
  15. //新的扩展方法符合隔离扩展,不影响旧方法,也支持单侧覆盖
  16. public void doNotifyMsg(List<Data> data){
  17.        //
  18. }
复制代码
这样的好处是显然易见的,不针对旧方法做修改,同时在更高维度的“上层”切流:保证新功能正常迭代演进,老功能维持不变
  1. boolean enableFunc=getClientVersion()>DEFAULT_CLIENT_VERSION;
  2. if (enableFunc){
  3.        return doSomethingWithNotifyMsg();
  4.    } else {
  5.        return doSomething();
  6. }
复制代码
可以看到“方法覆盖”不管用何总方式实现,它不会在当前旧方法里增加逻辑,而是通过使用新方法作为入口,这样避免新老逻辑耦合在一起;
“方法覆盖”可以再进阶一步,使用独立的类来隔离,也就是装饰者模式。通常情况下原有的类已经非常复杂了,已经不想在它上做功能迭代了,考虑使用装饰者来解耦:
  1. class DecoratedFoo extends Foo{
  2.    private Foo foo;
  3.    public DecoratedFoo(Foo foo){
  4.    }
  5.    @Override
  6.    public List<Data> doSomething(int key) {
  7.        List<Data> data = super.doSomething(key);
  8.        notifyMsg();
  9.        return data;
  10.    }
  11.    private void notifyMsg(){
  12.    }
  13. }
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册