你好,我是徐昊。今天我们来继续讨论测试驱动开发中的驱动。

在上节课,我们讲了单元级别功能测试能够驱动其对应单元(功能上下文或变化点)的外在功能需求。但是当对应单元过大时,会带来一系列麻烦(比如测试构造困难、问题定位不准确等),那么就需要将对应单元的功能上下文继续分解成更小的粒度,然后通过测试来驱动实现。

从“驱动”的角度讲,TDD并不是一种编码技术,它无法驱动你写出你不会实现的代码。TDD是一种架构技术,它能通过测试与重构,驱动单元的划分以及功能的归属,因而是一种更为落地的架构软件的方式。

在TDD中,重构是和测试一样重要的驱动力,驱使我们得到更好的架构和更清晰的代码结构。因而熟练掌握几种常用的重构手法,也是十分必要的。

语义化的查找替换(Semantic Find and Replace)

首先要介绍的重构手法是提取方法(Extract Method)和内联方法(Inline Method)。这是最重要的两种重构手法,它们相当于查找(Find)/替换(Replace)。所不同的是,这种查找替换是语义化的:在不破坏现在代码结构的前提下,完成查找替换。视频演示如下:

正如视频中所展示的,这个手法是将需要修改的代码提取到新方法中。在新方法内完成要做的修改,再通过内联方法在所有调用这个新方法的地方完成修改。我们需要将这两种手法看作修改代码的基本方式。

通过提取/合并单元进行重架构(Extract and Merge Units)

在提取方法的基础上,我们可以进一步将提取出的行为从当前对象中分离出去,也就是提取对象(Extract Object)。视频演示如下:

一旦提取出对象,我们就能通过类内字段(Field)、参数(Parameter)等方式,不再直接引用当前对象上下文,从而将其与当前对象上下文分离。对应地,我们可以使用的重构手法有引入字段(Introduce Field)、引入参数(Introduce Parameter)等。视频演示如下:

通过这些重构手法,我们对类结构进行了调整,也就是模块的重划分功能的重分配。都不用四舍五入,这就是对已有代码的重架构(Re-architecture)。

当然,能分自然能合。如果模块结构不合理,那么完全可以通过刚才介绍的语义化的查找替换来完成单元的合并。视频演示如下:

到此为止,我们介绍了重构的基本手法。通过这一组操作,我们可以完成对于代码的修改,以及对于架构的调整。这些手法如此基础,应该被看作修改代码的基本功,而不是重构:谈不上什么消除坏味道,就是高效修改代码而已。

使用多态替换条件

组合使用这些基础手法,我们就可以进行一些更大规模的重构。比如我们之前在Args例子中展示过的使用多态替换条件分支。现在请你重新看一遍第二讲中的前两段视频。

这两段视频中展示的重构与刚才介绍的几种手法不同,我们消除了坏味道并强化了开放封闭原则(Open-Closed Principle,OCP):新增的类型解析不需要修改Args的实现,可以通过扩展OptionParser接口实现。对修改封闭,对扩展开放

这种架构改进方法叫做重构到模式(Refactoring to Patterns),即:将架构上的坏味道替换为设计模式(Design Pattern)。这是一种更有效的架构软件的方法,用公认的好设计(模式)替换了公认的不好的设计(坏味道),还能满足功能的需求,必然能是更好的架构(而不用虚无缥缈地归结于“品味”或“经验”)。

对于TDD,行业中存在这样一种困惑:从功能测试出发,逐步完成软件开发,这或许没问题。但架构怎么办?实际上,红/绿/重构循环中的重构就是解决架构问题的。只不过架构并不是预先设计的(Upfront Design),而是在完成功能的前提下演进而来的,因而也称演进式设计(Evlutionary Design)。

通过重构到模式演进式地获得架构,是一种实效主义编码架构风格(Pragmatic Coding Architect)。这是习惯了预先设计的PPT架构师们不曾体验过的经历,因而不被理解也是很正常的了。

顺便说一句,Joshua Kerievsky在2004年写过一本书,就叫 _Refactoring to Patterns_ 。这本书的价值远被低估了,是关于软件架构非常重要的著作!

小结

这节课我们介绍了四种基本的重构手法,分别是提取方法、内联方法、引入参数和引入字段。这几种手法应该被看作修改代码的基本功。

此外,如果无法借助自动化重构工具高效修改代码,那么TDD带来的效率将会大打折扣。而无法支持这几个核心手法的IDE,也不足以支撑TDD的实施。

我们还展示了通过组合使用这些基本手法,来完成较大规模重构的例子。并简略介绍了TDD原生的架构方法,也就是重构到模式。对于常见的架构上的坏味道,我们都可以通过某些模式将其消除,从而得到更好的架构。

思考题

除了重构之外,如果架构预先设计好了,那么要怎么使用TDD?

编辑来信

TDD是一项技能,唯有动手实操、反复练习,才能有所小成。为了帮助你更快地进步,徐昊老师特发起了“代码评点”活动。
 
在第一个实战项目结束后,我们会根据你提交的学习反馈,手动选出其中几位进行代码评点与解疑答惑。而评点的详细内容我们也将制成加餐,展示在专栏里,供其他同学学习与参考。
 
划重点!如果学完第1-10讲再写反馈,将会大大提高你入选的机会!另,此次收集时间截至4月3日零点。所以非常希望你能跟上我们的更新进度,多动手实操,并记录学习体会。
 
最后,希望我们都能好好学习,更上层楼!