java 代码重构

article/2025/8/14 20:00:41

几天前的一次上线,脑残手抖不小心写了bug,虽然组里的老大没有说什么,但心里面很是难过。同事说我之所以写虫子是因为我讨厌if/else,这个习惯不好。的确,if/else可以帮助我们很方便的写出流程控制代码,简洁明了,这个条件做什么,那个条件做什么,说得很清楚。说真的,我从来不反对if/else,从经验上看,越复杂的业务场景下,代码写的越简单单一,通常越不容易出错。以结果为导向的现代项目管理方式,这是一种很有效实践经验。

同事说的没错,我的确很讨厌if/else。这个习惯很大程度是受Thoughtworks一位咨询师朋友影响,他经常在我耳边唠叨,写代码要干净,要简洁,要灵活多变,不要固守城规,不要动不动就if/else,switch/case。初入it领域,我一直把这句话奉为经典。在以后的学习工作中也时刻提醒自己要让自己的代码尽可能的看起来简洁,不失灵活。不喜欢if/else并不意味着拒绝它,该使用的时候必要使用,比如函数接口入参check,处理异常分支逻辑流程等。通常能不用分支语句,我尽量不会使用,因为我觉得if/else很丑,每每看到if/else代码,总会以挑剔的眼光看待它,想想能不能重构的更好。大多数时候,关于什么好的代码,大家的意见往往分歧很大,每个人都有各自的想法,审查你代码的人可能会选择另一种实现方式,这并不能说明谁对谁错。

OO设计遵循SOLID(单一功能、开闭原则、里氏替换、接口隔离以及依赖反转)原则,使用这个原则去审视if/else,可能会发现很多问题,比如不符合单一原则,它本身就像一团浆糊,融合了各种作料,黏糊糊的很不干净;比如不符合开闭原则,每新增一种场景,就需要修改源文件增加一条分支语句,业务逻辑复杂些若有1000种场景就得有1000个分支流,这种情况下代码不仅仅恶心问题了,效率上也存在很大问题。由此可见,if/else虽然简单方便,但不恰当的使用会给编码代码带来非常痛苦的体验。针对这种恶心的if/else分支,我们当然首先想到的去重构它--在不改变代码外部功能特征的前提下对代码内部逻辑进行调整和优化,但,如何做呢?前段时间在项目中正好遇到一个恶心的if/else例子,想在这篇博客里和大家分享一下去除if/else重构的历程。

java代码重构的经验总结分享

if/else的恶瘤

有句话说的好--好文章是改出来,同样,好的代码也肯定是重构出来的,因为没有哪个软件工程师能够拍着胸脯保证在项目之初代码设计这块,就考虑到了所有需求变化可能性的扩展。随着项目的不断成长,业务逻辑变的越来越复杂,代码也开始变的越来越多,原有的设计可能不再满足需求,那么此时必须要重构。就系统整体架构而言,重构可能需要很大的改动,可能在架构流程上需要评审;就功能内代码层次而言,这种重构在我们编码过程中随时可以进行,类似于if/else,swicth/case这种代码的重构也属于这种类型。今天我们要重构的if/else源码如下所示,针对不同的status code,CountRecoder对象会执行不同的set方法,为不同内部属性赋值。

public CountRecoder getCountRecoder(List countEntries) {CountRecoder countRecoder = new CountRecoder();for (CountEntry countEntry : countEntries) {if (1 == countEntry.getCode()) {countRecoder.setCountOfFirstStage(countEntry.getCount());} else if (2 == countEntry.getCode()) {countRecoder.setCountOfSecondStage(countEntry.getCount());} else if (3 == countEntry.getCode()) {countRecoder.setCountOfThirdtage(countEntry.getCount());} else if (4 == countEntry.getCode()) {countRecoder.setCountOfForthtage(countEntry.getCount());} else if (5 == countEntry.getCode()) {countRecoder.setCountOfFirthStage(countEntry.getCount());} else if (6 == countEntry.getCode()) {countRecoder.setCountOfSixthStage(countEntry.getCount());}}return countRecoder;
}

