Debugging读书笔记

来源:百度文库 编辑:神马文学网 时间:2024/04/29 13:32:04

Debugging读书笔记

2010-03-11 12:22:05
#-*- mode: org; -*-
Debugging
-- The 9 Indispensable Rules for Finding Even the Most Elusive Problems

* Introduction
1. When it took us a long time to find a bug, it was because it we had neglected
some essential, fundamental rule; once we applied the rule, we quickly found
the problem.
2. People who excelled at quick debugging inherently understood and applied these
rules. Those who struggled to understand or use these rules struggled to find
bugs.

** War Story

1. Apple会不停地发送payment transaction直到你对它调用finishTransaction函数, 即使
关闭机器再开机. 当不知道这个时, 会以为程序错误的通知对应的observer.
2. UIAlertView不能正确的弹出, 只是将屏幕变暗, 并且吃掉所有操作. 原因, 所有UI操作必须
在主线程完成. 在不知道这条的时候, 花费大量的时间去猜测中断问题, 而它的确也触发了中断.

以上两个例子都是 *搞错了最基本的东西*, 没有RTFM, 没有follow the insturctions.

在花费大量力气去debug, 请先问一下自己, 是否忽略了最基本的东西.

作者是个福尔摩斯迷, 第一章的引言是:
#+BEGIN_VERSE
"At present I am, as you know, fairly busy, but I propose to devote my decling
years to the composition of a textbook which shall focus the whole art of
detection into one volume."
-- Sherlock Holmes, The Adventure of the Abbey Grange
#+END_VERSE

看来作者目的也非常明确, 只有九条必不可少的规则, 帮助你快速的调试. 不只是应用到程序方面,
这同样可以应用于其他所有的方面, 包括设计, 读书, 生活等方方面面.

全书106页. 有人说, 把书写厚容易, 难的是怎样写薄, 让每个字都难以去掉.

* The Rules

: UNDERSTAND THE SYSTEM
: MAKE IF FAIL
: QUIT THINKING AND LOOK
: DIVIDE AND CONQUER
: CHANGE ONE THING AT A TIME
: KEEP AN AUDIT TRAIL
: CHECK THE PLUG
: GET A FRESH VIEW
: IF YOU DIDN'T FIX IT, IT AIN'T FIXED

牢记这些条目, 把它们放到任何看得到的地方, 桌上, 墙上, 桌面背景...

这张只有一页. 作者抛出了他总结的九个黄金法则. 似曾相识!

* Understand the system

#+BEGIN_VERSE
"It is not so impossible, however, that a man should possess all knowledge
which is likely to be usefull to him in his world, and this, I have
endeavoured in may case to do."
-- Sherlock Holmes, The Five Orange Pips
#+END_VERSE

"That's just common sense--when all else fails, read the instructions."

By the way, understanding the system doesn't mean understanding the
problem. You have to understand how things are supposed to work if you
want to figure out why they don't.

** TODO War Story

** TODO RTFM (read the fuck menual)

Read it first--before all else fails.

作者举出的例子很有趣, 如果你在商店购买东西, 那请先阅读说明书, 否则你会再制造一个
无用的垃圾.

同样对于代码调试, 假设是在调试别人的代码, 因为某种要求而嵌入到自己的代码中, 那首
先要弄清楚代码的开发者期望这这份代码如何运行(看注释, 看文档)

另外值得注意的是, 不要完全相信注释和文档.
#+BEGIN_VERSE
A caution here: Don't necessarily truct this information. Manuals (and
engineers with Beemers in there eyes) can be wrong, and many a
a difficult bug arises from this. But you still need to know what they
thought they built, even if you have to take that information with a
bag of salt.
#+END_VERSE

作者的例子是, 他发现B寄存器被冲掉, 原因是一段代码上写着
"Caution-this subroutine clobbers the B register"

在代码中搜'bug', 'fixme'也是一个好办法:D

*** War Story
SECTV对SDL的blit实现, 没有完全按照SDL的API说明运行, 将alpha通道拷贝到目标
内存-framebuffer中, 导致framebuffer中的alpha通道当作mask使用, 让后画上去
的东西显示错误.

** Read everything, cover to cover

Programming guides and APIs can be very thick, but you have to dig in
-- the function that you assume you understand is the on that bites
you.

*** War Story
在阅读Sandstorm的代码, 以创建一个新的Minigame, 在阅读了大概半天, 并成功的
创建出一个小游戏后, 发现我通过读代码所需要知道的东西, 在docs目录里面.

