软件质量这件事,说起来人人都有一套标准:有人看线上故障多不多,有人看代码好不好改,有人看需求响应快不快。但这些零散的评价标准往往不能用在同一张评审表里,你说产品做得好,他说功能经常崩,两个人说的根本不是同一层东西。McCall 软件质量模型的价值就在于,它是早期把软件质量从“靠感觉”变成“结构化描述”的系统化框架之一,从产品运行、产品修正、产品转移三个维度出发,把质量拆成可以讨论、可以测量、可以落到测试和评审动作里的具体特征。这篇文章,我想把这套模型拆开聊聊,它每个维度到底在说什么,每个质量因子怎么理解,以及放在今天的研发流程里,它还能怎么用。
1. 为什么质量模型要从“三个维度”讲起
1.1 从软件危机到质量特征的诞生
大概半个多世纪前,软件行业经历了一波被称为“软件危机”的混乱期。项目延期、预算超支、交付后的系统频繁出问题,大家终于意识到:写代码不能完全靠个人英雄主义,软件产品本身需要被系统地管理。于是有人开始琢磨,到底什么东西决定了软件好不好?效率高算不算好?功能全算不算好?不出错算不算好?
答案是不好说。因为“好”这个字在不同的使用场景下含义完全不同。McCall 模型做的第一件事,就是把这个模糊的“好不好”拆成一组有名字、有定义的质量特征。它把质量特征按照软件的整个生命周期分成三个维度:产品运行(Product Operation)、产品修正(Product Revision)、产品转移(Product Transition)。
这三个维度的命名,本身就是一种值得学习的思维:不去问“软件好不好”,而是问“软件在什么阶段、面对什么主体、体现什么能力”。产品运行解决的是用户和系统直接交互时,软件表现如何;产品修正解决的是出问题、要改需求的时候,软件好不好改动;产品转移解决的是软件要换环境、要复用、要对接其他系统的时候,能不能走得通。
1.2 三个维度的分工:运行、修正、转移
用一个生活化的类比来理解这三个维度。你买一台家用汽车:
- 产品运行就是日常驾驶体验:启动是否顺利、操控是否稳、油耗是否合理。换成软件,就是用户每天点按钮、查数据、提交表单时,系统是否能用、是否够快、是否不容易出故障。
- 产品修正是后期维护体验:车需要保养、零件需要更换时,维修手册清不清晰,结构好不好拆。换成软件,就是线上出了 Bug 能不能快速定位,产品经理提出新需求时,代码改起来有没有那么痛苦。
- 产品转移是换场景的能力:这辆车能不能适应不同气候、不同路况,配件能不能通用。换成软件,就是系统能不能从一种服务器迁到另一种,某个模块能不能拿到另一个项目里复用,A 系统和 B 系统能不能顺畅对接。
这个划分让我觉得很了不起的地方在于,它把“维护”和“迁移”提升到了和“运行”同等重要的位置。在工程实践里,很多团队只顾着上线时的功能表现,上线之后代码变成一团乱麻、接口文档完全没有,换个人接手根本不敢动——这些恰恰是产品修正维度上的严重缺陷,McCall 模型早就点到这个要害上了。
2. 产品运行维度:五个可直接对应到日常测试的质量特征
2.1 特征清单与度量思路
产品运行维度包含五个质量特征:正确性、可靠性、效率、完整性、可用性。这一层最接近大家对软件质量的直觉,也最容易被翻译成具体的测试用例和验收标准。
正确性(Correctness):软件是否按照规格说明完成功能。用大白话说,需求文档写了什么,它就做什么。对应到实践里,就是各种功能测试用例、接口测试断言,比如一个订单提交接口,传入合法参数后返回的结果是否完全符合接口契约。
可靠性(Reliability):软件在持续运行过程中,能否避免故障、能否在故障后恢复。它关注的是频率和恢复能力。比如系统连续运行一个月,崩溃了几次;进程挂了之后,能否自动重启;数据库连接断了之后,能否自动重连。压力测试、故障注入测试、混沌工程,都属于这个特征下的活动。
效率(Efficiency):软件使用系统资源的分寸。资源不只包含 CPU、内存、磁盘,还包括网络带宽。对应实践是性能测试:单接口响应时间、吞吐量、并发用户数、内存占用是否存在持续上升。
完整性(Integrity):软件能否抵御非法访问和未授权操作,数据是否安全。今天的信息安全测试、权限测试、漏洞扫描,都可以归到这个特征下面。比如未登录用户是否能访问管理接口,用户 A 的 token 是否能查到用户 B 的数据,都属于完整性范畴。
可用性(Usability):这个特征经常被误读为“操作界面好不好看”,实际上它指的是软件对使用者的友好程度,包括用户学会使用软件需要付出的时间、操作路径是否顺畅、错误提示是否明确。可用性测试、用户走查、原型评审,是这一特征的落地方式。
2.2 日常评审中最容易被误读的两对特征
实操中我发现,很多人会把“可靠性”和“正确性”混为一谈,也会把“完整性”和“安全性”画上等号。它们虽然有重叠,但关注点不同。
拿电商系统举例子。购物车增加一件商品后,页面显示数量加一,这是正确性;用户连续下单时系统不会因为并发请求而崩溃,这是可靠性。当一个功能在测试环境跑了一百遍都是对的,但是双十一流量一上来就故障,你说这个系统质量是高还是低?用 McCall 模型的分类方法,答案很清晰:正确性没问题,可靠性不达标。测试方案自然也要分两套来设计。
完整性和普遍说的“安全性”也不在一个粒度上。安全性是个很大的课题,涉及加密算法、密钥管理、网络防护、合规要求,很多内容超过了软件的范畴。McCall 模型里的完整性,更接近于“软件自身对权限和数据访问的控制能力”。视野小,但更容易落地测试。我在做测试设计时,通常会把完整性问题拆成几类问题:身份鉴别是否可能被绕过、访问控制是否在服务端而非前端、敏感信息是否被日志打印、数据在传输链路中是否被篡改。拆成这几个问题之后,测试用例就好写了。
这一维度对应的价值,是团队在需求阶段就能确定非功能测试的边界。一提到性能测试大家就知道要做效率,但有多少人想到要测试完整性呢?需求评审时把五个特征逐一过一遍,能找出很多原来根本不会写进测试计划的活动。
3. 产品修正与产品转移:容易被忽略的“研发体验”指标
3.1 修正维度三要素的实操含义
产品修正维度包含可维护性、可测试性、灵活性。这三个特征的共同点,是它们都站在软件研发和维护者的视角上。
可维护性(Maintainability):定位故障、修改代码、验证修改的成本有多低。它在代码层面体现为模块划分是否清晰、函数职责是否单一、代码结构是否易于理解。在工程实践中,有一类客观指标可以间接反映可维护性:千行代码注释率、模块间耦合度、单元测试的覆盖率。虽然这些指标都不能完全代表可维护性,但没有这些指标,可维护性就完全不可见。
可测试性(Testability):软件是否容易被测试验证。有些系统写测试特别费劲,因为模块之间耦合得死死的,接口不可注入,日志缺失导致问题定位困难。可测试性差的系统,测试环境都需要特殊处理后才能跑起来。提升可测试性的常见做法包括:为模块预留测试接口、把依赖抽象成接口以便在测试中替换、提供清晰完善的日志、具备环境开关来方便构造场景。
灵活性(Flexibility):软件能够被扩展以满足新的需求的程度。任何一个活着的重要系统,需求都一定会变。灵活性低的系统,增加一个字段要改十张表,增加一个渠道要复制一个模块来改。这里我要说一点:灵活性跟“提前做很多抽象设计”不完全是一回事。提前做太多过度抽象,反而可能因为预判错误让代码更复杂。实际工程里,灵活性更可靠的做法是保持模块边界清晰、公共组件独立、配置项外置。需求真正到来时,改动被控制在合理范围。
这三个特征在研发团队内部非常容易产生争议,就是因为它们都偏主观。一个人觉得这段代码挺好维护,另一个人可能觉得跟天书一样。McCall 模型本身没有给出这些特征的定义细节,但它的方法论给了启发:把抽象特征向下拆解,拆到可以用具体操作判断的程度,比如模块化程度、日志完善程度、依赖注入点位是否足够,可维护性从这个角度去评审,可执行性就高得多。
3.2 转移维度三要素的实操含义
产品转移维度包含可移植性、可复用性、互操作性。这三个特征关注的是软件跨越技术边界的能力。
可移植性(Portability):软件可以从一个环境迁移到另一个环境的难易程度。环境包括操作系统、应用服务器、数据库、云平台。可移植性好的系统,环境迁移时只需要做配置层面的调整,而不是大改业务代码。技术选型时选择跨平台中间件、尽量避免依赖某个操作系统独有的特性、把环境相关配置统一外置,都是可移植性的落地手段。
可复用性(Reusability):软件中的模块能否在多个项目或产品中被重复使用。它比“复制粘贴代码”高一个层级,强调的是模块被独立设计、接口清晰、不跟具体业务深度绑定。从一个单体中抽取一个权限管理模块,做成一个独立组件,在另一个项目中直接使用——这个过程的顺畅程度,就是可复用性的体现。
互操作性(Interoperability):软件能否与外部系统进行有效的数据交换和服务调用。今天几乎所有软件都不是孤立存在的,要对接支付系统、短信服务、第三方登录、上下游系统。互操作性要求对外提供稳定、契约清晰的接口,遵循通用的数据交换协议,对报文头、报文体、异常信息都要有明确的约定和容错处理。
转移维度对很多中小团队来说是“以后再说”的部分。我的看法不太一样:可复用性和互操作性不需要大量前期投入,只需要在开发过程中遵循两个习惯——对外接口先定义契约再实现,模块内部不以某个具体项目的业务逻辑写死。做到这两点,转移维度的基础就保住了。
4. 从特征到度量:McCall模型真正有方法论价值的部分
4.1 质量特征、质量准则、度量三层的逻辑
McCall 模型最容易被忽视、也最值得借鉴的,是它提出了一条从质量特征到度量的路径。在这套模型里,质量特征处在最顶层,中间是关键的质量准则,最底层是可执行的度量。要理解这个结构,可以把它想象成一个层层翻译的过程。
质量特征回答的问题是“软件在某个维度上应该达到什么目标”,比如可维护性要强。但“要强”这个概念没法直接验证。于是中间层引入质量准则——一组与具体实现方式有关的属性和设计原则,比如模块化、描述性、简洁性、自文档化、可扩充性、通用性。这些准则比特征更接近代码层面,但还是偏设计理念。准则之下,才落到底层的度量——具体的指标、问题列表、检查项。比如“模块化”这个准则,可以向下拆出“模块平均代码行数”“模块间接口数量”“是否出现模块间的环状依赖”等可量化的指标。
很多质量模型只给了特征清单就结束了,像一张购物清单,你知道要买“新鲜”“好吃”却不知道具体怎么挑。McCall 模型把特征到准则到度量的链路打通,这是它在方法论上的重要贡献。它把软件质量从哲学式的思辨,拉到了可评审、可追踪、可改进的工程轨道上。
4.2 一个从特征到度量的完整拆解例子
我拿“可维护性”做一个完整的拆解演示,这样大家能看清这套三层结构怎么用。
- 质量特征:可维护性。
- 中级准则:模块化、简明性、自文档化。
- 底层度量:模块平均规模、函数圈复杂度、注释覆盖率、是否有模块间循环依赖、核心业务逻辑与外部框架代码是否分层。
这三层之间的关系是:如果在底层度量中发现“函数圈复杂度普遍偏高”,就可以判断“简明性”这条准则没有满足,进而推断“可维护性”这个特征存在风险。反过来也一样,如果你想要提升可维护性,你不用直接去改一个凭空“维护性不好的地方”,而是去修正底层度量对应的代码问题。
这个层层递进的结构,在需求评审和代码评审里都非常好用。需求评审时,把质量特征作为“我们需要这个系统达到什么水平”的目标;代码评审时,把底层度量作为“当前代码离目标还差多远”的证据。目标和证据之间,就是用质量准则来连接的。
另外还要注意一点:这套拆解并不局限于代码。完整性特征同样可以往下拆:质量准则是权限最小化、数据加密、审计日志完备;底层度量是接口访问是否校验了 token、权限控制是否实现了数据行级隔离、日志中是否可能打印明文密码。大家会发现,用这套方法做测试需求分析,能把很多原来模糊的风险变成具体的检查点。
5. 在现代软件研发流程里怎么用它
5.1 在需求评审中把质量特征变成验收条件
现在的产品需求评审,大多数时候讨论的还都是功能逻辑:按钮放哪里、状态怎么流转、异常分支怎么处理。非功能需求要么不写,要么写一句“系统需保证安全稳定”就结束了。这句空话到了测试阶段怎么验收?没法验收。McCall 模型正好可以做一个清单工具。
我的做法是,在每次需求评审的后期,预留二十分钟,拿 McCall 模型的三个维度做一轮快速扫描。不是把所有十一个特征都翻一遍,而是结合本次需求挑选相关的特征,逐条确认验收条件。比如这次需求涉及用户登录,那就过“完整性”:未登录状态访问接口返回什么,登录态失效后前端是否跳到登录页,密码是否加密传输;过“正确性”:错误密码、正确密码、多次错误锁定这些分支是否都定义了预期结果;再注意“互操作性”:如果需要对接统一登录平台,就要确认协议版本、超时时间、异常回调方式。
我发现一个规律:只要在需求评审时把相关质量特征的验收条件说清楚了,后续的测试设计基本是水到渠成,开发自测也会更有据可依。因为验收条件本身就是把模糊目标翻译成了具体行为。
5.2 在测试设计和质量度量中的具体用法
在测试设计阶段,McCall 模型可以直接作为测试类型的梳理框架。把十一个特征映射到测试活动,会得到一张很清晰的测试全景图:
| 质量特征 | 对应测试活动 |
|---|---|
| 正确性 | 功能测试、接口测试、联调测试 |
| 可靠性 | 稳定性测试、故障恢复测试、混沌工程 |
| 效率 | 性能测试、压力测试、资源占用监控 |
| 完整性 | 安全测试、权限测试、数据加密校验 |
| 可用性 | 用户验收测试、易用性走查 |
| 可维护性 | 代码评审、静态扫描、结构评审 |
| 可测试性 | 单元测试框架评审、日志评估 |
| 灵活性 | 配置变更测试、扩展性评估 |
| 可移植性 | 跨环境部署验证、兼容性测试 |
| 可复用性 | 组件评审、接口契约评审 |
| 互操作性 | 接口兼容性测试、系统集成测试、协议测试 |
这张表的用途不是让团队把所有测试类型都做一遍,而是帮助团队做测试策略决策。一次版本发布,如果核心改动在底层服务,那就把重点放在可靠性和正确性上;如果是涉及用户操作的界面改动,那重点应该是可用性和正确性;如果这个版本是为新环境部署准备,那可移植性和互操作性就变得重要。有了这张表,测试经理在排优先级时就不容易遗漏维度。
质量度量方面,我也习惯用这个模型来做裁剪。并不是指标越多越好,一套几百个指标的度量体系没人能坚持维护。我会选取三到五个最能代表当前阶段风险的特征来建指标。比如一个新项目,重点关注正确性和可维护性,用缺陷逃逸率作为正确性指标,用单元测试覆盖率与代码重复率作为可维护性的间接指标。老系统做架构改造,可能就要把可修改性和可移植性的评估纳入例行巡检。
6. McCall模型的局限,以及它和后续质量框架的关系
6.1 局限:静态的、面向交付能力的、依赖评审者的
McCall 模型不是一个放之四海而皆准的模型,它的局限也很明显。
首先,它更关注的是软件交付后的质量状态,对软件开发过程本身的质量,比如需求管理、项目计划、过程控制,几乎没有什么说明。软件质量是需要过程保障的,代码写得再好,需求本身是错的,最后交付的东西质量也高不到哪里去。现代质量管理更强调“过程质量和产品质量并重”,在这点上 McCall 模型的覆盖是不够的。
其次,它的质量特征之间没有明确的权重和优先级关系。对于用户来说,一个信息管理系统的可用性可能比效率更重要;对于平台工具类软件,效率和可靠性可能排在第一位。McCall 模型告诉你应该关注十一个特征,却没说在资源有限时先保哪个。实际使用中,这个优先级只能由团队自己根据产品定位来确定。
再次,虽然模型给出了从特征到准则到度量的拆解框架,但具体准则和度量的选择,很大程度上依赖评审者的经验水平。同一个系统,有经验的评审者能找出准确的质量风险,缺乏经验的人可能只能机械地逐条打钩。模型本身不提供自动化判断能力,它只是帮人把经验结构化。
6.2 与Boehm、ISO/IEC 9126模型对照
后续出现了若干重要的软件质量模型,把它们和 McCall 模型对照起来看,能让它的定位更清晰。
Boehm 质量模型与 McCall 模型处于同一时期,同样采用了层次化的质量特征拆分方法,也包含了可移植性、可靠性、效率、易用性、可测试性、可理解性等特征。它在“可用性”上的表述更接近用户感受,并且引入了“人类工程学”(human engineering)的思考,在一些教材中作为对 McCall 模型的补充。两者的共同点是都在建立从高层特征到底层度量的桥梁,只是拆分角度略有差别。
ISO/IEC 9126 及后来的 ISO/IEC 25010 系列,则把软件质量拆分为内部度量、外部度量和使用质量,特征上也出现了功能适合性、性能效率、兼容性、易用性、可靠性、信息安全性、可维护性、可移植性等更细的分类,体系化程度更进一步。
站在今天的视角,McCall 模型确实“老”了,有些分类也有模糊地带,比如“效率”到底应该包含哪些资源指标、它和“性能”的边界在哪,现代工程中已经有更细的标准。但它作为早期把软件质量系统化的模型,有一个到今天仍然值得学的精髓:任何质量目标,如果不能往下层层拆到可以验证和度量的东西,那它就只是口号。这种拆解思维,是跨时代通用的。
我的个人体会是,学习这类经典模型不要只记忆三个维度和十一个特征的列表,而是用一次完整的目标拆解去实践它。拿一个自己手头的项目功能,试着从特征拆到准则,再从准则拆到指标,你很快就能发现 Cha 论和实操之间的差距在哪里。实践一遍之后,McCall 模型就不再是教科书上的历史名词,而是内化成了你做需求评审和测试策略时的骨架。