本篇主要总结《代码不朽 编写可维护软件的10大要则》中的高层级部分。主要内容如下:
一. 分离模块之间的关注点
此处的模块对应的是类的概念。模块级原则针对的是类之间的关系。本节介绍的原则是关于如何实现类之间松耦合
。
1. 原则:
- 避免形成大型模块,以便能达到模块之间的松耦合。
- 你应该将不同的职责分给不同的模块,并且隐藏接口内部的实现细节。
- 该原则能提高可维护性的原因在于,相比起紧耦合的代码库来说,对松耦合代码库的修改更容易监督和执行。
单一职责是面向对象设计的重要原则之一。在设计类时,要保持类的职责单一。
从面向对象的角度出发,可将类的组成分为接口和实现两部分。对类的使用者而言,仅需关注类的接口,分析类提供了哪些接口以及每个接口的功能。至于接口的功能是如何实现的,那是类内部设计的问题,外部使用者不应关注,更不能依赖这些接口的具体实现。
2. 动机:
保持类体积小的最大好处是它直接带来了类之间的松耦合。松耦合意味着对类的设计可以更灵活地适应将来的变化。这里的“灵活性”指的是你可以在变化的同时,降低变化所带来的预料之外的影响。
-
小型、松耦合的模块允许开发人员独立进行工作
当一个类与其它类紧耦合时,对该类实现的修改会波及整个代码库。 -
小型、松耦合的模块降低了浏览代码库的难度
分离模块的关注点,不仅能让代码库更灵活地适应未来的变化,还可以改进代码库的可分析性,因为每个封装数据和实现逻辑的类都只完成了一个内聚的任务。 -
小型、松耦合的模块避免了让新人感到手足无措
违反单一职责的类,其耦合度会随着时间逐渐变得越来越紧,并且会积攒大量的代码。这些类会让缺乏经验的开发人员感到害怕。一个代码库如果拥有大量不能很好分离关注点的类,会非常难以适应新的需求变更。
3. 如何使用本原则:
一般来说,本原则会要求保持类的体积尽可能小(专注于某一个关注点),并限制外部对该类的调用数量。
以下是帮助避免类之间紧耦合的三个开发最佳实践:
-
根据不同关注点拆分类
在面向对象系统的建模和设计过程中,设计完整实现软件功能的各个类是一个必不可少的步骤。在通常的软件项目中,我们看到这些类一开始都只是实现某个单独功能的逻辑实体,但都逐渐负责越来越多的职责。为了避免类变得越来越大,必须在类承担超过一个职责时,对类进行拆分。 -
隐藏接口背后的特定实现
可以通过隐藏高层接口背后特定、具体的实现细节,来达到松耦合的目的。在使用的时候,针对接口而非实现编程,这样可以使系统变得更加模块化,增加系统的可修改性。 -
使用第三方库、框架来替换自定义代码
提供通用功能或工具方法的类会导致模块紧耦合,最佳实践是保持这些类的体积限制在一个有限范围内,并经常性地检查其它(开源)库和框架是否可以替换掉这些自定义的实现。
4. 本节内容总结:
耦合意味着当发生变化时,系统的两个部分在某个程度上是相连的。这种连接可能是直接调用,也可能是通过配置文件、数据库结构,甚至只是假设上(从业务逻辑的角度看)发生的间接调用。
松耦合可以增加系统的灵活性。很多面向对象的设计原则和设计模式都体现了对松耦合的支持。
二. 架构组件松耦合
在构建或者维护软件时,拥有对软件架构的清晰视野是必不可少的。一个好的软件架构可以让你洞悉系统要做什么、系统如何做到、功能之间是如何组织的。它能够向你展示系统的高层结构,即系统的“骨架”。
本节主要描述的是组件层面的依赖关系。一个组件是系统顶层划分的一部分。由于组件由软件的系统架构所定义,所以它的边界应该从开始开发时就非常清晰。
组件之间应该是松耦合的,即它们之间应当被清晰地隔离开,只有几个很少的接入点,并且互相共享有限的信息。这样,方法的实现细节可以被隐藏(封装)起来,从而使得系统更加模块化。
1. 原则:
- 顶层组件之间应该做到松耦合。
- 你应该尽可能减少当前模块中需要暴露给其它组件中模块的相关代码。
- 该原则能提高可维护性的原因在于,独立的组件可以单独进行维护。
2. 动机:
当一个组件内发生的变化只在这个组件内部有效果时,系统更加容易维护。
能够提升可维护性的调用分为两种:
-
内部调用是健康的:
由于模块调用的是相同组件内部的其它模块
,它们应该实现了紧密相关的功能。它们的内部逻辑对于外部是隐藏起来的。 -
传出调用是健康的:
因为它们把要做的任务代理给其它组件
,所以创建了一个向外的依赖。一般来说,将不同的关注点代理给其它组件是一件好事。代理可以在一个组件内部的任何地方进行,并且不必受限于组件内模块的数量。
对可维护性有负面影响的调用包括:
-
传入调用通过提供一个接口,为其它组件提供功能
修改传入依赖中的代码,可能会对其它组件造成很大的影响。通过降低传入依赖所涉及到的代码,可以降低对其它组件的负面牵连。
组件内的其它代码应当尽可能地进行封装,即应当避免来自其它组件的直接调用,从而提高信息的隐藏程度。 -
透传代码是有风险的,必须要避免
透传代码既接收传入调用,又同时代理给其它组件。透传代码违反了信息隐藏的原则:它将自己的代理(实现)暴露给自己的客户。从代码的角度看,说明组件之间没有良好地划分职责。
-
低组件依赖允许独立的维护
低依赖度意味着你可以进行独立修改。它适用于当组件代码大多数是内部调用或者传出调用的时候。独立维护意味着更少的工作,因为对代码的修改不会影响相应功能之外的代码。 -
低组件依赖可以分离维护职责
如果组件之间都是相互独立的,就很容易将不同职责的维护工作交给不同的团队。这样也能利用独立修改所带来的好处。独立性是在团队成员或者不同团队之间有效划分开发工作的前提。
如果组件之间相互紧密地交织在一起,就无法在多个团队之间隔离并划分维护职责,因为对某个组件的修改会波及其他的团队。这样不仅代码难以测试,而且修改的影响也难以预测,同时开发人员也需要花更多的时间来进行沟通,以及浪费时间等待他人完成修改。 -
低组件依赖让测试变得容易
对于内部调用,在组件内部就可以对功能进行跟踪和测试。
对于传出调用,不需要对其它组件提供的功能进行mock或stub(假设其它组件中的功能都已经完成,且是正确的)。
3. 如何使用本原则:
本节原则的目的是让组件之间达到松耦合。
在实践中,在组件之间实现接口和请求时,要坚持并遵循以下几个规范:
- 限制作为组件接口的模块的大小
- 在更高的抽象层次上来定义组件接口。这是为了限制跨越组件边界的请求类型,避免请求“了解太多”实现的细节。
- 避免使用透传调用,因为它对功能测试的影响最为严重。换言之,避免调用其它组件的接口模块。如果存在透传代码,应当分析相关模块,消除对其它组件的调用。
4. 本节内容总结:
类之间需要松耦合,组件之间也需要松耦合。类的耦合度关注于单个类与系统其它部分的暴露程度,而组件耦合度关注的是一个组件中的模块,对其它组件中模块的暴露程度。
从组件层面来考虑,相同组件中模块之间的调用被认为是一个内部调用,但从类层面来考虑,就变成模块之间的耦合。
三. 保持架构组件之间的平衡
一个平衡良好的软件架构拥有的组件,数量不会太多也不会太少,各个组件体积大小也几乎都一样。这样的架构就拥有一个好的组件平衡。
1. 原则:
- 你需要平衡代码中顶层组件的数量和体积
- 你应该保持源代码中组件的数量接近于9(如在6-12之间),并且这些组件的体积基本一致。
2. 动机:
-
好的组件平衡能让查找和分析代码变得更加容易
清晰的代码组织结构会让查找代码变得更加容易。当组件数量处于可控范围内(9个左右),并且每个组件体积一致的时候,可以很容易地分析所需修改的代码。 -
好的组件平衡能隔离维护所带来的影响
当一个系统的组件平衡能够清晰地描述功能边界时,它能够正确地分离关注点,有利于形成系统中各个组件的独立行为。具有明确功能和技术边界的组件更容易被替换、删除和测试。 -
好的组件平衡能够分离维护职责
组件之间清晰的功能边界,可以让我们更容易地将维护职责分给不同的团队。
3. 如何使用本原则:
组件平衡的2个原则是:
- 顶层系统组件个数在理想状态下应为9,通常来说位于6-12之间。
- 各个组件的代码量应该大致相当。
确定将功能合成组件的合适原则
为了实现一个合适的系统划分,方便开发人员浏览代码,需要选择组合功能的合适原则。通常,软件系统会根据高层的功能领域(描述了系统为用户提供了何种操作的功能)来进行组织,还可按照技术专长来进行划分。
- 基于功能领域划分的系统
好处是可以在设计时就进行,而不用等到开发阶段,于开发人员而言,可以从高层功能的角度来分析代码。
如划分资料检索、发票管理、报表等组件。 - 基于技术划分的系统
好处是团队能够按照技术专长来划分职责。
如划分前端、后端、接口、日志等组件。
明确系统的领域并坚持下去
一旦决定了系统组件的划分类型,需要一直坚持下去。一个不能保持一致的架构不是一个好架构。
4. 本节内容总结:
组件平衡能够反映出组件分离是否清晰,但这并不是它本身的目的。它应该遵循系统设计和开发的流程。对组件的划分应该是自然而然的,而不是刻意要划分成9个组件。
四. 保持小规模代码库
代码库是存储在一个仓库中的所有源代码的集合,可以独立进行编译和部署,并且由一个团队进行维护。一个系统至少有一个代码库,较大的系统通常有多于一个的代码库。
1. 原则:
- 保持代码库规模尽可能小
- 应该控制代码库增长,并主动减少系统的代码体积。
2. 动机:
软件开发和维护会随着系统体积的膨胀而变得日益困难。构建大型系统需要更大的团队以及更持久的项目,同时又会带来额外的负担和失败的风险。
-
以大型代码库为目标的项目更容易失败
项目体积和项目风险之间有强烈的关联关系。一个大型项目会导致大型团队、复杂的设计以及长时间的项目周期。结果就是,在相关利益人和团队成员之间出现更复杂的沟通和协作,很难看清软件设计的整体情况,并且在项目过程中会出现大量的需求变更。这些都增加了质量下降、项目延期以及失败的可能性。 -
大型代码库更加难以维护
-
大型系统会出现更密集的缺陷
在绝对数量上大型系统会拥有更多的缺陷,同时,缺陷的密度(每1000行代码的缺陷)也会随着系统变大而增加。
3. 如何使用本原则:
在其它条件都一样的时候,功能较少的系统其体积也会较小。其次,对功能的实现也可能是简洁或繁冗的。因此,要想保持一个较小的代码库体积,首先需要对系统功能进行限制,然后再注意限制编写的代码量。
实现本原则可采用功能层面的方法和技术层面的方法:
-
功能层面的方法:
功能相关的评估并不一定总能在你的掌握之中,但是任何与开发人员讨论新功能或修改功能的时候,你都需要考虑以下几项:
-
控制需求蔓延
要避免一些随意的需求不断被添加到项目中。 -
功能标准化
将功能标准化,可以在程序的行为和交互方面保持一致,也有利于代码的重用。
-
技术层面的方法:
对于技术实现来说,目标是用更少的代码来实现相同的功能。要想做到这一点,可以主要通过引用的方式来重用代码,或者使用已有的代码库或框架来代替自己编写的代码。
- 不要复制粘贴代码
- 重构已有的代码
- 使用第三方库和框架
- 拆分大型系统
网友评论