由于没有仔细阅读NSURLConnection的文档, 没有发现在文档中有这么一句话,

"Note that these delegate methods will be called on the thread that started
the asynchronous load operation for the associated NSURLConnection object."

所以一直在奇怪的问, 为什么NSURLConnection没有任何的消息发出来.


** Know what's reasonable
最起码要知道要调试的东西是如何正常的工作的, 他涉及到的部分的作用是啥.

作者的War Story很有趣, 一个做硬件的兄弟对正在调试软件部分的兄弟说, "在Crash的
之前下断点, 断下来了看看周围的情况", 要是知道在哪下断点, 那也许已经找到修改的
办法了.

** Know the Road Map

至少要知道最上层的代码的大致流程和分块, 以便定位bug. "如果面包机烧糊了土司, 你
需要知道那个黑色的旋钮是用来控制时间的."

如果你面对的是一个二进制库, 那必须首先定位问题是出在里面还是外面. 通过分析输入
是否正确, 然后看输出是否期望的输出.

作者的例子如何判断车在行驶时发出的声响是属于引擎还是石头卡在轮胎里. 首先必须知道
引擎转速和速度的关系. 高转速引擎, 速度并不快, 如果声响跟着引擎速度加快, 那就是
引擎出问题了.

** Know Your Tools

调试器是观察系统的眼睛和耳朵. 必须选择正确的工具去调试所面对的bug.

单步调试可以调试逻辑问题, 但不可能去调试时序相关的问题.
剖析器(最简单的是printf大法)可以暴露时序问题, 但对逻辑上的bug帮助不太大(因为
到处塞满printf, 等于单步调试, 那如果可能的话, 何不如单步调试).

调试Objective C代码, 那起码要知道一些Objective C的知识.

** Look It Up

...1489A变热是由于芯片比原来的要小, 但必须是电压高的时候. Kneejerk说1489A替换
原来电路的1489会发热. 但Junior查看电路图后, 发现原来1489的引脚接错, 如果替换成
跟小的1489A就会变得更热.

原来的设计小组忽略了1489的说明, Kneejerk猜测1489A会更热.

所以, 绝对不要猜测. 查清楚所有相关信息. "它应该是这样的, 可能由于xxx, 所以
yyy了". 这句话不应该出现. 全部搞清楚.

*** TODO War Story
SKPaymentQueue的transactions事件....

* Make if Fail

#+BEGIN_VERSE
"There is nothing like first-hand evidence."
-- Sherlock Holmes, A Study in Scarlet
#+END_VERSE

** Crash了, 就来重现一遍.

"In 1975, I was alone in the lab late one night (it's always late at night isn't
it?)..." XD

作者在调试bug时, 发现很难通过示波器观察到错误发生的情况, 因为游戏会失败并且要等下一次发球,
这样很浪费时间, 后来他通过连接控制球高度的输出电压和击球板的输入电压, 让击球板跟随球移动,
这样就可以让击球板自动击球, 然后再在示波器上观察, 用来发现bug. (那个年代的调试居然这么费劲...)

重现bug,
1. 可以看到问题出现在哪, 并尽可能的规律的重现他.
2. 可以找到出现问题的原因. (小心, 别过早的猜测, 面包机烤糊面包的原因是由于里面有面包...)
3. 如果修复bug, 重现不出来, 那就说明已经修了.

** 怎样重现
重现时, 仔细记录每一个步骤. (通常QA给的步骤信息非常有用)

每次重现时, 必须回复到最初始的状态. 如果安装是第一步, 那必须删除后, 安装一个全新的版本, 然后
重现.(特别是跟存档相关的bug)

"Too bad I couldn't automate the debugging part and just play!" XD

如果发现许多步骤都是重现bug的必要步骤, 那如果可能的话, 让这些步骤自动的进行. 如果需要很长
时间才能重现, 那自动完成的步骤可以无休止的跑下去, 直到出现问题.

豪斯医生在诊断过敏症时, 向小女孩的皮肤上分格, 并涂上各种过敏源, 用来确定病症. (可惜他碰到了
罕见的光过敏症, 最后由于光照, 所有小格全部过敏...)

记得*重现的是步骤*, 而不是bug本身. 如果某个功能, 由于输入一个错误的值而出现bug, 你再怎么输入
这个值, 他肯定是错误的.

