2

好的重构方法才能摆脱"屎山" | 跨界架构师-Zachary的自留地

 3 years ago
source link: https://zacharyfan.com/archives/1381.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

好的重构方法才能摆脱“屎山”

这里是Z哥的个人公众号

每周五11:45 按时送达

当然了,也会时不时加个餐~

我的第「171」篇原创敬上

大家好,我是Z哥。

最近在整理一些项目,所以相关的文章写的多了些。之前的相关文章有《聊聊单元测试》,感兴趣的话可以点击文末链接去阅读。

这次整理项目的时候,做了比较多的codereview和重构。好久没做这么高强度了重构了,所以对重构这件事有了新的思考和理解。

突然发现叫我们程序员“码农”还挺形象的,因为写代码和种田很像,想有个好收成,就要好好管理代码,让它们井井有条。

吴军老师在《文明之光》里讲到一个「垄耕种植法」,它由中国人发明,后发扬到全球,影响了全世界的粮食生产。据说欧洲人民以前是把种子随意地撒在地里,任其自由生长,结果收成很低,如果种下20斤,大概只能收获60斤左右粮食。而中国早在先秦时期亩产最少都在240斤以上,最新的数据是今年11月初袁隆平的杂交水稻,早晚稻加起来达到3000斤,这都得益于「垄耕种植法」。

所以,当你看到那些被随意“播种”的糟糕代码,是改,还是不改?改吧,花时间;不改吧,就像上面的欧洲人民。

其实很多人对「重构」的理解还有些误区。「重构」仅仅是所谓的优化代码吗?并不是。

Martin Fowler大神在他的《重构》一书中对「重构」的定义就非常准确。

重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。

所以,重构不仅仅是修改代码,是对软件结构的调整,修改代码只是其中的一个手段而已。

那么具体应该怎么做呢?在这之前,需要考虑清楚以下几个问题。

/01 什么时候重构?/

很多理想主义者认为的理想情况自然是随时发现坏代码就重构。但是这里存在两个问题:

  • 有很多客观因素导致了就算发现了坏代码,也不一定有充足的时间来修改。
  • 不同的人代码水平不一,一个人看起来没毛病的代码,可能在其他人眼里却是需要重构的代码。

所以我们需要几个更加客观的外部标准,Z哥建议你可以从以下三个方面来观察,如果发现了类似的现象,说明它在给你发出需要重构的信号。

  • 增加新功能的时候。此时,你发现既有的代码无法让你轻松添加新特性,需要花30%以上的时间做一些重复性的编码。
  • 修bug的时候。此时,你发现排查逻辑非常困难,需要花费半小时甚至是数小时才能搞清楚代码在处理什么。
  • 当从优秀的程序员口中说出觉得某段的代码写的太shit。(普通人的shit可能是到了无可救药的地步了,优秀的人觉得shit大概率还有救,否则他估计就离职不干了~)

/02 怎么重构?/

为了保证重构的质量,在你重构的过程中,一定要关注以下4个关键点:

  • 始终工作。这其实也是《重构》中提到的理念,“「不改变软件可观察行为」的前提下”。如果你的重构需要大刀阔斧的进行,会导致很长一段时间软件无法运行起来,那么这就不是一个好的重构方式,因为在很长一段时间里你都不知道它会不会走向万劫不复的“深渊”。(有没有遇到过越重构越糟糕的经历?欢迎分享你的吐槽)
  • 持续集成:很多人会将重构单独作为一个版本去做,其实这样会有一个新的问题,就是后续合版本是个痛苦的事情,而且容易出错。所以,应该就在平时的版本里去做重构,让每一次重构都可以跟着CI/CD持续进行。
  • 随时中止:不管你的重构代码写到什么阶段,只要编译不报错就可以随时中止,立马切换到功能开发。只有达到这个标准,你的Leader才不会对重构又爱又恨。
  • 过程可逆:假如我的重构出现了重大缺陷,它是可以快速回退的,而不需要从之前的版本当中去捞代码。毕竟,谁都无法100%保证每一次的重构都是perfect的。

可以回想一下,你之前做过的重构是否都符合了以上的这些要求?反正Z哥最近做的重构是不符合的,所以感觉很累很痛苦~

具体的重构工作其实说起来很简单,因为一段代码无非就是「输入参数」、「输出参数」、「方法体」3个东西,重构也自然以这几个地方展开。

/01 输入参数/

对于输入参数的重构,主要关注在参数的个数上。那些优秀的开源项目里,你几乎看不到参数很多的方法。

因为过多的参数个数,不但不容易理解,而且你在写调用这个方法的代码的时候也会很头疼,时不时要数一下这是第几个参数,对应的参数说明是什么。

