例分析有人会疑问,我凭什么可以给别人讲这些经验,我自己为此有什么成功的案例呢?所以现在来讲讲我做过的几个东西,以及我亲眼目睹的测试教条主义者们的失败案例。
Google很多人可能听说过我在 Google 做的 PySonar。当时 Google 的队友们战战兢兢,说这么高难复杂的东西要从头做起,几乎是不可能的。特别是某位队友,一开头就吵着要我写测试,一直吵到最后,烦死我了。他们为什么这么担心呢?因为对 Python 做类型推导是非常高难度的代码,需要相当复杂的数据结构和算法,需要精通 Python 的语义实现。
作为一个训练有素的专家,我没有在乎他们的咋呼,没有信他们的教条。我按照自己的方式组织代码,进行精密的思考,设计和推理,最终在三个月之内做出了非常优雅,正确,高性能,而又容易维护的代码。PySonar 到现在仍然是世界上最先进的 Python 类型推导和索引系统,被多家公司采用,用于处理数以百万计的 Python 代码。,
如果我当时按照 Google 队友的要求,采用已有的开源代码,或者过早的写了测试,别说无法在三个月的实习时间之内完成这个东西,就算折腾好几年也没有可能。
Shape Security这种思维方式最近的成功实例,是给 Shape Security 做的一个先进的 JavaScript 混淆器(obfuscator)和对集群(cluster)管理系统的改进。不要小看了这个 JS 混淆器,它的混淆能力要比 uglify 之类的开源工具强很多,也快很多。它不但包含了 uglify 的变量换名等基本功能,而且含有专门针对人类和编译器的复杂化,使得没人能看出一点线索这个程序到底要干什么,让最先进的 JS 编译器也无法把它简化。
其实这个混淆器也是一种编译器,只不过它把 JavaScript 翻译成不可读的形式。在这个项目中,由于失之毫厘就可以差之千里,我采用了从 Chez Scheme 编译器学过来的,非常严密的测试方法。对每一个编译器的步骤(pass),我都给它设计一些正好可以测到这个步骤的输入代码(比如,具有函数定义的,for循环,try-catch的,等等)。Pass 输出的代码,经过 JavaScript 解释器执行,把结果跟原来程序的执行结果对比。每一个测试程序,经过每一个 pass,输出的中间结果都跟标准结果进行对比,如果错了就表明那个 pass 有问题,出错的小程序会指出大概是哪一个部分出了问题。遵循小巧,不冗余,不重复的原则,我总共只写了40多个非常小的 JavaScript 程序。由于这些测试涵盖了 JavaScript 的所有构造而且几乎不重复,它们能够准确的定位到错误的改动。最后,这个 JS 混淆器能够正确的转换像 AngularJS 那么大的项目,确保语义的正确,让人完全无法读懂,而且能有效地防止被优化器(比如 Closure Compiler)简化掉。
相比之下,过度鼓吹测试和可靠性的人,并没能制造出这么高质量的混淆器。其实在我进入团队之前,里面的两三位高手已经做了一个混淆器,项目延续了好多个月。这片代码一直没能发布给客户用,因为它的换名部件总是会在某些情况下输出错误的代码,修改了好多次仍然会出错。不是100%的正确,这对于程序语言的转换器来说,是不可接受的。换名只是我的混淆器里的一个步骤,它还包含大概十个类似的步骤,可以把代码进行各种转换。
在实现换名器的时候,队友们让我直接拿他们以前写的换名代码过来,把 bug 修好就可以。然而看了代码之后,我发现这代码没法修,因为它采用了错误的思路,缝缝补补也不可能达到100%的正确,而且明显效率低下,所以我决定自己重写一个。由于轻车熟路,我只花了一下午的时间,就完成了一个正确的换名器,它完全符合 JavaScript 的语义,各种奇葩的作用域规则,而且结构非常简单。说白了,这个换名器也是一种解释器。对解释器的深刻理解,让我可以很容易的写出任何语言的换名器。
Google很多人可能听说过我在 Google 做的 PySonar。当时 Google 的队友们战战兢兢,说这么高难复杂的东西要从头做起,几乎是不可能的。特别是某位队友,一开头就吵着要我写测试,一直吵到最后,烦死我了。他们为什么这么担心呢?因为对 Python 做类型推导是非常高难度的代码,需要相当复杂的数据结构和算法,需要精通 Python 的语义实现。
作为一个训练有素的专家,我没有在乎他们的咋呼,没有信他们的教条。我按照自己的方式组织代码,进行精密的思考,设计和推理,最终在三个月之内做出了非常优雅,正确,高性能,而又容易维护的代码。PySonar 到现在仍然是世界上最先进的 Python 类型推导和索引系统,被多家公司采用,用于处理数以百万计的 Python 代码。,
如果我当时按照 Google 队友的要求,采用已有的开源代码,或者过早的写了测试,别说无法在三个月的实习时间之内完成这个东西,就算折腾好几年也没有可能。
Shape Security这种思维方式最近的成功实例,是给 Shape Security 做的一个先进的 JavaScript 混淆器(obfuscator)和对集群(cluster)管理系统的改进。不要小看了这个 JS 混淆器,它的混淆能力要比 uglify 之类的开源工具强很多,也快很多。它不但包含了 uglify 的变量换名等基本功能,而且含有专门针对人类和编译器的复杂化,使得没人能看出一点线索这个程序到底要干什么,让最先进的 JS 编译器也无法把它简化。
其实这个混淆器也是一种编译器,只不过它把 JavaScript 翻译成不可读的形式。在这个项目中,由于失之毫厘就可以差之千里,我采用了从 Chez Scheme 编译器学过来的,非常严密的测试方法。对每一个编译器的步骤(pass),我都给它设计一些正好可以测到这个步骤的输入代码(比如,具有函数定义的,for循环,try-catch的,等等)。Pass 输出的代码,经过 JavaScript 解释器执行,把结果跟原来程序的执行结果对比。每一个测试程序,经过每一个 pass,输出的中间结果都跟标准结果进行对比,如果错了就表明那个 pass 有问题,出错的小程序会指出大概是哪一个部分出了问题。遵循小巧,不冗余,不重复的原则,我总共只写了40多个非常小的 JavaScript 程序。由于这些测试涵盖了 JavaScript 的所有构造而且几乎不重复,它们能够准确的定位到错误的改动。最后,这个 JS 混淆器能够正确的转换像 AngularJS 那么大的项目,确保语义的正确,让人完全无法读懂,而且能有效地防止被优化器(比如 Closure Compiler)简化掉。
相比之下,过度鼓吹测试和可靠性的人,并没能制造出这么高质量的混淆器。其实在我进入团队之前,里面的两三位高手已经做了一个混淆器,项目延续了好多个月。这片代码一直没能发布给客户用,因为它的换名部件总是会在某些情况下输出错误的代码,修改了好多次仍然会出错。不是100%的正确,这对于程序语言的转换器来说,是不可接受的。换名只是我的混淆器里的一个步骤,它还包含大概十个类似的步骤,可以把代码进行各种转换。
在实现换名器的时候,队友们让我直接拿他们以前写的换名代码过来,把 bug 修好就可以。然而看了代码之后,我发现这代码没法修,因为它采用了错误的思路,缝缝补补也不可能达到100%的正确,而且明显效率低下,所以我决定自己重写一个。由于轻车熟路,我只花了一下午的时间,就完成了一个正确的换名器,它完全符合 JavaScript 的语义,各种奇葩的作用域规则,而且结构非常简单。说白了,这个换名器也是一种解释器。对解释器的深刻理解,让我可以很容易的写出任何语言的换名器。