深圳幻海软件技术有限公司 欢迎您!

假如我能在一天内解决任意bug……

2023-02-27

在某问答平台有个有意思的问题:假如任何bug都能被我在一天内定位并给出修复方案,在编程领域我能混成什么地位?除开一些搞怪的回答外,有些人觉得,把这项技能用到世界级的伟大项目中,可以创造巨大价值。我发现,大家对bug的定义是多样的、模糊的。主要分为两类:观念意义上的bug,即代码出现任意不满足人的需求

在某问答平台有个有意思的问题:假如任何 bug 都能被我在一天内定位并给出修复方案,在编程领域我能混成什么地位?

除开一些搞怪的回答外,有些人觉得,把这项技能用到世界级的伟大项目中,可以创造巨大价值。

我发现,大家对 bug 的定义是多样的、模糊的。主要分为两类:

  • 观念意义上的 bug,即代码出现任意不满足人的需求的行为,都叫 bug。
  • 程序意义上的 bug,即代码执行时不停机,或者抛出错误,才叫 bug。

如果我们围绕观念意义上的 bug,去讨论这个问题。很容易发散联想,把问题变成超能力的小说创作。

如果我们围绕程序意义上的 bug,去讨论这个问题,会发现,这个能力实际上在做的,是对代码进行静态分析。即,在不运行代码的情况下,对代码的结构进行分析,判断它是否能正确运行。

Type Checking 类型检查技术,可以对代码实现这种静态分析。在特定 Type System 类型系统下,推断出程序接收给定的所有可能输入,能否正确输出指定类型的值。

main: input -> output

将代码简化成 main 函数看待,input 为参数类型,output 为返回值类型。main 函数执行的可能结果如下:

  • 接收 input 类型的参数,在有限时间内返回 output 类型的结果
  • 接收 input 类型的参数,永远执行,不停机,没有返回值
  • 接收 input 类型的参数,执行期间抛出错误(output 类型或者内部中间环节出现类型匹配错误)

根据上述 3 种结果,我们可以对 main 函数或者编写 main 函数的语言,做出分类:

  • function 或 language 的行为包含上述 3 种可能性
  • function 或 language 只有第一种行为,即输出指定类型的 output

我们将第二种称之为 total 的,第一种则是 partial 的。

大家可以理解为,total 就是所有可能输入,都有正确类型的输出,是全量的,无遗漏的。而 parital 则是所有可能输入,只有部分才有正确输出,是非全量的,有遗漏的。

回到那个问题,任何(程序意义上的) bug 都能被我在一天内定位并给出修复方案,可以认为等价于低效的 Type Checker 类型检查程序。

而任何复杂的大型代码库,它里面包含的会引发不停机或者抛错的类型错误,往往数不胜数。

观念上的一个 bug,它对应的程序意义上的 bug 数量可能是很多很多个。

其中大多数 bug 是无关紧要且琐碎的。每次花一整天时间,定位到一个低级的类型问题,然后给出简单的修改,这种效率难以满足有价值的开发工作。

因此,从程序意义上的 bug 来看,一天内定位任意 bug,由于 type check 的精确性,极大概率每次定位到的是代码里首次出现的低级类型错误。随便一个 lint 或 type check 程序,都可以在 1 秒钟找出代码里成千上万的 bug(其中绝大多数是无关紧要,在观念上不构成 bug,在程序上则构成)。

也就是说,假如任何 bug 都能被我在一天内定位并给出修复方案,在编程领域我能混成低效的 Type Checker。

没有完备的类型检查,程序员修复一个 bug 的同时,可能引发另一个 bug,或者另一批 bug 的出现。

大家的代码对严格的 Type Checker 来说错漏百出,为什么它们被那么多公司发布到生产环境,为什么我们大部分人还能相对正常地使用各种技术产品?

这是因为,尽管用户数量级庞大,但绝大部分的产品操作流程,访问路径,行为数据等等,是同质化的,只占程序的 input 类型的极小部分(如 int 数字类型对应的成员数量理论值为无限,实际值根据不同语言有不同边界,数量级通常很庞大。发生数值边界溢出属于少数情况,然而一旦遇到,却是很严重的)。

因此,尽管代码对 Type Checker 来说,是 partial 的,有效的输入,只有极少部分有正确输出。但对用户来说,这极少部分,往往也够用了。

要写出 total 的代码,需要的 type system 能力是可以做数学命题的证明级别的。学习和开发的成本都相对较高。而用相对不那么严谨的语言,用相对不那么可靠的方式编写代码,从现实角度,可以更快地构建出在某个范围内可用的产品,在后续迭代中不断扩大可用范围。

这正是我们能在不完美的代码中前进的原因所在。