如何判断架构设计的优劣?

架构设计的基本准则是非常重要的,它们指导着我们如何构建可靠、可维护、可测试的系统 。下面是这些准则的转换表达方式:
简单即美(KISS):KISS原则的核心思想是保持简单 。在设计系统之前,首先要正确理解系统需求,然后才进行设计 。要避免过度设计,除非有人能承担复杂性的成本 。这里的“简单”强调易实施性和易理解性 。接口应该自然地表达语义,让人一看方法名就能理解其功能 。
模块化(Modularity):模块化强调的是将系统分解成互相独立的模块 。从架构设计的角度来看 , 模块的接口比实现更为重要 。我们应该专注于模块而不是框架,因为框架是易变的,而模块是更加稳定和可复用的 。设计模块时,应忽略框架的存在,专注于模块的接口设计 , 并确保接口足够通用 。
可测试性(Testable):设计应该以可测试性为第一目标 。可测试性通常意味着低耦合 , 因为低耦合的模块更容易进行单元测试 。模块测试的第一步是创建环境模拟 , 即模拟模块所依赖的其他模块 。测试能够帮助我们发现架构调整的潜在问题,并且在代码重构时尤其重要 。
正交分解(Orthogonal Decomposition):正交分解是指对系统进行独立且相互无关的分解过程 。这个原则强调的是乘法而不是加法,即组合而不是继承 。通过组合相互独立、没有相关性的模块,可以构建出我们所需的业务场景,而不是通过继承叠加能力来改造模块 。
核心系统的伤害值正交分解首先涉及确定核心系统和周边子系统 。核心系统是业务的最小功能集 , 而周边子系统则通过逐步增加新功能来扩展系统的功能 。对核心系统的变更必须谨慎对待 。如果某个新功能在早期未被规划,后来又被确定为核心功能,我们必须认真评估其对现有架构的影响 。周边功能方面,我们关注的是如何降低添加新功能对核心系统的影响 。无论情况如何,系统都会因功能增加而变得复杂 。为了减少新功能的负面影响,相关代码应尽可能地内聚,即使不写入独立的模块中,也要放在独立的文件中 。这些代码被视为周边系统的功能实现代码,而不是核心系统的一部分 。我们关注的是周边功能对核心系统的影响 。为了添加某个功能 , 核心系统需要添加相关代码 。根据经验,核心系统为新功能添加的代码量越少,该功能与核心系统的耦合度就越低 。是否可能添加功能而不修改核心系统的代码?这是可能的 , 但需要核心系统提供插件机制 。
我们将在后续讨论这个话题,现在暂且搁置 。让我们把话题转回到架构设计质量的评估上 。虽然我们已经讨论了一些架构设计的基本准则,但尚未涉及质量评估的方法 。质量评估可以是定性的或定量的 。定性评估方法有一定的数据支持 , 但可能有些主观 。例如,“从某个角度来看,我感觉这个更好” 。定量评估方法更理想,但目前我个人尚未听说过任何用于确定架构设计优劣的定量评估方法 。今天我会介绍一些我个人想出的判断公式 。这些公式都是经验性的,并没有经过严格的数学证明 。假设一个架构设计方案将系统分成了n个模块,表示为:[M1, M2, ..., Mn] 。其中M1是核心系统,其他模块是周边子系统 。为简化起见,假设周边子系统之间是正交的 , 相互没有耦合 。
模块的耦合度测量我们第二个关注的问题是每个模块自身的质量,包括模块接口的质量和模块实现的质量 。首先,我们来看模块接口的质量,这是模块级别最重要的部分 。模块接口的质量取决于以下两个方面:
接口与业务的匹配性:接口应尽可能自然地反映业务需求 。然而 , 从机器判断的角度来看,这一点是无法计算的 , 完全取决于个人主观判断 。我们将在下一讲“少谈框架,多谈业务”中继续探讨这个话题 。
接口的外部依赖:即模块接口对外部环境的耦合程度 。下面我们将介绍模块的“耦合度测量公式”,它同时适用于模块实现和模块接口的耦合度测量 。
假设我们的模块实现(或模块接口)依赖了模块A , 那么我们的模块实现(或模块里的“符号”是指被引用的类型,包括typedef(类型别名)、class或struct,以及被引用的全局变量、全局函数或成员函数 。
接下来 , 我们看模块实现(或模块接口)的所有外部依赖,即该模块的总耦合度公式为其中,耦合度A表示该模块与依赖模块A的耦合程度 , 如前文所述 。而不成熟度系数A则表示依赖模块A的不成熟度程度 。若依赖模块A完全成熟,不再发生变化,则为0;若发生非常剧烈的变动,规格甚至无法确定,则为1 。


推荐阅读