CountRecoder对象是一个简单的Java Bean,用于保存一天之中六种状态分别对应的数据条目,提供了get和set方法。CountEntry是对应数据库中每种状态的数据条目记录,包含状态code和以及count两个字段, 我们可以使用mybatis实现数据库记录和java对象之间的转换。上面getCountRecoder的方法实现了将list转换为CountRecoder的功能。

看到这段代码,想必已经有很多人要呵呵了,像一坨啥啥啥,长得这么丑,真不知道它"爸妈"怎么想的,怎么敢"生"出来。啥都不说了,直接回炉重构吧。重构是门艺术,Martin flow曾写过一本书《重构改变代码之道》,里面详细的记录了重构的方法论,感兴趣的朋友可以阅读一下。说到重构,通常我们在重构中会遇到一个问题,那就是如何能够保证重构的代码不改变原有的外部功能特征 ?经过TDD训练的朋友应该知道答案,那就是单元测试,重构之前要写单元测试,准确的来说应该是补单元测试,毕竟TDD的核心理念是测试驱动开发。对于今天博客中分享的例子,因为代码逻辑比较简单,所以偷了懒,省却了单元测试的历程。

重构初体验--反射

要重构上面的代码,对设计模式精通的人可以立马可以看出来这是使用策略模式/状态模式的绝佳场景,将策略模式稍微变换,工厂模式应该也是ok的,当然也有些人会选择使用反射。对于这些方法,这里不一一列出,主要想讲一下使用反射和工厂模式如何解决消除if/else问题,那先说反射吧,代码如下所示:

private static Map methodsMap = new HashMap<>();static {methodsMap.put(1, "setCountOfFirstStage");methodsMap.put(2, "setCountOfSecondStage");methodsMap.put(3, "setCountOfThirdtage");methodsMap.put(4, "setCountOfForthtage");methodsMap.put(5, "setCountOfFirthStage");methodsMap.put(6, "setCountOfSixthStage");
}public CountRecoder getCountRecoderByReflect(List countEntries) {CountRecoder countRecoder = new CountRecoder();countEntries.stream().forEach(countEntry -> fillCount(countRecoder, countEntry));return countRecoder;
}private void fillCount(CountRecoder shippingOrderCountDto, CountEntry countEntry) {String name = methodsMap.get(countEntry.getCode());try {Method declaredMethod = CountRecoder.class.getMethod(name, Integer.class);declaredMethod.invoke(shippingOrderCountDto, countEntry.getCount());} catch (Exception e) {System.out.println(e);}
}

重构初体验--所谓模式

使用反射去掉if/else的原理很简单,使用HashMap建立状态码和需要调用的方法的方法名之间的映射关系,对于每个CountEntry,首先取出状态码,然后根据状态码获得相应的要调用方法的方法名,然后使用java的反射机制就可以实现对应方法的调用了。本例中使用反射的确可以帮助我们完美的去掉if/else的身影,但是,众所周知,反射效率很低,在高并发的条件下,反射绝对不是一个良好的选择。除去反射这种方法,能想到的就剩下使用策略模式或者与其类似的状态模式,以及工厂模式了,我们以工厂模式为例,经典的架构UML架构图通常由三个组成要素:

  1. 抽象产品角色:通常是一个抽象类或者接口,里面定义了抽象方法
  2. 具体产品角色:具体产品的实现类,继承或是实现抽象策略类,通常由一个或多个组成类组成。
  3. 工厂角色:持有抽象产品类的引用,负责动态运行时产品的选择和构建

策略模式的架构图和工厂模式非常类似,不过在策略模式里执行的对象不叫产品,叫策略。在本例中,这里的产品是虚拟产品,它是服务类性质的接口或者实现。Ok,按照工厂模式的思路重构我们的代码,我们首先定义一个抽象产品接口FillCountService,里面定义产品的行为方法fillCount,代码如下所示:

public interface FillCountService {void fillCount(CountRecoder countRecoder, int count);
}

接着我们需要分别实现这六种服务类型的产品,在每种产品中封装不同的服务算法,具体的代码如下所示:

