代码重构:面向单元测试
作者 | 杜沁园(悬衡)
重构代码时,我们常常纠结于这样的问题:
需要进一步抽象吗?会不会导致过度设计?如果需要进一步抽象的话,如何进行抽象呢?有什么通用的步骤或者法则吗?
单元测试是我们常用的验证代码正确性的工具,但是如果只用来验证正确性的话,那就是真是 “大炮打蚊子”--大材小用,它还可以帮助我们评判代码的抽象程度与设计水平。本文还会提出一个以“可测试性”为目标,不断迭代重构代码的思路,利用这个思路,面对任何复杂的代码,都能逐步推导出重构思路。为了保证直观,本文会以一个 “生产者消费者” 的代码重构示例贯穿始终。最后还会以业务上常见的 Excel 导出系统为例简单阐述一个业务上的重构实例。阅读本文需要具有基本的单元测试编写经验(最好是 Java),但是本文不会涉及任何具体的单元测试框架和技术,因为它们都是不重要的,学习了本文的思路,可以将它们用在任意的单测工具上。
不可测试的代码
程序员们重构一段代码的动机是什么?可能众说纷纭:
代码不够简洁?不好维护?不符合个人习惯?过度设计,不好理解?
这些都是比较主观的因素,在一个老练程序员看来恰到好处的设计,一个新手程序员却可能会觉得过于复杂,不好理解。但是让他们同时坐下来为这段代码添加单元测试时,他们往往能够产生类似的感受,比如
“单测很容易书写,很容易就全覆盖了”,那么这就是可测试的代码;“虽然能写得出来,但是费了老大劲,使用了各种框架和技巧,才覆盖完全”,那么这就是可测试性比较差的代码;“完全不知道如何下手写”,那么这就是不可测试的代码;
一般而言,可测试的代码一般都是同时是简洁和可维护的,但是简洁可维护的代码却不一定是可测试的,比如下面的“生产者消费者”代码就是不可测试的:
public void producerConsumer() {
BlockingQueueblockingQueue = new LinkedBlockingQueue