使用二分法排查问题

数学上的二分法,是一种方程根的近似解法。


如果要求已知函数的根,

那么我们要先找出一个区间,使得异号(正负值不同)。

根据连续函数的中值定理

该区间内必存在方程的一个根。


于是,我们就可以取该区间的中点,逐渐缩小区间,

如果异号,则取新区间为,否则取新区间为

就这样一直迭代,直到达到我们期望的精度为止。


二分法的主要思路是这样的:

(1)找到使函数值异号的区间

(2)在区间内取点试验,缩小范围


幸运的是,这种思想不仅可以用来求解方程,

还可以用于在软件开发过程中排查问题。


基础二分法

我们都知道,排查问题对于开发软件来说,简直是家常便饭,

经常会遇到莫名其妙的突发状况,软件不能运行了。

从一开始我们用C语言写Hello world,这种事情就没有间断过,

导致问题的原因,可能会非常愚蠢,也可能会异常微妙。


稍微长一点的程序,我们就会发现,

刚开始还是对的,中途不知道怎么的就出现了错误。

下面我们来看一下如何借用二分法思想,排查这些问题。


首先,我们要找到“使函数值异号的区间”,

即,找到在什么情况下程序是对的,而在什么情况下是错的。

那么程序出错的原因,肯定在这两种情况中间,我们只要不断缩小范围即可。


对于长程序而言,把程序删完,显然应该是对的,

而整个程序因为其结果不正确是有问题的,

因此,我们就找到了一个“异号区间”,

我们可以清楚的看到两套环境,一套是有问题的,而另一套则没有。


然后我们就可以贴一半代码上去了,看看中间结果是否正确,

如果是正确的,则证明到目前为止,贴的那一半代码是正确的,

否则,这一半代码就是有问题的。


通过界定两套环境,我们就可以大胆的尝试,

从而迅速将问题规模减半,这正是二分法的威力。


极端情况法

从排查代码问题的小例子中,我们看到了一个极端情况,

那就是,我们直接把程序删完,来断定这时候应该是没有问题的,

这种办法,我们可以称为“极端情况法”。


在寻找“异号区间”时,这种办法非常有用。

我们通常会选取一个极端的情况,在这种情况下,绝对不会出现问题。

因为如果不这样,我们经常会在问题环境中折腾很久,结果不论怎么弄都有问题。

此外,如果没有一个极端情况来告诉我们没有问题,就无法二分。


我曾经遇到过一个JavaScript的问题,

有一段代码,在服务器上总是无法运行,提示有非法字符,但是在我本机却没有问题。

我甚至将每一个字符都逐个对比了,服务器上还是有问题。


怎么折腾都有问题,就意味着我们必须选取极端情况,

找到极端的什么情况下,一定是没有问题的。

于是,我将代码都删完了,只写了一个alert(),很明显代码是可以运行的,

我找到了没有问题的环境。


紧接着,我采用二分法,逐渐缩小范围,

最终发现了一个在编辑器中无法显示的字符,是它导致了JavaScript语法错误。


干净实验室法

最近用Lua写了一个脚本,用于转发Nginx中的get/post请求,这是一个稍微复杂的场景,

如果对Lua和Nginx不熟悉,直接写出可用的正确代码简直是不可能的。


因此,我采用了“干净实验室法”,

指的是,重新建立了一套干净的环境用于实验,

在这个“实验室”中检验成功的写法,才会移植到正式的开发环境中。


我先后在这个实验室中,练习并检验了基本的Nginx用法,以及Lua的Hello world

接着,如何用Lua发起http请求,如何保留Cookie,都逐个被验证成功。

最后,我将这些被验证的案例进行总结,集成到开发环境中,

实现了这个稍微复杂一些的功能。


这种“干净实验室法”看起来和二分法无关,

实际上却正是二分法思想的应用。


在解决这个问题的时候,我手里总是有两套环境,

一个没有问题——实验室环境,另一个有问题——功能尚未实现,

通过不断试验,功能被一个接一个实现,逐渐接近成功。


排除变量法

以上我们提到情况中,有问题的场景都是显而易见的,

我们清楚的知道在什么情况下会出现错误。

然而,在另外一些场景中,问题环境就不容易被构造了。


在进行手机移动端开发的时候,

我实现过一个滚动容器,可以通过手指上下移动,让它滚动。

这个功能在iOS上是没问题的,在几乎所有Android手机上也是可以的,

但是仅在某台测试机上出现了问题,容器滚动后不会停止。


这时候会有很多因素干扰到我,

是不是测试机的问题?是不是Android原生浏览器的问题?

是不是代码逻辑的问题?是不是应用中其他库或框架的影响?


这些因素看起来是同时起作用的,因为稍微变动一点,问题就没了。

这时候,我们就需要谨慎的排除变量,小心的保留问题环境了。


使用排除变量法,我们要在其他因素保持不变的前提下,

只变动一个因素,并且还要让现象保留。

于是,我们就可以像抽积木一样,把这些因素逐渐剥离出去了。


最终我们发现,这类现象是Android 1.5机型上原生浏览器的问题,

该浏览器有Bug,点按屏幕的时候,会触发touchstart和touchend事件,这个是好的,

而手指在屏幕上移动的时候,将只触发touchstart和touchmove事件,

touchend事件不被触发了,这是有问题的。


为此,我们排除了框架的问题,代码的问题,某一台测试机的问题,

等等这些非决定性因素。


结语

本文介绍了二分法,它可用于在软件开发过程中排查问题,

还介绍了二分法思想的几个简单应用。


使用“极端情况法”,我们总是可以找到一个没有问题的环境,

使用“干净实验室法”,我们就可以逐渐产出经过验证的代码,

使用“排除变量法”,我们可以一一排除干扰,找出问题的直接原因。


这些方法都是我个人经验的总结,它们使我能更有效的解决问题。

但是,向有经验的人进行咨询,无疑是更有效的。

很多问题在见识广博的人眼中,根本不是问题,问路比速跑好。