计算机带来了社会的剧变。因为有了它,我们可以用更少的人,干更多的事情。这就是计算机的价值——它可以干很多的事情,而且速度相当快。这挺棒的。 但是,计算机会出问题,而且总出问题。如果你家里其他东西出问题有计算机那么频繁,你多半会退货。生活在现代的大多数人,每天至少会遇到一次系统崩溃或者程序错误。这就没那么棒了。 1.1 计算机出了什么问题? 为什么计算机这么容易出问题?如果是软件的问题,那么有而且只有一个原因:程序写得太糟糕。有些人怪罪管理,有些人怪罪客户,但是调查发现,问题的根源通常都在于编程。 那么,“程序写得太糟糕”是什么意思?这是个很模糊的说法。通常来说,程序员都是很聪明、很理智的人,他们怎么会编出“糟糕”的程序呢?说穿了,这一切都与复杂性有关。 今天,计算机大概是我们能生产的最复杂的设备了。它每秒钟可以计算数十亿次,它内部数以亿计的电子元件必须精密协调,整台计算机才可以正常运行。 计算机上跑着的程序同样复杂。举例来说,微软的Windows 2000 还在开发时,就可算有史以来规模最大的软件了,它包含3000 万行代码,这大概相当于2 亿字——是《不列颠百科全书》字数的5 倍。 程序的复杂性问题可能更加麻烦,因为程序里没有摸得着的东西。程序出问题的时候,你也找不到什么实实在在的东西,打开瞧瞧里面发生了什么。程序完全是抽象的,非常难处理。其实,常见的计算机程序就已经足够复杂,没有人可以从头到尾理解所有代码是如何工作的。程序越大,越是如此。 这样说来,编程就成了把复杂问题化解为简单问题的劳动。否则,一旦程序达到某种复杂程度,就没有人可以理解了。程序中复杂的部分必须以某种简单方式组织起来,这样,不需要神那样强大的思维,普通程序员也可以开发出来。 这就是编程所要用到的艺术和才能——化繁为简。“差程序员”是不会化繁未简的。他们总以为用编程语言(这东西本来就够复杂了)写出“能跑通”的程序,就已经化解了复杂性,而没有考虑降低其他程序员需要面对的复杂性。大概就是这么一回事。 设想一下,为了把钉子钉到地板上,工程师发明了一台机器,上面有皮带轮,有绳子,还有磁铁。你多半会觉得这很荒唐。再设想,有人告诉你说:“我需要一段代码,它能用在任何程序的任何地方,能够通过任何介质,实现两台计算机之间的通信。”这个问题无疑很难化繁为简。所以,有些程序员(大多数程序员)在这种情况下给出的解法,就像是一台装备了皮带轮、绳子、磁铁的机器,其他人当然很难看得懂。并不是这些程序员缺少理性,他们的脑子也没有进水。他们面对的问题确实困难,设定的期限也很紧,他们能做的就是这些。在这些程序员看来,写出来的东西是能用的,它符合要 求。这就是他们的老板需要的,看来也应该是客户需要的。不过,这些程序员毕竟没做到化繁为简。完工之后,他们把结果交给其他程序员,其他程序员又会在这之上继续增添复杂性,完成自己的工作。程序员对化解复杂性考虑得越少,程序就越难懂。 于是程序变得无比复杂,最终没办法找出其中的各种问题。喷气式飞机差不多也有这么复杂,但它们的造价是几百万甚至几十亿美元,而且仔细排查过错误。大多数软件的售价只有50 ~ 100 美元。价钱这么低,没有人有足够的时间和资源,在几乎无限复杂的系统里找到所有的问题。 所以,“好程序员”应当竭尽全力,把程序写得让其他程序员容易理解。因为他写的东西都很好懂,所以要找出bug 是相当容易的。这个关于简单性的想法有时被误解为,程序不应当包含太多代码,或者是不应当使用先进技术。这么想是不对的。有时候,大量的代码也 可以带来简单,只不过增加了阅读和编写的工作量而已,这是完全正常的。你只要保证,那些大段的代码提供了化解复杂性所必须的简短注释,就满足了。同样,通常来说,更先进的技术只会让事情更简单,只是一开始你得学习,所以整个过程可能没那么简单。有些人相信,把程序写得简单所花的时间,要比写“能用就好”的程序更多。其实,花更多的时间把程序写简单,相比一开始随意拼凑些代码再花大量的时间去理解,要快得多。这个问题说起来轻巧,显得轻描淡写,其实软件开发的历史教训很多,诸多事例已经反复证明了这一点。许多大型程序的开发之所以会停滞数年,就是因为一开始没 有做好,结果必须等上这么长的时间,才能给之前开发出来的怪物加上新功能。 正因为如此,计算机经常出问题——因为大多数程序都有这个问题,许多程序员在写程序时并没有化解复杂性。是的,这么做很难。但是如果程序员做不到这一点,设计出的系统过于复杂、经常出问题,用户在使用的过程中就会经受无穷无尽的折磨。这么一比,把程序写简单所费的工夫实在算不了什么。 1.2 程序究竟是什么? 大多数人说的“计算机程序”,其实有完全不同的定义: (1)给计算机的一系列指令 (2)计算机依据指令进行的操作 第一种定义是程序员写程序时所用的。第二种定义是使用程序的普通用户所用的。程序员命令计算机:在屏幕上显示一头猪。这就是第一种定义,它包含若干指令。计算机接收到指令之后,会控制电信号,在屏幕上显示一头猪。这是后一种定义,即计算机执行的操作。程序员和用户都会说自己在和“计算机程序”打交道,但是他们的用法是很不一样的。程序员面对的是字母和符号,用户看到的是最终结果——计算机执行的操作。 所以,计算机程序其实是这两者的混合体:程序员的指令、计算机执行的操作。编写指令的最终结果就是让计算机执行那些操作——如果不需要执行操作,就没必要去写代码了。这就好像在生活里,你列了一张购物单(相当于指令),告诉自己该买哪些东西。如果你只是列了单子,但没去商店,单子就没有任何意义。指令必须得到实际的结果。 但是,列购物单和写程序有显著的区别。如果购物单列得很乱,只不过会降低买东西的速度。但是如果程序写得很乱,实现最终的目标就显得尤其困难。为什么呢?因为购物单是简单短小的,用完就可以扔掉。而程序是很复杂很庞大的,你可能还需要维护很多年。所以,同样是没有秩序,购物单只会给你造成一点儿小麻烦,程序却可以给你增添无尽的烦恼。 而且,除软件开发之外,没有任何领域的指令和结果联系得这么紧密。在其他领域,人们先编写指令,然后交给其他人,指令通常要等很长的时间才会执行。比如设计房子,建筑师首先给出指令——也就是蓝图。这份蓝图经很多人的手,过了很长的时间,才能建起真正的房子。所以,房子是大家所有人解读建筑师指令的结果。相反,写程序时,在我们和计算机之间没有任何人。我们让计算机干什么,就会得到怎样的结果;计算机绝对服从命令。结果的质量完全取决于机器的质量、我们想法的质量,代码的质量。在这三个因素当中,代码的质量是如今软件工程需要面对的最重要问题。根据这一点,以及上面提到的其他原因,本书的大部分内容都在论述如何提高代码质量。会有一些地方涉及机器的质量和想法的质量, 但是大多数内容都在论述怎样改善你交给机器的指令的结构和质量。虽然花了这么多的时间来谈代码,但也很容易忘记,改善代码质量的一切努力都是为了获得更好的结果。本书绝不姑息任何差劲的结果——我们学习提高代码质量的全部原因都在于,要想改进结果,提高代码质量是最重要的问题。 所以,我们最需要掌握的,就是提高代码质量的科学方法。