ADD 属性驱动设计方法 — 软件架构课程 Lecture-12
来源:周立新《软件体系结构》课程 Lecture-12 原文:Attribute-Driven Design Method.pdf,49页,文字版 PDF 关联:质量属性与构架商业周期-软件架构课程Lecture-9(上一讲)
一、ADD 方法要解决的四大问题
- 质量属性需求的精确规约 — 如何把模糊的”要快、要稳”变成可度量、可验证的场景
- 架构决策的枚举 — 有哪些架构手段(tactics / patterns)可用来达成质量需求
- 质量需求与架构决策的关联 — 每一条质量场景对应哪些架构手段
- 架构决策的组合 — 多个决策如何拼合成一个完整设计
ADD = Attribute-Driven Design,属性驱动设计法。核心思想:用质量属性来驱动架构分解,而不是先分模块再考虑质量。
二、软件设计的概念与原理(基础回顾)
2.1 三个”适合”的质量观
- 设计质量:软件设计规格说明 ↔ 用户要求 的适合程度
- 程序质量:程序 ↔ 内部规格说明 的适合程度
- 用户满意:最终质量 = 用户的满意程度
2.2 概要设计的主要任务
- 确定系统的整体模块结构
- 把功能需求分配给软件结构,形成模块结构图
- 矩形 = 功能单元/模块,线段 = 调用关系
2.3 概要设计的过程
- 选取最佳方案(设想供选择 → 选取合理 → 推荐最佳)
- 功能分解
- 设计软件结构
- 数据库设计(E-R 图、概念模型与规范化)
- 制定测试计划
- 书写文档(系统说明、实现计划、用户手册、测试计划、数据库设计结果)
- 复查和复审
2.4 模块化
- 分而治之:C(P1) > C(P2) → E(P1) > E(P2)
- C(P1+P2) > C(P1) + C(P2)(复杂度可分解)
- 但 E(P1+P2) > E(P1) + E(P2)(接口成本存在,不是分越多越好)
- 模块数与成本的关系:模块数↑ → 单模块成本↓ + 接口成本↑ → 总成本有一个最小成本区
2.5 抽象、信息隐蔽、模块独立
- 抽象:数据抽象 + 过程抽象
- 信息隐蔽和局部化:模块内部的实现细节对外不可见
- 模块独立:高内聚 + 低耦合
2.6 耦合(从低到高,前两个推荐)
| 耦合类型 | 说明 | 使用建议 |
|---|---|---|
| 数据耦合 | 模块间通过参数传递基本类型数据 | 推荐,最多用 |
| 控制耦合 | 传递控制信号(如开关量) | 少用 |
| 公共环境耦合 | 共享全局数据区 | 限制范围 |
| 内容耦合 | 一个模块访问另一模块内部数据 / 不通过正常入口转入 / 代码重叠 / 多入口 | 坚决避免 |
2.7 内聚(从高到低,前三个推荐)
评分:(10, 9, 7, 5, 3, 1, 0)
- 功能内聚(10分):模块内所有元素完成一个单一功能
- 顺序内聚(9分):处理元素密切相关且必须顺序执行
- 通信内聚(7分):所有元素使用同一输入数据 / 产生同一输出数据
- 过程内聚(5分):处理元素相关且必须按特定次序执行
- 时间内聚(3分):任务必须在同一段时间内执行(如初始化)
- 逻辑内聚(1分):任务在逻辑上属于相同或相似的一类
- 偶然内聚(0分):任务关系松散,碰巧放一起
2.8 启发式设计规则(7 条)
- 改进软件结构,提高模块独立性
- 模块规模要适中
- 深度、宽度、扇入、扇出都应适当
- 模块的作用域应该在控制域之内
- 力争降低模块接口的复杂程度
- 设计单入口单出口的模块
- 模块功能应该可以预测
三、架构在生命周期中的位置
3.1 什么时候可以开始架构设计?
- 架构由功能需求 + 质量需求 + 业务需求共同塑造 → 这些叫 架构驱动力(architecture drivers)
- 识别最高优先级的业务目标(<10 个)
- 通过 质量场景、用例、效用树(utility tree) 来明确驱动力
- 一旦驱动力明确,架构设计就可以开始(不需要等所有需求都清楚)
- 反向:架构设计中发现问题,也可以回推需求澄清
3.2 ADD 方法概述
ADD takes as input a set of quality attribute scenarios and employs knowledge about the relation between quality attribute achievement and architecture in order to design the architecture.
- 输入:一组质量属性场景 + 功能需求 + 约束
- 核心机制:把质量属性实现 ↔ 架构决策 的关联知识应用到设计中
- 定位:可以作为 RUP 等大多数开发方法的扩展
- 本质:一个递归分解过程,每一层:
- 满足一组质量场景 → 选择 tactics 和 patterns
- 把功能分配到 pattern 提供的模块类型中
- 输出:架构的前几层模块分解视图 + 其他必要视图
四、ADD 方法详细步骤
总览:三大步
I. 选择要分解的模块
II. 按以下5小步细化该模块:
2a. 选择架构驱动力
2b. 选择架构模式
2c. 实例化模块并分配功能,用多视图表示
2d. 定义子模块的接口
2e. 验证并细化用例和质量场景,作为子模块的约束
III. 对每个需要继续分解的模块重复以上步骤
4.1 第 I 步:选择要分解的模块
- 从系统开始,然后是子系统,再是子模块
- 车库门例子:从”整个车库门开启器系统”开始分解
- 约束示例:必须与家庭信息系统(HIM)互操作
4.2 第 2a 步:选择架构驱动力
针对当前模块,确定最重要的质量属性驱动力。车库门例子的驱动力:
- 可修改性:支持产品线架构(设备与控制器、处理器都可变)
- 实时性能:障碍物检测必须在 0.1 秒内停止
- 可诊断性/可管理性:可通过家庭信息系统进行诊断和管理
4.3 第 2b 步:选择架构模式
- 每个 tactic 设计用来实现一个或多个质量属性
- 但 tactic 所在的 pattern 会对其他质量属性产生副作用(如解释器模式影响性能)
- 选择 tactic 的两个主要因素:
- 驱动力本身
- 实现该 tactic 的 pattern 对其他质量的副作用
主要质量属性的 tactic 方向:
| 质量属性 | 核心 tactic 方向 |
|---|---|
| 可修改性 | 局部化变化、防止涟漪效应、推迟绑定时间;语义一致性、信息隐藏、为受影响区域定义虚拟机 |
| 性能 | 资源需求管理、资源仲裁;提高计算效率、选择调度策略 |
车库门例子的架构模式(综合可修改性 + 性能 + 可诊断性):
┌─────────────────────────────────────────────────┐
│ User Interface │ ← 非性能关键计算 + 用户交互
├─────────────────────────────────────────────────┤
│ Non-Performance-Critical │ ← 门的常规升降、诊断功能
│ Computation Section │
├─────────────────────────────────────────────────┤
│ Virtual Machine │ ← 抽象硬件差异(产品线可修改性)
│ (Communication / Sensor/Actuator) │
├─────────────────────────────────────────────────┤
│ Performance-Critical │ ← 障碍物检测(0.1s 硬实时)
│ Computation Section │
├─────────────────────────────────────────────────┤
│ Scheduler That Guarantees Deadline │ ← 保证截止期的调度器
└─────────────────────────────────────────────────┘
4.4 第 2c 步:实例化模块并分配功能,用多视图表示
实例化模块(车库门例子)
- 性能关键区 → 障碍物检测与停止功能(因为有截止期)
- 非性能关键区 → 门的常规升降 + 诊断功能
- 虚拟机层 → 通信、传感器读取、执行器控制(封装硬件差异,支持产品线)
功能分配的要点
- 把父模块的用例分配到子模块,验证功能完整性
- 分配过程中可能发现需要新增或删除子模块
- 发现必要的信息交换关系
- 某些 tactic 会引入特定的模块交互模式
三种架构视图
| 视图 | 作用 |
|---|---|
| 模块分解视图 | 静态结构,模块职责划分 |
| 并发视图 | 动态并行活动、同步、资源竞争、死锁、数据一致性;发现新职责(如资源管理器) |
| 部署视图 | 多处理器/专用硬件时的部署;决定哪些模块需要多实例(可靠性需求);不是随意推导的 |
并发视图要点:
- 组件 = 模块的实例;连接器 = 虚拟线程的载体
- 连接器类型:synchronize with、starts、cancel、communicates with
- 用例驱动:两用户同时操作、一用户多活动、启动、关闭
4.5 第 2d 步:定义子模块的接口
- 接口 = 模块提供和要求的服务与属性(不等于函数签名)
- 从三视图中推导交互假设,记录为接口
| 视图 | 接口含义 |
|---|---|
| 模块视图 | 信息的生产者/消费者;服务提供/使用的交互模式 |
| 并发视图 | 线程间交互 → 服务的提供/使用;组件是否主动(有自己的线程);同步、顺序化、阻塞调用 |
| 部署视图 | 硬件要求(如专用硬件);时序要求(如计算速度 ≥10 MIPS);通信要求(如信息每秒更新不超过一次) |
4.6 第 2e 步:验证并细化用例和质量场景,作为子模块的约束
三类需求分配给子模块:
- 功能需求 → 分配给具体子模块
- 约束 → 三种情况:
- 组合满足约束(如定义操作系统为子模块)
- 单个子模块满足(如封装协议的模块)
- 多个子模块协作满足(如 Web 需要 client+server)
- 质量场景 → 四种情况:
- 分解后已完全满足,无额外影响
- 当前分解满足,但对子模块有约束
- 分解对其是中性的(不影响)
- 当前分解无法满足 → 需要权衡(tradeoff)
车库门例子的场景分配:
| 质量场景 | 分配给 |
|---|---|
| 不同产品的设备与控制不同(可修改性) | 用户界面模块 |
| 不同产品使用不同处理器 | 所有模块(通过虚拟机隔离) |
| 0.1 秒内停止/重开(性能) | 调度器 + 障碍物检测模块 |
| 可通过家庭信息系统诊断管理 | 诊断模块 + 通信模块(拆分) |
4.7 每步分解结束时的产出
- 模块分解为子模块,每个子模块有一组职责
- 一组用例
- 接口定义
- 质量场景
- 一组约束
五、ADD 的延伸影响
5.1 团队组织结构
- 稳定的模块分解结构 → WBS(工作分解结构)
- 团队之间低带宽通信就够了(这一点很关键,也验证了康威定律)
- 按模块组建团队,按专长分配人员
- 反过来,现有组织结构也会影响架构(因为组织有专门能力和既得利益)
5.2 骨架系统(Skeletal System)
- 哪些部分需要打桩(stub)?
- 演进交付生命周期(Evolutionary Delivery Life Cycle)
- 里程碑 MS → “完整的”骨架系统 → “可工作的”低保真版本
- 好处:早期验证架构,降低风险
六、ADD 方法总结
- 起点:关键架构驱动力确定后即可开始,不需要完整需求
- 过程:自顶向下的递归分解,用质量需求定义架构模式,用功能需求实例化模块类型
- 输出:前几层架构分解 + 多视图 + 接口 + 约束
- 组织影响:决定团队结构和通信路径;现有组织也影响架构
- 验证:每步分解都要验证质量场景和功能需求是否能满足
七、关联
- 前置:质量属性与构架商业周期-软件架构课程Lecture-9(质量属性场景是 ADD 的输入)
- 延伸:架构模式与风格-软件架构课程Lecture-2(ADD 选择的 pattern 从这里来)
- 方法对比:ATAM(架构权衡分析方法)是评估,ADD 是设计;一正一反