别过早的猜测bug出现的原因, 然后去给他制造这个出错的条件, 这样重现的bug没有意义. 因为你知道
这样会出错...

" Your word-processing application (the one you're going to knock off Microsoft
with) is dropping paragraphs, and you guess that it has something to do with
writing out the file. So you build a test program that writes to the disk
constantly, and the operating system locks up. You conclude that the problem is
that Windows is too slow and proceed to develop a new Microsoft-killing
operating system."

已经有够多的bug了, 不要尝试加入必然导致这个bug的代码. 保持现在的运行机制不变. 在上面的例子
里面, 最好是自动向wp里面输入随机的字符, 然后去点击存档. (用按键精灵:)

当然, printf大法对于这种难重现的bug是非常有效的.

如果可能的话, 在相似的环境下重现bug比较有意义. 如果他能在很多环境下都出现, 那肯定是逻辑bug.

拿iPhone做例子, 如果是由于机器压力造成的crash(非内存不够而kill掉), 那找个差不多的机器来
重现. 如果是某些特别机器出现问题(希望是TP), 而你手上没有, 那就想办法输出log(比如存档),
然后证明只有那个机器上会出现问题(通过log). 或者尝试去模拟那个机器的行为(记得以前三星的某个
中断问题).

"Don't do it--bite the bullet and either bring the equipment to your engineers
or send an engineer to the equipment (with a taxi-load of instrumentation). If
your customer site is in Aruba, you'll probably reject the 'bring the equipment in'
option out of hand--and, by the way, are you currently hiring experienced
engineers with good writing and debugging skills?" XD

当然, 如果你没法模拟那个机器的行为, 那最好还是给我寄台机器来吧....(曾经的iPhone3GS和现在的
project27)

自动重现步骤, 可以极大的缩短重现时间. 放大重现的步骤, 可以让bug更加容易被发现(比如在内存
压力大的情况下出现的bug, 那就把内存再撑大点).

但是别太过火, 引起其他bug就郁闷了. 虽然说又找到个bug, 但可能就偏离了当前的目标.

如果是10%几率之类的bug, 重现步骤就不怎么靠谱了. 因为根本不知道哪个步骤是关键. 记得, 没有随机
crash一说, crash了就是crash了.

列出所有可能的条件, 每次改变一个条件或者改变他们的组合, 当然10, 20个条件时, 最好分下类, 让
所有的条件都是互斥的. *Change One Thing at a Time* 也列在9个黄金法则之中.

有些时候可能会发现当改变某些条件的时候, bug消失了, 那可能已经找到关键原因了, 但离找到bug还有
些远, 那可以让这个条件参与到其他条件组合中, 继续重现.

有时候尝试了所有条件, 还是随机出现...那就是原因找错了...

请详细的记录所有log, 仔细的比较出错时的log和正常的log.

千万别被这些'有可能'的原因偏离原始的bug.

当运气好的时候, 改变某个条件, 让bug重现不了了(在你尝试的这10次,20次, 30次...).
那请找出原因, 否则不叫修复bug. 可能第n+1次的时候bug出现了x.

作者碰到的随机bug是, 通过电话先传送的数据包有时候会乱序. 当他们修复bug后, 发现, 随机出现
bug的时间集中在这个城市每天的一个固定时间, 也就是年轻人们煲电话粥的时候, 电话局会调整电话
线路, 导致包乱序.

不要说测试搞砸了(我们经常说测试乱报bug:P). 有些时候, 的确是测试搞错了, 但大部分时候, 的确是
程序搞砸了.

说"这个bug不能重现", "这个"指的不是bug出现的现象, 而是一些固定的条件, 比如测试报告的详细步
骤. 当说"这个bug不能重现"的时候, 要好好想一想, 我们是不是严格的按照测试所给的步骤, 去重现
bug.

** 调试代码
在调试的时候, 肯定有许多辅助调试的代码, 千万别丢掉他们, 慢慢修改, 让他们成为一个工具库.

** Make If Fail
It seems easy, but if you don't do it, debuggin is hard.

*Do it again*. Do it again so you can look at it, so you can focus on the cause,
and so you can tell if you fixed it.

*Start at the beginning*. The mechanic needs to know that the car went through
the car wash before the windows froze.

*Stimulate the failure*. Spray a hose on that leaky window. But don't simulate
the failure. Spray a hose on the leaky window, not on a different, "similar" one.

*Find the uncontrolled condition that makes it intermittent*. Vary everything you
can--shake it, rattle it, roll it, and twist it until it shouts.

*Record everything and find the signature of intermittent bugs*. Our bonding system
always and only failed on jumbled calls.

*Don't trust statistics too much*. The bonding problem seemed to be related to
the time of day, but it was actually the local teenagers tying up the phone lines.

*Know that "that" can happen*. Even the ice cream flavor can matter.

*Never throw away a debugging tool*. A robot paddle might come in handy someday.

** TODO War Story
做Tennis Open时, 调试AI击球动作, 让球不断的从对方场地发过来, 把击球区域显示出来, 让AI在这
边击球, 然后录像, 一帧一帧的观察.

* Quit Thinking and Look

#+BEGIN_VERSE
"It's a capital mistak to theorize before one has data. Insensibly on begins to
twist facts to suite theories, instead of theories to suit facts."
-- Sherlock Holmes. A Sacandal in Bohemia
#+END_VERSE

别先入为主.

"面包机把面包烤糊了, XXX牌子的面包容易糊, 换个牌子就不会糊了."

当有着"这个应该是这样的.. "这种想法, 并且只在自己的脑子中寻找支持这个"想法"的证据, 不如先
看看bug怎么出现的. 用实际的数据来证明自己的猜测.

"我打赌肯定是xyz系统的问题, 因为一xyz, 就crash了", 这个话是不是非常耳熟.

*See the Failure*

"如果abc不给xyz提供数据, 一xyz还是一样crash", 做个简单的替换, xyz替换成声音系统, abc
替换成文件系统. 然后再次把xyz替换成文件系统, 把abc替换成数据包.

所以, 先得到实际的数据证明猜测, 然后再着手修bug. 免得吃不到XXX牌子的面包, 并且也浪费了
YYY牌子的面包.

*See the Details*

获取是最详细的数据(log), 帮助确定bug的位置. 同一个bug可能有不同的表现, 同一个表现不一定
是同一个bug引起. 越详细的数据, 越能帮助定位bug.

*Instrument*

安插自己的*instrument*代码, 通常是一个非常有效的调试方法. 特别是对多线程问题. 在
*Make if Fail*一节, 作者说"别丢掉任何调试代码", 特别是自己的instrument系统. 他会在很多
地方被使用. 维护这样一个工具库非常有用. 在开始构建系统的时候, 就必须包含这个模块. 最简单
的形式就是DEBUG宏分开的printf. 可以在上面加入时间戳, 分级输出, 输出到console或者输出到log文件,
甚至输出到游戏存档. 并且还可以加入变量的tweek功能等.

请参考glitch代码, 或者就使用glitch提供的log系统. 再或者找一个自己喜欢的log系统. 实在不满足
需求, 那自己写个把. (比如想把帧率跟函数执行时间等用曲线表示出来, 那可以对log系统设置消息的
callback, 然后绘制到某个canvas上.)

当使用instrument时, 注意请在正确的包含这个bug的版本上, 保持所有宏定义以及编译环境. 加入
instrument后, 重现bug. Instrument应该是一个没有任何副作用的系统(或者尽量降低其副作用, 比如帧率,
时间上相关, 或者调试文件IO时用instrument, instrument的输出又依赖文件IO).

试错法调试时(比如打开这一段, 关闭另一段), 注意保持所有的代码, 只用"#ifdef"等来隔开, 不要直接
删除或者使用多行注释. 因为debugging的时间越长, 就可能记不住那段代码究竟是原来就注释掉的. 请保持
和原来文件的diff.

TODO 没有代码, 怎么调试? 反编译, 挂上各种动态, 静态调试器(softice, ida等), 观察输入, 输出,
猜测, 验证.

Instrumatation every day, SandStorm把各种监视信息绘制在屏幕顶端, 并且用颜色来分辨性能, 输出各种
常见的profiling信息.

"Only the spoon know what is stirring in the pot." -- Sicilian Proverb

海森堡测不准原理 -- 越多的instrument, 就越可能影响被测试的系统, 也就是上面所说的, 请保持instrument
的副作用降到最低.


*Guess Only to Focus the Search*

这章指出的别猜测, 要获得实际的数据支持. 不是说不要对bug进行任何形式的猜测. 猜测可以让你知道在哪些
地方去获取实际的数据, 用来排除, 或者更加确信这个bug发生的地方. 所以, 别太相信猜测, 一切以数据为准.
请避免被猜测带离bug.

一个例外, 如果这个猜测的原因确实会引起某种相似的bug, 并且这个非常容易修改, 那请先修复他.
如果确信bug被修复了, 结束. 如果没有, 请恢复原来的代码. 因为这非常可能影响到原来的流程, 让bug更加难找.

如果处理这个猜测的引起bug的原因需要花费很大力气, 那还是先找到真正的原因再说.


** Quit Thinking and Look
You can think up thousands of possible reasons for a failure. You can see only
the actual cause.

*See the failure*. The senior engineer saw the real failure and was able to
find the cause. The junior guys thought they knew what the failure was and fixed
something that wasn't broken.

*See the details*. Don't stop when you hear the pump. Go down to the basement
and find out which pump.

*Build instrumentation in*. Use source code debuggers, debug logs, status messages,
flashing lights, and rotten egg odors.

*Add instrumentation on*. Use analyzers, scopes, meters, metal detectors,
electrocardiography machines, and soap bubbles.

*Don't be afraid to dive in*. So it's production software. It's broken, and you'll have
to open it up to fix it.

*Watch out for Heisenberg*. Don't let your instruments overwhelm your system.

*Guess only to focus the search*. Go ahead and guess that the memory timing is bad, but
look at it before you build a timing fixer.

* Divide And Conquer
#+BEGIN_VERSE
"How often have I said to you that when you have eliminated the impossible,
whatever remains, however improbable, must be the truth?"
-- Sherlock Holmes, The Sign of the Four
#+END_VERSE

这个时候, 一支笔一张纸份会非常有用. 将系统按照前面所说的"Know the Roadmap", 分
成相对独立的模块. 在模块和模块之间, 放入instrument代码, 用来确定bug所在的位置.
前提条件是知道在什么范围内分, 猜1到100之间的数, 猜个135就奇怪了.

常用的二分法找bug的方法. 请特别注意模块之间的联系, 别出现分开了没有, 合起来又
有了这样的问题.

分割模块的时候, 请一定按照分割的模块测试, 在完成之前, 请不要重现再分一次.

*Which side are you on?*

必须知道在分割的部分中, 在哪个部分之前所有的东西都是正确的, 在哪个部分之后就变
得异常起来.

用固定的明显的输入数据来测试, 当这些数据经过哪个部分后, 变得不是期望的东西, 那
就可以大致确定位置.

总是从出错的地方往前找, 这样会节约很多时间.

当仔细分析的时候, 往往会找到一些隐藏的bug. 当确定 *确实* 是一个bug的时候, 请修
复. 请注意, 这里不是去修复猜测的引起bug的那个原因.
请参照 *Guess Only to Focus the Search*.

如果发现A这个变量*可能*会引起bug, 那请检查所有A变量出现的地方, *确信* 它在这里会
引起bug之后, 再修复它.


* Divide and Conquer ::
It's hard for a bug to keep hiding when its hiding place keeps getting cut in
half.
+ Narrow the search with successive approximation. ::
Guess a number from 1 to 100, in seven guesses.
+ Get the range. ::
If the number is 135 and you think the range is 1 to 100,
you'll have to widen the range.
+ Determine which side of the bug you are on. ::
If there's goo, the pipe is upstream. If there's no goo, the pipe is
downstream.
+ Use easy-to-spot test patterns. ::
Start with clean, clear water so the goo is obvious when it enters the
stream.
+ Start with the bad. ::
There are too many good parts to verify. Start where it's broken and
work your way back up to the cause.
+ Fix the bugs you know about. ::
Bugs defend and hide one another. Take 'em out as soon as you find 'em.
+ Fix the noise first. ::
Watch for stuff that you know will make the rest of the system go
crazy. But don't get carried away on marginal problems or aesthetic changes.

* Change One Thing at a Time

#+BEING_CENTER
"Thay say that genius is an infinite capacity for taking pains. It's a very
bad definition, but it does apply to detective work."\\
-- Sherlock Holmes, A Study in Scarlet
#+END_CNETER

作者的例子是声音系统, 声音源被分割成数据块, 传送到声音模块中处理后输出到扬声器. 输出声音质量很差, 但
音源质量很好. (类似于我们使用OpenAL的方案, 不过他们是硬件.)

#+BEGIN_VERSE
The whiz came in to help and immediately insisted that they put known data
through the pipes and watch for the failure, using instrumentation. It took
a while to come up with the right test patterns and to look in the right
places, but they finally saw the data getting clobbered and traced the cause
to a buffer pointer error. They fixed the bug, saw that the test pattern went
through the trouble spot okay, and with quiet confidence tried it out on real
audio data. It still sounded bad.

They sat, confused, and began to relook at the data stream. Maybe they had
done the fix wrong? Maybe the fix wasn't really in? They spent an hour
reconfirming that the fix they had done really did fix a problem. They
returned to the code to reconfirm that they were running the fix, and at that
point the engineer slapped his forehead. "I changed a handler to add framing.
It didn't fix anything, but I never changed it back!" It turned out that the
downstream processors thought this extra framing was more audio data, and just
played it. It sounded bad. The engineer removed the previous "fix," and the
system worked perfectly. The whiz quoted the "Change One Thing at a Time" rule
and passed the story along for inclusion in this book.
#+END_VERSE

*Use a Rifle, Not a Shotgun*

简单的例子, 当电脑不能启动了, 不会一下子换掉显卡, 内存, CPU然后再看. 肯定是先换内存, 看能启动不, 4个
内存插槽, 一个一个的插, 两条内存, 一共插8次. 然后都启动不起, 再插上内存, 换掉显卡...(当然, 启动时候
PC喇叭的声音也可以指出哪个地方出问题了)

当确实知道需要修改哪个问题是, 的确只需要修改那个问题相关的代码, 不需要'顺便'修改一票毫无关系的东西, 即使
修改的东西有可能看起来会比原来的更加好. 修改后, diff一下, 确保只动了bug.

一次修改, 一次提交. 提交前, 对比代码, 看是否修改到无关的地方, 若是, 请恢复.

很多时候, 想改改某些地方, 看看是不是会影响到bug. 注意, 这通常是在猜测而没有去使用各种工具去发现到底是
什么错了. 并且这有可能隐藏掉原来的bug.

前面 *Make if Fail* 中提到过, 当重现bug时, 每次都要恢复到最初始的状态. 当改变某个重现步骤或者调整重现
步骤的顺序时, 请 *每次只改变一项*, 如果确实让bug重现更加规律, 那就会缩小寻找bug的范围, 若不是, 请恢复
到初始状态.

当运气好, 试错法找到并修复了bug, 请对比原始代码, 并分析原因. 否则, 只可能是这次运气好而已.(随机bug)

TODO 例子

** Change One Thing at a Time
You need some predictability in your life. Remove the changes that didn't do what you
expected.They probably did something you didn't expect.

*Isolate the key factor*. Don't change the watering schedule if you're looking for
the effect of the sunlight.

*Grab the brass bar with both hands*. If you try to fix the nuke without knowing what's
wrong first, you may have an underwater Chernobyl on your hands.

*Change one test at a time*. I knew my VGA capture phase was broken because nothing
else was changing.

*Compare it with a good one*. If the bad ones all have something that the good ones don't,
you're onto the problem.

*Determine what you changed since the last time it worked*. My friend had changed the
cartridge on the turntable, so that was a good place to start.

* Keep an Audit Trail

"There is no branch of detective science which is so important and so much
neglected as the art of tracing footsteps."
-- Sherlock Holmes, A Study in Scarlet.

作者在这章的War Story太搞笑了.

热了没问题, 冷了没问题, 站着没问题, 坐着没问题, 有一天有问题, 另一天完全没问
题. 一开机然后在镜头前站起来, 出问题了. 原来是衣服的花格子让图像压缩芯片速度
急剧下降(压缩算法上有问题). 完全没出问题的那一天是因为没穿那件花格子衬衫.

作者在向芯片的提供商报告这个bug时, 详细记录了所有操作, 让芯片提供商完全重现了
这个bug, 花格子让压缩速度降低.

有些时候, 一些不经意的修改或者步骤可能会影响这个bug. 请向QA索要最详细的操作记
录, 并且请记录所有操作步骤, 修改步骤.

*The Devil Is in the Details*

在使用log系统记录信息时, 请详细描述正在记录的条目, 发生的位置. 标准是, 根据详细
的记录, 能完全重现bug, 或者较为规律的重现bug.

从详细的记录里面找出相关的症状.

*烂笔头胜过好记性*

** Keep an Audit Trail
Better yet, don't remember "Keep an Audit Trail." Write down "Keep an Audit Trail."

