软件工程中的“忒修斯之船”

晚上洗衣服的时候刚好碰到一个同学,他说关于他们实验室正在写的软件,如果有什么不懂的能不能来问我。我了解到,他们做的是一个软件的二次开发,于是就对他说:“二次开发最难的不是在你写的东西上,而是在如何对接和使用宿主已有的功能。”我说他们写东西不难的意思是他们仅是一般本科生的水平,实干的是一些类似查询零件信息存入数据库之类的相对简单的活,作为毕业前的一次训练,正是所谓“不掌握核心技术”。我说,二次开发的一种策略是,先写一个最简单的,往工具栏上加入一个按钮,按下之后弹出“hello world”对话框,接下来函数啊属性啊什么的都好办。这个 hello world 确定了三点:

  1. 你的插件(或者类似物)能被正确加载;
  2. 这个插件提供了最基础的输出,一个按钮(显示的信息可用于调试);
  3. 这个插件能和用户进行交互。

然后再在它的基础上开始写功能。就好像,要车,先造出一辆手推车,车架不散,轮子能滚。要加速?装上发动机。要省力?加上轮胎。这样一点一点改进,时间足够最终能造出跑车。怕就怕开始选个方轮子就直接开始造跑车了。

这方法比起测试驱动来说真的是老土多了,不过仍然能给他们上一课。不过今天的重点不在这里,我想的问题是,忒修斯之船的争辩是不是这种“框架式”(包括测试驱动)开发的思想基础呢?

忒修斯之船的悖论产生自人对“整体”、“部分”,以及在第二问中加上的“时间”,这些要素的认知是不统一的。部分有机组合成的整体,是大于部分之和的,大于的部分就是人为赋予的“含义”,比如纪念意义。就拿工业产品而言,具有互换性的产品对于不同人来说还是不一样的,用工具用久了就产生了熟悉感甚至感情,这部分是不属于互换性的、使用中添加的“含义”。忒修斯之船之所以是一艘特定的船,是因为这个问题的提问者已经假设了它的独一无二性。

回到软件工程中。软件的标准之一,来自于算法,是可复现性——给定输入,环境相同,算法不变,输出就不变。在测试进程的眼里,整个程序由测试用例组成,这些用例只有“通过”和“不通过”两种状态。而不管用例内的代码是如何组织的、存在何等的关联,对于测试进程而言它们仍然是独立的用例。在这样的一个设定中,“程序”这个整体是由各个“用例”部分简单列出的,关系是整体等于部分之和。有了无关性保证,只要所有的部分不缺失(测试通过),就可以认为整体是完整的。

所以,当我按下按钮弹出“hello world”的时候,我就可以认为鼠标事件响应是没问题的了。之后无论这个处理函数内写了什么功能,和“可以响应”这个结果都无关。这是我挂在嘴边的“功能上等价”的内涵,也就是一些时候我看到或者做了非常抽象的功能实现,就认为这个结果已经表明最后很可能成功。他们总是将思维局限在当前这个“快照”(snapshot)上,而不是后面代表的设计上,所以不理解简单的示例的意义在哪。

分享到 评论