3 理解质量属性

第3章 理解质量属性

质量绝非偶然;它始终是高远的意图、真诚的努力、明智的引导以及精湛的执行共同作用的结果。

—— 威廉・A・福斯特(William A. Foster)

许多因素决定了系统架构必须具备的质量特性。这些质量特性超出了功能范畴,功能只是对系统能力、服务及行为的基本描述。尽管功能与其他质量特性密切相关,正如你将会看到的那样,在开发方案中功能往往占据首要位置。然而,这种偏向是短视的。系统常常需要重新设计,并不是因为它们在功能上有缺陷 —— 替代系统在功能上往往是相同的 —— 而是因为它们难以维护、移植或扩展,或是运行速度太慢,又或是遭到了黑客攻击。在 第 2 章 中,我们提到在软件创建过程中,架构是能够着手解决质量需求实现问题的首要环节。正是将系统功能映射到软件结构上的这一过程,决定了架构对质量特性的支持情况。在 第 4 章第 14 章 中,我们会讨论架构设计决策是如何对各种质量特性提供支持的。在 第 20 章 中,我们将展示如何把包括质量属性决策在内的所有驱动因素整合到一个协调一致的设计当中。

我们之前一直在宽泛地使用 “质量属性” 这个术语,但现在是时候更严谨地对其进行定义了。质量属性(QA)是系统的一种可度量或可测试的特性,用于表明系统在满足利益相关者需求方面的表现如何,这里的需求是超出系统基本功能之外的。你可以把质量属性看作是沿着利益相关者感兴趣的某个维度对产品 “实用性” 的一种衡量。

在本章中,我们的重点在于理解以下内容:

  • 如何表述我们希望架构展现出的质量特性;
  • 如何通过架构手段来实现这些质量特性;
  • 如何确定针对这些质量特性可能做出的设计决策。

本章为 第 4 章第 14 章 中对各个质量属性的讨论提供了背景知识。

3.1 功能性

功能性是指系统具备执行其预期工作的能力。在所有的需求当中,功能性与架构有着最为奇特的关联。

首先,功能性并不能决定架构。也就是说,给定一组所需的功能,能够创建出用以满足该功能的架构可以有无数种。最起码,你可以用任意多种方式对功能进行划分,并将各个子功能分配给不同的架构元素。

事实上,如果只有功能性是重要的,那你根本无需将系统拆分成多个部分:一个没有内部结构的单一整体模块就完全可以了。然而,我们会将系统设计成由相互协作的架构元素(如模块、层、类、服务、数据库、应用程序、线程、对等节点、层级等等)构成的结构化集合,目的是使其易于理解,并支持各种各样的其他用途。那些 “其他用途” 就是我们将在本章剩余部分以及 第二部分 后续的质量属性章节中探讨的其他质量属性。

尽管功能性独立于任何特定的结构,但它是通过给架构元素分配职责来实现的。这一过程产生了最基本的架构结构之一 —— 模块分解结构。

虽然职责可以随意分配给任何模块,但当其他质量属性比较重要时,软件架构会对这种分配加以约束。例如,系统通常(或者说总是)会进行划分,以便多人能够协作构建它们。架构师对于功能性的关注点在于它如何与其他质量属性相互作用以及如何对它们构成约束。

功能需求

在对功能需求和质量需求之间的区别进行了 30 多年的著述与讨论之后,功能需求的定义仍然让我捉摸不透。质量属性需求有着明确的界定:性能与系统的时间特性相关,可修改性关乎系统在初次部署后支持自身行为或其他特性变更的能力,可用性涉及系统在出现故障时仍能正常运行的能力,等等。

然而,功能是一个更为模糊的概念。一项国际标准(ISO 25010)将功能适用性定义为 “软件产品在规定条件下使用时,提供满足既定及隐含需求的功能的能力”。也就是说,功能性就是提供功能的能力。对这一定义的一种解释是,功能性描述的是系统做什么,而质量描述的是系统执行其功能的好坏程度。即质量是系统的属性,而功能是系统的目的。