*Write down what you did, in what order, and what happened as a result.* When did you
last drink coffee? When did the headache start?

*Understand that any detail could be the important one.* It had to be a plaid shirt to
crash the video chip.

*Correlate events.* "It made a noise for four seconds starting at 21:04:53" is better than
"It made a noise."

*Understand that audit trails for design are also good for testing*. Software configuration
control tools can tell you which revision introduced the bug.

*Write it down!* No matter how horrible the moment, make a memorandum of it.*


* Check the Plug

"There is nothing more deceptive than an obvious fact."
-- Sherlock Hollmes, The Boscombe Valley Mystery

"原来是那个变量!"

别忽略一些最基本的问题, 如网络死活连接不到, 只是因为路由器没通这样简单的原因.

质疑自己的猜测. 汽车不能启动, 首先看看是不是没油了. 在开始进行复杂的测试, 调试
之前, 首先确保所有前置条件都已经具备.

打包资源的时候, 抱怨打包不出来, 并开始打开打包脚本期望在里面发现bug. 不如首先
检查所要求的外部程序是不是都安装了, 版本是否一致, 特别地, 看下路径是否正确, 外
部程序路径中间的空格是否影响这个程序的调用.

播放声音crash, 首先看看是否文件存在, 文件格式是否正确.

文件存在, 但读取错误, 看一眼是不是以二进制方式打开文件.

这些检查都是非常简单易行的.

* Get a Fresh View

#+BEGIN_VERSE
"Nothing clears up a case so much as stating it to another person"
-- Sherlock Holmes, Silver Blaze
#+END_VERSE

Ask for help. 如果自己百思不得其解, 那请一定向别人求助. 并不是指望别人帮助修复
这个bug, 也许别人不还不知道这个系统是如何工作的, 但是别人对的一些新的看法, 一些
经验会对调试有很大的帮助.

#+BEGIN_CENTER
"And people are usually willing to help because it gives the a change to
demonstrate how clever the are." XD
#+END_CENTER

