4. 调试的思维与逻辑
当代码文件数量变动,代码量也跟着变多的时候,很容易出bug,有bug的地方就需要调试。
首先要在bug发生的时候我们要先观察它的表现,如果有日志或者出错信息的直接看出错堆栈。如果没有的情况下收集案发现场表现,进行初步排查。这边很重要的是要练习去猜测问题的答案,这方面主要是结合最近团队成员的代码变更以及对项目本身代码的熟悉程度进行。
对于可以重现bug,通过多次重现收集它的信息,将问题进行2分缩小,直到问题被最终定位。对于不可重现的bug,思考如何在发生异常的时候及早将错误的代码断下,可以使用我们前面说过的asset进行断言。
分析过程主要依赖于2个思维:
正向思维
逆向思维
举个简单的例子,点击一个按钮产生了一个不在预期的飘字。
对于正向思维而言,我们可以在代码中找到按钮对应的响应事件,从响应事件中找是否有飘字的代码出现,递归直到找到该飘字。阅读代码主要通过命名的识别以及关键的数据结构进行快速浏览,配合函数引用以及文本查找。
对于逆向思维而言,我们可以在飘字的地方下断点,当重复操作使该飘字代码断下后,沿着堆栈找它是在哪里被调用的。
在一个bug的处理过程,我们需要交叉使用正向和逆向思维去排查问题。通过正向思维去简单获得bug的上下文信息,通过逆向思维去排查。简单或者少量的代码(300行左右的代码问题),建议直接采用正向思维。对于复杂的代码,建议2种思维并用。
那是个发版前的夜晚,小A一直愁眉苦脸的坐在电脑前,看着一个一个代码文件和禅道的一个bug单,他还是妥协了,决定找老A帮忙解决问题。有个问题是这样的:在一个窗口使用了一个道具,正常情况使用后对应的显示道具数量的地方就会发生变化。但是使用后,窗口上面的道具数量还是没变。
老A坐到小A的位置上,准备复现看看什么问题。老A使用命令添加了一个道具,使用后,正如小A的描述,对应系统界面的道具数量没有变化,老A再点了一次使用,客户端提示:该道具数量不。很明显,老A在收集案发现场中的有用信息。老A立刻断定数据层是对的,因为第二次点击的提示是对的上数据本身的变化的,所以这个问题应该是界面问题。
将问题锁定在界面之后,老A找到数据层变更时界面注册的响应。在响应函数中下个断点,再次重复操作重现这个异常表现。断点被断住了,说明这个事件已经被窗口响应到了。那么问题就进一步缩小在这个响应代码里面了。这时候该正向思维出场了,快速阅读代码,发现这个道具可能触发不同的刷新行为。当出现分支的时候,在代码中也就是if else这样的东西,通常我们要高度警惕,bug往往就因为执行流程走了其他的分支导致。这时候我们可以使用正向思维也可以使用逆向思维继续排查。对于正向思维,我们要在断点之后一步一步执行代码流程,专业术语叫单步执行。看看在哪个分支代码执行流程与我们的设想不符。对于逆向思维,我们可以在所有的if else分别下断点,看看断下的地方哪个不是我们预期的。当分支很多的时候,如果没有新的信息支撑我们下断点,我们就需要下大量的断点且可能会遗漏一些分支的执行流程。这个时候我们比较推荐的是使用正向思维,它有助于更多的收集信息。
从上面的例子我们可以看到,逆向思维的排查速度更快,依赖于有效的断点信息。正向思维排查速度慢,但有利于收集到更多的代码信息,加深对代码的理解。所以我们总是切换着使用它们。
我们再来看另外一个案例。小C跟着团队转去做h5,开始学着使用白鹭引擎。在ui的上手过程中,他遇到了一个问题。有A,B两个界面,B在A的上面。小C希望B界面不要响应而A界面正常响应。大部分的时候是正常的,但是当B界面上面有个图片的时候,A界面就无法得到响应了。小C严重怀疑那个图片把响应事件吞了,他把该图片的touch也及时触摸相关的设置都设置为了false,也就是不响应触碰事件,但依然有这个问题。这时候小C就懵逼了,于是只好求助老A。
老A听了小C的描述,没有立刻去找问题,他先确定了小C所描述的情况都是正确的(如果描述者的性格是严谨的,那么我们可以花较少的时间去确认当前情况。如果不是,我们第一步就要先验证现象)。通过确认,属性以及相关设置都是对的。小C怀疑可能是引擎问题,立刻去升级了测试。鉴于这是个基础问题,所以老A觉得引擎不会也不应该犯这种基础错误,所以他怀疑是别的地方,这是个经验判断。
重新审视了现有的条件,存在正常的点击和不正常的点击。那么,我们可以通过正常点击,并使用逆向思维在函数响应的地方下断点获取到正确的调用路径(堆栈)。在调用路径上依次下断点,重复不正常的点击。哪个断点没有触发,就是对应的分支出了问题。
整个操作过程如下:
先在正常的A界面的响应里面下个断点(如下图所示):
断点断住后获取到堆栈(如下图所示):
堆栈上有7个函数,从上往下,依次在调用的地方下断点。然后再点击B界面上面的图片,看看哪个断点没有正常触发,没有正常触发的地方就是异常的地方。定位到异常的地方后,再次使用正向思维,可以快速的找到异常的根本原因。最终,这个问题的原因是触摸事件被别的对象截获了点击事件。这是逆向思维为主导的一个应用。
在我们的日常开发中,要提高调试能力实际上是不容易的。大量的错误,大部分的重复性问题,比如说打字错了,忘了窗口是异步加载等。调试能力的训练需要在我们完成手头开发任务的情况下,主动去禅道或者项目bug的平台上去找一些自己认为能提高能力的单子。主动反复通过这些单子去训练,可以慢慢的积累解决bug的推理能力。当然,最终的提高是通过总结这些bug的来源,在系统上提出解决方案来规避它的出现。解决只是手段,规避是核心。主动去接单修复是提高bug调试能力的一个重要态度。
极端的bug,也就是很多程序程序口中诡异的bug。这类bug复现比较难,我们可以通过输出日志的方式来记录它的行为。在下次发生的时候,通过这些输出来进行推理,就像柯南破案一般。但是这样依然可能没有办法获得有效信息。这时候要做的就是细化,细化我们的输出,达到输出信息可以有效进行分析为止。所谓的可以有效分析,需要输出这个bug发生时候的上下文,包括局部变量,对象成员,甚至是全局变量。通过这些信息来尝试使用逆向思维。如果依然无法解决,那么就转用正向思维来收集信息。