不过,当你考虑某些 “功能” 的本质时,这种区分就站不住脚了。如果软件的功能是控制发动机的行为,若不考虑时间特性,又怎能正确实现该功能呢?要求用户名 / 密码组合来控制访问的能力难道不是一种功能吗,即便它并非任何系统的主要目的所在?

我更倾向于使用 “职责” 一词来描述系统必须执行的计算任务。诸如 “那一组职责有哪些时间限制?”、“针对那一组职责预计会有哪些修改?”、“允许哪类用户执行那一组职责?” 之类的问题是合理且可操作的。

质量的实现会引发职责;想想刚才提到的用户名 / 密码的例子就明白了。而且,人们可以确定与特定需求集相关联的职责。

那么,这是否意味着不应使用 “功能需求” 这个术语呢?人们对这个术语是有一定理解的,但当需要精准表述时,我们应该谈论具体的职责集合才对。

保罗・克莱门茨(Paul Clements)长期以来一直在抨击随意使用 “非功能” 一词的做法,现在轮到我来抨击随意使用 “功能” 一词的做法了 —— 可能同样收效甚微。

—LB

3.2 质量属性考量

正如系统的功能若不充分考虑质量属性就无法独立存在一样,质量属性自身也无法孤立存在;它们与系统的功能息息相关。如果一项功能需求是 “当用户按下绿色按钮时,‘选项’对话框出现”,那么一项性能质量属性注释可能会描述该对话框出现的速度有多快;一项可用性质量属性注释可能会描述此功能允许出现故障的频率以及修复的速度有多快;一项易用性质量属性注释可能会描述学习这项功能的难易程度。

至少自 20 世纪 70 年代以来,软件界就一直在对质量属性这一独特的主题进行研究。已经发布了各种各样的分类法和定义(我们会在 第 14 章 中讨论其中的一些),其中许多都有各自的研究和从业者群体。然而,关于系统质量属性的大多数讨论存在三个问题:

  1. 为某一属性提供的定义是不可测试的。说一个系统是 “可修改的” 毫无意义。对于某一组变更而言,每个系统可能是可修改的,但对于另一组变更则可能是不可修改的。其他质量属性在这方面也是类似的:一个系统对于某些故障可能很健壮,但对于其他故障可能很脆弱,等等。
  2. 讨论往往聚焦于某个特定问题属于哪种质量属性。针对系统的拒绝服务攻击是可用性方面的问题、性能方面的问题、安全性方面的问题,还是易用性方面的问题呢?这四个属性相关群体都会声称对拒绝服务攻击拥有 “所属权”。在某种程度上,它们都是正确的。但这种关于分类的争论对我们架构师理解并创建用以实际管理相关属性的架构解决方案并无帮助。
  3. 每个属性相关群体都发展出了自己的术语。性能相关群体有 “事件” 抵达系统的说法,安全相关群体有 “攻击” 抵达系统的表述,可用性相关群体会说 “故障” 来临,而易用性相关群体则用 “用户输入” 来描述。实际上,所有这些可能指的都是同一情况,只是用了不同的术语来描述。

解决前两个问题(不可测试的定义和重叠的问题)的一个办法是使用 “质量属性场景” 作为描述质量属性的一种手段(见 第 3 章第 3 节)。解决第三个问题的办法是以一种通用形式阐释对该属性群体来说至关重要的概念,我们会在 第 4 章第 14 章 中进行此项工作。

我们将重点关注两类质量属性。第一类包括那些描述系统运行时某些特性的属性,比如可用性、性能或易用性。第二类包括那些描述系统开发过程中某些特性的属性,比如可修改性、可测试性或可部署性。

质量属性绝不可能孤立地实现。实现任何一个质量属性都会对其他质量属性的实现产生影响 —— 有时是积极影响,有时是消极影响。例如,几乎每一个质量属性都会对性能产生负面影响。以可移植性为例:实现可移植软件的主要技术是隔离系统依赖项,这会给系统的执行引入开销,通常体现为进程或程序边界,进而影响性能。确定一个可能满足质量属性需求的设计,在一定程度上是要进行适当权衡的问题;我们会在 第 21 章 中讨论设计相关内容。