实际上, 在向别人解释这个bug是怎么发生的, 会给自己一些新的提示. 参见橡皮鸭调试法.
[http://www.reddit.com/r/programming/comments/83i5n/the_rubber_duck_method_of_debugging/][Rubber duck method of debugging]

对于正在调试自己还没搞清楚的复杂代码, 那请向写这个代码的人求助. 他们一定
*Understand the System*

别人的经验给自己非常大的帮助. 这时候, 请不要犹豫, 向邮件组发一封求助信是最好的办
法.

请不要害怕向别人求助, 反过来, 请不要拒绝别人来寻求帮助. "白痴, 这个都不会做"这样
的想法请千万不要有.

** 如何寻求帮助?
* 寻求帮助时, 请详细说明症状, 不要把自己对这个bug的看法, 理论都全部说明, 这对
*Get a Fresh View* 一点帮助都没有, 反而让别人陷入自己可能错误的分析当中.
如果自己是提供帮助的, 别人在向你灌输他的分析时, 请捂住耳朵跑开, 别被毒害.

* 如果别人提供的想法看起来有些奇怪, 并且自己完全不知道为什么要那样, 但请记住,
这是一个信息, 会对调试有帮助的.

** 如何提问
参见[http://catb.org/~esr/faqs/smart-questions.html][How To Ask Questions The Smart Way], [http://www.beiww.com/doc/oss/smart-questions.html][中文版: 提问的智慧]

* If you Didn't Fix it, It Ain't Fixed

#+BEGIN_VERSE
"It is stupidity rather than courage to refuse to recognize danger when
if is close upon you."
-- Sherlock Holmes, The Final Problem
#+END_VERSE

请反复确认bug是被修复了. 请确认这个bug是被修改过的代码给修复了. 否则这个bug一定
还存在.

Bug不会自己消失掉, 请修复引起这个bug的原因. 如果一个地方有个assert(pointer)
不成立, 那换成 if (!pointer) return; 完全是白搭.

请确认这个修复不会引起其他的bug.


* 可怕但流行的编程风格: [http://coolshell.cn/%3Fp%3D2058][link]

* 散弹枪编程 ::
这种编程风格是一种开发者使用非常随意的方式对待代码。“嗯,这个方法调用出错了
……那么我会试着把传出的参数从 false 变成 true!”,当然依然出错,于是我们的
程序员会这样:“好吧,那我就注释掉整个方法吧”,或是其它更为随意的处理方式,
直到最后让这个调用成功。或是被旁边的2某个程序员指出一个正确的方法。
如果我们把一个正规的程序员和一个撞大运的程序员放在一起做结地,那么,那个正规
的程序可以马上变得发疯起来,并且,可以把正规的程序员的智商降到最低。两个撞大
运的程序员不应该在一起做结对编程,这是因为他们破坏性的才能会造成的伤害会比只
有一个还差。

* 撞大运编程 ::
这是一种比散弹枪编程要温和一些的编程方式,我相信这种方式可能会是大多数程序员
都会使用的方式。这种编程方式经常出现于程序员并不确切知道他们在干什么,也不知
道所写的程序的本质和实际,但是可以让程序工作起来。他们以一种撞大运的方式在写
程序,某些时候,他们根本就不知道某个错误的原因,就开始稀里糊涂地修改代码。一
旦出现问题,他们会用两条路:1)停下来,理解一下程序,找到出错的原因。2)使用
散弹枪编程方式开始解决问题。测试驱动开发(Test Driven Development)是一种
可以用来拯救上百万的撞大运编程的程序员。于是,他们有了一个更为NB的借口:只要
我的程序通过测试了,你还有什么话好说?别骂我,测试驱动开发是一个不错的事物,
其主要是用来控制撞大运开发所带来的问题。

* Cargo-Cult 编程 ::
关于Cargo Cults 这个词儿来自二战期间的某些太平洋上小岛里的土著人。在战争期间,
美国利用这些小岛作为太平洋战场上的补给站。他们在这些小岛上修建自己的飞机跑道以
用来运输战争物资。而那些小岛上的土著人从来没有见过飞机,当他们看到飞机的时候,
觉得相当的牛,可以为那些白人带来各种各样的物品和食物。当二战结束后,那些土著
人仿照着修建了飞机跑道,并用竹子修建了塔台。然后就在那期望着有飞机为他们送来
物品和食物。Cargo Cult 编程是一种非常流行的编程方法,使用这种方法的程序员
会学习其它编程高手的编程方法,虽然他们并不知道为什么高手们要那样做,但是他们觉
得那样做可以让程序工作起来。举个例子,当时有大量的程序员在J2EE出现的第一年中
过度地使用了EJBs和Entity Beans。

* 刻舟求剑编程 ::
刻舟求剑是一个很流行的寓言了。这种风格的编程在程序员的圈子里是非常常见的。
比如,有一天,你发现了一个空指会的异常,于是你到了产生空指针异常的地方,简单
地放上一个判断: if (p != NULL)。 是的,这样的fix可以让你的程序工作起来,
但你并没有真正地解决问题。你只不过是在你的船边记下了剑掉下去的位置,这样做只
不过把问题隐藏起来,最终只会让你的程序的行为变得神出鬼没。你应该找到为什么指
针会为空的原因,然后再解决这个问题。

* 设计模式驱动型编程 ::
正如这种编程的名字所说的,这种编程风格使用大量的设计模式,在你的程序中,四处都是
设计模式,你的代码到处都是Facade,Observer ,Strategy,Adapter,等等等等。
于是,你的程序要处理的业务逻辑被这些设计模式打乱得无法阅读,最后,也不知道是
业务需求重来,还是设计模式重要,总之,实际业务需求的程序逻辑被各种设计模式混
乱得不堪入目。

* 侦探型编程 ::
在解决一个Bug的时候,侦探型程序员会调查这个Bug的原因。然后,则调查引发这个BUG
的原因的原因。再然后,其会分析修正代码后是否会导致其它代码失败的因果关系。再然
后然后,他会使用文本搜索查找所有使用这个改动的代码,并继续查找更上一级的调用代
码。最后,这个程序员会写下30个不同的情形的测试案例,就算这些测试案例和那个Bug
没有什么关系,最最后,这个程序员有了足够多的信心,并且精确地修正了一个拼写错误。
与此同时,其它一个正常的程序修正了其它5个Bug。

* 屠宰式编程 ::
使用这种风格的程序员,对重构代码有着一种难以控制的极端冲动。他们几乎会重构所有
经手的代码。就算是在产品在Release的前夜,当他在修正几个拼写错误的bug同时,其
会修改10个类,以及重构与这10个类有联系的另20个类,并且修改了代码的build脚本,
以及5个部署描述符。