本文只提供给新手程序员阅读。
多年前的一个 IBM 的老鸟曾经教过我一个 5 分钟上手的思维习惯,当我听到以后,醍醐灌顶,惊人天人,一改日常的编码风格。到现在,它还持续保持在我的日常的编码习惯当中。
我不知道应该怎么称呼这种思维习惯的名字,top-down,自顶而下,或者分治?不管怎么样,它的核心是非常清晰的:编码的时候只思考同一个思维层次的逻辑,在这层完成之后再思考下一层。
它基于这么一个事实:我们每个人智力是有限的,同一个时间只能思考有限内容的东西,我们都不是天才。由于这个事实的存在,所以程序 bug 就会存在(废话)。因此,思考问题的时候(编码)不要跨抽象层级思考,在我们有限的智力里面现保证这个层级的逻辑正确,再思考下一个。什么叫跨抽象层级思考,什么叫同一个抽象层级的思考。我们通过一个例子来看看,假设现在我要编写程序打车去上班。
首先穿衣服,然后洗脸刷牙上厕所,然后下楼打车。
这句话就算是一个三岁的人都能说出来。上面的描述是在同一个思维层次,把“打车上班”这个事情分成了几个符合顺序和操作逻辑的模块和动作,我的大脑能够承载这些逻辑,这一套逻辑下来就能够把这件事情完成。于是我把它写下来:
def goWorkByCar ()
dressUp()
wash()
goDownstairs()
goByCar() }
这个抽象层次的东西已经完成了,我看这个函数就知道这个操作的顺序和逻辑是什么,甚至还不用注释。我们有四个函数都是没有完成的,它们的具体实现就是下一个抽象层次的东西。我们现在下到下一层,看看 dressUp
。现在我的注意力已经不在打车上班这件事情上了,这一层的逻辑已经完成了。我的注意力在怎么穿衣服这件事情上:
从柜子里把衣服拿出来,先穿上半身,再穿下半身。
符合正常的操作,不错:
def dressUp ()
clothes = takeClothesFromWardrobe()
dress(clothes.up)
dress(clothes.down)
我们看看洗漱怎么做:
上厕所,刷牙,洗脸
def wash ()
urine()
brushTeeth()
washFace()
下楼呢?
我要先出门,然后锁门,然后坐电梯。
def goDownstairs ()
openDoorAndOut()
lockTheDoor()
takeTheLift()
打车呢?
用手机打车,然后坐车走人
def goByCar ()
car = callACarByPhone()
takeTheCar(car)
...然后再用这种方式实现像 brushTeeth
和 washFace
具体的实现,想想它们需要什么操作。
你会发现我们不停地在填函数,函数里面又不停地出现新的函数需要我们去完成。每个函数体里面的操作都和这个函数所要完成的目的相关,在一个函数里面,我们只关心这个函数需要完成的目标所需要操作。例如“打车上班”这个任务所依托的 goWorkByCar,我们在这里并不关心怎么刷牙,因为它是下一层的东西,我们只关注完成“打车上班”这个目标需要哪几步。如果我们刷牙、开门这些操作都放在这个函数里面,那么一开始就会陷入无限的细节当中而失去了主要目的的关注,而且大脑也无法承载如此复杂多端的细节,处理不好就很容易出现 bug。
而这种思维方式可以让我们不停地把一个大的任务在同一个层级做分解,保证这一层的步骤和逻辑正确以后,然后再下一层分解,最后其实是一个树状的结构,树的叶子结点才是具体的代码算法实现。在编码的时候,我只用我有限的脑力关注当前层级的任务,不关注上一层也不关注下一层的细节。
这样的代码写出来不仅 bug 少,逻辑清晰而且还很轻松,因为你每一层思考的东西其实都很简单,不知不觉就把代码写完了。为什么很多人觉得编程很难,因为你接到一个“打车上班”的任务以后就开始脑子里面就填满了刷牙打车穿衣服这种不成逻辑和顺序的任务,并且都把它写到一个函数里面去。
接到任务以后你可以像上面一样用人类语言描述一下如果你要解决这个任务,你需要什么步骤。这些步骤才是这一层任务的关注点。例如跟女朋友看电影:你要先看看有什么好电影,然后再约女朋友,再买票。
(UPDATE:有朋友说这里第一步应该先找女朋友,同意!!必须同意!全身同意!每个细胞都同意!)
当你写一行代码的时候可以经常看看你的函数名,你这行代码和你的函数名所代表任务的是不是同一个逻辑层次的东西,不是的话,把它分出去。