有一些工具推荐的默认参数最大长度是7个(如SonarQube)。如果你没有更好的定义和理解,那么不妨以“7”这个标准来执行。

/02 输出参数/

输出参数只有一个,能够出乱子的空间也很小,所以一般来说不需要怎么优化。

唯一值得提醒的两点是:

  1. 参数类型尽量用强类型。弱类型的返回值虽然让你的Function向后兼容性很好,但是也带来了很多无法在编译期间被发现的问题。
  2. 不返回不需要的参数。添加更多参数在最初肯定是为了“跑在业务前面”,但这份好心往往最终带来的是更多“意料之外的耦合”,导致后续的重构成本大增。

/03 方法体/

对于方法体的重构是花费时间最多的地方,具体的方式方法也很多。但是我建议你一定要坚持一个核心要点,我将它称为「NRD重构法」,这3个字母分别表示:New、Replace、Delete。也就是说,做重构的时候不要直接在原来的方法体里改,重新建一个新的方法,然后等单测跑通之后再替换掉老方法,最后再把老方法删除。

只要做到这点,要满足前面提到的4个关键点,就没那么困难了。

具体的重构内容自然是以减少复杂度为核心思路去做。衡量代码复杂度有一个概念叫「圈复杂度」(也叫「循环复杂度」),在1976年由Thomas J. McCabe, Sr. 提出。现在有不少工具有统计这个指标。

复杂度大说明程序代码可能质量低且难于测试和维护,根据经验,程序的可能错误和高的圈复杂度有着很大关系。

复杂度大的代码往往伴随着大量的if/switch/for/foreach/try…catch/while等等。每一次试用都会让「圈复杂度」+1,并且其中的条件判断越多,增加的越快。

所以,常见的重构方式大多以降低代码的圈复杂度为主。比如,

  • 将相同逻辑的代码抽离并封装到一处,可以避免在多个方法体里增加圈复杂度,只在一个方法体里增加。
  • 通过AOP技术,不但可以将重复的代码剔除出当前方法体,还可以将try…catch之类的代码剔除出去,以降低复杂度。
  • 通过一些语法糖或者框架,也可以降低复杂度。如,lambda表达式。

还有很多小众的重构技巧这里就不赘述了,真是觉得大家都应该读一读《重构》这本书。

多说一句,不提倡刻意降低代码行数的方法,因为你的复杂度不下降,减少代码行数只是“掩耳盗铃”而已。

另外,重构有一个最佳伴侣,就是单元测试。你想象一个画面,当你重构之前通过率100%的单元测试在重构完成后跑一遍,发现了10%的失败。此时你的心情肯定是“真香,否则一堆bug等着我修”。

不过,如果你的代码「圈复杂度」越高,单元测试写起来越费劲。如何写好单元测试可以看我之前写的文章《聊聊单元测试》。

最后,怎么判断重构的效果好不好呢?自然是工作效率是否提高了。

  • 增加一个功能或者接口的时间是不是缩短了?
  • 测试那边回归测试的平均时间是不是缩短了?

好了,就这么多。如果你还是觉得无从下手,不妨试试《重构》作者推荐的一种做法:

随机挑选一个目标,比如,“去掉一堆不必要的子类”。然后朝着目标前进,没把握就停下来。当你无法证明自己所做的修改能够保证原有程序的逻辑和语义时,立马停下来思考:当前做的重构是改善了?还是毫无成果需要撤销?

最后再次强力推荐《重构》这本书,里面有很多非常具体的代码重构方法,值得每一位程序员入手一本。

好了,总结一下。

这篇呢,Z哥和你分享了我对代码重构这件事的看法。要想提高你代码的“产出”,那么就得好好重视重构这件事。

在重构代码的「输入参数」、「输出参数」、「方法体」的时候需要持续保持以下4个关键点:

这才能使得你的重构工作平稳的进行,而不会是一场赌博。

并且,重构方法体的时候要以降低「圈复杂度」为目的,而不是代码行数。如果条件允许,尽量多写一些单元测试来保障重构的稳定性。

希望对你有所启发。

重构可以使软件更容易地被修改和被理解,这个意义甚至大于所谓的“优化和改进”。Kent Beck大神曾也经说过:首先让代码架构易于改变,然后再进行简单的改进。

如果你想摆脱代码越改越痛苦的困境,那么赶紧行动起来吧。


原创文章,转载请注明本文链接: https://zacharyfan.com/archives/1381.html

关于作者:张帆(Zachary,个人微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。欢迎扫描二维码~

微信公众号

定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些思考。

如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思维导图。

如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的“仓库”。欢迎关注我的公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思维导图。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK