重构读书笔记二

重构 Dec 7, 2019

第二章 重构的原则

2.1 何为重构

作者认为这个词既可以用作名次也可以用作动词。
名词形式的定义:
重构是对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
动词形式的定义:
重构是使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。

2.2 重构与功能添加

功能添加:不应该修改既有代码,只管添加新功能。通过添加测试让测试正常运行。
重构:不能添加新功能,只管添加代码的结构。此时不应该添加任何测试,只在绝对必要时(接口变化时)才修改测试。

2.3 为何重构

重构不是万能的,重构是为了将变坏的代码修正过来。

重构改进软件的设计

如果没有重构,程序的内部设计会逐渐腐败变质。当人们为了短期目的而修改代码时,经常没有完全理解架构的整体设计,于是代码逐渐失去了自己的结构。

重构使软件更容易理解

完成一个功能时候,第一次的结构可能不太理想。虽然完成了功能,但是理解起来会比较麻烦。在重构上花一点时间,就可以让代码更好地表达自己的意图。

重构帮助找到bug

重构的时候,对代码进行理解,可以帮助我们找出 bug。清晰的代码,bug 就很难隐藏。

重构提高编程速度

这可能有些反直觉,重构上花费的时间不算到功能的开发上,大部分都会觉得这会让我们的开发周期变长。但是有经验的开发者都知道:一个项目,开始的时候功能开发的很快,但是随着时间推移,想要添加一个新功能会长得多。需要花费更多的时间区考虑如何把新功能塞进现有的代码库,不断出现的 bug 修复起来也越来越慢。代码看起来像打补丁,需要精致的考古工作才能理解系统是如何工作的。最后程序猿无法忍耐,将整个系统进行了重构。下面的图片展示了他们的情况

image_1drfdr4vspcq1qckdvpoq0uqk9.png-19.2kB

但是会重构的团队会有不同的情况

image_1drfdtgutjb41afod28ldi1kqb1m.png-34kB

2.4 何时重构

作者给了一个三次法则:
第一次做某件事只管去做;第二次做类似的事情会产生方案,但无论如何还是可以去做;第三次再做类似的事情,就该重构了。
我的理解是如果三次都需要修改同样的地方,那么这个地方的代码一定很糟糕。好的代码应该是原则上是对扩展开放,对修改关闭的。

预备性重构:让添加新功能更容易

重构的最佳时机在添加新功能之前。在添加新功能之前,看看现有代码,经常会发现:如果对代码代码做一些调整,添加新功能会很容易(对扩展开放)。

帮助理解的重构:使代码更易懂

就像第一章的例子,重构是为了让我们对代码更容易懂,也就更容易修改。

捡垃圾式重构

有的时候我们见到一段代码,我们已经理解他了。但是我们发现我们有更好的方式实现这个功能,比如:更少的语句,更好的性能等。这时候我们也可以进行重构

有计划的重构和见机行事的重构

见机行事的重构:预备性重构、帮助理解的重构、捡垃圾式重构。这些重构都是自然的编程流的一部分。
有计划的重构:如果团队忽视了重构,那么常常会需要专门花一些时间来优化代码库,以便更容易添加新功能。很多时候有计划的重构会花上一个星期,甚至一个月。这时候为了不影响新功能的开发会将重构版本和新功能分开提交。这样做的重构脱离了上下文。新功能的开发依赖于旧有的版本,如果一直有新版本的开发,重构会不停的追赶新版本的进度,直到追上才能切换重构的版本。

长期重构

如果需要一个长期的重构,我们可以在“重构区”的时候进行一些重构。这样的好处是重构不会破坏代码,每次小改动后,整个系统仍然照常工作。例如:我们想替换一个正在实用的库,可以先引入一个抽象层,使其兼容心就两个库的接口。一旦调用方已经完全改为使用这层抽象,替换下面的库就会容易很多。(这个方法叫 Branch By Abstraction)

Review 代码时重构

有经验的开发者在 Review 别人代码时,可以检查出代码中那些”坏味道“的代码,帮助新人建立好的代码习惯,了解重构的方法。

何时不应该重构

如果不需要更改和使用,那就没有重构价值,不需要重构。
如果重写比重构还容易,就不要进行重构了。

2.5 重构的挑战

一个事物总是有他好的一面,也有他不太好的一面。在使用重构的时候,我们会遇到哪些挑战?

延缓新功能的开发

这是大部分人的观点,花在重构上的时间,会让新功能的开发延缓。但是重构的目的其实是让我们开发更快,用更少的工作量创造更大的价值。不过我们不应该以代码整洁或者良好的工程实践来作为重构的必要性。我们之所以重构是因为重构后的工程,可以让我们添加功能更快,修复 bug 更快。

代码所有权

重构的时候我们还会遇到代码所有权的问题。因为重构的时候,不仅仅会修改模块内部,还会修改模块与系统其他部分的关系。我们可能会对对外接口做出修改(为了统一对外接口等原因)。但是这可能没办法做到,因为我们没有代码所有权。如果是开源的库,我们可以自己下载源码然后做出定制化修改。还有可能是另外的团队提供的库,这时候我们要和对方沟通,如果团队内部的代码是共享的那么就太棒了,我们可以修改后提出 pr 让对方评审后合并到新的版本中。但是遇到是其他公司的不开源的代码怎么办?并且我们没办法和对方沟通。这样的话,我想到的是,我们需要添加一个中间层,把无法修改的代码封装起来,让上层的调用者不用直接感知这些代码。

总结

本章提到了一些重构的原则,最重要的是纠正了很多对重构的概念上的错误认识。我也学到了一些重构的实践性法则,希望在以后的工作中应用