核心问题
错误多久之后会被发现?
软件工程里的反馈回路,关心的是:
从做出改变,到知道这个改变是否正确,中间隔了多久。
反馈越慢,错误越贵。
反馈越快,系统越容易演化。
为什么反馈回路重要
一个 bug 在不同阶段被发现,成本完全不同。
写代码时发现
-> 改几秒
本地测试发现
-> 改几分钟
CI 发现
-> 改几十分钟
Code Review 发现
-> 需要上下文切换
测试环境发现
-> 需要排查部署和数据问题
生产环境发现
-> 用户受影响,可能要回滚、补数据、写事故复盘
同一个错误,发现得越晚,牵扯越多。
所以成熟工程体系的目标不是“永远不犯错”,而是:
让错误尽早暴露,并让修复路径尽量短。
反馈回路的层级
1. 编辑器反馈
最快的反馈来自写代码时:
- TypeScript 类型检查
- lint
- formatter
- IDE 提示
- 静态分析
这些工具的价值是把错误拦在进入代码库之前。
例如:
canAccessCourse(accountId, courseId)
如果 courseId 传成了 organizationId,类型系统最好能立刻提醒。
这就是为什么领域 ID 不应该全是裸 string。
可以考虑更明确的类型:
type AccountId = Brand<string, "AccountId">
type CourseId = Brand<string, "CourseId">
这类设计让错误在编辑器阶段暴露,而不是到生产环境才发现。
2. 本地测试反馈
本地测试负责快速验证规则和关键协作。
理想状态:
npm test
能在短时间内告诉你:
- 状态机有没有坏
- 权限规则有没有坏
- 访问权判断有没有坏
- 核心服务协作有没有坏
本地测试太慢,开发者就会少跑。
少跑之后,反馈回路就被推迟到 CI 或生产。
3. CI 反馈
CI 是团队共同边界。
它回答:
这次改动是否仍然满足项目的最低质量承诺?
CI 通常包括:
- typecheck
- lint
- unit tests
- integration tests
- build
- migration check
- contract tests
- 少量 smoke tests
CI 不是仪式。CI 是自动化的认识论系统。
它让团队不必靠“我觉得没问题”合并代码。
4. Review 反馈
Code Review 不应该重复机器能做的事。
机器应该检查:
- 格式
- 类型
- 基础测试
- 构建
- 明显规则
人应该关注:
- 领域概念是否清楚
- 边界是否放对
- 是否引入错误抽象
- 是否有遗漏的失败路径
- 是否符合系统演化方向
如果 review 里大量时间都在讨论格式、命名风格和低级错误,说明自动反馈不够强。
5. Staging 反馈
测试环境适合验证:
- 配置是否正确
- 数据迁移是否可执行
- 第三方集成是否通
- 部署产物是否可运行
- 关键用户路径是否成立
但 staging 永远不能完全代表生产。
它的数据、流量、并发、用户行为都不一样。
所以 staging 只能提供一部分信心,不能替代生产可观察性和渐进发布。
6. Production 反馈
生产反馈来自:
- logs
- metrics
- traces
- error reporting
- user reports
- business dashboards
- support tickets
生产反馈最真实,也最昂贵。
好的系统会让生产反馈尽可能可控:
- 小流量发布
- feature flag
- 快速回滚
- 告警清楚
- dashboard 明确
- 数据修复路径存在
CI/CD 的真正意义
CI/CD 经常被理解成:
自动构建、自动部署。
但更深层的意义是:
缩短从改变到证据的距离。
CI 让你更快知道:
这次改动是否破坏了已有承诺?
CD 让你更快知道:
这次改动在真实环境里是否成立?
如果部署很痛苦,团队会倾向于攒大版本。
大版本的问题是:
- 变更多
- 风险大
- 回滚难
- 排查复杂
- 反馈慢
小批量、频繁、可回滚的发布,反而更安全。
反馈回路和批量大小
反馈速度和变更批量强相关。
大 PR:
改了 80 个文件
包含重构、功能、样式、迁移
问题是:
- review 慢
- 测试失败难定位
- 回滚困难
- 风险集中
小 PR:
先改名
再抽规则函数
再迁移调用方
再删除旧路径
反馈更快,也更容易确认每一步是否正确。
原则:
小批量改变能缩短反馈回路。
好反馈的特征
1. 快
反馈越快,越容易被使用。
如果一个测试套件要跑 40 分钟,开发者不会频繁依赖它。
2. 准
失败时要能告诉你哪里坏了。
坏反馈:
E2E failed
好反馈:
refund should revoke purchase-based course access
expected canAccessCourse=false, received true
3. 相关
反馈应该和系统承诺有关。
如果告警天天响但没人行动,它就不是反馈,是噪音。
4. 可行动
反馈应该指向下一步。
例如:
payment_success_count normal, course_access_grant_created_count dropped 40%
这比:
error rate high
更容易行动。
慢反馈的坏味道
- 开发者不敢重构。
- PR 越积越大。
- 测试只在合并前跑一次。
- 部署需要专门挑时间。
- 出问题只能靠用户反馈。
- 回滚比修 bug 还痛苦。
- 团队靠资深工程师记忆维持质量。
这些都说明系统的认识论基础太弱:
团队不知道怎么快速知道自己是否正确。
反馈回路设计清单
对一个关键业务承诺,比如:
支付成功后用户应该获得课程访问权。
可以设计多层反馈:
| 层级 | 反馈 |
|---|---|
| 类型 | paymentId、purchaseId、courseId 不混用 |
| 单元测试 | 购买访问权规则正确 |
| 集成测试 | payment succeeded 后创建 purchase 和 grant |
| E2E | 用户购买后能进入课程页 |
| 日志 | course_access_grant_created |
| 指标 | grant created / payment succeeded ratio |
| 告警 | 支付成功正常但 grant 创建下降 |
| 客服后台 | 能查询用户访问权历史 |
这就是把一个业务承诺变成一整套证据链。
小结
- 反馈回路回答“错误多久之后会被发现”。
- 反馈越慢,错误越贵。
- 编辑器、本地测试、CI、Review、Staging、Production 是不同层级的反馈。
- CI/CD 的本质是缩短从改变到证据的距离。
- 小批量改变能缩短反馈回路。
- 好反馈应该快、准、相关、可行动。
- 对关键业务承诺,要设计多层证据链。