第二章

识别复杂性的能力是至关重要的设计技能。它使您可以先找出问题,然后再付出大量努力,并可以在其他选择中做出正确的选择。

复杂性的定义

如果一个软件系统难以理解和修改,那就很复杂。如果很容易理解和修改,那就很简单。

复杂性是开发人员在尝试实现特定目标时在特定时间点所经历的。它不一定与系统的整体大小或功能有关。

复杂性取决于最常见的投入。如果系统中有非常复杂的部分,但是几乎不需要触碰到这些部分,那么它们对系统整体的复杂性影响不大。
复杂度表达式:
系统的总体复杂度(C)由每个部分的复杂度(cp)乘以开发人员在该部分上花费的时间(tp)加权。在一个永远不会被看到的地方隔离复杂性几乎和完全消除复杂性一样好

那么问题来了,部分的复杂度是怎么确认的。感觉是一个递归问题,最终无解。
但是这个公式说明一个点,投入的大量时间的地方,无论原本复杂性如何,整体复杂度也会提高。可以通过合理的设计不提高复杂度的情况下,减少改动工作量也是系统设计的要点。同时合适地隐藏复杂度,将极度负载的地方隔离起来,平时不会去动他,那么系统复杂度就不会高了。

读者比作家更容易理解复杂性。如果您编写了一段代码,对您来说似乎很简单,但是其他人则认为它很复杂,那么它就是复杂的。
作为开发人员,您的工作不仅是创建可以轻松使用的代码,而且还要创建其他人也可以轻松使用的代码

复杂的代码难以理解和修改

复杂性的症状

变更放大:复杂性的第一个征兆是,看似简单的变更需要在许多不同地方进行代码修改。

认知负荷:复杂性的第二个症状是认知负荷,这是指开发人员需要多少知识才能完成一项任务。
较高的认知负担意味着开发者必须花更多的时间来学习所需的信息,并且由于错过了重要的东西 而导致错误的风险也更大。
系统设计人员有时会假设可以通过代码行来衡量复杂性。
他们认为,如果一个实现比另一个实现短,那么它必须更简单;如果只需要几行代码就可以进行更改,那么更改必须很容易。但是,这种观点忽略了与认知负荷相关的成本

未知的未知:复杂性的第三个症状是,必须修改哪些代码才能完成任务,或者开发者必须获得哪些信息才能成功地执行任务,这些都是不明显的。
在复杂性的三种表现形式中,未知的未知是最糟糕的。一个未知的未知意味着你需要知道一些事情,但是你没有办法找到她是什么,甚至是否有一个问题。你不会发现它,直到错误出现后。
同样,高的认知负荷会增加改变的成本,但如果明确要阅读哪些信息,改变仍然可能是正确的。
对于未知的未知....唯一确定的方法是读取系统中的每一行代码,这对于任何大小的系统都是不可能的。甚至这可能还不够,因为更改可能依赖于一个从未记录的细微设计决策。

良好设计的最重要目标之一就是使系统显而易见。这与高认知负荷和未知未知数相反。
在一个显而易见的系统中

  • 开发人员可以快速了解现有的代码的工作方式以及进行更改所需的内容
  • 开发人员可以在不费力地思考的情况下快速猜测要做什么,同时又可以确信该猜测是正确的

复杂性的原因

复杂性是由两件事引起的:依赖性和模糊性。

当无法孤立地理解和修改给定的一段代码时,便存在依赖关系。
该代码以某种方式与其他代码相关,如果更改了给定代码,则必须考虑和/或修改其他代码。

依赖关系是软件的基本组成部分,不能完全消除。
但是软件设计的目标之一是减少依赖关系的数量,并使依赖关系保持尽可能简单和明显。

软件设计的目标之一,减少依赖关系的数量
Spring的依赖反转和单实例,将依赖都聚焦到了一个实例上,也在某种程度上减少的依赖关系。虽然翻阅系统代码,并不觉得依赖关系有所减少。

复杂性的第二个原因是晦涩。当重要的信息不明显时,就会发生模糊。
晦涩常常与依赖项相关联。
不一致性也是造成不透明性的一个重要原因:如果同一个变量名用于两个不同的目的,那么开发人员就无法清楚地知道某个特定变量的目的是什么

在许多情况下,由于文档不足而导致模糊不清。
模糊性也是设计问题。
对于大量文档的需求通常是一个告警,即设计不正确。减少模糊性的最佳方法是简化系统设计。

依赖性导致变化放大和高认知负荷。
晦涩会产生未知的未知数,还会增加认知负担。
如果找到最小化依赖关系和模糊性的设计技术,那么我们就可以降低软件的复杂性

复杂度是递增的

复杂性不是由单个灾难性错误引起的:它堆积成许多小块。
单个依赖项或模糊性本身不太可能显著影响软件系统的可维护性。
之所以会出现复杂性,是因为随着时间的流逝,成千上万的小依赖性和模糊性逐渐形成。最终,这些小问题太多了,以至于对系统的每次可能更改都会收到其中几个问题的影响。

复杂性的增量性质使其难以控制。可以很容易说服自己,当前更改所带来的的一点点复杂性没什么大不了....一旦积累了复杂性,就很难消除它,因为修复单个依赖项或模糊性本身不会产生很大的变化。

为了减缓复杂性的增加,必须采用“零容忍”理念