2.2 复杂性的症状
Last updated
Last updated
复杂性一般有三种表现形式,在下面的段落中有所描述。每种表现形式都会使开发任务更难执行。
变更放大。复杂性的第一个症状是,一个看似简单的变更需要在许多不同的地方进行代码修改。例如,考虑一个包含多个页面的网站,其中每个页面都显示一个带有背景颜色的横幅。在许多早期的网站中,颜色是在每个页面上明确指定的,如图2.1(a)所示。为了改变这样一个网站的背景,开发者可能不得不手工修改每一个现有的页面;这对于一个有数千个页面的大型网站来说几乎是不可能的。幸运的是,现代网站使用了类似于图2.1(b)的方法,即在一个中心位置指定一次横幅颜色,所有的独立页面都引用这个共享值。通过这种方法,整个网站的横幅颜色可以通过一次修改来改变。好的设计的目标之一是减少受每个设计决定影响的代码量,所以设计的改变不需要非常多的代码修改。
认知负荷。复杂性的第二个症状是认知负荷,它指的是为了完成一项任务,开发人员需要知道多少东西。认知负荷越高,意味着开发人员必须花更多的时间来学习所需的信息,并且出现错误的风险也更大,因为他们可能会错过一些重要的东西。例如,假设C语言中的一个函数分配了内存,返回一个指向该内存的指针,并假定调用者将释放该内存。这增加了使用该函数的开发人员的认知负荷;如果开发人员未能释放内存,就会出现内存泄漏。如果系统可以被重组,使调用者不需要担心释放内存的问题(分配内存的模块也负责释放内存),这将减少认知负荷。认知负荷产生于许多方面,例如具有许多方法的 API、全局变量、不一致性(inconsistency)以及模块之间的依赖。
系统设计者有时认为复杂性可以用代码行来衡量。他们认为,如果一个实现比另一个更短,那么它一定更简单;如果只需要几行代码就能做出改变,那么这个改变一定很容易。然而,这种观点忽略了与认知负荷有关的成本。我曾见过一些框架,它们允许只用几行代码就能写出应用程序,但想要理解这几行代码是什么意思,却非常困难。有时候,需要更多行代码的方法实际上更简单,因为它减少了认知负荷。
未知的未知因素。复杂性的第三个症状是,要完成一项任务必须修改哪些代码,或者开发人员必须掌握哪些信息才能成功执行任务,这一点并不明显。图2.1(c)说明了这个问题。该网站使用一个中心变量来决定横幅的背景颜色,所以它看起来很容易改变。然而,有几个网页为了强调,使用了一种较深的背景颜色,而且这种较深的颜色在各个网页中都是显式指定的。如果背景色改变了,那么强调色也必须改变以与之匹配。不幸的是,开发人员不太可能意识到这一点,所以他们可能会改变中央bannerBg
变量而不更新强调色。即使开发者意识到了这个问题,也不会很明显地发现哪些页面使用了强调色,所以开发者可能不得不搜索网站中的每个页面。
在复杂性的三种表现形式中,未知的未知因素是最糟糕的。未知的未知因素意味着有一些你需要知道的东西,但你没有办法发现它是什么,甚至没有办法发现是否有问题。你不会发现它,直到你做了一个变更后出现了错误。变更放大的确很烦人,但只要清楚哪些代码需要修改,一旦变更完成,系统就能正常工作。同样地,高认知负荷会增加变更的成本,但如果清楚哪些信息需要阅读,变更仍然很有可能是正确的。而在面对未知的未知因素时,我们不知道该怎么做,也不知道提出的解决方案是否会成功。唯一能确定的方法是阅读系统中的每一行代码,这对任何规模的系统来说都是不可能的。即使这样做也可能是不够的,因为一个变更可能取决于一个从未被记录下来的微妙的设计决策。
良好设计的最重要的目标之一就是让系统易于理解(obvious)。这与高认知负荷和未知的未知因素恰恰是对立面。在一个易于理解的系统中,开发者可以很快理解现有的代码是如何工作的,以及需要做什么变更。在一个易于理解的系统中,开发人员可以快速地猜测要做什么,而不需要非常努力地思考,但又能确信猜测是正确的。讨论了使代码更易于理解的技术。