20120822

14条原则 (4) 把大问题划分成小问题

14条原则 (4) 把大问题划分成小问题

这条原则的全文是,"把大问题划分成小问题,然后系统地检查每一个小问题,看是不是问题的起因。再大的问题都能按这种方法分成小问题加以解决。"

把整体划分成若干局部,这正是两千多年来人类一直在执行的方针,亚里士多德就论述过了,此后也鲜有进步。看起来很简单,但是执行起来非常困难。比如,如何划分一个问题。

这一条太难了,我也只能谈几个体会,求过路的牛人们不吝补充事例和给出自己的解释。

1. 当前只有一个问题

假设我们已经把大问题划分成了几个小问题,那么,重要的是假装当前只有一个问题。然后集中精力去解决这唯一的一个问题。

当我做一百个俯卧撑的时候,60以后会变得非常困难,尤其是呼吸和心脏的压力,那个时候最想做的是放弃。我采取的最重要的措施就是关注当前的这一个,调整"它"的动作。下一个?下一个再关注当前的这一个,调整"它"的动作。这样,一个又一个就做完了。

传说古代的男人是以打猎作为主要职业的,提供的蛋白质和脂肪(糖份是由女人采摘提供的)大大加速了人类的发展。而打猎中非常关键的一个因素就是:专注。所以,如果我们同时关注很多步骤,很多目标,很多集点的时候,我们就什么也看不清了。直到现在,我们似乎还保留着这种天性没进化完。

所以,当我们把自己当成一台机器操作的时候,就应该按它的内在逻辑和性能指标去操作。如果它需要专注并善于专注,那么,我们就应该使用专注的方法。

只关注当前的这个问题,假装它是唯一的问题。

有的同学可能要问,那如果同时存在两个问题呢,这种事情难道不是真的存在么?答案是:当我们解决了这个唯一的问题的时候,接下来遇到的,是下一个唯一的问题。

一个简单的例子,比如在编译的时候,经常有同学大叫,"哇靠,100多个错误啊!"改个地方再编译,"刚刚的错误消失了,但是……200多个错误啦!"

如果我们坚信错误只有一个,唯一的一个,那么,所有那么多错误信息,就都是编译系统的愚蠢而已。而事实上,正是如此。绝大多数情况下,只有第一个编译译错误才是有意义的,后面的,可能都是编译器对这个错误所做的愚蠢猜测导致的。

比如我们写

main() { a = 3; printf ("%d", a); // 其他对a的使用}

编译器先抱怨我们没有声明a就赋值了 (即a=3这行) ,然后抱怨了一大堆没赋值就引用。而只要 int a = 3,a就满足声明了,然后后面的所有错误就全消失了。

也许我们应该这样规定编译器的行为更好一些:一旦发现错误,就停下来,因为只要有一个错误存在,整个程序就都不可能运行,下面的错误,是没有必要讨论的。

就像 a && b && c && d ... 只要a是假的,后面的是没有必要求值的。所以写程序是个需要有洁癖和强迫症的事。

只关注唯一的问题,这也大大减少了我们的心理负担。如果知道人生后面还有那么多苦难等着呢,可能要跳楼跳桥的人数会大大增加吧。那些苦难一望无际,但是一个个解决下来,我们也就成长了。所有的RPG游戏,WOW之类的,无不如此。人生也是一样,调程序也是的。

2. 说起心理负担,说个题外话。

不少人认为程序员都是有点强迫症的,或者更泛化一些,IT男大抵如此?IT女就惨了,有些认为自己有强迫症啊,这多么可怕。其实没啥,不损害国家和社会,对周围人也没有啥负面影响,就无所谓,不妨当成一种可爱的品质接受下来。

比如我经常锁了门以后下楼,然后挠头"我锁了呢,还是没锁。"上去看一眼吧,恩,锁了。注意,此处有闹心,好像这么点小事都没处理明白似的。再下楼,再挠头,"我锁了呢,还是没锁。"如是者三,就真闹心了。

其实没啥。我们有解决手段的。不然程序员这么精细的生物如何在这个粗糙的世界上生存啊。

简单粗暴的手段是,在某次上重重把门关上,用手使劲拧钥匙,让手生疼。记住这个感觉。

复杂点的手段是:随身携带相机,拍下来锁门了。照片上都有时间戳的,怀疑自己的时候就拿出来看一眼。

综上所述,认真严谨带来的副作用不可怕,可以伴随终生。

3. 当问题真的有两个

同时有两个问题的情况,一个一个解决。这在前面说过了。要补充的是,在解决的过程中,观察故障现象的变化。

经常有牛人修机器调程序的时候,旁边一群观摩的。观摩的人们就发现,牛人修了半天还没修好,但是却一点也没慌。一个可能的原因是:故障的现象一直在变化,牛人正试图找出规律。

最可怕的不是故障,或是故障现象一直不稳定,而是没现象。或者你对故障的现象无从解释,即没有一条理论指出现象理应如此。

人类倾向于追究问题的原因。古代天打雷了,下雨了,下多了,下少了,祖先都要找出个原因来。然后针对这个原因做点工作,再观察。比如不下雨了,就扔河里几个童男童女。这个时候如果还不下,大家就有点毛了。得给出新的解释或假说来。

所以,在每一步里故障现象的变化对于诊断非常重要。

琼瑶女士有部作品忘了是什么,还是你们还是小学生时候的。当然,跟现在的路数区别倒是不大。不过统而言之,从诗经到现在,琼瑶读者关注的内容似乎区别也不大。

琼瑶剧里一个女的把男朋友惹翻了,很久以后
(我猜顶多几个月,不过片子里似乎是半辈子)又见面。见面以后似乎是吵了一顿。事后女的问她爸,他生气了吗。她爸说:孩子,我看你们算是完了,他气得不行了。女的说:太好啦,这说明他还爱我。

这位女士具有成为程序员的潜力,她的依据就是故障现象的变化表明问题。最可怕的不是憎恨,而是冷漠。最可怕的不是天崩地裂,而是没现象。


3. 孤立的原则,分析

没现象,就离没救不远了。这个时候,我们要做的就是整出现象来。如果现象不停地变,似乎没有规律,我们要做的就是确定设置与现象之前的关系。

以上两种都要求,孤立。每次只检查一个坏部件. 假设其他的都是好的,直到遇到矛盾。这也正是后面的另一条原则。

就像肌肉训练一样,孤立有很多好处。只有孤立才能不让有毛病的部分被好的部分掩盖起来。这时需要特意设置一些操作,以验证自己的假设。福尔摩斯,Spock,还有柯南都说:排除所有的不可能,剩下的就是唯一可能。

排除所有不可能的第一步,是确定有哪些可能。第二步,是一个一个地单独排除。前者,就是问题的划分,后者,就是孤立的原则。或者说,分析
(相对于综合) 的方法。要确保每种可能
(或步骤)间的节奏,不要混在一起。在处理某种可能的时候,不要试图走捷径把别的可能一起测了。那样,你最终也搞不清楚到底哪个才是问题的原因。

可类比的案例不少,比如集中优势兵力,各个击破敌人;比如,在实验室,人工限制约束条件,确定变量间的因果关系。

4. 实验计划

所以,在实验开始前,要先列出实验计划。你认为都有哪些可能,应该用哪些方法
(根据实验现象)判断这种可能是否就是问题的原因,需要哪些步骤,先什么后什么。这些都要在实验动手前写下来。然后一步步执行的时候标注结果。你猜对了么,为什么,这个猜测是错的么,是什么现象否定了你的猜测。

先假设各种可能,然后再做实验。当所有的假设都验证了,没有新的假设,而现象还没有得到解释的时候,就是该停下来的时候。我们打猎的时候是假定那里有只兔子,然后才放箭的,而不是到处放箭,然后跑过去看那里有没有兔子。打哪指哪的效率太低,那么干的都得饿死。虽然你可能看到过那样的事:一抬头,啊呀,这不就是问题的原因么。灵感这种事,不是工程师应该依靠的。

类似的,大的项目和大的学习计划也需要拆成小步,不然令人叹为观止的工作量会在我们开始之前把我们压垮。一般四五个小时的就可以称为令人叹为观止,更不用说需要一年半年才能读完的书,或者完成的东西。别以为我们这么大了控制能力就提高了,在这方面,我们仍然是希望在第一时间能检验自己成果的孩子,是希望快速判定这条路线能否成功的原始人。

即使你意志坚定,或者说信仰坚定吧,你的老板也一样么?

所以,工程师需要掌握的基本素养是:估算和度量。在最初的时候分解任务,估算时间和成本,在每一步骤结束后度量结果。有的同学可能会问,那初学的时候我还没有能力分解,没有能力估算怎么办?

我一直面临这一困惑,时时为自己的计划没有顺利执行而痛苦。尤其是新知识结构的书,简直无法预测啥时候能完成。后来为了平复自己,我这么做:去读,去做,啥时候完成啥时候算。不去估算。因为没有能力估算。等到知识结构逐渐建立起来,这一领域的知识丰富一些了,估算的能力也就提高了--估算的能力,正是在一次次的度量之后与最初估算的结果对比后提高的。

5. 如何划分问题

如何划分问题,依据我们的学科中的那些理论依据。依据我们对世界的理解。这就不是我能讨论的了。


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

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

[http://giftdotyoung.blogspot.com]

[http://blog.csdn.net/younggift]

No comments: