1.5 需求到技术的翻译

一句话

翻译不是 “把需求写成设计”,而是把业务口语换成三样技术可判定的东西——可测量的验收口径、不可谈判的硬边界、显式列出的待定项;翻不出数字,通常不是技术不行,而是场景没交代清楚。

先分清三个动作:翻译 ≠ 设计 ≠ 选型

动作 输入 输出 谁做 失败表现
翻译 业务原话 场景 + 指标 + 硬边界 + 待定项 架构师 + 业务方 输出里出现 “Redis / K8s / 微服务”
设计 翻译结果 分层、模块、接口、数据模型 架构师 + 开发 翻译还在变,设计已经冻结
选型 设计约束 + 候选清单 一个具体产品 + 退出路径 架构师 + 采购 用产品名代替需求(比较维度见 4.3 技术选型的评估维度)

⚠️ 三个动作混在一起,最典型的症状是需求会上讨论 “要不要上 Kafka”。这不是会议跑偏,是翻译没做完就跳到了选型。

第一步:按三类归口,三类翻法完全不同

按 1.1 功能性需求与非功能性需求 的三分法先归类,因为三类的翻译产物不一样:

类型 翻译成什么 判据 例子
功能性需求 触发 / 输入 / 规则 / 输出 / 异常分支(五要素) 换个技术栈仍然成立 订单 30 分钟未支付自动取消
非功能性需求 六要素质量属性场景 有数字、有负载前提、有测量方法 P99 ≤ 200ms @ 5000 TPS
约束 一张 “硬边界清单”,不可谈判 没有优化空间 数据必须境内存储

约束最容易被当成 NFR 讨论,然后在会上被反复问 “能不能优化一下”——它不能。

第二步:把一句话扩成 “质量属性场景” 六要素

一个可验收的指标,必须能回答六个问题(SEI 的 quality attribute scenario 结构):

要素 要回答 缺了会怎样
刺激源 谁 / 什么系统发起 不知道峰值从哪来,容量估算无依据
刺激 具体动作是什么 把 “查询” 与 “下单” 混成一个指标
环境 什么状态下发生(常态 / 峰值 / 降级中 / 故障中) 常态指标冒充峰值指标
制品 哪个组件 / 接口 / 数据 指标无法归属,没人认领
响应 系统做什么 只写 “要快”,不知道快在哪一步
响应度量 用什么数、什么分位、什么窗口判定 验收退化成主观判断

三条填完的完整场景:

ID 场景
QA-PERF-01 大促峰值(环境)时,约 8000 QPS 的普通买家(刺激源)并发提交订单(刺激),订单服务(制品)在 P99 ≤ 300ms 内返回 201 且订单落库(响应 + 度量)
QA-AVAIL-01 单可用区故障(刺激 + 环境)时,订单库主节点(制品)在 60 秒内自动切主(响应),RPO = 0、RTO ≤ 60 秒(度量)
QA-SEC-01 外部攻击者(刺激源)用撞库脚本(刺激)打登录接口(制品)时,同 IP 10 分钟内失败 50 次即封禁 30 分钟(响应),正常用户登录成功率下降 ≤ 0.1%(度量)

三条的共同点:没有一个词需要再追问。这就是 “翻译完成” 的判据——读完不用开会就能写出测试用例。

模糊词替换表:业务原话 → 必须追问 → 翻译结果

业务原话 必须追问 翻译结果(示例)
“要快 / 不能卡” 哪个操作?分位还是平均?多少并发? 商品详情 P99 ≤ 50ms @ 3000 QPS,缓存命中率 ≥ 95%
“高并发” 峰值多少?读写比?持续多久? 峰值 8000 QPS,读写 4:1,持续 30 分钟不降级
“实时” 秒级还是毫秒级?允许最终一致吗? 订单状态变更后 3 秒内可查(允许最终一致,最长滞后 3 秒)
“海量数据” 现在多少行?3 年后多少?增长率? 3 年后 50 亿行,日增 800 万,按 8 条件组合查询 P99 ≤ 500ms @ 200 QPS
“要稳定 / 高可用” 几个 9?维护窗口算不算?只读算不算可用? 99.95%(年停机 ≤ 4.38 小时),月度维护窗口 ≤ 30 分钟且不计入(换算见 35.1 可用性定义与 9 的换算)
“要安全” 防谁?数据什么级别?泄露后果? 手机号加密存储(AES-256),审计日志留存 ≥ 180 天,越权访问拦截率 100%
“要灵活 / 可配置” 谁在什么场景改?改了要不要审批?多久生效? 运营可在后台改活动规则,5 分钟内生效,需二级审批并留痕
“和 XX 系统打通” 谁的数据谁负责?同步还是异步?冲突谁赢? 订单状态由交易域单向推送 CRM,异步、至少一次,以交易域为准
“要可扩展” 加一个维度要多久?要不要发版? 新增一类支付方式 ≤ 2 人日,不发版(走配置)

追问的通用句式只有三句:“多少”(量化)、“什么时候”(环境与时序)、“如果不行怎么办”(降级与异常)。

🔑 翻译的产出是 判据,不是方案。一段翻译如果没法直接翻成压测脚本、监控告警或测试用例,它就还没翻译完。

第三步:落到哪一层,停在 “层 + 属性”

需求里出现 落层 主语落在
数据能不能出境、异地容灾、就近接入 L0 运行时底座 5.2 部署模型:公有 / 私有 / 混合
不能丢、不能重、要顺序、跨服务一致 L1 分布式原理 25.4 幂等设计与去重
独立发布、独立扩缩容、团队边界 L2 架构风格 服务怎么切由设计阶段决定,翻译只给 “要独立发布” 这条约束
快、稳、安全、省、好改 L3 质量属性 34.1 性能指标 与 35.1 可用性定义与 9 的换算
用什么存、要不要缓存、消息可靠吗 L4 组件与技术 翻译阶段不出现;出现即越界
多久能改完、能不能回滚、谁来值班 L5 工程效能 先记成约束,实现方式在治理阶段定

这张表的价值是防越层:需求说 “要扛 8000 QPS”,翻译停在 L3 的指标(P99、错误率、降级策略),不上到 L4 的 “上 Redis”。谁先写出产品名,谁就替后面的人做了决策,而且没留退出路径。

翻译单:一张能填完的表 + 一条能跑的自检

# TR-1042.yaml —— 需求翻译单,一条需求一张
id: TR-1042
source: "大促的时候系统要稳住,别像去年那样崩了;下单要快,不能重复扣款"   # 原话,不许改写
type: [NFR-性能, NFR-可用性, FR-幂等]      # 按三类归口
scenarios:                                  # 每条都要六要素齐全
  - id: QA-PERF-01
    source: 大促峰值买家
    stimulus: 并发提交订单
    environment: 峰值 8000 QPS,持续 30 分钟
    artifact: 订单创建接口 POST /orders
    response: 返回 201 且订单落库
    measure: P99 <= 300ms,错误率 <= 0.1%
  - id: QA-AVAIL-01
    source: 可用区故障
    stimulus: 主库不可用
    environment: 业务高峰
    artifact: 订单库
    response: 自动切主,期间下单降级为预约单
    measure: RTO <= 60s,RPO = 0,降级错误率 <= 0.5%
hard_boundaries:                            # 约束:不可谈判
  - 订单数据不出境
  - 支付回调必须幂等,重复回调不得二次扣款
verification:                               # 谁能验收:压测 / 演练 / 用例
  - k6 压测 8000 QPS × 30min,看 P99 与错误率
  - 混沌演练:kill 主库,计时切主
  - 用例:同一购物车 5 秒内重复提交,只生成 1 单
open_items:                                 # 待定项:写清谁在什么时候定
  - 峰值 8000 QPS 由谁确认(业务方,10-31 前)
  - 降级为预约单是否需要法务确认(法务,11-05 前)
  - 跨单元下单是否必须强一致(架构组,11-02 前)
owner: 交易域架构师

open_items 最容易在评审时被删,也最该保留:待定项显式化,等于把 “开发自己猜” 换成 “有人负责拍板”。这一节空着,会被默认成 “按最省事的做法实现”。

每条指标都该过一遍机器检查。二十来行的脚本能拦两类问题:形容词没有数字、有分位却没给负载。

#!/usr/bin/env python3
"""req_lint.py —— 检查需求条目是否「可判定」:模糊词必须有数字兜底。
用法:python3 req_lint.py < reqs.txt     (每行一条需求,允许 # 注释)
退出码:0 全部可判定;1 存在模糊且无量化的条目
"""
import re, sys

FUZZY = ["快", "稳", "高并发", "实时", "海量", "安全", "灵活", "易用", "友好", "高效"]
NUM = re.compile(r"\d")                                  # 有数字 = 至少给了量化起点
PCT = re.compile(r"P\d{2}|分位|平均")                     # 延迟必须说清分位还是平均
LOAD = re.compile(r"@\s*\d|并发|QPS|RPS|TPS|峰值")         # 指标必须带负载前提

bad, checked = 0, 0
for lineno, line in enumerate(sys.stdin, 1):
    s = line.strip()
    if not s or s.startswith("#"):
        continue
    checked += 1
    hits = [w for w in FUZZY if w in s]
    if hits and not NUM.search(s):                        # ① 形容词没有数字兜底
        print(f"✗ L{lineno} 模糊词 {hits} 无数字:{s}")
        bad += 1
    elif PCT.search(s) and not LOAD.search(s):            # ② 有分位/平均,却没给负载
        print(f"⚠ L{lineno} 缺负载前提:{s}")
print(f"检查 {checked} 条,不可判定 {bad} 条")
sys.exit(1 if bad else 0)

跑一次,输入是这次大促需求的原话,加上已经翻译出来的几行:

cat > reqs.txt <<'EOF'
# 大促需求翻译单(节选)
下单接口要快,P99 <= 300ms @ 8000 QPS,持续 30 分钟
系统在大促期间要稳定,不能崩
峰值 8000 QPS 下,订单服务可用性 99.95%,年停机 <= 4.38 小时
库存服务不可用时降级为预约单,错误率 <= 0.5%,需人工兜底
订单查询 P99 <= 200ms
支持海量订单查询
EOF
python3 req_lint.py < reqs.txt; echo "退出码: $?"

本机跑出来的输出:

✗ L3 模糊词 ['稳'] 无数字:系统在大促期间要稳定,不能崩
⚠ L6 缺负载前提:订单查询 P99 &lt;= 200ms
✗ L7 模糊词 ['海量'] 无数字:支持海量订单查询
检查 6 条,不可判定 2 条
退出码: 1

补上数字与负载前提后重跑:

检查 6 条,不可判定 0 条
退出码: 0

脚本只做一件事:把 “还说不清” 的需求挡在评审之前。它不判断指标是否合理(P99 ≤ 1ms 也能通过),合理性归容量估算与压测,见 57.1 压测方法论。

一次完整翻译:从一句抱怨到四条可验收需求

原话:“大促的时候系统要稳住,别像去年那样崩了,而且下单要快,不能重复扣款。”

步 动作 结果
① 拆句 一句话里有三个诉求 + 一次历史事故 稳定性 / 延迟 / 幂等 / 事故复盘
② 归口 稳定性与延迟是 NFR,幂等是 FR,事故是约束来源 3 条需求 + 1 份历史数据
③ 追数字 “稳住” 问出峰值与 9;“崩” 追问去年崩在哪 峰值 8000 QPS(去年实测 3200 × 2.5 倍余量)、99.95%
④ 写场景 六要素填满 QA-PERF-01、QA-AVAIL-01
⑤ 标待定 谁确认峰值、降级要不要法务过、跨单元是否强一致 3 条 open_items,各有 owner 与截止日

形成的四条可验收需求:

ID 需求 验收方式
TR-1042-1 峰值 8000 QPS 持续 30 分钟,下单接口 P99 ≤ 300ms,错误率 ≤ 0.1% k6 压测 + 生产 SLO 双验证
TR-1042-2 主库故障 60 秒内自动切主,RPO = 0;切换期间下单降级为预约单,降级错误率 ≤ 0.5% 混沌演练 + 故障注入
TR-1042-3 同一用户同一购物车 5 秒内重复提交,只创建 1 个订单 自动化用例 + 生产重复率监控(实现见 25.4 幂等设计与去重)
TR-1042-4 订单数据不出境(约束,不可谈判) 部署拓扑核查,不允许出现境外 Region

第 3 条值得单独看:“不能重复扣款” 是结果,不是需求。翻译要做的是把它变成可判定条件(时间窗 5 秒、范围同一购物车、判定只生成 1 单),否则测试只能靠 “手工点两下试试”。

💡 数字的来源要写清:“8000 QPS” 来自 “去年实测 3200 × 2.5 倍余量”,这是可推导的;写成 “预计 8000” 就成了无根据的猜测。数字的来源与数字本身同等重要。

翻译失败的五个信号

