读《重构-改善既有代码的设计》的一些思考|社区征文

2023总结开发与运维技术服务知识库

前言

在现代的软件开发中,重构是非常重要的组成部分。与以往的既有观念不同,软件系统的架构和实现代码不是一成不变的,而是随着使用人数增加和需求的不断变化而变化。这就要求我们对代码进行重构,来保证代码容易被修改,并且更加健壮。

picture.image

重构的定义,我的理解是,在保证代码可观测的功能不变的前提下,使用某种手法,改变代码的结构,从而使代码的架构及设计得到改善。这里的重构不会提高性能,甚至可能会使得软件的性能降低,但是,经过重构的代码更加清晰可读,也更加容易找到代码执行的瓶颈,从而使得优化有的放矢。

重构,第一个示例

作者在第一章使用了一个简单例子:通过重构一个产生字符串的代码来实现增加产生 HTML 代码的功能。重构使得代码的可读性大大增加,修改起来也非常方便。在重构时我们要遵循测试驱动,小步前进的原则,一旦测试未能通过,我们就改用更小的步子重新进行代码的编写。

看了这本书的第一章我就后悔没有早一点看。之前在工作的时候就有因为单元测试不完善导致返工。如果有单元测试,在修改代码的时候能够更加有信心,同时对之前的例子有测试覆盖也能保证没有对已经存在的代码产生破坏。

重构的原则

这章主要讲了重构的定义,执行过程以及和代码相关的性能等问题。

picture.image

重构要保证可观察性,也就是说改动前后软件的行为应该保持一致。我印象最深的还是 两顶帽子,说的是修改代码架构和重构这两个步骤,应该交替进行,并且保证充分的单元测试,以适应软件的需求变化。

代码的坏味道

知道什么是代码中的坏味道,实际上就是如何避免写出不好维护的代码,以及培养自己对坏代码的意识。比如没有意义的命名,重复代码,过长的函数和参数。

这一章还提醒我们要警惕意料之外的改变,不要过多使用全局变量和传递可变的对象,以免带来复杂难以追踪的 bug

在有了类之后,我们应该多使用类来记录数据,而不是使用过多的参数或者单纯使用字符串来记录所有属性。

构筑测试体系

这一章主要讲了如何编写测试。最重要的应该是让测试自动进行,因为人很容易忘记进行这个步骤。我觉得我们可以把测试放在编译之后立即执行,确保每一次编译都能通过单元测试。

俗话说,工欲善其事,必先利其器。我们在做重构之前必须要写好单元测试,不然没有十分的把握保证重构前后代码的表现维持一致。我在做 feature 的时候经常出现改了这个 bug 就出现另一个 bug 的情况,这通常是由于我没有对每一个 feature 写好测试。这样反而花了大量的时间在定位 bug 和修复上面,而且不敢确定自己的代码没有问题,就会反复尝试,浪费了很多时间。

在编写测试上面也不是一劳永逸的,不用想着一次就写出一个完美的测试。而是随着代码的编写切换自己的工作,如果测试不够就补充测试,完善好了测试再继续编写业务代码,这样事半功倍。

介绍重构名录/第一组重构

第五章只有薄薄的两页,讲的是如何收集,分类和总结重构。虽然篇幅很少,但是也是一个启发,很多精彩的内容都是日积月累形成的。

picture.image

第六章则是具体地讲解一些重构的方法。比如內联和提炼。我觉得用到內联的情况应该是,当变量可以通过类的属性或者函数名称很好的表示,并且只使用少量次数的时候就可以使用。

封装/搬移特性

我们在写代码时常常一不小心就写出了参数非常多的函数,如果不加以注意,很容易使得函数难以修改。碰到这种情况我们就可以用书中的用对象取代基本类型,将多个函数参数封装为一个对象方便调用,修改扩展起来也会更加便捷。

picture.image

在工作中我还见到过在发起请求的时候,查询范围大小不一致的情况,我觉得也可以使用书中的重构手法进行修改,保证代码的逻辑一致。

第八章主要讲的是如何合理组织代码。可以对代码的位置,对象的属性进行重新调整。

这里面我觉得最让人印象深刻的还是拆分循环:让循环专注于一件事。在之前我总是觉得循环的次数越少越好,把所有功能一次完成,既减少了代码量,又能提升效率。这是不对的。现代的编译器可以很容易的优化多个循环,同时在时间复杂度不变的情况下,多次循环对于代码本身执行效率也不会有太大的影响。真要在这个过程中出了效率的问题,也很好定位。最重要的是,拆分循环是为了将这个过程提取出来,用函数代替,这样方便对代码进行扩展。

picture.image

重新组织数据/简化条件逻辑

不要把同一个变量用于多个用途,可以再多用一个变量。尤其是变量会被多次赋值时,容易隐藏 bug。在类中修改变量时,可以记录下变量的初始值,利用初始值的相对变化量来查询变量。

picture.image

有的时候如果复制和保存对象比较多,可以使用引用对象。这样能够统一管理可变的对象,同时修改和扩展也更方便。

有的时候如果代码需要立即返回,可以用卫语句(比如检查闰年时,年份不能使用字符串之类的)。

picture.image

以后用到的最多的还是多态代替条件语句,多使用类能够更好的组织代码。

重构API/处理继承关系

这一章节使用了很多成对的手法,比如用查询替代参数和命令替代参数。总而言之是看当下对于代码的要求,如果足够简单而且不需要过多修改,那可以使用简单的函数;如果代码越来越复杂,功能或者越来越多,持续变化,那应该用类来代替函数,方便扩展和管理。

picture.image

此外,我们应该注意保持函数中的变量不可变。如果经常使用全局变量,很容易导致难以察觉的bug。书中给出了一个标准:引用透明性,即在函数调用前后没有变量发生改变,这样的函数无论调用多少次都不会对其他函数产生影响。

这里面也有很多成对的重构手法,比如,字段下移以及函数下移等等。使用上移重构的判断依据是去除重复的代码,即将子类相同的代码移动到超类。使用下移重构则是把某些子类并不关心的属性从超类中移除。在上移时如果是 Python 或者 TS 这种动态类型,可以在超类中加入陷阱:即一定会抛出异常的函数,来强制子类实现。

picture.image

如果继承的场景过于复杂,常见于本来已经有了某种分类方式,又提出了一种新的分类方式,这时可以使用委托的方式来代替继承。这样可以满足 组合优于继承 的原则,保证代码有更好的可扩展性。

结语

总的来说,这本书的核心观点就在于:单元测试和小步尝试。为了写出健壮的代码,我们最好在编写业务代码之前就想好一些测试用例,使用测试驱动代码生成。在编写代码时,如果觉得有必要重构就应该大胆进行重构,同时小心的进行单元测试,确保重构前后代码逻辑没有被破坏。如果测试失败,我们可以用更小的步子进行重构。同时,不要使用机械思维,代码和需求都是不断变化的,以前的架构也许已经不适应现在的代码,需要对代码和对应的单元测试进行不断的迭代和优化。

0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论