收录于话题
#聊技术
45个内容
这里是Z哥的个人公众号
每周五11:45 按时送达
当然了,也会时不时加个餐~
我的第「171」篇原创敬上
最近在整理一些项目,所以相关的文章写的多了些。之前的相关文章有《聊聊单元测试》,感兴趣的话可以点击文末链接去阅读。这次整理项目的时候,做了比较多的codereview和重构。好久没做这么高强度了重构了,所以对重构这件事有了新的思考和理解。突然发现叫我们程序员“码农”还挺形象的,因为写代码和种田很像,想有个好收成,就要好好管理代码,让它们井井有条。吴军老师在《文明之光》里讲到一个「垄耕种植法」,它由中国人发明,后发扬到全球,影响了全世界的粮食生产。据说欧洲人民以前是把种子随意地撒在地里,任其自由生长,结果收成很低,如果种下20斤,大概只能收获60斤左右粮食。而中国早在先秦时期亩产最少都在240斤以上,最新的数据是今年11月初袁隆平的杂交水稻,早晚稻加起来达到3000斤,这都得益于「垄耕种植法」。所以,当你看到那些被随意“播种”的糟糕代码,是改,还是不改?改吧,花时间;不改吧,就像上面的欧洲人民。其实很多人对「重构」的理解还有些误区。「重构」仅仅是所谓的优化代码吗?并不是。Martin Fowler大神在他的《重构》一书中对「重构」的定义就非常准确。重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。 重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
所以,重构不仅仅是修改代码,是对软件结构的调整,修改代码只是其中的一个手段而已。那么具体应该怎么做呢?在这之前,需要考虑清楚以下几个问题。很多理想主义者认为的理想情况自然是随时发现坏代码就重构。但是这里存在两个问题:
所以我们需要几个更加客观的外部标准,Z哥建议你可以从以下三个方面来观察,如果发现了类似的现象,说明它在给你发出需要重构的信号。
为了保证重构的质量,在你重构的过程中,一定要关注以下4个关键点:
可以回想一下,你之前做过的重构是否都符合了以上的这些要求?反正Z哥最近做的重构是不符合的,所以感觉很累很痛苦~具体的重构工作其实说起来很简单,因为一段代码无非就是「输入参数」、「输出参数」、「方法体」3个东西,重构也自然以这几个地方展开。对于输入参数的重构,主要关注在参数的个数上。那些优秀的开源项目里,你几乎看不到参数很多的方法。因为过多的参数个数,不但不容易理解,而且你在写调用这个方法的代码的时候也会很头疼,时不时要数一下这是第几个参数,对应的参数说明是什么。有一些工具推荐的默认参数最大长度是7个(如SonarQube)。如果你没有更好的定义和理解,那么不妨以“7”这个标准来执行。输出参数只有一个,能够出乱子的空间也很小,所以一般来说不需要怎么优化。参数类型尽量用强类型。弱类型的返回值虽然让你的Function向后兼容性很好,但是也带来了很多无法在编译期间被发现的问题。
不返回不需要的参数。添加更多参数在最初肯定是为了“跑在业务前面”,但这份好心往往最终带来的是更多“意料之外的耦合”,导致后续的重构成本大增。
对于方法体的重构是花费时间最多的地方,具体的方式方法也很多。但是我建议你一定要坚持一个核心要点,我将它称为「NRD重构法」,这3个字母分别表示:New、Replace、Delete。也就是说,做重构的时候不要直接在原来的方法体里改,重新建一个新的方法,然后等单测跑通之后再替换掉老方法,最后再把老方法删除。只要做到这点,要满足前面提到的4个关键点,就没那么困难了。具体的重构内容自然是以减少复杂度为核心思路去做。衡量代码复杂度有一个概念叫「圈复杂度」(也叫「循环复杂度」),在1976年由Thomas J. McCabe, Sr. 提出。现在有不少工具有统计这个指标。复杂度大说明程序代码可能质量低且难于测试和维护,根据经验,程序的可能错误和高的圈复杂度有着很大关系。复杂度大的代码往往伴随着大量的if/switch/for/foreach/try...catch/while等等。每一次试用都会让「圈复杂度」+1,并且其中的条件判断越多,增加的越快。所以,常见的重构方式大多以降低代码的圈复杂度为主。比如,
还有很多小众的重构技巧这里就不赘述了,真是觉得大家都应该读一读《重构》这本书。多说一句,不提倡刻意降低代码行数的方法,因为你的复杂度不下降,减少代码行数只是“掩耳盗铃”而已。另外,重构有一个最佳伴侣,就是单元测试。你想象一个画面,当你重构之前通过率100%的单元测试在重构完成后跑一遍,发现了10%的失败。此时你的心情肯定是“真香,否则一堆bug等着我修”。不过,如果你的代码「圈复杂度」越高,单元测试写起来越费劲。如何写好单元测试可以看我之前写的文章《聊聊单元测试》。最后,怎么判断重构的效果好不好呢?自然是工作效率是否提高了。增加一个功能或者接口的时间是不是缩短了?
测试那边回归测试的平均时间是不是缩短了?
……
好了,就这么多。如果你还是觉得无从下手,不妨试试《重构》作者推荐的一种做法:随机挑选一个目标,比如,“去掉一堆不必要的子类”。然后朝着目标前进,没把握就停下来。当你无法证明自己所做的修改能够保证原有程序的逻辑和语义时,立马停下来思考:当前做的重构是改善了?还是毫无成果需要撤销?最后再次强力推荐《重构》这本书,里面有很多非常具体的代码重构方法,值得每一位程序员入手一本。这篇呢,Z哥和你分享了我对代码重构这件事的看法。要想提高你代码的“产出”,那么就得好好重视重构这件事。在重构代码的「输入参数」、「输出参数」、「方法体」的时候需要持续保持以下4个关键点:这才能使得你的重构工作平稳的进行,而不会是一场赌博。并且,重构方法体的时候要以降低「圈复杂度」为目的,而不是代码行数。如果条件允许,尽量多写一些单元测试来保障重构的稳定性。重构可以使软件更容易地被修改和被理解,这个意义甚至大于所谓的“优化和改进”。Kent Beck大神曾也经说过:首先让代码架构易于改变,然后再进行简单的改进。如果你想摆脱代码越改越痛苦的困境,那么赶紧行动起来吧。
推荐阅读:
原创不易,如果你觉得这篇文章还不错,就「在看」或者「分享」一下吧。鼓励我的创作 :)
如果你有关于软件架构、分布式系统、产品、运营的困惑
可以试试点击「阅读原文」