首页 > 其他分享 >再谈如何优雅修改代码

再谈如何优雅修改代码

时间:2024-10-07 22:49:06浏览次数:7  
标签:doSomething data 代码 List 再谈 优雅 key 方法 public

书接上回( https://www.cnblogs.com/OceanEyes/p/18450799)再做下扩展

上文谈到:“基于抽象接口编程确实是最佳实践:把易于变动的功能点通过定义抽象接口的形式暴露出来,不同的实现做到隔离和扩展,这体现了开闭原则”
public class Foo {     private Bar bar ;     @Inject     public Foo(Bar bar) {         this.bar = bar;     }     public String doSomething(int key) {         //Bar#getResult 体验了代码的复杂性,通过注入不同的 Bar 实现对象,做到功能点的隔离和扩展         return bar.getResult(key);     } }

但在真实项目里,往往是多人协作一起开发,一些历史原因导致某些代码片段的实现往往“千奇百怪”,既不能很好的单侧覆盖,同时也充斥着违反了开闭原则的“代码坏味道”;

而此时的你,作为“被选中的人”,需要对其功能迭代;

或许经过你的评估后,可以去大刀阔斧的架构演进,这是点赞的;

但有时也要全局 ROI 去评估大刀阔斧重构收益是否足够大,有时候我们只能妥协(trade-off)。即:如何在紧张的交付周期内做到比较好的重构,不让代码继续腐化;

所以这次继续介绍两种修改代码的艺术:方法新增方法覆盖

策略 1:方法新增

通过新增方法来隔离旧逻辑,即:在旧方法里横切“缝隙”,注入新的业务逻辑被调用;

拿之前的 Case 举例,一个历史老方法,需要对返回的数据集合过滤掉空对象

public class Foo {   private Bar bar;   public Foo() {     bar = new Bar();   }   public List<Data> doSomething(int key) {     //依赖三方服务,RPC 调用结果集     List<Data> result = bar.getResult(key);     //过滤掉空对象     return result.stream().filter(Objects::nonNull).collect(Collectors.toList());   } }

此处逻辑很简单,使用了Java Lambda 表达式做了过滤,但这样的写法无疑雪上加霜:确实原先方法已经很 Low了,也无法单侧。本次只是在最后加了一段简单的逻辑。已经驾轻就熟了,可能不少人都会这样搞;

但作为好的程序员,眼前现状确实我们只能妥协,但后续的每一行代码,需要做到保质保量,努力做到不影响原有业务逻辑下做到可测试;

“方法新增”:通过新增方法 getDataIfNotNull 来隔离旧逻辑:

public List<Data> doSomething(int key) {   //依赖三方服务,RPC 调用结果集   List<Data> result = bar.getResult(key);   return getDataIfNotNull(result); }

如下 getDataIfNotNull 作为新增方法,很容易对其进行独立测试,同时原有的方法 doSomething 也没有继续腐化

public List<Data> getDataIfNotNull(List<Data> result) {   return result.stream().filter(Objects::nonNull).collect(Collectors.toList()); }

可以看到优点很明显:新老代码清晰隔离;当然为了更加职责分明,使用新增类隔离会更好;

策略 2:方法覆盖

将待修改的方法重命名,并创建一个新方法和原方法名和签名一致,同时在新方法中调用重命名后的原方法;

假设有新需求:针对 doSomething 方法做一个消息通知操作,那么“方法覆盖”即:

将原方法 doSomething 重命名为 doSomethingAndFilterData,再创建一个与原方法同名的新方法 doSomething,最后在新方法中调用更名后的原方法:

//将原方法 doSomething 重命名为 doSomethingAndFilterData public List<Data> doSomethingAndFilterData(int key) { //依赖三方服务,RPC 调用结果集   List<Data> result = bar.getResult(key);   return getDataIfNotNull(result); } //创建一个与原方法同名的新方法 doSomething public List<Data> doSomething(int key) {   //调用旧方法   List<Data> data = this.doSomethingAndFilterData(key);   //调用新方法   doNotifyMsg(data);   return data; } //新的扩展方法符合隔离扩展,不影响旧方法,也支持单侧覆盖 public void doNotifyMsg(List<Data> data){   // }
方法覆盖的另一种写法:通常是再定义一个新的方法,然后在新的方法依次调用新老业务逻辑;

一般在架构演进的时候,用于切流新老逻辑;例如:基于客户端版本,大于 3.10.x 的客户端切流使用新的逻辑——我们创建一个新的方法调用新旧两个方法。

//老的历史代码,不做改造 public List<Data> doSomething(int key) {   //依赖三方服务,RPC 调用结果集   List<Data> result = bar.getResult(key);   List<Data> data = getDataIfNotNull(result);   return data; } //新创建一个方法,聚合调用新老逻辑 public List<Data> doSomethingWithNotifyMsg(int key) {   List<Data> data = this.doSomething(key);   //调用新方法   doNotifyMsg(data);   return data; } //新的扩展方法符合隔离扩展,不影响旧方法,也支持单侧覆盖 public void doNotifyMsg(List<Data> data){   // }

这样的好处是显然易见的,不针对旧方法做修改,在更高维度的“上层”切流:保证新功能正常迭代演进,老功能维持不变

boolean enableFunc=getClientVersion()>DEFAULT_CLIENT_VERSION; if (enableFunc){   return doSomethingWithNotifyMsg(); }else{   return doSomething(); }

可以看到“方法覆盖”不管用何总方式实现,它不会在当前旧方法里增加逻辑,而是通过使用新方法作为入口,这样避免新老逻辑耦合在一起;

“方法覆盖”可以再进阶一步,使用独立的类来隔离,也就是装饰者模式。通常情况下原有的类已经非常复杂了,已经不想在它上做功能迭代了,考虑使用装饰者来解耦:

classDecoratedFooextendsFoo{

private Foo foo;   public DecoratedFoo(Foo foo){ }   @Override public List<Data> doSomething(int key) {   List<Data> data = super.doSomething(key);   notifyMsg();   return data; } private void notifyMsg(){} }

标签:doSomething,data,代码,List,再谈,优雅,key,方法,public
From: https://www.cnblogs.com/OceanEyes/p/18450797

相关文章

  • 代码随想录算法训练营day8|344.反转字符串 ● 541. 反转字符串II ● 卡码网:54.替换数
    学习资料:https://programmercarl.com/0344.反转字符串.html#算法公开课在python中字符串不可变,所以要增加空间lst=list(str)344.反转字符串(reverse库函数的基本代码)点击查看代码classSolution(object):defreverseString(self,s):""":types:List......
  • 代码随想录算法训练营 | 动态规划,509. 斐波那契数,70. 爬楼梯, 746. 使用最小花费爬楼梯
    动态规划:1.动态规划中每一个状态一定是由上一个状态推导出来的2.确定dp数组(dptable)以及下标的含义,确定递推公式dp,数组如何初始化,确定遍历顺序,举例推导dp数组;3.Debug:dp数组打印509.斐波那契数题目链接:509.斐波那契数文档讲解︰代码随想录(programmercarl.com)视频讲解︰斐波那......
  • 代码源Csp-S模拟赛Day10-11赛后总结
    代码源Csp-S模拟赛Day10-11赛后总结Day10赛时T1赛时感觉很难维护时间以及多个精灵同时到达同一格子的情况,后来想了一种做法:对于每个格子最早被遍历到的时间我们的处理是容易的,你考虑我们可以对每行每列都建一棵线段树(数据范围保证了\(rc\leq5e5\),所以总空间大致是一个\(4rc......
  • 5、在设备树中指定中断——在代码中获取中断
    1设备树里中断节点的语法参考文档:内核Documentation\devicetree\bindings\interrupt-controller\interrupts.txt设备树里的中断控制器中断的硬件框图如下: 在硬件上,“中断控制器”只有GIC这一个,但是我们在软件上也可以把上图中的“GPIO”称为“中断控制器”。很多芯片有多......
  • 人工智能前沿研究热点与发展趋势原理与代码实战案例讲解
    人工智能前沿研究热点与发展趋势原理与代码实战案例讲解作者:禅与计算机程序设计艺术/ZenandtheArtofComputerProgramming1.背景介绍1.1问题的由来人工智能(ArtificialIntelligence,AI)作为计算机科学的一个分支,已经取得了长足的进步。从早期的专家系统到现在......
  • 14-恶意代码防范技术原理
    14.1概述1)定义与分类(MaliciousCode)它是一种违背目标系统安全策略的程序代码,会造成目标系统信息泄露、资源滥用,破坏系统的完整性及可用性。它能够经过存储介质或网络进行传播,从一台计算机系统传到另外一台计算机系统,未经授权认证访问或破坏计算机系统。常许多人认为“病毒”......
  • clang-format的代码格式化
    1.VSCodesettings.json{"C_Cpp.default.intelliSenseMode":"windows-msvc-x64",//"C_Cpp.clang_format_fallbackStyle":"Google","C_Cpp.clang_format_path":"D:/software/clang+llvm-18.1.8-x86_64-p......
  • 音频采样率转换的研究与代码实现
    音频采样率转换本文原始版本发布于https://www.52pojie.cn/thread-1959816-1-1.html,此处进行了适当的精简,同时更新了一下代码(最新代码以GitHub仓库为准)。前言两年前,我研究了WASAPI播放音频的方法,详见https://www.cnblogs.com/PeaZomboss/p/17035785.html,挖了个坑,就是重采......
  • 使用 Dune 编译和调试 OCaml 代码
    下载Duneopaminstalldune创建项目duneinitproject<project-name>如果创建成功,有Success:initializedprojectcomponentnamed<project-name>得到如下的一个文件结构project_name/├──dune-project├──test│├──dune│└──test_project_......
  • 代码随想录算法训练营day7|● 454.四数相加II ● 383. 赎金信 ● 15. 三数之和 ●
    学习资料:https://programmercarl.com/0015.三数之和.html#其他语言版本学习记录:454.四数相加(hash_dict,前两个数一组遍历a+b,后两个数一组遍历找0-(a+b))点击查看代码classSolution:deffourSumCount(self,nums1:List[int],nums2:List[int],nums3:List[int],nums4:......