20131130

bug的定位比修改重要1000倍

bug的定位比修改重要1000倍

砍倒一棵树,当然也有技巧,一斧子一斧子劈得要在正确的位置上,还要喊好顺山倒,别砸到别人和自己。不过,在一片林子里找到你想要的那一棵,才是更重要的事,也更困难。修改一个bug,可能只是一个字符,而在几千几万行代码里找到应该修改的地方,就困难得多。就像,修改一个变量,不过赋值语句一行而已,但是查找之难使得衍生出了像二分查找这样的许多算法。

前两天跟关同学郑同学一起修改了一个bug,也体现了这一点。

1. 现象

先是关同学在电话里说:包老师一眼就能看出来有毛病,出数据的时候一顿一顿的。

这个问题描述的是一个上位机/下位机结构的设备,所谓一顿一顿的,指的是上位机从下位机读了数据以后,显示的时候不流畅,卡。

我们先回顾了一下,先否定了这不是存在已久的同一个问题。以前提到过一次是怀疑优化显示效果的滤波器效果不好,后来证实是选择了错误的滤波器导致的,滤波算法本身无误。后来这个问题从我的视野里消失了一段时间。

这几天对上位机代码重新组织,有大的设计变动,所以关同学怀疑是变动中引入了bug。这是一个典型的正确思考方法:哪里有变动或不同,哪里就需要怀疑。但是实验表明,变动以前的版本也存在同样的现象。 (有效的版本控制多么重要)

以上,我还没有亲眼见到,都是电话里得到的消息。然后我亲眼见到,确实一顿一顿的。这也是解决bug的重要原则,即不能相信任何人的描述,一定要亲眼所见,一定要重现故障。如果病人对医生说"我发烧了",医生一定掏出个体温计测一下,而断然不会直接开退烧药的。

亲眼所见的,也还不够。我祭出神器bushound跟踪了usb的数据。绝大部分数据包都是30ms左右一次,每次实验里总有那么两次是600ms左右。将近半秒的时间,怪不得人类已经能够感觉到了。我们保留bushound的日志,作为证据--好了,bug确实存在。

这是第一步。

2. 猜想-设计实验-再猜想

一旦认定bug存在,接下来是定位bug,最后才轮到修改。

2.1 由整体到部分

定位bug,首先要对整个系统有个认识--模型或者说是猜想。我们以为整个系统是这样运行的,分成几个部分,它们之间如何藕合,数据沿什么样的路径在其间流动,有哪些控制信号。

当我们把整体肢解为部分以后,接下来,我们既可以假设bug可能在某个部分中,也可以一个一个遍历所有的部分,依次查找bug是否在其中。前者,据说是中国/东方的经典手段,后者是西方/德国的特色。传说,同样是一根针掉到地上,中国老太太会根据针掉落的方向、自己的坐姿、声音传来的方向,根据这些启发信息去查到特定位置,而德国老太太会把地板划分为M*N个小格,然后把每个小格找一遍。这两种方法也无所谓优劣,在实践中,有时一下瞎猜对了,确实效率很高,有时则找遍所有的模块,最后才命中。不论如何,事先计划查找的路径,查找中记录走过的模块,是很重要的。

2.2 孤立

在查找某个部分时,我们要把这个部分从整体中孤立出来。调试方法中的"最小系统法""对比法"都有此意。在最小系统法中,我们把其余的部分都去除,单单观察认为可能有问题的部分,确认它正常或不正常,从而避免其他部分的干扰。在对比法中,我们令两个系统其余的部分都是相同的,只有要观察的部分不同,这样,现象的不同就只能来源于要观察的部分的差异--而差异就是导致不同的原因。

对比,既可以是两个系统同时运行,也可以是同一个系统,修改配置以后两次运行。最重要的,保留运行的记录作为对比的证据。

我们首先猜想,是不是下位机有毛病啊,它可能向上传数据时,某些时候比别一些时候慢半拍。下位机是包师弟写的,他说:证据哩。

我见过很多工程师在这种时候开始争论,A说是你的毛病,B说不会,A说一定是,B说一定不是。我在这时对别人说过,这么争论毫无意义,咱们各自拿出证据来吧。他又说一定如何,我就只好走开。

证据,需要隔离其他的部分,把你假设有毛病的部分孤立出来。

我们应该开始剥皮,期待把下位机单独地暴露出来。但事实上,我们当实对下位机的怀疑到此为止,因为包师弟有个神器,他用labview做了第三方的工具,能模仿上位机的行为。所以,他只须出示labview的程序运行的结果无误,就证明了我们的上位机有毛病。这就是对比的力量。

我们转去孤立别的部分。我们只保留上位机读数据的那部分,把解析数据、显示数据全注释了。上位机代码空转,用bushound跟踪读数据的速度。结果表明,即使上位机除了读什么别的也不做,也有间隔600ms的时候。这表明,bug (或者说延迟/lag)不是由解析数据和显示数据引入的。

这时,问题仍可能是由下位机引入的。但是,找bug也可以有条不紊一步一步来,而不是东一榔头西一扫帚想起来哪就查一查。我们继续切掉可能影响性能的上位机代码,直到,就剩了相当于ReadFile的一句。

题外话。我们以前被ReadFile手册骗过,或者说我们没有成熟到那种程度,认识到ReadFile这个API的后面是由驱动支持的。换句话说,驱动如果有毛病,ReadFile的表现就跟手册里写的不一样。我们以前遇到的问题是,实验表明根本没有读到数据,但ReadFile的返回值和参数里的out部分按MSDN里的解释,数据应该读回来了。后来才想到,ReadFile依赖的是我们自己写的驱动,而这个家伙不像我们想的那么靠谱。大多数情况下,它的表现都不错,但是沧海横流的时候,它就被吐跑了,而这正是我们需要它的时候。所以,这个故事告诉我们,实践是检验真理的唯一标准。你以为自己多么正确也是白扯,不能解释客观事实的猜想都只是猜想而已。题外话结束。

这次的ReadFile是阻塞读 (同步读) ,它后面的支持就是郑同学写的winusb驱动的DLL部分了。我们准备在这一句以前和以后各加一个console输出,把ReadFile花费的时候输出来。关同学质疑了一下,console输出影响性能,解除疑虑也容易--试一下呗,万一对性能的影响可以接受呢?瓶颈也许不在这里。

console输出表明,ReadFile性能不稳定,大部分时侯耗时30ms左右,个别时候600ms。个别时候就是我们关注的地方。同时,console输出启用了debug模式,暴露出郑同学在winusb驱动中输出的一些console调试信息在发布的时候没有关闭。这显然会影响性能。

2.3 修改bug和测试

郑同学去除winusb驱动中的console输出,发布了新的驱动。关同学把更新驱动以后,跑了一遍,console和bushound都表明,没有600ms这样的延迟了。

这一步耗时很短。就是按你的猜想改bug,改完了以后测一下,与此前的记录对比。如果毛病没了,那么你十有八九猜对地方了。

2.4 恢复现场和测试

切掉或修复有毛病的地方以后,还要把为孤立这个部分做的工作恢复成原样。我们把解析数据和显示数据都恢复了,去除console显示,再测,bushound表明性能符合要求,bug的现象消失了。

3. 总结

bug的最终解决,上位机代码没有作任何修改,虽然整个bug定位都是围绕上位机代码展开的;只有winusb驱动的代码作了修改,虽然整个定位bug的过程中都没有深入过其中。

这个故事告诉我们,事情发生的背后,往往深藏着很久以前或更加深刻的原因,这些原因在世界运行的法则驱动下,在很久以后或层层推演,才以表象的形式出现在我们的面前。科学和工程的责任,正是猜想然后发现地下暗河存在的证据,通过修改在直觉认识上似乎毫不相关的变量,从而改变这个世界运行的结果。除了人本身不可以被操控,可假此以动太阳而移群星。

--------------------

博客会手工同步到以下地址:



20131116

挺简单的?那你就自己整

挺简单的?那你就自己整

人生在世,会遇到各种奇葩。奇葩们观我,估计也应如是。

二猫妈和大哥分别告试过我,别在工作中掺杂感情因素。但是我只喜欢做乐于付出感情的工作,没感情的,连碰也不想碰,不然有的科目怎么会不及格好几次。所以,只好按马老师教导的"要注意态度",然后自己生闷气。

有些人,仿佛他们生出来就注定了没法跟我合作。衷心希望他们这一生能离我远点,这样他们自己也能过得幸福快乐一些。

一类人是"挺简单的"人。他们在交给你一个任务的时候,乐意像幼儿园老师一样补一句诸如,"这活儿挺简单的,容易干",又或者"你们这么厉害,一下子就能搞定"。我没上过幼儿园,对于幼儿园老师的刻板印像完全是瞎猜的。也许,幼儿园老师也不会把讲话的对方看得如此弱智。

这活复不复杂,简不简单,费不费劲,我自有我的评判,你真是犯不着在这多余罗唆,附加贬低我的工作,我又不会因为你这非专业的评论就降低对报酬的要求。

我第一次回应这句话是对小丁老师,也是因为关系实在不错。此前,我都是忍了,然后在做费用的时候报复性提高价格。小丁老师当时说,"据他 (还是她)说,这个挺简单的。"我说,"一定不简单。要是真简单,他就自己整了。"

后来,我做了些类似的回答模板,视心情回复给对方。比如,要是简单,你就自己整吧;要是简单,你就找别人吧,我忙着呢。

这帮家伙有点像我十多年前遇到的老板们。那些老板习惯性用语是,"你顺便把什么什么做了吧","这个活还挺锻炼你能力的","在这个活里,你还能学到啥啥技能"。用人,还要表示我所用不多。杀人,还要表示我的刀够利,因此你死得不那么痛苦。给你干活,或者等价交换,我还要感谢你怎么的?

也可能有的同学会说,人家的意思是夸你呐。这活儿他本人干着可能不行,但是你干就特顺手。这样想的老板们,适合到马路边找个力工大哥,说,"看你一身腱子肉,闲着也是闲着,帮我把半吨扛楼上去吧。"然后别走,看腱子肉揍他不。

而且,这么说的老板,据我所知,通常真的认为你干的活非常不值钱,任谁都能轻松完成。活,可以干,但是我们绝不能自轻自贱,也绝不允许别人轻贱我们。而且要收钱。

说到收钱,还有另一类人,如果他们不出现在我面前,他们和我也都会感到更幸福。这一类人和"挺简单的"那一类人比,有更深厚的文化传统。他们一般先不谈价格,而是说"必有重谢",或者"忘不了你"。十几年前有些老板对我说过类似的话,"小杨,我一定忘不了你"的老板们,现在估计他们都不记得我了。但是我记得他们,我还记得等到付钱的时候,他们会说"哎呀,实在是紧张/投资失败了/这把项目做亏了"。后来遇到这样的人多了,我就有了答复模板,"你失不失败跟我有个P关系,成功了你又不多给我钱",或者"如果你现在真没钱,那咱们谈谈股份吧"。

这帮家伙遵循着丛林法则生存,或者说,他们自以为有咬人一口占了便宜跑得了的本事,甚至有些还认为自己不仅这次能跑,以后有机会还能跑回来再咬一口,而且还有能力再跑。董同学指着在马路上违规加塞的前车,把这种行为称为投机,并认为投机得利会破坏规则。董同学所定性被破坏的规则,显然不是丛林法则。

他们打算步步为营,能多赚你一分就多赚一分,在讨价还价中不断试探你的底线。他们在寻物启示和招聘中写着,"必有重谢""报酬面议"。郑同学说得好,"什么是重谢"。我下了决心,如果捡到巨宝,看到寻物启示里写"重谢"而不标明数额和支付期限的话,我就把巨宝扔伊通河里去--自己留着是不当得利,但是我没有替他保管的义务,就当不知道那是什么玩意。

我曾经在饭店里旁听两位食客的对话,1999年,跟二猫妈在地质宫旁边吃羊汤肉夹馍的时候。一位是老板,一位是潜在雇员,他们是朋友。潜在雇员一直在问报酬几何,老板一直在说"我还能亏了你么""那还能少了你的么""咱还能赔么""咱俩之间,还得有个准数么"。是的,需要。要么,有准数,你挣得更多我也不多要,你全赔光也跟我没关系;要么,按比例,我们利益同进退;要么,我就是赔你玩,啥也不要,但是,那得我乐意。这些都要事前明确约定,可以变更,但要我同意才行。很多年以后,我逐渐下定决心,欠我工资的,我一定把他挂在塔吊上,而不是把我自己挂上去;我说你是我兄弟的,你才是,钱一分也不必谈,谈了伤感情,我说不是的,你就一分钱也不能少。



又,发上述感慨,当然是因为不爽到了一定程度。起因是七转八转的关系,一个家伙找到我,然后变成了我求某同学做个私活。此处略去经过。整件事情整得我非常愤慨。天下多奇葩,以后再有这样的事,麻烦各位同学一定要提醒我,就说"你想想以前那次,还有那次。"尤其是关同学和郑同学,请一定提醒我。我先提前多谢提醒,届时会为你的提醒支付巧乐滋。


又及。以前有同学提到过,看你写的说的,好像快意恩仇的样子,到头来还不是要向世俗低头么。是这样,你的理解和我的表达有所不同。我快意的是我的恩仇,不是你的恩仇。所以,一切都是由着我的性子来的,不是演给你看的。并且,不是因为我有本事这样,而是,我能够承担这种快意的结果,也愿意付出快意的代价。而且,我也的确付出了。打人的时候就得想到自己手疼,及事后要支付的医院费,及自己可能被反击。都非常惨痛,如非经过同样的专业训练,请勿模仿。

--------------------

博客会手工同步到以下地址: