不需要强类型, 需要强测试?

这是Joel的观点, 据文章中说, Bob大叔也是这个观点。 他的这篇文章 (应该是他引用并做了个前言的这篇Bruce Eckel的文章,多谢J老兄指正)认为编译器的检查, 实际上就是编译期的测试。 而且他俩, 据说都达到了“测试成瘾”的境界。

我当然没他俩牛, 考虑到Knuth他老人家也已经那么大年纪了, 程序正确性自动化证明工作现在又是起步阶段,也就没必要拉别人壮胆了。很多人说我是要特立独行, 我自己早就说过, 那我就是哗众取宠好了。不过要说明的一点是, 我并不喜欢当少数派, 我只是希望有一个清晰的概念, 任何不清晰的, 就是我要发问的; 所以反调还是要唱下去:

1. 编译器甚至运行时的检查, 很难说是测试。 如果测试和检查是一码事, 就根本不会是两个词了。 检查是如下过程: 我们描述一个东西A, 它具有特性B和C,那么所谓的检查, 就是观察一下是不是有B和C。 而测试呢,则是我不管什么A、B、C,先用一下, 如果没有用成功,我就给出一个错误报告。无论在运行时,还是在编译期, 如果是前者, 就是检查, 如果是后者就叫测试。

所以即使是运行期类型检查, 仍然是检查, 不能理解为测试(当然,那些为了执行运行时检查所写的代码本身是测试)。 另一个例子是汽车碰撞的测试, 我们无法找出一个办法知道一辆车的强度是不是能够承受多少公斤的力, 所以我要撞一下它。 如果有测量方式, 让我无需撞一下, 那么这就是检查了。

2. Joel有道理的地方在于,检查并不能找出所有错误: 比如过程代码在逻辑上的错误。因为这些过程代码或者逻辑, 是无法被测量的。就好比, 假设有一个不完备的检查车辆强度的方法, 你测量一下, 觉得行了, 没有做碰撞测试; 但是既然隐藏着你没有考虑到的因素, 则很有可能在撞车时,人还是被夹死了。但我个人认为, 这不是干脆全盘用测试代替检查的理由。

比如,很多对日系车的评价, 就从另一个方面体现了这些问题。 比如用吸能来保证成员的安全这件事, 日系车总是用NACP几颗星来说, 也就是用测试说话。 如果测试不是面面俱到的, 比如某种特殊角度的撞击没有测试, 当这种撞击发生的时候, 很多事情就一点保证也没有了。而欧系车在吸能的同时,还增加车体的刚性,虽然也不能说特殊角度的碰撞就一定不会烂,但是总能通过一些推导得知会不会有改进。

所以, 这里面存在三个概念:

1. 完全性。 无论是测试, 还是检查, 都存在完全性的问题。 出不出问题, 不是看你是检查还是测试, 而是看你是不是完全。

2. 可测量性。 只有可测量的东西, 才能用检查完成, 而剩下的则有可能需要使用测试。 为什么说有可能呢?

3. 可推导性。因为某些情况下, 从可测量的东西可以推导出不可测量的东西的情况, 这样就无需测试了。

我想这应该是一个比较明确的、 “回”字有多少种写法的描述了。 那么继续。

关于完全性的问题, 实用主义出发的,我的疑问目前有两点: 单元测试的真实覆盖率是不是真的能做到完全? 见《 再问TDD: 扩散角模型》。 单元测试造成的重复表达问题, 见《 三问TDD: 单元测试总是好的吗?》; 另外, 对结果进行采样式的确认, 并无法保证被测目标的逻辑100%正确。 暂时还不实用的、我在研究的问题:对于所有运行前就可以确认的东西,检查是不是真的不能做到完全?

这就涉及到了第二点, 可测量性。 而一个设计、一段程序的可测量性, 从根本上讲是确定的。 以我目前的见识看来, 是否可测量, 除了测量目标的特征之外,在于我们使用的工具(如某一种编程语言)是否支持对那些符合特征的目标进行测量。主要是还是我们能不能把这些运行前可以确认的东西表达到系统内部, 不能表达或者工具忽略它们, 导致不能测量, 那就是现有工具的错, 而不代表这些地方应该被测试占领。 (另外,可表达还代表着这些知识的可使用, 而测试是外部的, 也丧失了这种好处。)

然后我们从易用的角度来看看第三点,是不是足够好用的语言, 就必须使用测试或者动态检查: Joel的文章, 主要提到duck typing等设施的方便之处, 我想这个又是一个混淆, 这些易用没有一样是放弃静态检查带来的。 duck typing和其它设施本身并不会造成真正的未知。 duck typing和其它设施,并不会造成在任何情况下都未知的情况(多谢脑袋的提醒)。 因为哪怕可以临时的扩展比如一个类的方法, 只要这些是写在代码中的,并且不和运行时造成的变化纠缠在一起,就已经留下了足够的信息, 而导致剩下的事实变成可推导的。