信号 说明 修法
输出里出现具体产品名 已经跳到选型 删掉产品名,只留指标与边界
输出里还有形容词 没量化 逐个过模糊词替换表,追问 “多少 / 什么时候 / 不行怎么办”
有 5 个硬指标但没排序 指标之间会打架 显式写 “当 A 与 B 冲突时优先 A”,排序依据见 1.3 需求优先级
待定项为 0 几乎不可能,多半是没人敢写 逐条问 “这条谁拍板、什么时候定”
业务方看不懂输出 翻译过头,语言没换回来 每条技术指标补一句业务含义(“P99 300ms = 用户点击后基本感觉不到等待”)

最后一条是很多架构师栽的地方:翻译的结果要给业务方签字,所以技术语言必须能翻回业务语言。写完指标补一句人话,成本一句,收益是对方真的会看。

反模式

反模式 后果
把需求会开成选型会 方案先于指标确定,后面所有指标都被方案反向裁剪
用平均延迟验收 平均值达标、P99 崩盘,用户体感差(判据见 34.1 性能指标)
指标不带负载前提 5 QPS 下的 200ms 与 5000 QPS 下的 200ms 被当成同一件事
“高可用” 直接翻译成 “上多活” 跳过 9 的定义,成本翻倍却没解决真问题
待定项留空或只写 “待确认” 开发按最省事的方案实现,上线后返工
翻译单只有 NFR,没有异常分支 主流程能跑,分支靠临场发挥
一条需求塞 6 个指标 无法定位瓶颈,也不知道该优化哪个
业务方没签字 上线验收时对 “稳不稳” 各说各话

三条铁律

🔑 翻译的产物是判据(能直接翻成压测脚本、监控告警、测试用例),不是方案;输出里出现产品名,就是越界。

🔑 每个指标必须带 负载前提 + 分位 + 窗口;缺一个,验收就只能靠吵。

🔑 待定项必须显式列出并指定 owner;留白的部分,开发一定会用最省事的方式填。

自检

Q1 “下单要快” 要追问哪几个问题,才能变成可验收的指标?

▸ 答案:① 哪个操作、哪个接口(下单创建还是订单查询——制品必须唯一);② 多少并发下的快(8000 QPS 与 8 QPS 是两个系统);③ 看分位还是平均、什么窗口(P99 ≤ 300ms @ 5 分钟滑动窗口);④ 什么环境(峰值还是常态)。四问补齐才是完整场景,缺 “环境” 那一项时,峰值指标会被常态数据验收掉。

Q2 为什么 “不能重复扣款” 不是一条需求?

▸ 答案:它是结果,不是可判定的条件。同一个结果有多种实现(幂等键、去重表、状态机),需求该写的是判定口径:范围(同一用户 + 同一购物车)、时间窗(5 秒内)、判定标准(只创建 1 个订单、只扣 1 次款)。写清这三样,测试才能自动化;只写 “不能重复扣款”,验收只能靠手工点两下。

Q3 需求会上有人问 “我们是不是该上 Kafka”,正确的回应是什么?

▸ 答案:先确认翻译是否完成。上 Kafka 对应的是 “异步解耦 + 至少一次投递” 这类判断,属于 L4 的选择;翻译阶段只该出现 “订单创建后 3 秒内库存服务收到扣减事件,允许重复但消费必须幂等” 这样的判据。若 “多久内、允不允许重复、丢了怎么办” 都没答,讨论产品就是替一个尚未定义的需求做决策。

Q4 翻译单的 open_items 写了 3 条,评审时有人说 “先不写,开发先做起来”。风险是什么?

▸ 答案:留白会被最省事的实现填满,而最省事的实现通常最难改:跨单元一致性留白 → 选了本地事务;降级策略留白 → 直接抛错,大促时用户看到 500。显式写 open_items 的成本是三行字加三个 owner,收益是把 “返工” 换成 “开会拍板”。

Q5 为什么翻译要停在 “层 + 属性”,不落到产品?

▸ 答案:因为产品是可替换的战术选择,需求是不可替换的约束。翻译一旦写到 “用 Redis”,后续讨论全被锁在这条路上,退出成本没人算过;写到 “商品详情 P99 ≤ 50ms @ 3000 QPS”,实现可以是本地缓存、多级缓存或读副本,等设计阶段再按退出成本比较。越早写产品名,越早失去选择权。

关联