发散式变化、霰弹式修改和单一职责
“发散式变更与霰弹式修改之间的精妙点:单一职责(SRP:Single responsibility principle)”
“发散式变更与霰弹式修改之间的精妙点:单一职责(SRP:Single responsibility principle)”
本文适合以下小伙伴们观看:
- 遇到过问题:只改一个小功能,却需要修改一堆分散在各处的代码,有时还会漏改
- 遇到过问题:只改了一行代码,有些地方意外地发生了变化
- 想要学习或者了解发散式变化、霰弹式修改或单一职责这些概念
- 想要杀杀时间,看看图
发散式变化
一处代码的修改影响多个功能,称作发散式变化。
查看图片的方向:从左向右(CODE->feature)
负面影响:风险(项目会因此出现更多不确定与风险)
霰弹式修改
一个功能的变更需要多处同时修改,称作霰弹式修改。
查看图片的方向:从左向右(CODE->feature)
负面影响:成本(修改更慢)、风险(修改时更易出错)
怎么办呢?
1 完美的解决方案:单一职责
其实两个问题都是关于代码与功能的关系不匹配的问题。一个是代码多于功能,另一个是功能多于代码。所以只要平衡代码与功能的关系,问题就解决了。
案例解说:一个"存在5段代码 对应 5个功能"的系统优化
首先,有一个系统,它有5个核心功能,同时也有5段代码段。
图中其实看得到,现在的系统非常混乱,像一堆线胡乱地缠在一起。
仔细观察可以发现:有五个代表代码段的线条,和五个代表功能(特性)的线条
但中间缠在一块了,仔细看还可以发现,代码段和功能竟然还不是一一对应的。
有一段代码对应三个功能的(图中红色,1份代码对应3个功能);又有三段代码对应一个功能的(图中橙色)。
也就是说,现在系统中既存在发散式变化,又存在霰弹式修改,要是改这块代码可算是倒大霉了。
但读者们可能会有个想法,整理一下,变成这样不就行了吗?
是的,相当可以!一个代码对应一个功能,真是干净又清晰。
那接下来,让我来帮你细化一下脑中的想法。
请先看扩充细节的系统结构图:
图中有几个关键元素:开发人员、用户、系统、核心模块、代码、功能(特征)。
这里解释一下什么是元素
图中表达各种事物的不可再分的最小实体,都是元素。比如,表示关系的线条,表示用户和开发者的小人,甚至表示核心模块的虚线方框都是元素。
图中元素描述:
- developer(开发者):负责系统设计、开发、维护的人员
- user(用户):本软件系统的使用者
- Core(核心):核心模块。有一些核心机制,不是所有开发者都全部了解的
- Code:N(代码N):编号为N的某段代码
- feature:N(功能N):编号为N的某功能(或特征)
- System(系统):被分析的本软件系统
梳理一下各元素关系:
- 开发者看得到所有元素,但核心模块对开发者算半封闭(并不是全部了解,且不一定能任意修改)
- 开发者修改代码后,功能就会变化。但不知道哪个会变,甚至不知道几个会变。
- 用户看得到系统、功能,看不到其他元素
现在一起看看用户视角看到的系统是什么样的:
用户只看得到功能和系统,看不到细节。所以做开发时会遇到那种用户看起来很简单的功能,实际开发起来很麻烦的情况,因为内部结构用户看不到,也不关注。
对于用户来说,五个功能互相并无关联,所以图中使用了5种颜色对功能进行标记。
那我们要怎么做才能做好本次的优化呢?
其实关键在于“谁”能修改核心模块的代码。是哪个固定的开发者吗?你,还是我?
其实都不是。是具备核心模块全部知识的开发者。具备核心模块全部知识的开发者就有能力修改核心模块。
如果不具备核心模块的全部知识,则需要通过外部获取并学习,之后就可以对核心模块进行修改了。
所以,完整的优化步骤:
- (若不具备核心模块知识)学习核心模块知识(通过文档、源码、口口相传?等方式)。
- 使用核心模块知识,整理代码(设计、重构等)
- 重构/整理完成。系统更可控(明确代码与功能的映射关系),且不具备核心模块知识的其他开发人员也可以可控地修改代码。
1)学习核心模块知识
可以通过各种方式,学习核心模块知识。具备核心模块知识的开发人员才能理解核心模块中的各种机制与复杂,才有能力进行相关内容的修改和重构。
知识不仅包括技术方面的知识,还包括业务知识(业务知识在很多时候都相当重要)。
2)使用核心模块知识整理代码
吸收完核心模块知识的开发人员,就有能力对核心模块中的混乱部分进行整理了。
在核心模块中,整理代码与功能,使其一一对应(满足单一职责)。
3)重构/整理完成
重构/整理完成后,每个功能与代码都能清晰地对应起来。仔细观察也可以发现,图中的开发人员现在并没有携带核心模块知识。这也代表:现在不了解核心模块知识的开发人员也可以可控、并行地修改功能(feature1-feature5)了。
重构时另外要注意的事情
一定要有测试层这层做保障,利用测试拦截与预期不符的错误重构操作。
若测试失败后,代码应该能够回滚至上一版本。最起码先保证系统不会更坏,再思考向前一步。
重构尽可能小步走,这样回滚和测试都会更稳健。
2 其他不太优雅的方案
1)记录法
梳理过部分逻辑后,通过文档或注释记录下来代码与功能的映射关系。适用于发散式变化与霰弹式修改。
总结
单一职责是解决发散式变化和霰弹式修改的利器。
重构时要注意一定要有测试,否则对系统的影响很大,风险完全不可控。风险与收益也不成比例。
出现类似问题时,若不容易重构,可以使用记录法暂时缓解其产生的副作用。
内容还感兴趣吗?公众号中会有更多相关内容持续更新哦
后记
其实今天的内容也算老生常谈了,很多技术博客和公众号都聊过。今天在这里画画图,让大家更容易理解和记忆。
发散式变换、霰弹式修改的关系是很早之前就有的一个灵感,其实就是上面单一职责里面那个五条线缠在一起的图,当时的脑中有一个动图,好像一堆绳子缠起来又解开的样子,今天才刚刚把它和单一职责关联起来。
这也和单一职责的定义呼应了:一个类应该只有一个发生变化的原因
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)