软件架构师 第一部分 基础篇 第五章 架构特性的度量和控制

架构师必须处理软件项目所有不同方面的各种架构特性。 诸如性能,弹性和可伸缩性之类的运营方面与诸如模块化和可部署性之类的结构性问题融合在一起。本章着重于具体定义一些较常见的架构特性并为其建立治理机制。

测量架构特性

组织中有关架构特性的定义存在几个常见问题:

他们不是物理的

常用的很多架构特性含义不明确。例如,架构师如何设计敏捷性可部署性?业界对通用术语的看法大相径庭,有时是由合理的不同上下文所驱动,有时是偶然的。

定义千差万别

即使在同一组织内,不同部门也可能在关键功能(例如绩效)的定义上存在分歧。在开发人员,架构和操作没有统一的定义之前,进行适当的对话是很困难的。

太多的复合

许多所需的架构特性在较小规模上包含许多其他特性。例如,开发人员可以将敏捷性分解为诸如模块化,可部署性和可测试性之类的特性。

架构特性的客观定义解决了所有三个问题:通过在组织范围内就架构特性的具体定义达成一致,团队围绕架构创建了一种普遍存在的语言。同样,通过鼓励客观定义,团队可以解开组合特性以发现他们可以客观定义的可测量特性。

运行度量

许多架构特性具有明显的直接度量,例如性能或可伸缩性。 但是,根据团队的目标,即使这些提供了许多细微的解释。例如,也许一个团队测量了某些请求的平均响应时间,这是操作架构特性测量的一个很好的例子。但是,如果团队仅测量平均值,那么如果某些边界条件导致1%的请求花费的时间比其他时间长10倍,会发生什么情况?如果该站点有足够的流量,则异常值甚至可能不会出现。因此,团队可能还需要测量最大响应时间以捕获异常值。

性能的多种表述

我们描述的许多架构特性都有多个细微的定义。性能就是一个很好的例子。许多项目着眼于总体性能:例如,Web应用程序的请求和响应周期需要多长时间。但是,架构师和DevOps工程师在建立性能预算(针对应用程序特定部分的特定预算)方面进行了大量工作。例如,许多组织已经研究了用户行为,并确定第一页渲染(浏览器或移动设备中网页进度的第一个可见进度)的最佳时间为500毫秒(半秒);大多数应用程序都属于该指标的两位数范围。但是,对于试图捕获尽可能多的用户的现代站点而言,这是要跟踪的重要指标,并且其背后的组织已建立了非常细微的衡量标准。

其中一些指标对应用程序的设计有其他影响。许多具有远见的组织将K权重预算用于页面下载:在特定页面上允许的最大字节数的库和框架。它们在此结构背后的原理来自于物理限制:一次只能有这么多字节通过网络传输,尤其是对于高延迟区域中的移动设备而言。

高水平的团队不只是建立严格的绩效数字,他们的定义基于统计分析。例如,假设视频流服务希望监视可伸缩性。工程师没有设置任意数字作为目标,而是随着时间的推移测量规模并建立统计模型,然后在实时指标超出预测模型时发出警报。失败可能意味着两件事:模型不正确(哪些团队想知道)或某些问题(哪些团队也想知道)。

结合工具和细致入微的理解,团队现在可以衡量的各种特性正在迅速发展。例如,许多团队最近集中精力于性能预算,以衡量指标,例如第一笔满意的绘制第一笔CPU闲置,这两项都为移动设备上的网页用户谈论了性能问题。随着设备,目标,功能和众多其他事物的变化,团队将找到新的事物和测量方法。

结构度量

一些客观指标并不像绩效指标那么明显。 内部结构特性(如定义明确的模块化)又如何呢?不幸的是,内部代码质量的综合指标还不存在。但是,某些度量标准和通用工具的确可以使架构师解决代码结构的某些关键方面,尽管范围很窄。

代码的一个明显的可测量方面是复杂度,由循环复杂度度量定义。

圈复杂度

循环复杂度(CC)是一种代码级度量,旨在为复杂度提供对象度量1976年由托马斯·麦凯布(Thomas McCabe,Sr.)开发的功能,方法,类或应用程序级别的代码集。

它是通过将图论应用于代码(尤其是决策点)来计算的,而决策点会导致不同的执行路径。例如,如果一个函数没有决策语句(例如if语句),则CC

= 1。如果函数具有单个条件,则CC