在接下来的三节中,我们将重点关注质量属性如何能够被明确规定、哪些架构决策能够促成特定质量属性的实现,以及关于质量属性的哪些问题能够让架构师做出正确的设计决策。

3.3 明确质量属性需求:质量属性场景

我们采用一种通用形式将所有质量属性(QA)需求规定为场景形式。这解决了我们之前提到的术语问题。这种通用形式是可测试且明确无误的;它不受分类随意性的影响。因此,它为我们处理所有质量属性的方式提供了规范性。

质量属性场景包含六个部分:

  • 触发事件(Stimulus):我们使用 “触发” 这一术语来描述作用于系统或项目的一个事件。对于性能相关群体而言,触发可以是一个 “事件”;对于易用性相关群体来说,它可以是一个 “用户操作”;对于安全性相关群体,它则可以是一次 “攻击”,等等。我们用同一个术语来描述引发开发质量相关情况的触发性动作。因此,对于可修改性而言,触发就是一项修改请求;对于可测试性来说,触发就是一个开发单元的完成。
  • 触发源(Stimulus source):一个触发源必须有其来源 —— 它必定来自某个地方。某个实体(人、计算机系统或任何其他参与者)必定产生了这个触发源。触发源的来源可能会影响系统对其的处理方式。来自可信用户的请求不会像来自不可信用户的请求那样受到严格审查。
  • 响应(Response):响应是触发源出现后所产生的活动。响应是架构师为满足需求而采取的行为。它由系统(针对运行时质量属性)或开发人员(针对开发时质量属性)为响应触发源而应履行的职责构成。例如,在性能场景中,一个事件发生(触发源),系统就应该处理该事件并生成响应。在可修改性场景中,一项修改请求出现(触发源),开发人员就应该实施修改(且无副作用)然后对修改进行测试和部署。
  • 响应度量(Response measure):当响应发生时,它应该能以某种方式进行度量,以便该场景可以被测试 —— 也就是说,以便我们能够确定架构师是否达成了相应目标。对于性能而言,这可以是延迟或吞吐量的度量;对于可修改性来说,它可以是进行修改、测试以及部署修改所需的人力或实际耗时。

场景的这四个特征是我们质量属性规范的核心内容。但还有另外两个重要却常被忽视的特征:环境和作用对象。

  • 环境(Environment):环境是场景发生所处的一系列情形。它通常指的是一种运行时状态:系统可能处于过载状态、正常运行状态或其他相关状态。对于许多系统来说,“正常” 运行可以指多种模式中的某一种。对于这类系统,环境应该明确说明系统正在执行的是哪种模式。但环境也可以指系统根本未运行时的状态:比如处于开发阶段、测试阶段、数据刷新阶段,或者在运行间隙进行电池充电阶段。环境为场景的其余部分设定了背景。例如,在代码为某次发布而冻结之后收到的修改请求,其处理方式可能与冻结之前收到的请求不同。某个组件出现的第五次连续故障,其处理方式可能与该组件首次出现故障时不同。
  • 作用对象(Artifact):触发作用于某个目标。这通常仅被视作系统或项目本身,但如果可能的话,更精确一些会更有帮助。作用对象可能是一组系统、整个系统,或者系统的一个或多个部分。失效或变更请求可能只影响系统的一小部分。数据存储中的失效与元数据存储中的失效处理方式可能不同。对用户界面的修改可能比对中间件的修改响应速度更快。

总而言之,我们将质量属性需求以场景形式(包含六个部分)呈现出来。虽然在考虑质量属性的早期阶段,通常会省略这六个部分中的一个或多个部分,但知晓所有这些部分的存在会促使架构师去考虑每个部分是否相关。

我们为 第 4 章第 13 章 中介绍的每个质量属性都创建了一个通用场景,以方便进行头脑风暴以及引出具体场景。我们区分了 “通用” 质量属性场景(通用场景)—— 它们与具体系统无关,可适用于任何系统,以及 “具体” 质量属性场景(具体场景)—— 它们特定于正在考虑的具体系统。

要将这些通用属性特征转化为针对特定系统的需求,就需要使通用场景具体化到特定系统上。但正如我们所发现的,对于利益相关者来说,将通用场景调整为适合其系统的场景,要比凭空生成一个场景容易得多。

图 3.1 展示了刚刚讨论过的质量属性场景的各个部分。图 3.2 展示了一个通用场景的示例,这里是以可用性为例的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图3.1 质量属性场景的各个部分

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.2 可用性的通用场景

与我无关

不久前,我正在对劳伦斯利弗莫尔国家实验室自行创建并供其内部使用的一个复杂系统进行架构分析。如果你访问该机构的网站(llnl.gov),并试图弄清楚利弗莫尔实验室的业务范围,你会反复看到 “安全” 这个词。该实验室专注于核安全、国际与国内安全以及环境和能源安全等重要事务……

考虑到他们对安全的重视,我请我的客户描述一下我正在分析的这个系统所涉及的质量属性。我敢肯定你能想象到我当时有多惊讶,因为他们一次都没提到 “安全”!系统的利益相关者提到了性能、可修改性、可演进性、互操作性、可配置性和可移植性等,还有另外一两个属性,但就是没提到 “安全” 这个词。

作为一名优秀的分析师,我对这个看似令人震惊又明显的遗漏提出了质疑。他们的回答很简单,现在回想起来也很直白:“我们不关心这个。我们的系统没有连接到任何外部网络,而且我们有带刺铁丝网围栏,还有持枪警卫呢。”

当然,利弗莫尔实验室里肯定有人对安全问题非常关注。但软件架构师们并不关心。这里得到的教训就是,软件架构师可能并不需要对每一项质量属性(QA)需求都承担责任。

—RK

3.4 通过架构模式和策略实现质量属性

现在我们来探讨架构师可用于 “实现” 所需质量属性的技术:架构模式和策略。

策略是一种影响质量属性响应达成的设计决策 —— 它直接影响系统对某些触发的响应。策略可能会给一种设计赋予可移植性,给另一种设计带来高性能,给第三种设计提供可集成性。

架构模式描述了在特定设计情境中反复出现的特定设计问题,并针对该问题给出了经充分验证的架构解决方案。该解决方案通过描述其组成元素的角色、它们的职责与关系以及它们相互协作的方式来明确。与策略的选择一样,架构模式的选择对质量属性(通常是多个质量属性)有着深远的影响。

模式通常包含多个设计决策,实际上,往往还包含多个质量属性策略。我们说模式常常将策略捆绑在一起,因此也常常会在质量属性之间进行权衡。

在我们各章针对特定质量属性的内容中,我们会查看策略与模式之间的示例关系。第 14 章 解释了如何构建针对任意质量属性的一组策略;实际上,这些策略就是我们用来生成本书中所呈现的各策略集的步骤。

虽然我们在讨论模式和策略时,仿佛它们是基础性的设计决策,但实际情况是,架构往往是诸多小决策和业务因素共同作用的结果而逐渐形成并演进的。例如,一个原本具有一定可修改性的系统,随着开发人员添加功能和修复漏洞等操作,可能会随着时间的推移而逐渐变差。同样,系统的性能、可用性、安全性以及任何其他质量属性都可能(而且通常确实)会随着时间的推移而变差,这同样是由于那些专注于眼前任务而不注重维护架构完整性的程序员的善意之举造成的。

这种 “千刀万剐式死亡”(逐渐恶化)在软件项目中很常见。开发人员可能由于对系统结构缺乏了解、进度压力,或者一开始架构就不够清晰等原因而做出次优决策。这种恶化是一种被称为架构债务的技术债务形式。我们会在 第 23 章 中讨论架构债务。要扭转这种债务局面,我们通常会进行重构。

进行重构可能有诸多原因。例如,你可能会重构一个系统以提高其安全性,根据不同模块的安全属性将它们置于不同的子系统中。或者你可能会重构一个系统以提高其性能,消除瓶颈并重写代码中运行缓慢的部分。又或者你可能会重构以提高系统的可修改性。例如,当两个模块因为彼此(至少部分)重复而反复受到相同类型的变更影响时,可以将公共功能提取出来形成一个独立的模块,从而提高内聚性,并减少下一次(类似的)变更请求到来时需要修改的地方。

