我停止了“Scaling Fast”,开始设计失败——以下是改变的结果
Source: Dev.to

很长一段时间,我以为作为开发者的工作就是让系统正常运行。
现在我相信我的真正职责是让系统以清晰的方式失败。
本文描述了一种转变,它悄然改变了我设计架构、API,甚至团队的方式——尤其是在构建看似正常却随时可能出问题的复杂产品时。这不是给初学者的文章;它讨论的是在你已经发布、弄坏、在凌晨 3 点修复并对自己承诺“再也不这样”之后才会在意的决策。
问题:复杂性隐藏在“可运行代码”背后
大多数系统并不是因为代码本身的错误而失效。
它们失效的原因在于:
- 假设是隐式的
- 约束没有文档化
- 失效模式不可见
- 成功路径被优化,失效路径被忽视
在我的一个产品的早期版本中,一切“都能工作”:
- API 有响应
- UI 流畅
- 指标显示为绿色
直到一个单一的边缘案例导致:
- 部分数据损坏
- 重试放大了负载
- 日志记录的不是事实,而是故事
系统没有大声报错;它是礼貌地失效了。这才是真正的问题。
转变:首先为失败而设计
而不是问:
“我们如何让它可扩展?”
我开始问:
“它会怎么崩溃——我们如何立刻知道?”
这让我采纳了几条不可妥协的设计原则。
1. 约束是特性,而非限制
每个复杂系统都有约束。错误在于假装它们不存在。
我现在在编码前写下的显式约束示例:
- 最大请求大小(硬性失败,而非尽力而为)
- 可接受的数据陈旧度
- 每个依赖的超时预算
- 重试次数限制(使用指数退避或根本不重试)
- 所有权边界(该服务不负责修复其他服务的 bug)
如果约束没有明确写出,它就会沦为口口相传的“传说”。传说在故障时撑不住。
2. 必须为失效模式命名
如果你说不出某件事会怎样失效,就无法对其进行推理。
我现在这样记录失效模式:
- 上游不可用 → 返回缓存的降级响应
- 部分写入成功 → 触发补偿事件
- 客户端误用 → 大声拒绝并返回可操作的错误信息
- 状态未知 → 停止处理,报警人工介入
这不是悲观,而是工程上的诚实。
3. 可观测性不是日志
- 日志是叙事。
- 指标是聚合。
- 追踪是时间线。
单独任意一种都不能完整呈现真相。对于关键路径,我会问:
- 哪个信号告诉我系统已经坏了?
- 从故障到检测的时间有多长?
- 我能否在不猜测的情况下知道受影响的对象是谁?
如果答案是“我们去看日志”,那系统就在对我撒谎。
4. API 应该严苛(以保护系统)
“对接受的内容宽容”听起来不错——直到它变成带利息的技术债务。
我现在设计的 API:
- 积极进行校验
- 拒绝模糊的输入
- 返回的错误要说明如何修复,而不仅仅是说明哪里失败
友好的 API 保护用户,严格的 API 保护系统。优秀的 API 两者兼顾。
5. 团队是架构的一部分
如果所有权模糊,责任就会被每个人共享,故障会被归咎为“别人的层”,系统也会映射出这种模糊。
明确的所有权边界可以减少:
- 静默故障
- 重复修复
- 事故期间的情绪负担
技术架构与组织架构密不可分。
对我而言的变化
在采用这种思维方式后:
- 事件变得更少,更重要的是,持续时间更短。
- 调试从“发生了什么?”转变为“正是这个具体的东西失败了”。
- 新开发者的入职速度加快了。
- 我自己的认知负荷显著下降。
系统并没有变得更简单;它变得更诚实了。
结论
复杂性是不可避免的。
混乱是可选择的。
为失败而设计并不意味着消极;它让你更可靠。如果你的系统出现故障:
- 明确地
- 迅速地
- 并且以你已经理解的方式
你就做对了。