class FirstStageService implements FillCountService {@Overridepublic void fillCount(CountRecoder countRecoder, int count) {countRecoder.setCountOfFirstStage(count);}
}class SecondStageService implements FillCountService {@Overridepublic void fillCount(CountRecoder countRecoder, int count) {countRecoder.setCountOfSecondStage(count);}
}class ThirdStageService implements FillCountService {@Overridepublic void fillCount(CountRecoder countRecoder, int count) {countRecoder.setCountOfThirdtage(count);}
}class ForthStageService implements FillCountService {@Overridepublic void fillCount(CountRecoder countRecoder, int count) {countRecoder.setCountOfForthtage(count);}
}class FirthStageService implements FillCountService {@Overridepublic void fillCount(CountRecoder countRecoder, int count) {countRecoder.setCountOfFirthStage(count);}
}class SixthStageService implements FillCountService {@Overridepublic void fillCount(CountRecoder countRecoder, int count) {countRecoder.setCountOfSixthStage(count);}
}

紧接着,我们需要是实现工厂角色,在工厂内需要实现产品的动态选择算法,使用HashMap维护状态code和具体产品的对象之间的映射关系,
就可以非常容易的实现这一点,具体代码如下所示:

public class FillCountServieFactory {private static Map fillCountServiceMap = new HashMap<>();static {fillCountServiceMap.put(1, new FirstStageService());fillCountServiceMap.put(2, new SecondStageService());fillCountServiceMap.put(3, new ThirdStageService());fillCountServiceMap.put(4, new ForthStageService());fillCountServiceMap.put(5, new FirthStageService());fillCountServiceMap.put(6, new SixthStageService());}public static FillCountService getFillCountStrategy(int statusCode) {return fillCountServiceMap.get(statusCode);}
}

客户端在具体使用的时候就变的很简单,那getCountRecoder方法就可以用下面的代码实现:

public CountRecoder getCountRecoder(List countEntries) {CountRecoder countRecoder = new CountRecoder();countEntries.stream().forEach(countEntry -> FillCountServieFactory.getFillCountStrategy(countEntry.getCode()).fillCount(countRecoder, countEntry.getCount()));return countRecoder;
}

重构初体验--Java8对模式设计的精简

和反射一样使用设计模式也同样完美的去除了if/else,但是不得不引入大量的具体服务实现类,同时程序中出现大量的模板代码,使得我们程序看起来很不干净,幸好Java 8之后引入了Functional Interface,我们可以使用lambda表达式来去除这些模板代码。将一个接口变为Functional interface,可以通过在接口上添加FunctionalInterface注解实现,代码如下所示:

@FunctionalInterface
public interface FillCountService {void fillCount(CountRecoder countRecoder, int count);
}

那么具体的服务实现类就可以使用一个简单的lambda表达式代替,原先的FirstStageService类对象就可以使用下面的表达式代替:

(countRecoder, count) -> countRecoder.setCountOfFirstStage(count)

那么工厂类中的代码就可以变为:

public class FillCountServieFactory {private static Map<Integer, FillCountService> fillCountServiceMap = new HashMap<>();static {fillCountServiceMap.put(1, (countRecoder, count) -> countRecoder.setCountOfFirstStage(count));fillCountServiceMap.put(2, (countRecoder, count) -> countRecoder.setCountOfSecondStage(count));fillCountServiceMap.put(3, (countRecoder, count) -> countRecoder.setCountOfThirdtage(count));fillCountServiceMap.put(4, (countRecoder, count) -> countRecoder.setCountOfForthtage(count));fillCountServiceMap.put(5, (countRecoder, count) -> countRecoder.setCountOfFirthStage(count));fillCountServiceMap.put(6, (countRecoder, count) -> countRecoder.setCountOfSixthStage(count));}public static FillCountService getFillCountStrategy(int statusCode) {return fillCountServiceMap.get(statusCode);}
}

这样我们的代码就重构完毕了,当然了还是有些不完美,程序中的魔法数字不利于阅读理解,可以使用易读的常量标识它们,在这里就不做过多说明了。

总结