= 2因为存在两个可能的执行路径。

用于计算CC的公式 单一功能或方法是 CC= E− N+ 2,其中N代表节点(代码行),并E代表(可能的决策)。考虑示例6-1中所示的类似C的代码。

示例6-1 用于圈复杂度评估的示例代码

public void decision(int c1, int c2) {

    if (c1 < 100)

        return 0;

    else if (c1 + C2 > 500)

       return 1;

    else

      return -1;

}

示例6-1的圈复杂度为3(= 3 – 2 + 2);该图出现在图6-1中

6-1决策函数的圈复杂度

圈复杂度公式中出现的数字2表示单个函数/方法的简化。对于其他方法的扇出调用(在图论中称为连接组件),更通用的公式是CC= E− N+ 2 P,其中P表示连接的组件数。

架构师和开发人员普遍认同,过于复杂的代码代表着代码的坏味道。它实际上损害了代码库的每个令人希望的特性:模块化,可测试性,可部署性等。但是,如果团队不关注逐渐增长的复杂性,那么这种复杂性将主导代码库。

循环复杂度有什么好的价值?

作者在谈论此主题时遇到的一个常见问题是:CC的一个好的阈值是多少?当然,就像软件架构中的所有答案一样,这取决于问题域的复杂性。例如,如果您遇到算法复杂的问题,则解决方案将产生复杂的函数。CC可供架构师监视的一些关键方面:由于问题域或编码不良,功能是否复杂?或者,代码的分区是否较差?换句话说,是否可以将大型方法分解为较小的逻辑块,从而将工作(和复杂性)分配给结构更完善的方法?

通常,CC的行业阈值表明,可接受10以下的值,除非考虑其他因素(例如复杂域)。我们认为该阈值非常高,希望将代码降到5以下,这表明内聚的,结构合理的代码。Java世界中的一个度量工具Crap4J试图通过评估CC和代码覆盖率的组合来确定代码的糟糕程度。如果CC增长到50以上,则没有多少代码覆盖率可以使该代码免于崩溃。Neal遇到过的最可怕的专业工件是单个C函数,它是CC超过800的商业软件包的核心!它是一个具有4,000行代码的函数,包括自由使用GOTO语句(以逃避不可能的深度嵌套循环)。

像测试驱动的开发这样的工程实践具有偶然的(但是积极的)副作用,即针对给定的问题域平均生成较小,较不复杂的方法。在练习TDD时,开发人员尝试编写一个简单的测试,然后编写最少的代码以通过该测试。对离散行为和良好测试边界的关注鼓励了构造合理,具有高内聚力的方法,这些方法表现出较低的CC。

过程度量

一些架构特性与软件开发过程相交。 例如,敏捷性经常表现为理想的功能。 但是,这是架构师可以分解为可测试性和可部署性等功能的复合架构特性。

可通过几乎所有评估测试完整性的平台的代码覆盖率工具来衡量可测试性。像所有软件检查一样,它不能代替思想和意图。例如,一个代码库可以具有100%的代码覆盖率,但是断言不佳,实际上并不能使人们对代码正确性产生信心。但是,可测试性显然是客观可测量的特性。同样,团队可以通过多种指标来衡量可部署性:成功部署与失败部署的百分比,部署需要多长时间,部署引发的问题/错误以及其他许多问题。每个团队都有责任进行一组良好的度量,以捕获其组织在质量和数量上有用的数据。其中许多措施归结为团队的重点和目标。

敏捷及其相关部分显然与软件开发过程有关。但是,该过程可能会影响架构。例如,如果易于部署和可测试性是重中之重,那么架构师将在架构级别上更加注重良好的模块化和隔离性,这是驱动结构决策的架构特性示例。实际上,如果软件项目范围内的任何事情都能够满足我们的三个标准,那么它就可能会升至架构特性的水平,从而迫使架构师做出设计决定来考虑它。

治理和适度函数

一旦架构师确定了架构特性并对其进行了优先排序,他们如何才能确保开发人员会遵从那些优先事项? 模块化是架构方面的一个重要例子,它很重要但并不紧迫。在许多软件项目中,紧迫性占主导地位,但是架构师仍然需要一种治理机制。

治理架构特性

治理,源自于希腊语kubernan(引导),是架构师角色的一项重要职责。顾名思义,架构治理的范围涵盖了架构师(包括企业架构师之类的角色)想要施加影响的软件开发过程的任何方面。例如,确保组织内的软件质量属于架构治理的范畴,因为它属于架构的范畴,如果有过失可能导致灾难性的质量问题。

