想写这篇博客的起因是看到王垠同学的博客中提到了之前在 Google 工作的时候,他完成了自认为无比艰难,代码却写的无比清晰的工作,然而 code review 过程中他的同事却逼迫他写测试。他认为,代码写的“Obviously no bug”,就不应该再强调测试了。

这里王垠犯了一个“Scientist”在“Engineering”中经常会犯的一个错误:忽视合作。我完全不怀疑王垠可以写出漂亮、优美、清晰易懂的代码,但是在工程中,一个人是写不出完整的 Google Maps 服务的,你需要跟无数的人合作,这就意味着:

  1. 别人可以容易地读懂你的代码。你可能拥有别人所没有的领域知识,包括对某些计算机理论的理解,或是对某个系统组件的了解。这使得某些你看上很明显的设计,在他人眼里是很不直观甚至是错误的设计。测试可以帮助他人从组件所期待的输入输出来理解它。
  2. 别人可以信赖你的代码。你的测试已经保证了,在这样一个输入下,可以得到这样一个输出。那么我重用你的组件的时候就知道如果我给你同样的输入,也可以得到同样的输出,我就不必去阅读和理解你具体如何实现的了。这对于中小型组件来说很有效。但对于大型组件来说,由于输入输出更为多样化,详细的文档才能解释清楚。
  3. 别人可以放心修改你的代码和你依赖的代码,而不必担心会搞坏现有的系统。这是个人认为大型软件公司必须要解决的问题。在 Google,任何一个工程师都可以看到整个代码仓库里 99% 的代码,修改它们,并向合适的人做审核申请提交。比如说一个工程师阅读代码的时候,发现一个基础组件的一个方法声明了不接受 null,但是没做检查。他加上一个检查很容易,但如何知道整个系统里会搞坏哪个组件的行为呢。没人会想要遍历所有调用它的组件(还有调用调用它组件的组件)检查是不是有人做错事了。这种消耗大量人力的检查,机器却可以任劳任怨。所有受影响的组件都跑遍测试便可找到绝大多数的错误。如果你没有写测试,或是测试没有触及这种情况,那没办法,系统 down 掉了,你只能默默地 debug,看到底哪个假设发生了变化。

所以我常说,测试是给其他人写的,包括未来的你自己。

在软件领域,我的定义里一般有三种人,科学家,工程师和艺术家。这些人都写代码,但是目的迥然不同。 科学家想要理解目标,他们写代码注重抽象,用最一般化的符号来解决最复杂的问题; 工程师想要搞定目标,他们写代码注重效率,用尽可能短的时间实现尽可能大的系统; 艺术家想要使系统优美,他们写代码会注重形态,用干净直观的方法来实现目标,消除重复。