Craig Larman曾经说过软件开发最重要的设计工具不是什么技术,而是一颗在设计原则方面训练有素的头脑。重构的最终结果不一定会让代码变少,相反还有可能增加程序的复杂度和抽象性,就本例中的if/else而言,确实如此。我非常赞同我的一位朋友说的话,做技术要有追求,没错if/else可以在代码中工作的挺好,也可以很容易的被接替者所理解,但是我们可以有更好的选择,因为简单的代码也可以变得很精彩。多勤多思,也许有一天真的就可以达到Craig所说的在设计原则方面拥有训练有素的头脑,谁说不是这样呢?加油吧。


http://chatgpt.dhexx.cn/article/gvrC2e6Y.shtml

相关文章

代码重构终极指南!!

&#x1f447;&#x1f447;关注后回复 “进群” &#xff0c;拉你进程序员交流群&#x1f447;&#x1f447; 作者丨Alex Omeyer译者丨弯月出品丨CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;原文链接&#xff1a; https://medium.com/swlh/the-ultimate-engineers-gu…

如何进行代码重构?

作为一个程序员&#xff0c;大部分时候&#xff0c;如果不是一个人承包整个项目&#xff0c;那么有1/3的时间在写代码&#xff0c;1/3的时间在看别人写的代码&#xff0c;剩下的1/3的时间在摸鱼。 那么如何提高摸鱼的时间&#xff1f;看别人写代码十分头疼&#xff0c;分分钟想…

代码重构学习

一、代码重构是什么 用重构手法去重构代码 二、为什么需要代码重构 1.方便维护。为了在修改代码时&#xff0c;添加的地方有个切入点&#xff0c;减少bug。 2.有利于理解设计思想和代码。 三、代码重构怎么做&#xff08;养成习惯&#xff09; 思想&#xff1a; 1.修改一…

代码重构技巧:如何将烂代码变成好代码?

这是本文的目录 前言重构入门1. 格式化代码2. 注释3. 废弃的代码4. 变量命名5. 常量命名6. 负值条件的重构7. {} 作为单独的一行8. 变量定义和使用距离太远 重构进阶1. 重复代码2. 函数参数3. 变量多余4. 缺少变量5. 复杂条件 老旧代码的重构利用工具总 结零基础Python学习资源…

代码重构新手教程:如何将烂代码变成好代码?

作者 &#xff5c; 王莉敏 策划 &#xff5c; 蔡芳芳 作为有几年工作经验的程序员&#xff0c;都会对 bad code 不满意。如何将烂代码变成好代码&#xff0c;本文将由浅入深、一步步带你理解重构的奥秘&#xff0c;让你对重构有个基本的了解。本文基于文章《The Simple Ways to…

常见代码重构技巧(非常实用)

你已选中了添加链接的内容点击上方“芋道源码”&#xff0c;选择“设为星标” 管她前浪&#xff0c;还是后浪&#xff1f; 能浪的浪&#xff0c;才是好浪&#xff01; 每天 8:55 更新文章&#xff0c;每天掉亿点点头发... 源码精品专栏 原创 | Java 2020 超神之路&#xff0c;…

hue执行workflow工作流出现直接FAILED

场景&#xff1a; 执行以下任务流的时候就出现了直接失败的情况 在第一次执行并且数据完善的情况下测试的&#xff0c;状态是成功的 但是第二次是在第一次基础上测试的&#xff0c;基础数据有所缺失 日志为&#xff1a; Failing Oozie Launcher, Main class [org.apache.oo…

Git Workflow工作流示意图

来自&#xff1a;http://blog.osteele.com/posts/2008/05/my-git-workflow UPDATE: git pretty 来自: http://justinhileman.info/article/git-pretty/full/ from: http://iccm.cc/git-workflow/

使用Python开源库Couler编写和提交Argo Workflow工作流

Python 是用户在 Kubernetes 上编写机器学习工作流的流行编程语言。 开箱即用时&#xff0c;Argo 并没有为 Python 提供一流的支持。相反&#xff0c;我们提供Java、Golang 和 Python API 客户端[1]。 但这对大多数用户来说还不够。许多用户需要一个抽象层来添加组件和特定于…

SharePoint 2013 Nintex Workflow 工作流帮助(八)

博客地址 http://blog.csdn.net/foxdave 工作流动作 15. Complete Workflow Task&#xff08;User interaction分组&#xff09; 此工作流动作将完成任何进行中的任务&#xff0c;它将处理足够的单独任务来达到选择的结果&#xff0c;接下来工作流引擎会依据工作流的设置处理…