幸运的是,存在越来越复杂的解决方案,可以减轻架构师的困扰,这是软件开发生态系统中功能不断增长的一个很好的例子。极限编程催生的软件项目实现自动化,从而实现了持续集成,从而进一步实现了操作的自动化,现在我们将其称为DevOps,一直持续到架构治理。《演进式的架构》(O'Reilly)一书描述了称为适应度函数的一系列技术,这些技术可用于自动执行架构管理的许多方面。

适度函数

构建进化的架构中的 “进化” 一词更多地来自于计算的进化,而不是生物学的进化。其中一位作者Rebecca Parsons博士在进化计算领域花费了很长时间,其中包括遗传算法之类的工具。遗传算法执行并产生答案,然后通过进化计算世界中定义的众所周知的技术进行变异。如果开发人员试图设计一种遗传算法以产生一些有益的结果,则他们通常希望对算法进行指导,从而提供指示结果质量的客观指标。该指导机制称为适应度函数:一种目标函数,用于评估输出与达到目标的接近程度。例如,假设开发人员需要解决旅行营业员问题,这是用作机器学习基础的著名问题。给定一个销售员和他们必须访问的城市列表,以及它们之间的距离,最佳路线是什么?如果开发人员设计了一种遗传算法来解决此问题,则一个适合度函数可能会评估路线的长度,因为最短的可能代表最大的成功。另一个适度函数可能是评估与路线相关的总成本,并尝试将成本保持在最低水平。另一个可能是评估销售人员拜访离开的时间,并进行优化以缩短总拜访时间。

演进式架构的实践借用了这个概念来创建架构适度函数:

架构适度函数

提供对某些架构特性或架构特性组合进行客观完整性评估的工作机制。

适度函数不是供架构师下载的某些新框架,而是对许多现有工具的新视角。注意定义中的“ 任何机制 ” 一词-用于架构特性的验证技术随其特性而变化。适应度函数与许多现有的验证机制重叠,具体取决于它们的使用方式:度量,监视器,单元测试库,混沌工程等等,如图6-2所示

6-2。适度函数的机制

根据架构特性,可以使用许多不同的工具来实现适度函数。例如,在“耦合”中,我们引入了度量标准,以允许架构师评估模块性。以下是适合度函数的几个示例,它们可以测试模块化的各个方面。

循环依赖

模块化是大多数架构师关心的隐式架构特性,因为维护不当的模块化会损害代码库的结构;因此,架构师应高度重视保持良好的模块化。但是,在许多平台上,做法与架构师的良好意图背道而驰。例如,在任何流行的Java或.NET开发环境中进行编码时,一旦开发人员引用了尚未导入的类,IDE就会帮助显示一个对话框,询问开发人员是否要自动导入该引用。这种情况经常发生,以至于大多数程序员习惯于像条件反射动作一样将自动导入对话框拖走。但是,任意地在彼此之间导入类或组件对于模块化来说是灾难。例如,图6-3展示了一种架构师渴望避免的特别有害的反模式。

6-3组件之间的循环依赖

图6-3中,每个组件都引用了其他组件。拥有这样的组件网络会破坏模块化,因为开发人员无法重用单个组件,而又不能使其他组件一起使用。而且,当然,如果将其他组件耦合到其他组件,则该架构将越来越倾向于“ 大泥球”反模式。架构师如何在不经常监视开发人员的情况下控制这种行为呢?代码审查虽然有帮助,但在开发周期中为时已晚,无法生效。如果架构师允许开发团队在代码评审之前的一个星期内粗暴地导入代码库,那么代码库中已经发生了严重的破坏。

解决此问题的方法是编写一个适应度函数以查看周期,如示例6-2所示。

示例6-2 适度函数可检测零件循环

 

在代码中,架构师使用指标工具JDepend来检查包之间的依赖关系。该工具理解Java包的结构,如果存在任何循环,则测试失败。架构师可以将此测试连接到项目的持续构建中,并且不再担心开发人员偶然引入文件触发周期。这是一个适度函数,可以保证重要而不是紧迫的软件开发实践,这是一个很好的例子:架构师应该重点关注,但对日常编码几乎没有影响。

主序列适度函数的距离

“耦合”中,我们介绍了距主序列更远的距离度量,架构师也可以使用适度函数进行验证,如示例6-3所示。

示例6-3 与主序列适应度函数的距离

 

在代码中,架构师使用JDepend为可接受的值建立阈值,如果类超出范围,则测试失败。

这既是针对架构特性的客观度量的示例,又是设计和实现适度函数时开发人员与架构师之间协作的重要性的示例。这样做的目的不是让架构师在象牙塔高高在上,开发开发人员无法理解的深奥适度函数。

小提示

架构师必须确保开发人员在将适度函数加给他们之前了解其功能。

在过去的几年中,适度函函的工具(包括一些专用工具)的复杂性有所提高。有一个这样的工具ArchUnit,它是一个Java测试框架,它受JUnit生态系统的某些部分启发并使用了它。ArchUnit提供了各种预定义的管理规则,这些规则被编码为单元测试,并允许架构师编写解决模块化的特定测试。考虑图6-4所示的分层架构。

 

6-4分层架构

当设计分层整体结构(例如图6-4中的整体结构)时,架构师有充分的理由来定义层(动机,权衡和分层结构的其他方面在第10章中进行了描述)。但是,架构师如何确保开发人员会尊重这些层?一些开发人员可能不了解这些模式的重要性,而其他开发人员则可能会采用“寻求宽恕胜于允许”的态度,这是由于一些诸如性能之类的本地问题所致。但是,允许实施者侵蚀架构的将会损害架构的长期健康。

ArchUnit允许架构师通过适度函数解决此问题,如示例6-4所示。

示例6-4 ArchUnit适应度函数可控制图层

 

示例6-4中,架构师定义了各层之间的理想关系,并编写了验证适合度函数来对其进行控制。

.NET空间中的类似工具NetArchTest允许对该平台进行类似的测试。例6-5中显示了C#中的层验证。

示例6-5 NetArchTest用于层依赖性

 

另一个例子适度函数的一部分是Netflix的捣乱的猴子和随之而来的猴子军团。特别是一致性,安全性和看门人猴子(容错性)就是这些方法的例证。一致性猴子允许Netflix的架构师定义生产中由猴子执行的治理规则。例如,如果架构师决定每个服务都应该对所有RESTful动词做出有用的响应,那么他们会将检查内容构建到一致性猴子中。同样,安全猴子会检查每个服务是否存在众所周知的安全缺陷,例如不应启用的端口和配置错误。最后,看门人猴子寻找不再有其他服务路由到的实例。Netflix具有不断发展的架构,因此开发人员通常会迁移到较新的服务,而无需协作者即可运行旧服务。由于在云上运行的服务会消耗金钱,因此看门人猴子会寻找孤立的服务并将其分解到生产环境之外。

猴子军团的起源

当Netflix决定将其运营转移到亚马逊的云时,架构师担心他们不再拥有对运营的控制权这一事实-如果缺陷在运营中出现了怎么办? 为了解决这个问题,他们使用原始的“混沌猴子”和最终的四面军产生了混沌工程学的学科。混沌猴子在生产环境中模拟了一般的混乱情况,以查看其系统将如何忍受。延迟是某些AWS实例的问题,因此Chaos Monkey会模拟高延迟(这是一个问题,他们最终为其创建了一个专门的猴子Latency Monkey)。像Chaos Kong这样的工具可以模拟整个Amazon数据中心的故障,帮助Netflix避免了此类故障的发生。

混沌工程学为工程学提供了一个有趣的新视角:这不是问题是否最终会破裂的问题,而是时间。预期这些破坏和测试以防止它们使系统更坚固。

几年前,Atul Gawande(皮卡多)撰写的颇有影响力的书《清单宣言》描述了航空飞行员和外科医生等职业如何使用清单(有时候是法律规定的)。这不是因为这些专业人士不了解工作或遗忘。而是,当专业人士一遍又一遍地进行非常详细的工作时,细节容易造成损失。简洁的清单可以有效地提醒。这是适度函数的正确视角-适度函数不是重量级的治理机制,而是为架构师提供了一种机制,可以表达重要的架构原理并自动对其进行验证。开发人员知道他们不应该发布不安全的代码,但是对于繁忙的开发人员来说,该优先级会与数十或数百个其他优先级竞争。特别是像安全猴子这样的工具,以及一般的适度函数,允许架构师将重要的治理检查编入架构的基础。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值