2.5 足够多的眼睛,就能驯服复杂性
从宏观上说,集市模式能极大加速调试和代码迭代,这好理解。但要准确说清它在开发者、测试者日常行为的微观层面是怎么起作用的、为什么起作用,就是另一码事了。 在本节(写于原论文三年后,参考了许多开发者重读论文、审视自身行为后得出的见解)中,我们将深入考察它实际的机制。没有技术背景的读者大可放心跳到下一节。
要理解这一点,关键是想通一个问题:不懂源码的用户,他们提交的 bug 报告为什么往往没多大用?这类用户通常只报告表面症状,因为他们把自己的环境当作理所当然。于是:(a)会漏掉关键的背景信息;(b)很少能给出一个靠谱的、能重现 bug 的步骤。
这背后的根本问题是,测试者和开发者对程序的心智模型不匹配。测试者是站在外面往里看,开发者则是站在里面往外看。在闭源开发里,双方都被困在自己的角色里,常常鸡同鸭讲,都觉得对方简直不可理喻。
开源开发打破了这种僵局,让测试者和开发者容易得多地建立起一套基于实际源码的“共同语言”,并在此基础上有效沟通。实际上,对开发者来说,一份只描述表面症状的 bug 报告,跟一份能直接对上他基于源码构建的心智模型的报告,利用价值有很大的差别。
大多数时候,绝大多数 bug,只要有人在源码层面给出一个哪怕不完整、但有启发性的描述,就很容易被揪出来。比如,当你的某个测试者能够指出“第 nnn 行有个边界问题”,或者哪怕只是说“在 X、Y、Z 条件下,这个变量的值会溢出”,那么通常瞟一眼出问题的代码,就足以锁定具体的出错模式并给出修复了。
所以说,双方都能看懂源码,这既能大大改善沟通,也能让测试者提交的报告和核心开发者掌握的知识形成强大的合力。这样一来,即便有很多人参与协作,这种合力也能很好地节省核心开发者的时间。
开源模式还有一个能节省开发者时间的特点,体现在它独特的沟通架构上。前面我用过“核心开发者”这个词,它反映了一种区分:一边是项目核心,通常非常小,一般是一个人,典型情况也就 1-3 人;另一边是项目外围,由测试者和潜在贡献者组成,这个圈子常常有几百上千个人。
传统软件开发组织面对的那个根本难题,就是布鲁克斯定律:“向已经延期的项目里加人,只会让它更延期。”更宽泛地讲,这条定律预言,项目的复杂度和沟通成本会随开发者人数呈平方级增长,而干完的活儿只会呈线性增长。
布鲁克斯定律的基础,是这样一条经验:bug 总爱扎堆出现在不同人写的代码的衔接处;而项目的沟通协调开销,又往往随人际接口的增多而增加。这样一来,问题规模就随开发者之间沟通路径的数量一起涨,而沟通路径的数量正是开发者人数的平方(更精确地说,公式是 N * (N – 1) / 2,其中 N 是开发者人数)。
布鲁克斯定律的分析,以及由此产生的对大团队恐惧,其实都基于一个隐含的前提:项目的沟通结构必定是个“完全图”,也就是人人都和其他所有人直接沟通。但在开源项目里,外围开发者干的活儿实际上是互不干扰、可并行的子任务,他们彼此很少打交道。代码变更和 bug 报告都流向核心小组,只有在这个小小的核心圈里,我们才需要付出完整的布鲁克斯式开销。6
源码级 bug 报告之所以往往这么高效,还有其他原因。这主要围绕一个事实:一个 bug,往往能表现出多种不同的症状,具体表现取决于用户的使用习惯和环境细节。 这类 bug 恰好就是那种最复杂、最微妙的(比如动态内存管理的 bug,或者中断窗口带来的随机异常),最难靠随机重现或者静态分析锁定,也最容易给软件造成长期隐患。
如果测试者能为这种多症状的 bug 提交一份试探性的源码级特征描述(比如“在我看来,信号处理代码第 1250 行附近存在一处时序窗口问题”,或者“你那缓冲区是在哪儿清零的?”),这或许就能给开发者一个关键线索——要不然,开发者可能离代码太近,反而“当局者迷”。这一条线索,也许就能解释五六种看似八竿子打不着的症状。这种情况下,也许很难甚至不可能分清哪个表面故障是由哪个具体的 bug 引起的——但因为软件版本发布频繁,这根本不重要。其他协作者很快就能知道他们遇到的 bug 有没有修复。很多时候,源码级的 bug 报告会让一些故障悄无声息地消失,甚至从来没定位到某个具体的修复上。
复杂的多症状错误,从表面症状追溯到真正 bug 的路径也往往有好几条。 某位开发者或测试者能追踪到哪条路径,这取决于他自身环境中一些微妙的细节,而且这种关联还可能随时间以一种不是那么确定的方式变化。实际上,每个开发者和测试者在排查 bug 根源的时候,都像是在程序的状态空间里进行半随机的抽样。Bug 越微妙、越复杂,个人技能就越难保证他抽到的那份样本管用。
所以,对于简单且好复现的 bug,重点落在“半”上,而不是“随机”上;此时调试技巧和对代码及架构的熟悉度就至关重要。然而对于复杂的 bug,重点就落在“随机”上了。这种情况下,许多人同时跑追踪,效率远比少数几个高手一个个地接力追踪要高——哪怕那几个高手的平均技能水平高得多。
如果从不同表面症状追溯到同一个 bug 的各条路径,它们的难度差异巨大,而且这种差异无法从表面症状判断出来,那么上述并行追踪的效果就会被极大地放大。如果一个开发者自己一条条地试这些路径,他第一次就撞上一条高难度路径的几率,和撞上简单路径的几率差不多。反过来,假如很多人一边并行尝试,一边快速发布新版本,那很可能其中一个人会立刻发现那条最容易的路径,然后用更短的时间就把 bug 给揪出来。项目维护者会看到这个修复,发布新版本,而其他正在追同一个 bug 的人就能适时停手,不必在难度更高的追踪路径上耗费过多精力。7
-
把开源项目那种典型的“核心+外围”组织,看作是布鲁克斯为解决 N 平方复杂度问题所提出的“外科手术团队”在互联网时代的一种变体,这想法很诱人,也并非完全没道理——但两者的核心差别其实非常大。 布鲁克斯设想围绕团队领袖配置的“代码管理员”等专家角色,现实中其实并不存在;这些活儿是由通才借助比布鲁克斯时代强大得多的工具链来完成的。另外,开源文化深深根植于 Unix 在模块化、API 和信息隐藏方面的强大传统——而这些,没一样在布鲁克斯的药方里。
-
有读者提出,不同追踪路径的复杂程度差异,会加大我们判断故障特征的难度,同一个 bug 多个症状的追踪难度,可能呈“指数级”分布(我理解为高斯或泊松分布,也觉得这非常合情合理)。 要是能通过实验摸清这种分布的形态,那数据将极有价值。一旦追踪难度的分布显著偏离平坦的等概率状态,就意味着哪怕是单打独斗的开发者,也应该效仿集市策略:给单个症状的追踪设个时限,时间一到就换下一个。执着有时候还真不一定是美德……