工作流(Workflow) -- 实现简单工作流程

工作流(activiti) 工作流简介 业务过程的部分或整体在计算机应用环境下的自动化 工作流就是多个参与者,按照某种预定义的规则,传递业务信息,进行审核的功能一个框架(activiti) 核心 23张表 ACT_RE_*:RE’表示repository。 这个前缀的表包含了流程定义和流程静态资源 (…

wf工作流java_WF Workflow 状态机工作流 开发

概述 工作流是对业务流程的建模&#xff0c;当我们设计工作流的时候&#xff0c;我们首先要分析业务处理过程中要经历的步骤。然后&#xff0c;我们就可以利用WF创建工作流模型来模拟业务的处理过程。 我们知道&#xff0c;WF包含两种类型的工作流&#xff1a;顺序工作流和状态…

Mendix 9.6 - Workflow(工作流)基础设计

一 概述 Workflow(工作流)是Mendix 9版本中新增的一种可视化语言功能模块&#xff0c;我们可以用它来构建可扩展的流程。此外它还与其他可视化语言完全集成&#xff0c;例如微流编辑器和页面编辑器。 在公司我们往往需要填写许多表单&#xff0c;包括行政、人事、IT等方面的&a…

workflow工作流(三):画流程图工具对比

一、总结 我所用过能画工作流的工具有2个&#xff1a;1.eclipse插件&#xff0c;2.flowable官方网页版&#xff0c;结论是eclipse插件好用&#xff0c;后者要上传下载&#xff0c;还容易丢数据 注意事项&#xff1a;eclipse插件画出来的bpmn文件的schama是activiti&#xff0…

Bladex Workflow工作流引擎开发进阶-版本v1.2.2

Bladex Workflow工作流引擎开发进阶 1 Bladex Workflow简介2 Flowable简介3 workflow部署和配置-参考文档《BladeX插件 - Blade-flow使用帮助文档 》4 配置任务监听4.1 问题&#xff1a;监听类注入不了service 5 配置事件监听6 复杂表单-配置外部表单&#xff08;不建议配置超大…

workflow工作流(二):34张表

1.常用的表 2.总共34张表 Activiti的后台是有数据库的支持&#xff0c;所有的表都以ACT_开头。 第二部分是表示表的用途的两个字母标识。 用途也和服务的API对应。 ACT_RE_*: RE表示repository。 这个前缀的表包含了流程定义和流程静态资源 &#xff08;图片&#xff0c;规则&…

SAP Workflow 工作流开发步骤总结

一、配置 SWU3 设置管理员 二、新建业务对象 查看我们需要增强的业务对象&#xff0c;比如我们项目上用的是BUS2000114&#xff0c;一般我们工作流的触发 是用单据状态的改变来触发&#xff0c;这就需要我们增强业务对象 使用SWO1 新建一个业务对象 继承上面的标准对象 注意…

小小研究一下工作流WorkFlow

修房子 在小牧老家农村,小时候总是看到村里有人在修房子。每次看到有人修房子的时候,他就会爬到房子面前的沙粒堆上去,翻找随着沙子一起被挖出来的贝壳。虽然也不知道拿来干嘛,不过总觉得收集贝壳很好玩。 小牧也喜欢看他们修房子。修房子的时候,专业的修房师傅会指导工…

微软 workflow 工作流总结

1.状态机工作流 (1)状态机工作流从state1 流转到 state2 到stateN (2) state中有执行状态和退出状态 (3) 在进入下一个步骤(state1 到 state2)前,首先会判断进入下一个状态的条件,下面我把它叫做判断模块 (4)在判断模块中,有三个生命周期:Trigger,Condition,Action (5)判断模…

WorkFlow工作流

工作流 一、什么是工作流 历史发展&#xff1a; 工作流的理论起源于70年代中期办公的自动化领域。90年代。工作流技术的研究与开发进一步发展。1993年8月&#xff0c;成立第一个工作流技术标准话的工业组织WFMC&#xff08;工作流管理联盟&#xff09;。现在&#xff0c;工作…