代码重构是敏捷开发项目中的一项主要实践活动,作为一种清理步骤,以确保团队不会生成重复或过于复杂的代码。不过,这一概念同样适用于架构元素。

要成功实现质量属性,除了与架构相关的决策外,往往还涉及与流程相关的决策。例如,如果你的员工容易受到网络钓鱼攻击或者不选用强密码,那么再好的安全架构也毫无价值。我们在本书中不涉及流程方面的内容,但要知道它们很重要。

3.5 运用策略进行设计

系统设计由一系列决策构成。其中一些决策有助于控制质量属性响应;其他决策则确保系统功能的实现。我们在 图 3.3 中展示了这种关系。与模式一样,策略也是架构师多年来一直在使用的设计技术。在本书中,我们对它们进行了梳理、分类并加以描述。我们并非在此创造策略,而只是对优秀架构师在实践中的做法进行总结归纳。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.3 策略旨在控制对触发的响应。

我们为何要关注策略呢?原因有三:

  1. 模式是许多架构的基础,但有时可能并不存在能完全解决你问题的模式。例如,你可能需要高可用性、高安全性的代理模式,而不是教科书式的常规代理模式。架构师经常需要根据特定的应用场景对模式进行修改和调整,而策略为扩充现有模式以填补空白提供了一种系统性的方法。
  2. 如果不存在可实现架构师设计目标的模式,策略能让架构师依据 “基本原理” 构建出一个设计片段。策略能帮助架构师深入了解所生成设计片段的特性。
  3. 策略在一定限制条件下为使设计和分析更具系统性提供了一种途径。我们将在下一节探讨这一理念。

与任何设计概念一样,我们在此介绍的策略在应用于系统设计时可以而且应该进行细化。以性能为例:“资源调度” 是一种常见的性能策略。但为了特定目的,这一策略需要细化为具体的调度策略,比如最短作业优先、轮询等等。“使用中间件” 是一种可修改性策略。但中间件有多种类型(仅举几例,如分层、代理、代理服务器、层级结构等),它们的实现方式各不相同。因此,设计人员会进行细化,使每个策略变得具体。

此外,策略的应用取决于具体情境。还是以性能为例:“管理采样率” 在某些实时系统中是适合的,但并非在所有实时系统中都适合,在数据库系统或股票交易系统中肯定不适合,因为在这些系统中丢失单个事件都会造成很大问题。

请注意,存在一些 “超级策略”—— 这些策略非常基础且应用极为广泛,值得特别提及。例如,封装、限制依赖关系、使用中间件以及抽象出公共服务这些可修改性策略,几乎在以往实现的每一种模式中都能见到!但其他策略,比如来自性能方面的调度策略,也会在很多地方出现。例如,负载均衡器就是一种进行调度的中间件。我们发现监控会出现在许多质量属性中:我们对系统的各方面进行监控以实现能效、性能、可用性以及安全性等目标。因此,我们不应期望一个策略仅适用于一处,仅针对单一的质量属性。策略是设计的基本要素,正因如此,它们会在设计的不同方面反复出现。这实际上也说明了策略为何如此强大,值得我们以及你们去关注。去了解它们吧,它们会成为你的好帮手。

3.6 质量属性设计决策分析:基于策略的调查问卷

在本节中,我们将介绍一种分析人员可用来了解架构设计各个阶段潜在质量属性表现的工具:基于策略的调查问卷。

分析质量属性的实现程度是架构设计任务中的一个关键部分。(毫不意外的是)你不应等到设计完成后才开始进行此项分析。在软件开发生命周期的许多不同阶段,甚至是在非常早期的阶段,都会出现进行质量属性分析的机会。