也就是说:只要有足够的信息, 并且这些信息是 机器可获取、可理解的, 至少一般来说要么可测量、要么可推导。而所有可测量或者可推导的问题的验证,都可以放到运行前进行。那么导致当前流行的表达工具, 要么选择运行时检查甚至不检查,要么表达能力有缺陷, 其问题是什么呢?

最终的实现不同,我想这是因为不同的作者关注点不同的缘故。不过,也许是实现成本: 现在很多人都在说编译器前端没难度, 我想这很可能是因为大多数编译器前端处理的问题还不够多; 另一个问题是编译计算的成本, 很显然, 如果我改了两行代码, 计算了3分钟, 告诉我是错的, 那我还不如试一试算了。 很多人认为占用CPU时间比占用程序员值, Joel就是这么说的; 但他也承认, 如果动态检查, 则可能导致比如一个Web服务器或者集群提供需要10倍的计算能力。 在这方面,Knuth认为(虽然不是在这个问题上), 如果能改进一次, 永久受益, 那么程序员付出代价是值得的。

另一方面, 是对现有方式的保护, 就好比HTML的缓慢进化一样, 一个新的表达工具, 不可能突然冒出来, 并且大家都很快的掌握它。 从另一个角度讲, 对大型商业公司而言, 这样的更新换代, 可能比从C# 1.0直接到C# 4.0还要大一些; asp -> asp.net,就让很多人投靠了php阵营。不过这些也只能当作一个八卦讲讲而已。有一点是确定的: 实现一个有很大不同的表达工具,如何保留老的软件资产和知识资产, 确实是一个不得不关注的问题。

我有时候会把问题归结为一个统一的可行性问题: 要么是, 要么不是; 也许我太机械了,还是要具体问题具体分析。 但是Joel本人也曾一天到晚的思考, 是不是有一个最佳的方法,可以尽量少的付出代价, 尽量多的得到不同做法的优点; 不过他、Fowler、Robert Martin, 似乎已经都放弃了, 他们放弃的对吗?



不同的人如何阅读这篇文章:

1. 这些都不关心的兄弟, 点右上角的X, 或者骂两句也可。

2. 对如何舒服、合理的进行测试,测试、检查以及正确性之间的关系有见解的兄弟,请不吝赐教。但我知道TDD不只在于保证正确性, 本文涉及的仅仅是一个很小的方面,不要扩展它。

3. 不关心用不着的东西的兄弟, 可以通过我这篇文章的脉络, 看看有哪些词汇是未来可能要了解的;这些词汇包括Python/单元测试/duck typing等等, 他们所代表的一些特征, 很多已经开始流行了;其它的,几乎可以肯定说,很快也会出现在.NET平台上。而比如: 自动推导、机器验证等,就当没看见。

4. 正好对这个话题本身感兴趣的兄弟, 跟你们就没啥可多说的了,我自己准备好小板凳,等待上课 :)。



题外话:

最后在征集一次英文文章的合著者, 可惜我英文水的不行, 又不想写一篇文章1个小时, 翻译10个小时。我知道社区里大多数人对这些问题不感冒, 还有以为我仅仅是在对流行趋势唱反调的; 但是据我观察, 国外有不少爱讨论这些的社区,我想去试试。 我的目标是, 收集出一个当前表达工具支持度还不够、 而实际上是有改善的可能的特性列表, 也许我比较菜, 不能够实现这个列表上的东西, 但是我想即便是这个收集和论证工作本身也有些好处。

大家可能觉得我像个牛皮糖,天天琢磨不能让任何人挣钱或变轻松的东西, 还在别人耳边嗡嗡,一点也没有自知之明; 但是第一是我确实感兴趣, 第二是我也相信自己讨论这些的意义。 不是说我对那些刺耳的话一点感觉没有, 说实在的, 我每次都感觉很难受。 我过去有个想法, 就是大家说中文, 这是一个非常大的优势; 老外的东西大多咱们都看得懂, 但是咱们的东西老外看不懂。

如果咱们能通过有讨论者讨论、有实践者真干这一系列过程, 在一些虽然大多数人不会接触的关键领域作出突破, 那我们真有可能走在世界前面,尤其是软件学科是唯一一个不要求基础工业的学科了。 作为使用者, 不接触这些领域, 并不代表没有资格参与讨论; 而且只要敢于去想、去要求,也肯定能提出甚至重量级的意见。唯一一点小小的要求就是感受力: 感受到不爽的能力。

虽然就算有这种领先也不会持续多久, 但仍然会给我们挣点面子; 从投资环境讲, 如果能争取到这种声誉, 对我们每一个IT从业人员的就业等各个方面, 都会起到一些作用。 可能有人又要说了, 这种大而无当的事情, 根本不是你怪怪应该考虑的...

你可能感兴趣的:(测试)