最近一个项目因为测试覆盖率不达标,在恶补单元测试。故事基本上是熟悉的老套路:跟许多项目一样,前期赶进度堆积代码功能,对代码质量重视不够。单元测试很少,也谈不上什么重构了。到后来慢慢还债。“还债”过程中的一些所思所得,记录在此。
可读的代码
有位大师说,因为代码最终是给计算机读的,所以傻瓜都能写出代码。真实项目中代码是给人读的,所以只有优秀的程序员才能写出可读性高的好代码。
工作中,我们花在读代码上的时间远远超过写代码的时间。想一想自己读代码时候的感受:
读自己写的代码(疑惑、惊叹、悔恨,各种复杂感受不一而足),
读同事写的代码(想骂人但最好换种方式表达),
读前同事的代码(可以骂人),
读开源项目代码(各种复杂感受。。。)
以C++为例,程序员可以很容易写出炫技的代码,例如在项目中我见过无比复杂的宏,见过无比繁杂的模板类套着模板类,但是很多代码可读性并不高。考虑整个团队人员不一定都是C++高手,还是尽量写朴实无华的代码。正向金庸小说里边的一句话:重剑无锋,大巧不工。
代码容易读,是好代码的第一重境界。大部分程序员经过一段时间,都能够达到这一重境界。
可测试的代码才是好代码
让代码可读不是很困难,例如使用合适的命名,简洁的类和函数,清晰的逻辑等等。写可测试的代码,比写可读的代码要更难,也更重要。
举个例子,C++支持匿名名字空间。曾经有段时间我喜欢将一些utilities实现在cpp文件的匿名空间里边,觉得这样封装很好,细节都不会暴露出来。因为这部分都是实现细节,类的用户不关心这些。而且我觉得这是利用C++语言功能能够达到的一种“美”。后来我认为自己错了。这样写出来的代码从外部来看的确是美 - 很小的接口,不污染命名空间,但是对于单元测试来说,这个就很不美好了。因为这些代码是藏在匿名名字空间里边,测试代码没办法直接访问。如果使用侵入式的方式例如放一个友元类进去,原有的美感嘎嘣一下就破坏光了。所以后来我再也不在cpp文件中写匿名名字空间这类东西。
代码本身的美,和作为一项工程考虑,这两者并不总是和谐统一的。工程上的便利性和可维护性,比代码行级别的美观,更加重要。
将功能代码和单元测试代码作为整体交付
程序员交付什么?大家第一感觉肯定是源代码,仅仅交付源代码是不够的。还有测试和文档,有些情况下还有其他的东西例如教程,视频形式的培训材料等等。这里我们只谈单元测试。
虽然测试驱动开发TDD的理念已经提出很久很久了,但是在我所经历的产品和项目中,运用得并不好。绝大多数情况下还是先实现功能,手动测试好了再去写单元测试。这里边原因有很多,简单地归咎于程序员是不公平的。一个原因是大部分项目,进度都卡的很紧,功能不做完程序员只能加班,而单元测试没有做完没有做好似乎是可以谅解的(至少管理层会这么想)。另一个原因,我认为是开发人员自身对于单元测试的理解不到位。例如有的团队让实习生或者新手写单元测试,而有经验的工程师只写功能代码。这样的错误我和我的团队都犯过,因为我们觉得实习生和新手做不了那么多难度大的事情,先让他们写单元测试上手吧。其实让新人上手的最好方式,是结对编程(此处省略证明过程)。单元测试也是程序员交付的一部分,不管是谁,编写代码都应该自己写单元测试。功能代码和单元测试代码,作为一个整体交付。好的程序员,写出来的单元测试代码也是一等一的。
这就是我对于单元测试的一点看法。如果你有不同意见,欢迎留言私信交流。
网友评论