在任何阶段,分析人员(可能就是架构师本人)都需要对可供分析的各类成果做出恰当的回应。分析的准确性以及对分析结果的预期置信度会根据现有成果的成熟度而有所不同。但无论设计处于何种状态,我们都发现基于策略的调查问卷有助于深入了解架构提供所需质量属性的能力(或者说是随着不断完善而可能具备的能力)。

第 4 章第 13 章 中,我们针对各章所涵盖的每个质量属性都包含了一份基于策略的调查问卷。对于调查问卷中的每个问题,分析人员需记录以下信息:

  • 系统架构是否支持各项策略。
  • 使用(或未使用)该策略是否存在任何明显风险。如果使用了该策略,则记录它在系统中是如何实现的,或者打算如何实现(例如,通过自定义代码、通用框架或外部生成的组件来实现)。
  • 为实现该策略所做的具体设计决策,以及在代码库中的什么位置可以找到该实现(具体落实情况)。这对于审计和架构重构很有用处。
  • 在实现该策略过程中所依据的任何理由或假设。

要使用这些调查问卷,只需遵循以下四个步骤:

  1. 对于每个策略相关问题,如果架构支持该策略,则在 “支持” 栏中填 “Y”,否则填 “N”。
  2. 如果 “支持” 栏中的答案是 “Y”,那么就在 “设计决策与位置” 栏中描述为支持该策略所做的具体设计决策,并列举出这些决策在架构中体现(所处)的位置。例如,指明哪些代码模块、框架或软件包实现了该策略。
  3. 在 “风险” 栏中,使用(高 = H,中 = M,低 = L)等级来标明实施该策略的风险。
  4. 在 “理由” 栏中,描述所做设计决策的理由(包括决定使用该策略的理由)。简要解释该决策的影响。例如,从成本、进度、演进等方面的影响来解释决策的理由和影响。

虽然这种基于调查问卷的方法听起来可能比较简单,但实际上它可能非常有效且能给人启发。回答这一系列问题会促使架构师退后一步,从更宏观的角度去思考问题。这个过程也可以相当高效:一份针对单个质量属性的典型调查问卷通常需要 30 到 90 分钟来完成。

3.7 小结

通过在设计中纳入一组恰当的职责来满足 “功能” 需求。通过架构的结构和行为来满足 “质量属性” 需求。

架构设计中的一个挑战在于,这些需求往往即便有被记录下来,记录的情况也很不理想。为了获取并表述质量属性需求,我们建议使用质量属性场景。每个场景由以下六个部分组成:

  1. 触发来源
  2. 触发事件
  3. 环境
  4. 作用对象
  5. 响应
  6. 响应度量

架构策略是一种会影响质量属性响应的设计决策。策略的关注点在于单个质量属性响应。架构模式描述了在特定设计情境中反复出现的特定设计问题,并针对该问题给出了经充分验证的架构解决方案。架构模式可被视为策略的 “集合体”。

分析人员可以通过使用基于策略的检查表来了解架构中所做的决策。这种轻量级的架构分析技术能够在很短的时间内洞察架构的优势与劣势。

3.8 扩展阅读

一些展示了在设计中如何运用策略和模式的扩展案例研究可在 [Cervantes 16] 中找到。

在由弗兰克・布施曼(Frank Buschmann)等人所著的五卷本《面向模式的软件架构》(Pattern-Oriented Software Architecture)中,可以找到大量的架构模式目录。

能够表明许多不同架构可以提供相同功能的论据 —— 也就是说,架构和功能在很大程度上是相互正交的论据 —— 可在 [Shaw 95] 中找到。

3.9 问题讨论

1. 用例与质量属性场景之间的关系是什么?如果你想在用例中添加质量属性信息,你会怎么做?

2. 你认为针对某一质量属性的策略集合是有限的还是无限的?为什么?

3. 列举自动柜员机(ATM)应支持的职责集合,并提出一个能适应该职责集合的设计方案。对你的方案进行论证。

4. 选择一个你熟悉的架构(或者选择你在问题 3 中定义的自动柜员机架构),然后梳理一遍性能策略调查问卷(可在 第 9 章 中找到)。这些问题对你所做(或未做)的设计决策提供了怎样的洞察?


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值