如何在技术规格文档中界定非目标
项目能否按时交付,最大的预测指标不是目标清单——而是非目标清单。写了明确非目标的团队,大致能按计划完成。没写的团队,往往在三周后还在争论某件事到底算不算在范围内。我见过的每一个延期的项目,非目标章节要么缺失,要么就只有孤零零一行字。
内容整理说明
复查日期:2026-05-06。本文已重新纳入公开索引路径,作为 Spec-First 开发 Hub 的延伸阅读。我们补齐了专题路径、站内链接和可索引元数据,便于搜索引擎和读者理解它与核心主题的关系。
非目标到底指什么
非目标是一件具体的事情——当前工作不会去做,哪怕一个合理的人有理由以为会做。最后那半句才是关键。"我们不解决世界和平"不是非目标,因为本来就没人指望你去做。"我们不处理部分退款,即便它和全额退款流程看起来很像"才是非目标,因为两周后总会有人想把它塞进范围里。
好的非目标读起来像承诺,而不是愿望。"在本次发布中"或"在这个里程碑里"应该是隐含前提。检验标准是:如果实现阶段有人问"嘿,我们要不要顺便把 X 也做了?"——文档里是不是已经有答案了?
非目标从哪里来
大多数非目标不是凭空想出来的,而是从已经发生过的对话里提炼出来的。我常从这些地方挖:
- kickoff 时的范围争论。如果团队花了十分钟讨论"做 A 不做 B",那 B 就是非目标。原话抄下来。
- 相邻的用户故事。如果这个功能要和已有流程交互,那些不会改动的部分就是非目标。"登录流程保持不变;本规格不修改 session 生命周期。"
- 那些看起来顺手能做的优化。也就是"我们是不是还该顺便……"的时刻。"我们不优化列表页的 N+1 查询,那是另一个单独的工单。"
- 你不打算引入的依赖。"我们不会新增消息队列;这个功能复用已有的 HTTP 链路。"
做法很简单:每次你发现自己在说"哦,但那个不做",就写下来。这就是你的非目标清单。
我常用的三种写法
不是所有非目标都长一个样。我会按"为什么不在范围里"分成三种模式。
Deferred(延后)
事情是真实存在的,我们大概率会做,只是现在不做。
Non-goal: Partial refunds Reason: Out of scope for v1. Planned for v2 (tracked in JIRA-1234). The full refund flow will be implemented in a way that allows later extension without restructuring the refund table.
Deferred 类非目标需要一个向前的指针。没有这个指针,它们迟早会在一场范围蔓延的争论里重新冒出来。
Rejected(否决)
我们认真考虑过,然后决定不做。
Non-goal: SMS-based password reset Reason: SMS delivery has a 3% failure rate in our current provider's geography, incompatible with the 99% delivery expectation for password reset. Decision is final unless we change provider.
Rejected 类非目标是为了避免被反复翻案。把理由写进去,不然实现阶段一定会有人提出来重新讨论。
Out of domain(不属于本域)
这事归属于另一个系统或另一个团队。
Non-goal: Auth token refresh logic Reason: Owned by the identity team. This feature consumes the existing refresh behavior unchanged. Any refresh bugs go to them.
Out-of-domain 类非目标可以避免意外的跨团队工作。把负责方的团队名写清楚,升级路径就一目了然。
多少个非目标算合适
对一个中等规模的功能,我一般定 3 到 6 条。少于 3 条,说明你多半没认真想过相邻范围。多于 6 条,要么你在列一些根本没人会提的事情,要么这个功能本身就太激进了。
快速自检:把你的非目标念一遍,问自己"真的会有人想要这个吗?"如果答案是否定的,删掉——那是噪音。如果答案是"会,而且实现阶段他们一定会问",就留下。
非目标和验收标准是联动的
有个细微但重要的点:如果非目标是"我们不支持 X",那验收标准里必须交代——当有人偏要试 X 时会发生什么。只写"我们不做部分退款"是不够的。规格还得补上:"当用户通过 API 发起部分退款时,接口返回 400,错误码为 'unsupported_refund_type',附带文档链接。"
非目标没有配套处理 AC = 用户收到 500,然后提了一张 support ticket。非目标有配套处理 AC = 用户收到清晰的拒绝,然后干净利落地转身离开。
非目标什么时候需要变动
有时候某个非目标会在项目中途变成目标。这是被允许的,但过程很重要。我的规则是:
- 范围扩张必须由最初批准规格的那批评审者重新签字同意。
- 规格文档要先更新——删掉非目标、补上 AC——然后才能开始实现新范围。
- 时间线必须被重新评估。把非目标改成目标却不调整日期,就是 deadline 阵亡的地方。
好用的表达方式
这些句式能让非目标不被忽略:
- "This release does not include X." 动词开头,当场给出承诺。
- "X is deliberately out of scope for this work. X is tracked separately at [link]." 直接指明它现在在哪里被跟进。
- "We considered X and decided not to do it because Y." 把推理过程留给后来的评审者看。
容易被忽略的句式:
- "X might be added later." 软绵绵,邀请范围蔓延。
- "X is not a priority." 优先级会变,承诺不会。
- "We'll see how far we get." 这不是非目标,甚至算不上计划。
一个元非目标
有一条我基本每份规格开头都会放上:
Meta non-goal: This spec does not describe implementation. Architectural choices and data structures are engineering decisions, not product decisions. Changes to the architecture do not require spec changes unless they alter observable behavior.
这条能防止规格滑向设计文档——那是规格膨胀到三十页、然后作为评审物件彻底失效的典型路径。
底线
非目标是一种非常便宜的范围纪律手段。写它只要五分钟,却能在整个实现周期里每周省下一场争论。如果你的规格只有目标清单、没有非目标,那它只完成了一半。每一次我省掉这一步,到了第二周都会后悔。
评审时看什么
这篇文章适合用在给技术规格写非目标时。别从“原则”聊起,直接拿一条真实改动来对照,看看规格里还缺什么。
- 列出最容易被顺手加进去的需求。
- 说明它们是延期、拒绝,还是属于别的团队。
- 写清谁能重新打开非目标。
- PR 里出现非目标实现时,要求先更新规格。
好的非目标会帮团队少吵一次。不能挡住任何范围蔓延的非目标,基本就是装饰。
例子:“多币种结算不在范围内”只有在写清后续影响时才有用:本期仍按单币种计费,发票保留 currency 字段,FX 处理需要单独迁移方案。否则非目标只是把争议推迟。
落地例子
报表页可以写“自定义报表构建器不在范围内”,但最好继续说明:本期只支持固定过滤器,不支持保存视图;CSV 字段沿用现有导出契约;类似 SQL 的自由分组需要单独做数据模型评审。这样设计、后端和 QA 面对范围膨胀时能引用同一段话。
非目标不是拒绝沟通,而是把本期不做的事情讲清楚。真正有用的非目标会降低争论成本。
非目标要写到能挡住需求
“不做复杂功能”不是非目标。好的非目标会点名具体诱惑:这次不改哪个端点、不迁移哪张表、不支持哪个用户群、不处理哪个异常路径。它要能在 review 时挡住一句“顺手也做了吧”。
Weak non-goal: - We will not redesign billing. Useful non-goal: - We will not change subscription renewal dates. - We will not migrate existing invoices. - We will not support annual-to-monthly downgrades in this release. - We will not expose proration preview in the public API.
边界:非目标不是拒绝沟通。它只是把延期事项从当前 spec 里拿出去,并要求产品决定它是否进入下一份 spec。
非目标也要有后续去处
被排除的事项最好有 destination:下个 spec、backlog、明确放弃,或者需要产品重新决策。否则非目标只是把争议推迟。写清 owner 和重新评估条件,团队才知道这不是被忘了。
最后检查非目标是否挡得住代码改动:API 不改哪个字段,数据库不碰哪张表,测试不覆盖哪个场景,回滚不处理哪类历史数据。能挡住 diff 的非目标,才算真的有用。
我喜欢给非目标加“如果有人提出怎么办”:新建 ticket、更新 roadmap、写 ADR,还是直接拒绝。这样 reviewer 在 PR 里看到越界代码时,可以指向非目标和 owner,而不是重新讨论产品方向。
最后再补一列证据:哪些 API 字段不能改,哪些表不能迁移,哪些状态不能新增,哪些测试不在本次覆盖。非目标能对应到代码边界和 owner,reviewer 才能拦住越界 diff。
专题阅读路径
先读主题 Hub,再用下面的相邻文章和模板把这篇内容放进完整工作流。
继续阅读
填写表单,生成完整的功能规格 Markdown——免费使用,无需注册。
编辑说明
本文面向软件交付团队,介绍如何在技术规格文档中界定非目标。示例均为工程场景说明,不构成法律、税务或投资建议。
- 作者信息:Daniel Marsh
- 编辑政策:文章审阅与更新方式
- 纠错:联系编辑