一个简单需求竟让我改了十几处代码,必须控诉到底什么是重复代码
正文
我们这行都很幽默 ,总说编程就是CV,自黑写程序大部分都是靠复制粘贴。实际上,很多高级或资深程序员写代码也就是CV:把其他项目里的一段代码复制过来,稍加改动,run 一下没问题就能上线。
但这种做法就是在给子孙后代挖坑!
通常只要这些CV代码其中有一点逻辑要修改,就意味着所有CV的地方都要修改。因此很多人实际开发时,发现明明很简单的小需求,你却要改很多地方,需要花很长时间和测试解释如何才能测到,搞的PMO和领导,对你的人效都很不满意!只要你少改一处,就意味着留下一处潜在的bug,然后发布生产时发现又有各种小问题,继续回退并通宵 hotfix,最后重新发版。
发现CV的致命点了吗?这种做法是最容易产生重复代码的地方,所以 Do Not CV!
正确姿势:
- 提取出方法fun
- 在需要的地方调用fun方法
CV 的重复代码相对容易发现,但有些代码结构类似,这也是重复代码!然而有些人却对这类坏味道视而不见。其实还有很多制造重复代码的不竭恶意动力:
- 代码结构设计不合理,导致同一实现散落各处
由于项目初期的代码结构设计不合理,导致后续开发时无法快速找到现有实现,或找到了但是不好直接引用
改进:对不合理的地方及时重构 - 为了稳定性,坚死活不动老逻辑
拷贝一份。由于对于业务不熟 && 对自身代码能力不信任不敢重构
改进:通过微重构进行多次迭代小改进,慢慢优化 - 互联网敏捷开发,“快”
由于时间紧张或者能力问题无法识别出的坏代码。
改进:提升能力
重复结构
某系统要将作品相关信息发送给翻译引擎:
- sendBook 把作品信息发出去
- sendChapter 把章节发出去
- startTranslation 启动翻译
这几个业务都是以后台执行,所以,方法签名都增加了一个 Task注解,表明是任务调度入口。实际代码执行放到对应service业务方法。
够简洁了吗,但这段代码结构却是有重复的,注意catch语句。之所以要catch,是为防止系统出问题而无人发觉。捕获到异常后,把错误信息通过IM发给相关负责人。注意该逻辑是后来加上的,所以,这段代码作者不厌其烦地在每处都修改了代码。
虽然这三个函数调用的业务代码不同,但结构一致,基本流程可概括为:
- 调用业务函数
- 若出错,发通知
当你能够发现结构上的重复,我们就可以把这个结构提取出来。从OOP来说,就是提炼出一个接口:
使用函数式编程简化代码:
若再有一些通用结构调整,如在任务执行前后要加上一些日志信息,这样改动就可放到 executeTask 方法里,而不用四处改!还容易漏!
所以这类问题其实不复杂,关键在于发现结构重复。相比直接CV,结构重复极具迷惑性。很难让人一下反应出来干的三件事,居然也是重复代码。
一般参数是名词,而方法调用是动词。动词不同时,并不代表没有重复代码!懂得这点,就比较容易发现结构相似了。
比如案例中的:发作品信息、发章节、启动翻译,看上去是三件不同事,只是因为动词不同,但除了动词,其它部分都相同!所以,它们属于结构重复。
做真正的选择
对章节内容进行编辑。有一个业务逻辑,章节只有在审核通过之后,才能去做后续的处理,比如,章节的翻译。所以,这里的 editChapter 方法最后那个参数表示是否审核通过。
在这段代码里面,目前的处理逻辑:
- 如果这个章节是由作者来编辑的,那么这个章节是需要审核的
- 如果这个章节是由编辑来编辑的,那么审核就直接通过了,因为编辑本身同时也是审核人
if 选择的到底是啥?
感觉 if 选择的一定是两段不同业务处理。
但仔细观察,发现if 和 else 两段代码几乎一样,仅最后一个参数不同而已。
没错,这也是一种重复代码。只是这种重复代码是你自己新写出来的,而非CV所得。
因为写时,只想到 if 判断后要做啥,而未曾想过 if 语句判断的到底是啥,客观上就很容易导致重复。
写代码要有表达性
准确表达意图,是优秀代码基本要求。而这里的 if 判断区分的是参数,而非动作。可微调这段代码:
这里我把 user.isEditor() 判断结果赋值给 approved boolean变量,而 非直接作为参数传给 editChapter,这波操作只为提高代码可读性。
因为 editChapter 最后一个参数表示该章是否审核通过,引入了 approved 变量,即可清楚地看到,一个章节审核是否通过的判断条件是“用户是否是一个编辑”,这样代码更清晰!
若将来审核通过条件变了,变化点都在 approved 变量赋值这一处而已!若你追求更具表达性的做法,可提取一个方法,变化都放到该方法里:
实际项目经常能看到 if 语句未有效选择目标,有的是参数列表特长,有的是在 if 代码块有多个语句。
所以,只要你看到 if 语句出现,而且 if 和 else 的代码块长得又比较像,多半就是出现了这类坏味道。
不想玩“找茬”,给自己日后开发留坑,就尽快重构吧!
Don’t Repeat Yourself(DRY,不要重复自己)
在一个系统中,每一处知识都必须有单一、明确、权威地表述。Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
写代码要想做到 DRY,一个关键点是能够发现重复。发现重复:
- 在沼泽中挣扎后,被动发现
- 提升自己的鉴别能力,主动发现重复
这种主动识别的能力,其实背后要有对软件设计更好的理解,尤其是分离关注点
文章转自公众号: JavaEdge