首页 / 资讯中心 / 文章详情

从模糊NFR到可执行QAS:非功能需求场景化落地实践

从模糊NFR到可执行QAS:非功能需求场景化落地实践 ★ FEATURED ARTICLE
需求评审开到第四十分钟有人对着一行需求念出声“系统性能要好响应要快。”会议室安静了三秒。开发抬头问“多快算快”测试问“那我按什么标准提缺陷”运维问“压测上来算谁的问题”。这个场面我见过太多次问题不在人在那行字本身——它是一条非功能需求NFR却长得像一句口号。后来我们把这类条目全部强制转写成质量属性场景Quality Attribute Scenario下文简称 QAS情况才开始有变化需求评审从“感觉够不够快”变成“这个阈值在什么负载下、按什么口径统计”争论的焦点终于落在了同一个坐标系里。这篇就把这套做法完整拆一遍QAS 到底是什么、它为什么能治“性能要好”这种病、在我们内部那套需求流转流程内部代号 DevFlow里怎么落地、落地过程中踩过哪些坑。不管你是写需求的产品、接需求的开发还是负责验收的测试和运维都能从里面抄到可以直接用的东西。1. 那句“性能要好”缺的到底是什么一次评审僵局的事后解剖1.1 模糊 NFR 的三层失效测不了、拆不开、追不回很多人以为“性能要好”的问题只是“说得不够细”其实它同时在三层上失效而且这三层是连锁的。第一层是测不了没有度量项、没有阈值、没有统计窗口验收阶段根本无法判定“通过”还是“不通过”最后只能靠某个人的主观感受拍板一旦拍板的人换了结论就可能翻转。第二层是拆不开模糊的形容词没法分配到具体组件和具体人头上“系统要快”到底是网关要快、数据库要快还是前端首屏要快架构评审时没人能拿它做取舍依据因为它不指向任何具体制品。第三层是追不回需求条目和验收用例、代码模块、线上监控之间没有链路等到线上真的慢了谁也说不清当初承诺的到底是什么标准只能重新吵一遍。这三层失效叠在一起后果比想象中严重。我见过一个“系统要稳定”的需求在需求池里躺了四个月期间被三个团队认领过又退回来因为谁都说不清自己那部分要承担多少。后来把它拆成六条 QAS 之后两天就分完了三条归网关两条归订单服务一条归数据库运维。这不是因为团队突然变勤快了而是因为条目变得可以归属了——你能指着“order-query-service 在缓存预热完成、主从延迟低于 100ms 的环境下承受 800 QPS 时 P95 不超过 300ms”这条说这是我的那也是你的。所以判断一条 NFR 是不是废的有个很简单的自测把它原样贴到缺陷管理系统里看测试能不能据此写出一个必然通过或必然失败的用例。写不出来它就还是形容词。1.2 加了“响应时间小于 200 毫秒”为什么还是不够有人会说那我们加上数字不就行了“接口响应时间小于 200 毫秒”——这句话看起来已经很具体了但它依然会在验收阶段引发新的争吵。原因有四个每一个都对应 QAS 里的一个槽位缺失。缺环境。这 200ms 是冷启动后的第一次请求还是缓存预热完成之后的稳定态是单实例压测还是集群满配是空库跑出来的还是千万级数据量下跑出来的同一个接口这四种情况的实测值能差出五倍以上。缺制品边界。是接口自身的服务端处理耗时还是从客户端发起请求到收到完整响应的端到端耗时网关的鉴权、序列化、网络往返算不算在里面缺刺激规模。1 个并发也是“小于 200ms”1000 个并发也是“小于 200ms”但这两件事的工程难度完全不是一个量级。没有负载描述这条需求等于没有下限也没有上限。缺统计口径。平均值 200ms 和 P95 是 200ms是两种截然不同的系统前者允许 5% 的请求慢到几秒后者不允许。我做过一次复盘把同一个接口的四个数字摆在一起平均值 180ms、P95 是 420ms、P99 是 1.2s、端到端 P95 是 610ms。四个数字都对都“小于 200ms”以外的表述也都能解释得通。那次复盘之后我们内部定了一条死规矩任何性能类 NFR不写清统计口径、负载规模、环境前置条件、制品边界这四项一律不进开发排期。2. QAS 的六个槽位把形容词拆成能直接跑测试的六元组2.1 刺激源与刺激谁、在什么条件下、干了什么QAS 的第一组槽位是刺激源Source和刺激Stimulus。刺激源回答“谁或者什么引发了这件事”刺激回答“具体引发了什么”。这两个槽位必须成对出现因为脱离刺激源的刺激是没有意义的。同样是“800 QPS 的请求”来自真实终端用户和来自内部批量任务对系统的要求完全不同前者有突发的尖峰和极不均匀的分布后者可以精确控制速率、可以错峰、可以限流。写刺激源时最容易犯的错是写成“用户”。这跟没写差不多。合格的刺激源至少要带上身份属性和规模属性比如“促销开始瞬间涌入的移动端下单用户预计峰值同时在线 3 万人”。如果是内部触发的场景刺激源要写成具体角色或系统比如“运维人员在变更窗口内执行主库切换”或者“上游对账系统在凌晨 2 点发起批量文件拉取”。刺激源的粒度直接决定了你后面能不能找到对应的验证手段——真实用户需要压测工具模拟上游系统需要契约测试或者故障注入。刺激本身则要带上量级、分布和时间特征。以性能场景为例“以 50 QPS 线性递增到 800 QPS持续 10 分钟”比“高并发访问”有用一百倍因为它直接对应了一个可以执行的压测脚本参数。可用性场景的刺激通常是离散事件比如“主库实例在 3 秒内不可用”或者“下游服务连续 5 次返回超时”。可修改性场景的刺激是变更请求比如“新增一个支付渠道枚举值”。安全性场景的刺激是一次具体的异常请求构造比如“携带他人订单号发起详情查询”。这些写法有一个共同点刺激可以被单独复现出来不需要依赖上下文脑补。2.2 环境与制品在哪个运行状态下作用在哪一块**环境Environment**这个槽位是最容易被跳过、也是事后扯皮最多的。它描述的是场景发生时系统所处的状态正常运营、降级运行、启动中、变更窗口、灾备切换后。很多团队写 QAS 时会下意识填“正常情况”这个填法等于没填因为它既没有说清前置条件也没有排除掉那些真正会出问题的边界状态。我一般要求环境槽位至少包含三类信息运行态正常/降级/维护、数据态空库/常规量级/历史峰值量级、依赖态上下游是否健康、缓存是否预热。举个具体的对比。同一个订单查询接口环境写“正常情况”时你测出来的数字很漂亮但线上出事永远发生在数据态和依赖态异常的时候。把环境写成“缓存预热完成、主从复制延迟低于 100ms、单表数据量 5000 万”之后压测脚本的造数逻辑就必须跟着改你会发现原本 300ms 的 P95 在真实数据量下变成了 900ms。这个差距不是被 QAS 制造出来的它一直存在只是以前没人要求你去看见。**制品Artifact**回答“作用在哪一块”。这里的粒度需要拿捏写“系统”太粗写“第 137 行代码”太细合适的粒度通常是“一个可以独立部署和独立观测的单元”比如某个服务、某个接口、某张表、某个前端页面。写制品的时候有个技巧同时写出它的下游order-query-service 及其依赖的 order-db 与 Redis 缓存。这样写的好处是当响应不达标时排查范围天然被框定了不会一上来就全链路漫无目的地找。2.3 响应与响应度量可观测的动作加上带单位的阈值**响应Response**是系统在受到刺激后做出来的动作**响应度量Response Measure**是这个动作的量化标准。这两者经常被混为一谈写成“系统快速返回结果响应时间小于 300ms”实际上前半句是响应、后半句才是度量混在一起写会导致一个隐蔽的问题降级行为没有被描述。真实系统在压力下往往不是“变慢”而是“改变了行为”——返回缓存旧值、返回兜底默认值、拒绝部分请求、异步化处理。这些行为如果不写进响应槽位验收时就只能拿“有没有超时”这一把尺子量而那些优雅降级的设计反而成了没人认可的额外付出。所以响应槽位要写成可观测的动作序列比如“服务返回订单详情当查询耗时超过 500ms 时降级返回缓存中的最近一次结果并在响应头标记 staletrue”。这样写完之后响应度量自然就分成两组正常路径的延迟指标和降级路径的触发率与正确性指标。响应度量的写法有三条硬要求。带单位毫秒、百分比、次/秒、人日不写单位的数字没有意义。带统计窗口“P95 不超过 300ms”要补上“在 10 分钟统计窗口内”否则瞬时抖动和持续劣化无法区分。带边界错误率要写清分母是总请求数还是有效请求数可用性要写清是按自然月还是按滚动 30 天。这三条看着琐碎但每一条我都见过因为没写而返工的案例。2.4 六个槽位不是清单而是因果链很多人第一次接触 QAS会把它当成一张要填满六个空格的表格填完就完事了。这样用出来的效果很一般因为六个槽位之间存在因果关系刺激源决定了刺激的形态刺激和环境的组合决定了响应的合理范围响应又决定了度量该测什么。填表时如果发现某个槽位怎么写都别扭通常是上游槽位没想清楚。举个真实例子。有个团队写了一条可用性场景度量写“系统可用性达到 99.95%”但刺激写的是“机房断电”。这两者对不上机房断电这种级别的故障单靠应用层是扛不住的99.95% 这个月度指标也不是靠单次故障场景能验证的。后来把刺激改成“单台应用实例进程崩溃”度量改成“30 秒内被负载均衡摘除期间错误请求占比低于 0.5%”整条场景立刻变得可执行了。机房断电那种场景则被单独拆成一条灾备演练场景度量换成 RTO 和 RPO。这就是因果链在起作用——刺激的级别决定了响应的能力边界也决定了度量该用什么量纲。槽位常见的糊弄写法可执行的写法自检问题刺激源用户促销开始瞬间涌入的移动端下单用户峰值同时在线 3 万谁产生的有没有身份和规模描述刺激高并发访问5 分钟内从 100 QPS 线性爬升至 800 QPS什么动作量级、分布、时间特征是什么环境正常情况生产环境缓存预热完成主从延迟低于 100ms单表 5000 万行运行态、数据态、依赖态分别是什么制品系统order-query-service 及 order-db、Redis 缓存能不能独立部署、独立观测响应快速返回返回订单详情超过 500ms 降级返回缓存值并标记 stale有没有描述降级和异常路径的行为响应度量要快10 分钟窗口内 P95 ≤ 300msP99 ≤ 800ms5xx 率 0.1%单位、窗口、分母边界是否齐全3. DevFlow 里的四道加工从需求池条目到 QAS 卡片的转化流水线3.1 第一道加工用固定词表把形容词归到质量属性下需求池里的原始条目通常是这样的“系统要稳定”“页面要快”“以后好扩展”“不能出事”。这些句子的共同点是它们描述的是感受而不是属性。转化的第一步不是急着补数字而是先归类——把它归到哪一个质量属性下。我们内部用一张固定词表只有八个词性能、可用性、可修改性、安全性、可测试性、易用性、可运维性、互操作性。词表固定的好处是归类结果可以统计能看出一个版本里 NFR 的分布是否失衡。归类这一步看着简单实际最容易出错。比如“页面要快”产品心里的意思是首屏内容尽快可见开发理解成了接口响应快测试按接口耗时写了用例。三个人的理解偏差在归类阶段就能暴露出来如果归到性能那要问是加载性能还是交互性能如果归到易用性那度量的就不是毫秒而是任务完成率。我遇到过一条“用户操作要顺畅”争论了半天最后归到易用性度量写成“典型下单流程的点击次数不超过 5 次、无需滚动即可看到提交按钮”跟性能一点关系都没有。归类之后要给每条 NFR 打上来源标记来自业务方诉求、来自历史故障复盘、来自合规审计要求还是来自架构演进规划。这个标记非常重要因为它决定了这条 NFR 的优先级怎么定。历史故障复盘出来的可用性场景优先级天然高于拍脑袋想出来的性能指标因为前者已经用真实损失证明过自己的价值。3.2 第二道加工五个追问补齐场景上下文归类完成后进入补上下文阶段。我们固定用五个追问几乎能把所有缺失的槽位逼出来谁触发的触发的时候量级多大当时系统处在什么状态作用在哪一块期望它做什么、做到什么程度这五个问题一一对应 QAS 的六个槽位第二个问题同时覆盖刺激和部分环境。追问的过程中有个很实用的技巧叫边界值逼问。不要问“正常情况下要多快”要问“最慢不能慢过多少”和“数据量涨到十倍的时候还能不能守住”。人对区间的感知远不如对边界的感知敏锐一旦被问到“什么时候可以报警”真实标准往往就出来了。另一个技巧是反例锚定让对方回忆一次印象深刻的线上问题然后问“那次的情况如果重演你希望系统怎么表现”。这个方法特别好用因为人对故障的记忆比对指标的记忆清晰得多描述出来的场景天然带着环境、制品和响应细节。第二个追问“量级多大”经常被敷衍成“挺大的”。这时候不要接受换成具体参照物日活多少、峰值订单多少、历史最高并发是多少、压测跑过多少。如果对方确实不知道那就把它标成待定项同时约定一个取值来源——从监控里取近 90 天的峰值再上浮 30%或者按大促预估。待定项必须带责任人和截止时间否则它会永远待定下去。3.3 第三道加工度量项必须落在已有的观测手段上这一步是整套流程里最关键、也最容易被跳过的一环。很多团队写完 QAS 很兴奋结果验收时发现度量项根本测不出来——要么监控没埋这个点要么压测环境造不出那种数据要么统计口径和现有看板对不上。所以我们有一条硬规则每一条响应度量必须能指向一个现存的观测手段监控指标、日志字段、压测脚本、契约测试、人工检查表指不出来的要么补可观测性改造任务要么换一个能测的度量。这条规则一开始被不少架构师反对理由是“不能因为测不了就降低要求”。但实际跑下来它反而推动了可观测性建设一个季度里因为 QAS 度量项指不出来而新增的埋点和看板任务有十几项比专门提“加强监控建设”这种口号有效得多。因为每一条埋点都挂在一份具体的验收承诺上做的人知道自己在为哪条需求服务。度量选型上还有个经验优先选已经被用于线上告警的指标。如果一个指标平时没人看、不告警、不进值班手册那它在验收时大概率会被质疑“这个数字代表什么”。反过来如果 QAS 的度量项和线上告警用的是同一个指标、同一个统计口径验收结论和线上表现就天然一致不会出现“验收通过但线上还是被投诉”的尴尬。3.4 第四道加工评审卡点与打回规则最后一道是评审。我们设了一个轻量卡点不搞大评审会就是需求进入开发排期前的一次结构化检查六条规则任何一条不过就打回。检查项打回条件刺激源可识别写成“用户”“系统”“外部”等泛指未标明身份或规模刺激可复现无法据此写出压测脚本参数或故障注入脚本环境有前置条件只写“正常情况”未描述运行态、数据态、依赖态制品可独立观测粒度粗到“系统”或细到无法对应到部署单元响应含异常路径只描述成功路径未说明降级、拒绝、兜底行为度量可指向观测手段找不到对应的监控指标、压测脚本或测试用例这套卡点刚上线时被打回率接近七成两个月后降到三成左右不是因为标准放松了而是提需求的人逐渐内化了这六条。这个过程没什么捷径就是反复打回、反复给示例。我们做了一件很有用的事把被打回的条目和改写后的版本成对存到一个共享文档里新人进来先看二十组对比比讲一小时方法论管用。一条合格的 QAS 落成卡片大概是这个样子qas_id: QAS-PERF-001 quality_attribute: performance title: 订单查询接口在促销峰值下的响应能力 source: 促销开始瞬间涌入的移动端下单用户峰值同时在线 3 万 stimulus: 压测流量在 5 分钟内从 100 QPS 线性爬升至 800 QPS environment: 生产同构环境缓存预热完成主从延迟低于 100ms单表 5000 万行 artifact: order-query-service 及 order-db、Redis 缓存 response: 返回订单详情查询耗时超过 500ms 时降级返回缓存值并标记 staletrue response_measure: latency_p95: 300ms10 分钟统计窗口 latency_p99: 800ms10 分钟统计窗口 error_rate_5xx: 0.1%分母为有效请求数 cpu_saturation: 70%单实例峰值 verification: perf/order-query-peak.js纳入 nightly 回归 trace: requirement: REQ-NFR-0231 test_case: TC-PERF-0087 adr: ADR-0014 dashboard: order-query-latency4. 四类高频质量属性的写法差异性能、可用性、可修改性、安全性逐个对照4.1 性能场景P95、负载剖面和饱和度要一起写性能是最好写也最容易写歪的一类。好写是因为指标现成容易写歪是因为大家习惯只写一个延迟数字。我的经验是性能场景至少要三个维度同时约束延迟分位数、负载剖面、资源饱和度。只写延迟不写负载等于开了一张空白支票只写负载不写饱和度压测时机器跑满了你还以为系统扛住了。延迟指标优先用分位数而不是平均值这一点在第一章已经说过但分位数选哪个也有讲究。P95 适合面向用户的交互接口因为 5% 的尾部体验还能接受P99 适合涉及资金、库存这类强一致操作的接口因为长尾请求往往对应着锁等待或重试P999 一般只在极高频的底层服务上才要求写多了会让团队把精力耗在无意义的优化上。同一个系统里不同接口用不同分位数是完全合理的不要为了整齐划一全部写 P99。负载剖面比很多人想的更重要。同样是 800 QPS平稳流量和尖峰流量的工程应对完全不同前者可以靠弹性扩容慢慢加机器后者必须靠预热、限流和排队。所以刺激槽位要写清流量的时间形态是“5 分钟线性爬升”还是“10 秒内瞬间打到峰值”。我们内部有个默认约定凡是涉及营销活动的性能场景必须包含一次“瞬间打峰”的刺激描述因为真实世界里的促销就是这么来的。饱和度指标是最后一道保险。CPU 70%、内存 80%、连接池使用率 85%、磁盘 IO 等待时间这些数字不达标的意义在于即使延迟指标侥幸守住了系统也已经没有余量应对下一波流量。把饱和度写进 QAS 的好处是压测报告里必须包含资源曲线而不是只给一张漂亮的延迟分布图。场景: 促销峰值下的订单查询 假设 缓存预热完成且数据库主从延迟低于 100 毫秒 并且 订单表数据量为 5000 万行 当 压测流量在 5 分钟内从 100 QPS 线性爬升至 800 QPS 那么 统计窗口内 P95 响应时间不超过 300 毫秒 并且 P99 响应时间不超过 800 毫秒 并且 5xx 错误率低于 0.1% 并且 单实例 CPU 使用率峰值不超过 70%4.2 可用性场景把“高可用”翻译成故障注入剧本“系统要 99.99% 可用”这句话几乎是所有 NFR 文档里的标配但它作为需求几乎没有执行力因为它既没说清故障模型也没说清降级边界。可用性的本质是在什么故障下、保持什么程度的服务、多久恢复所以它的 QAS 写出来天然像一份故障注入剧本。刺激源在可用性场景里通常是两类内部的基础设施故障进程崩溃、实例失联、磁盘写满、线程池打满和外部的依赖故障下游超时、下游返回错误、第三方接口限流。这两类的响应策略不一样前者靠冗余和自动摘除后者靠熔断和降级。写的时候要分开写不要混成一条“系统要能应对故障”。响应度量在可用性场景里有四个常用量纲各有各的用途。检测时间衡量监控灵敏度恢复时间衡量自愈能力影响面衡量故障隔离做得如何比如“受影响的请求占比低于 0.5%”数据一致性损失衡量最坏情况下丢了什么。第四条最容易被漏掉但对涉及资金和库存的系统来说恰恰是最重要的。我坚持要求所有写操作相关的可用性场景必须有一条数据一致性度量比如“主库切换过程中不产生重复扣款允许的最长不一致窗口为 0”。降级行为必须在响应槽位写清楚而且要写到“降级到什么程度算成功”的程度。举个具体的推荐服务不可用时商品详情页应该返回什么是返回空列表、返回热门兜底、还是隐藏整个推荐区块这三种选择对用户体验的影响差别很大对下游系统的压力也完全不同。写清楚之后测试才能设计出对应的故障注入用例否则只能测“页面没白屏”这种最粗的标准。4.3 可修改性场景用变更波及面量化“好不好改”可修改性是四类里最抽象的一类很多人写不出来因为它不像性能和可用性那样有现成的运行指标。但其实它有一套很成熟的量化方式把变更本身当作刺激把变更的波及面当作度量。刺激源是开发人员刺激是一次具体的变更请求。这里的关键是变更要具体到真实发生过的类型比如“新增一个支付渠道”“给订单增加一个状态”“把某个字段从必填改为选填”。抽象地说“系统要易于扩展”是没有意义的而“在现有支付模块中新增一个渠道要求不改动订单核心流程代码”就非常具体因为它划定了波及面的边界。响应度量常用四个数字受影响文件数、涉及模块数、预估人日、需要回归的用例数。这四个数字可以从事后的变更记录里统计出来所以它不只是事前承诺还能做事后校准。我们做过一次统计把过去半年里“新增一个业务枚举值”这类变更的实际波及面拉出来发现中位数是 7 个文件、跨 3 个模块。这个数字一摆出来架构评审时关于“要不要抽一层配置”的争论立刻有了依据——现状是 7目标是 3差距清晰可见。环境槽位在可修改性场景里通常写“设计时”或“编译时”这两个词对应的是不同层级的可修改性设计时可变意味着改配置或改模型编译时可变意味着改代码重新构建。如果一条变更场景要求“设计时可变”那它实际上是在要求系统具备运行期配置能力这个约束会直接影响架构决策。很多团队写可修改性场景时忽略了环境槽位结果做出来的东西确实“好改”但每次改都要重新发版跟预期完全不符。4.4 安全性场景从异常请求的构造视角写刺激源安全性场景的写法和前三类有个明显区别刺激源往往是恶意或异常的。这时候用“正常用户”的视角是写不出来的必须切换到“构造一个什么样的异常请求”的视角。常见的几类包括未认证请求访问受保护资源、已认证用户访问他人资源、参数篡改、批量高频请求、异常格式输入。刺激要写成具体的请求构造方式比如“携带 A 用户的会话凭证请求 B 用户的订单详情接口订单号通过遍历获得”。这种写法看起来有点“攻击说明书”的味道但它恰恰是可测试的前提——测试人员拿到这句话就能直接写出用例安全评审也能据此判断防护措施是否覆盖。响应槽位在安全性场景里要写清是拒绝还是脱敏返回。这两者的选择直接影响用户体验和实现复杂度拒绝实现简单但可能误伤脱敏返回体验好但要求数据分级做得足够细。无论选哪种响应里都应该包含审计动作比如“记录一条包含请求方标识、目标资源、时间戳的审计日志”。度量项则围绕拦截率、误拦截率、审计完整性和响应时效来写其中误拦截率最容易被忽略但它是唯一能防止安全策略把正常用户挡在门外的指标。有一类场景特别值得单独立项越权访问。它的特点是攻击者已经通过了身份认证只是权限不够所以传统的认证防护完全失效。写这类 QAS 时刺激源要写成“已认证但无权限的用户”刺激写成“直接构造请求访问不属于自己的资源 ID”响应写成“拒绝并记录审计日志不泄露资源是否存在的信息”。最后半句很重要因为“资源不存在”和“无权限访问”返回不同的错误码本身就构成了一次信息泄露。质量属性刺激源典型写法核心度量项最容易被漏掉的槽位性能峰值时段终端用户、压测流量P95/P99 延迟、错误率、资源饱和度环境中的数据量与预热状态可用性基础设施故障、下游依赖超时检测时间、恢复时间、影响面、数据损失响应中的降级成功标准可修改性开发人员发起的具体变更受影响文件数、模块数、人日、回归用例数环境中的设计时/编译时区分安全性已认证但无权限的用户、异常请求构造拦截率、误拦截率、审计完整性响应中的错误信息泄露控制5. 可追溯的落地形态QAS 与用例、架构决策、线上指标的三角绑定5.1 编号与映射规则QAS-PERF-001 这类 ID 怎么定可追溯的前提是每条 QAS 有一个稳定且可读的标识。我们的编号规则很土但很好用QAS-属性缩写-三位序号比如QAS-PERF-001、QAS-AVAIL-007、QAS-MOD-003。属性缩写用固定词表里的八个词序号在属性内自增永不复用。永不复用这一条很重要——如果一条 QAS 被废弃了序号也保留状态标成废弃这样历史引用不会指向一个不存在的编号。编号之外还有三组关联 ID 必须写进 QAS 卡片需求 ID来自需求池、测试用例 ID来自测试管理系统、架构决策 ID来自决策记录库。这三组 ID 构成了追溯链的骨架需求往上追溯到业务目标用例往下追溯到具体验证手段决策横向关联到架构选择。有了这三组 ID任何一条 QAS 都能在三个方向上被检索到——从需求反查验证情况、从用例反查它保障的是哪条承诺、从架构决策反查它到底在支撑什么。实际执行中最容易断的是需求 ID 这一环因为需求池里的原始条目往往在转写成 QAS 之后就被标记成“已转化”然后不再维护。我们的做法是让原始条目保留但在描述末尾追加一行“已转写为 QAS-XXX-NNN”形成双向指针。这样产品经理看需求池时能看到转化结果架构师看 QAS 时能回溯到最初的业务表述。5.2 验收用例从 QAS 直接生成这一步是整个体系能不能落地的分水岭。如果 QAS 写完之后验收用例还是要测试人员从零开始设计那这套东西就只是多了一层文档负担。我们的做法是给每一类质量属性配一个用例模板把 QAS 的槽位映射成用例的给定条件、操作步骤和断言。以性能为例模板的映射关系是环境槽位 → 前置条件与造数逻辑刺激槽位 → 压测脚本参数制品槽位 → 被测服务与观测对象响应度量 → 断言阈值。测试人员拿到 QAS 卡片填参数就能生成用例骨架剩下的工作是补充异常路径和边界组合。我们在实际运行中发现一条合格的性能 QAS 大约能生成三到五条具体用例正常路径一条、边界负载一条、超限降级一条、依赖异常一条。可用性场景生成的用例更接近故障演练脚本注入什么故障、观察什么信号、判定恢复的标准是什么、数据一致性怎么校验。这类用例的执行成本比功能测试高得多所以不能全量跑。我们的策略是分两级核心链路的可用性场景进每日回归用轻量的进程级故障注入全量场景每月跑一次包含主从切换、机房级演练。这个分级本身就是从 QAS 的重要性排序里来的不是拍脑袋定的。5.3 与架构决策记录的互相引用QAS 和架构决策记录ADR之间应该是双向引用关系。一方面每条 ADR 都应该写明它在支撑哪些 QAS——如果一个架构决策找不到对应的 QAS那它大概率是过度设计或者技术偏好驱动的。另一方面每条 QAS 也应该写明它依赖哪些 ADR——这让后来的人明白这个指标之所以能达成是因为当初做了哪些取舍。举个具体的例子。ADR-0014订单查询引入读写分离与本地缓存这条决策支撑的 QAS 是QAS-PERF-001峰值查询延迟和QAS-AVAIL-004主库故障时的查询可用性。反过来QAS-PERF-001的卡片上会标注“依赖 ADR-0014缓存一致性窗口允许 3 秒”。这个 3 秒的窗口就是取舍的痕迹为了性能接受了短暂的数据陈旧。如果没有这行记录半年后有人发现查询结果有 3 秒延迟会当成缺陷来修把当初的设计意图整个推翻。这种双向引用还解决了一个很实际的问题需求变更时的冲击分析。当业务方要求把缓存一致性窗口从 3 秒收紧到 500 毫秒你能立刻顺着引用链找到受影响的 QAS、ADR 和用例评估出改动范围。没有这条链的时候这种变更往往要等到开发阶段才被发现代价高得多。5.4 上线后用同一套度量回填QAS 最有价值的地方在于它是贯穿全生命周期的而不是只在需求和验收两端出现。上线之后同一条 QAS 的度量项应该直接对应到监控看板和告警规则上。这意味着告警阈值不是运维凭经验拍的而是从需求承诺里来的也意味着线上指标一旦持续劣化能立刻定位到是哪条质量承诺在被侵蚀。我们做的具体事情是给每条 QAS 卡片加一个dashboard字段和一个alert_rule字段指向对应的看板和告警规则 ID。上线验收时做一次基线采集把当时的实测值写进卡片之后每个季度回看一次。这个季度回看机制抓出过不少问题有个接口的 P95 在三个季度里从 280ms 慢慢爬到了 460ms中间没有任何一次发布看起来是“罪魁祸首”但趋势线很清楚。如果没有基线这种缓慢劣化几乎不可能被发现因为它从来不会触发任何单次告警。回填还有一个副作用是正向的它让 QAS 从“一次性文档”变成了“活的资产”。团队会主动去更新卡片上的实测值因为那是他们工作的证明新人接手时看卡片就能知道这个系统的真实水位在哪里不用重新压一遍。这个习惯养成之后需求评审时引用历史数据的频率明显上升争论也从“我觉得”变成了“上次实测是”。6. 三个月里踩到的坑过度规格化、环境槽位错位与口径打架6.1 坑一试图把每条 NFR 都写成 QAS这套方法刚推的时候我们犯的第一个错误是用力过猛——要求所有 NFR 条目一律转写成 QAS。结果一个月内积累了上百条卡片其中大量是“日志要可检索”“配置要能热更新”这种低风险、低争议的条目为它们补场景、定度量、建用例的成本远超收益。团队的抵触情绪主要来自这里不是方法不好是它被用在了不需要它的地方。后来我们定了一个筛选标准只有同时满足两个条件的条目才强制转 QAS一是存在明确的取舍比如快与一致性之间的权衡二是有可量化的失败代价比如超时会直接导致下单失败。剩下那些低风险条目走轻量通道只写一句明确的验收标准就够了比如“所有对外接口的日志必须包含 traceId 字段”。这个二分法把 QAS 的数量压缩到了原来的三成左右但覆盖了绝大部分真正会引发争议的场景。判断一条 NFR 该不该走重流程有个很实用的问句“如果这条没达成会不会有人在半夜被叫起来”会就走 QAS不会走轻量通道。这个标准粗糙但极其有效因为它直接锚定在后果上而不是锚定在文档形式上。6.2 坑二环境槽位写“正常运行”等于整条场景作废第二个坑更隐蔽。有一批 QAS 在评审时全票通过验收时也顺利达标但上线后连续出问题。复盘的时候把卡片翻出来一看环境槽位清一色写着“正常运行状态”。这意味着压测是在空库、缓存全热、依赖全健康的情况下跑的而线上的真实状态是数据量五千万、缓存命中率七成、下游时不时超时。这个坑的根因是把环境理解成了“背景描述”而不是“约束条件”。修正的做法是给环境槽位定一个最小信息集必须包含三项数据规模表行数或数据总量、缓存状态冷/热/命中率区间、依赖健康度正常/降级/部分不可用。三项缺一评审直接打回。这个规则执行之后压测脚本的造数逻辑被迫升级工作量增加了不少但测出来的数字终于和线上对得上了。还有个衍生问题值得单独提环境槽位写得太理想会导致架构评审做出错误的容量规划。曾经有个服务按“缓存命中率 95%”的环境做容量设计实际上线后命中率只有 70%多出来的请求全打到数据库直接把主库压垮。这个教训之后我们要求所有涉及缓存的性能场景环境槽位必须同时写出命中率和穿透时的降级策略。6.3 坑三开发和测试量的不是同一个东西第三个坑是度量口径打架而且吵起来特别伤感情因为双方的数字都是真的。开发在自己机器上压出来 P95 是 200ms测试在测试环境用端到端工具测出来是 600ms谁也说服不了谁。查了半天发现三个差异叠加一是统计位置不同开发测的是服务内部处理耗时测试测的是包含网络往返和网关鉴权的端到端耗时二是统计方法不同开发取的是最近 1000 次请求的均值测试取的是全量请求的 P95三是环境不同测试环境少两台机器。这三个差异在 QAS 里其实都有对应的槽位可以约束——制品槽位写清边界度量槽位写清统计方法和窗口环境槽位写清机器配置。问题在于写的时候没写死留下了“服务端”和“端到端”这种模糊表述。后来我们加了一条规则所有延迟类度量必须标注统计位置可选值只有三个——服务端处理耗时、网关到服务端、客户端到服务端端到端。这个字段一旦必填口径打架基本消失。顺带说一个经验默认情况下面向用户的 QAS 应该用端到端口径因为用户感知的就是端到端面向容量规划的 QAS 应该用服务端口径因为它更接近资源消耗的真实情况。两种都要有但不能混用更不能用一个的达标去证明另一个也没问题。6.4 坑四响应度量写成“越快越好”的各种变体第四个坑比较微妙度量项看起来有数字实际上没有约束力。典型写法包括“响应时间尽可能短”“可用性不低于行业平均水平”“变更影响控制在合理范围内”。这些句子的共同特征是用了一个无法证伪的形容词做修饰验收时怎么解释都行。要治这个光靠提醒没用得用机制。我们的做法是在评审卡点上加一条机械检查度量项必须能被解析成“指标 比较符 数值 单位 统计窗口”的形式解析不了就打回。这条规则刚上时被吐槽“太死板”但它确实把那些“尽可能”“合理”“适当”之类的词清出了文档。有意思的是被迫写具体数字之后很多需求方自己发现原来的预期根本不合理——“响应时间尽可能短”变成“P99 不超过 500ms”之后他们才意识到这个要求在大促场景下意味着什么成本。还有一个相关的坑度量项写得太全一条 QAS 挂了八个指标结果验收时只要有一个不达标就整体不通过团队就倾向于把指标写得宽松一点来保证通过率。我们后来限制每条 QAS 的度量项不超过四个并且区分硬指标和观测指标硬指标不达标即验收失败观测指标只做记录不做判定。这个区分让团队敢于把真正重要的指标写严因为不用担心被次要指标拖累。我在实际操作中的体会是QAS 这套东西真正的价值不在那六个槽位的格式而在于它强迫所有人把“我觉得”换成“多少、在什么条件下、怎么量”。格式是可以简化的可能你们团队最后用的是一张五个字段的表甚至就是一句结构化的自然语言但只要那几个关键信息在里面效果就成立。反倒是那些把模板做得很华丽、字段填得很齐、但没人拿它去写用例和配告警的团队最后什么也没留下。真正值得投入时间的是把 QAS 的度量项和线上看板绑在一起这件事——绑上去之后需求文档就不再是归档材料而是每天都会被人打开的东西。
阅读完成 · 觉得有帮助?
咨询建站