天天看点

系统重构心得

重构概念

在不改变软件可观察行为的前提下,对软件内部结构的一种调整,提高其可理解性,降低修改成本。

重构的原因

  1. 难以理解的代码
  2. 难以修改的代码
  3. 难以进行自动化测试的代码

重构的核心目的

  1. 提高代码的可读性
  2. 提高代码的可测试性
  3. 提高代码的可扩展性

重构节奏

测试、小修改、测试、小修改…正是这种节奏让重构得以快速安全而安全的前行。

构筑测试体系

如果想要重构,我们必须拥有一个良好的测试环境。编写优良的测试程序,可以极大的提升编程速度和代码质量,即使不进行重构也一样如此。

每当我们收到一个bug报告时,请先写一个单元测试来暴露bug。

测试是一种风险驱动的行为,测试的目的是希望找出现在或未来才可能出现的错误。

测试的要诀是:测试你最担心出错的部分。这样你就能从测试工作中得到最大的利益。

“花费合理的时间找出大多数的bug”好过“穷尽一生抓出所有bug”。

重新组织函数

  • 重复代码

    重复代码应该考虑将其提炼到一个独立的函数或类中。

  • 过长函数

    当我们看见一个过长的函数或者需要一段注释才能让人理解其用途的代码时,我们就需要将这段代码放到一个独立的函数中。

    一个函数多长才算合适?在我看来长度不是问题,关键在于函数名称和函数体之间的语义距离。如果提炼可以强化代码清晰度,那就去做,就算函数名称比提炼出来的代码还要长也无所谓。

  • 内联函数

    有时候会遇到某些函数,其内部代码和函数名称同样清晰易读,我们就可以去掉这个函数直接使用其中代码。

  • 内联临时变量

    如果发现一个临时变量只被一个简单表示式赋值一次,并且妨碍了其他重构手法,我们就应该让他内联化。

    直接将变量声明成final的,可以检查变量是否真的是被赋值了一次。

  • 以查询来取代临时变量
  • 引入解释性临时变量

    将复杂表达式的结果放到一个临时变量,以变量名称来解释表达式用途。

  • 分解临时变量

在对象之间搬移特性

在对象设计过程中,决定把“责任”放在哪里是非常总要的。这曾经让我非常苦恼,但是现在我知道,在这种情况下,可以运用重构,改变原有的设计。

  • 搬移函数

    “搬移函数”是重构理论的支柱。如果一个类有太多行为,如果一个类和另一个类高度耦合,我们就需要搬移函数。

  • 搬移新的字段

    随着系统的发展,你会发现自己需要新的类,并将现有的工作拖到新的类中。如果发现对于一个字段,在其所驻类之外的另一个类中都有更多函数使用了它,我就会考虑搬移这个字段。

  • 提炼类

    一个类应该是一个清晰的抽象,处理一些明确的责任。

简化表达式

可以将一个复杂的条件逻辑分解成若干小块。这样可以使得分支逻辑和操作细节分离。

  • 分解条件表达式

    分解表达式可以突出条件逻辑,更清晰地表明每个分支的作用,并突出每个分支的原因。

    做法:将分支语句提炼出来,构成一个独立的函数。

  • 合并表达式

    有时会发现一串的表达式检查条件各不相同,但最终行为却一致。如果这样就需要使用“逻辑与”或“逻辑或”合并表达式。

  • 合并重复的条件片段

    当所有分支都会执行相同代码时,就需要将这段代码搬移到条件表达式之外了。这样我们能更加清晰的知道,那些东西是随着条件变化而变化的、那些是不变的。

  • 移除控制标记

    控制标记可以使用break或continue来代替,还可以使用return直接返回。

  • 以卫语句来取代嵌套表达式

    可以让代码逻辑看起来更加的清晰。

  • 以多态来取代条件表达式

    多态的好处在于:如果你需要根据对象的不同类型来采取不同的行为,多态使你不必写明显的条件表达式。

简化函数调用

  • 移除参数

    参数代表着函数所需的信息,不同的参数值有不同的意义。如果不去掉多余的参数,就会让每一位调用者多费一份心。

  • 将查询函数和修改函数分离

    任何有返回值的函数,都不因该有看到的副作用。

  • 引入参数对象

    为了缩短参数列,我们可以使用一个对象来包装所有的参数。这样可以降低理解和修改函数代码的难度。

  • 以异常来取代错误码

    如果调用者有责任在调用前检查必要状态,就抛出非受控异常。

处理概括关系

  • 划分边界

    确定各个字段、方法应该处于子类还是超类也是我们经常需要注意的一个点。

  • 提炼子类、超类和接口
  • 折叠继承体系

    有时我们会发现某个子类并没有带来该有的价值时,我们就需要将子类的超类合并。

  • 塑造模板函数

    你有一些子类,其中相应的某些函数以相同顺序执行类似的操作,但是各个操作细节上有所不同。将这些操作分别放进独立函数中,并保证他们有相同的签名,于是原函数也变得相同了,然后将原函数移至超类。

  • 以委托取代继承

    当某个子类只使用接口中的一部分,或者不使用继承而来的数据时,我们可以在子类新建一个字段用以保存超类:调整子类函数,令他改而委托超类;然后去掉两者间的继承关系。

大型重构

  • 梳理并分解机场体系
  • 将过程化设计转化为对象设计
  • 提炼继承体系

重构要点

  1. 需要有充分测试代码
  2. 每次只做小的改动,然后编译测试,然后在做改动
  3. 新加功能就不要去重构,重构就不要新加功能
  4. 重构一定要保证对外接口不要改动或者少改动
  5. 做性能重构时尽量使用度量工具,不要臆测
  6. 当你发现在加一个新功能的时候很费劲,这个时候就需要考虑重构了
  7. 重构一般发生在项目中期
  8. 性能重构是我们应该优先关注执行频率较高的代码
  9. 重构可以和系统设计达到一种平衡
  10. 任何情况下我们应该优先编写出可以调试的软件,然后再试图优化它使它更快
  11. 一个优秀的程序员是能编写出让别人很快看懂理解的代码
  12. 重构尽量不要直接删掉老的接口,而应该将其标注成废弃

大型重构心得

  1. 大型重构之前一定要先要了解业务,只有了解了业务,知道了业务未来发展的方向在进行重构,还有就是只有了解了业务才能知道那些代码可以删除。
  2. 大型重构一定做到,要胆大心细。在完全了解系统的情况下,不要怕删除和改动原来的代码。
  3. 常来路边走哪有不湿鞋,光胆大心细还不够,我们还需要有完备的测试体系来保驾护航,重构之前一定要先写单元测试。
  4. 即使这样也不能保证完全放心,我们在线上还应该构筑完整的告警体系,我是直接监控的ERROR数量,前提是需要先去掉系统中无效的ERROR输出。
  5. 上线的时候先灰度发布,尽量减少影响面。

参考