关于 fail-fast
fail-fast 是一项编程技能,主张在错误发生处明确暴露错误,而不是用默认值或静默处理掩盖问题,适用于编写和审查错误处理代码的开发者。
主要功能
- 01尽早验证输入
- 02断言不变量
- 03传播意外错误
- 04在边界处处理错误
适用场景
- 01审查错误处理代码
- 02替换静默回退行为
- 03防止无效状态扩散
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ yanko-belov/ code-craft --skill fail-fast安装后在 Claude Code 中输入/fail-fast确认已加载。
详细介绍
快速失败
概述
出错时,立即且明确地失败。
不要用会返回默认值的 try/catch 隐藏错误。不要让无效状态继续传播。在出错的地方失败,而不是等到三层之后数据已经损坏时才失败。
适用场景
- 编写错误处理代码时
- 想要捕获错误并返回默认值时
- 到处添加“防御性”的空值检查时
- 想把所有代码都包进 try/catch 时
- 想返回错误对象而不是抛出异常时
铁律
NEVER hide failures. Fail loud, fail early.
没有例外:
- 不能以“应用不应该崩溃”为由
- 不能以“返回点东西总比抛出异常好”为由
- 不能以“优雅地处理错误”为由
- 不能以“防御性编程”为由
检测:“吞掉错误”的坏味道
如果错误悄无声息地消失了,你就是在慢速失败:
// ❌ VIOLATION: Hiding failures
async function processPayment(userId: string, amount: number): Promise<PaymentResult> {
try {
const user = await getUser(userId);
if (!user) return { success: false, error: 'User not found' };
const card = await validateCard(user.cardToken);
if (!card.valid) return { success: false, error: 'Invalid card' };
const result = await chargeCard(card, amount);
if (!result.success) return { success: false, error: 'Payment failed' };
return { success: true, transactionId: result.id };
} catch (error) {
return { success: false, error: 'Internal error' }; // ← SWALLOWED!
}
}
问题:
- 调用方不知道究竟出了什么问题
- 堆栈跟踪丢失
- Bug 被掩盖成“内部错误”
- 无法了解实际发生的故障
正确模式:快速失败
在出错的地方抛出异常。让错误向上传播:
// ✅ CORRECT: Fail fast
async function processPayment(userId: string, amount: number): Promise<Transaction> {
// Validate early - fail fast on bad input
if (!userId) throw new ValidationError('userId is required');
if (amount <= 0) throw new ValidationError('amount must be positive');
// Let failures propagate - don't swallow
const user = await getUser(userId);
if (!user) throw new NotFoundError(`User ${userId} not found`);
const card = await validateCard(user.cardToken);
if (!card.valid) throw new PaymentError('Card validation failed', card.errors);
// This might throw - that's okay! Let it.
const transaction = await chargeCard(card, amount);
return transaction;
}
// Caller handles errors appropriately
try {
const tx = await processPayment(userId, amount);
res.json({ success: true, transactionId: tx.id });
} catch (error) {
if (error instanceof ValidationError) {
res.status(400).json({ error: error.message });
} else if (error instanceof NotFoundError) {
res.status(404).json({ error: error.message });
} else if (error instanceof PaymentError) {
res.status(402).json({ error: error.message });
} else {
// Unknown error - log it, return 500
logger.error('Payment failed', error);
res.status(500).json({ error: 'Internal server error' });
}
}
慢速失败为何危险
| 问题 | 影响 |
|---|---|
| 隐藏的 Bug | 错误变成“它没起作用” |
| 上下文丢失 | 堆栈跟踪显示的是捕获错误的位置,而不是原因 |
| 状态损坏 | 无效数据继续传播 |
| 调试噩梦 | 实际上是在哪里出错的? |
| 静默数据丢失 | 操作失败,但应用仍继续运行 |
快速失败技巧
1. 尽早验证
function createUser(data: unknown): User {
// Fail IMMEDIATELY on bad input
if (!data || typeof data !== 'object') {
throw new ValidationError('Invalid user data');
}
const { email, name } = data as Record<string, unknown>;
if (!email || typeof email !== 'string') {
throw new ValidationError('Email is required');
}
if (!name || typeof name !== 'string') {
throw new ValidationError('Name is required');
}
// Only proceed with valid data
return new User(email, name);
}
2. 断言不变量
function withdraw(account: Account, amount: number): void {
// Assert what must be true
assert(amount > 0, 'Withdrawal amount must be positive');
assert(account.balance >= amount, 'Insufficient funds');
account.balance -= amount;
// Post-condition check
assert(account.balance >= 0, 'Balance went negative - invariant violated');
}
3. 使用类型系统
// ❌ Fail slow: null checks everywhere
function processOrder(order: Order | null): void {
if (!order) return; // Silent failure
// ...
}
// ✅ Fail fast: require valid input
function processOrder(order: Order): void {
// If order is null, TypeScript catches it
// If it gets here with null, it will throw - good!
}
抵御压力的准则
1. “应用不应该崩溃”
压力:“如果我们抛出异常,用户会看到错误”
**回应:**让用户看到明确的错误,总比数据损坏或静默失败好。
**行动:**抛出异常,在边界处(API 层)捕获,并返回适当的 HTTP 状态码。
2. “返回点东西总比抛出异常好”
压力:“返回错误对象在函数式编程中更合适”
**回应:**如果调用方会检查错误对象,那当然没问题。但他们通常不会这么做。
**行动:**对意外故障抛出异常。只有在调用方确实会处理两种情况时,才使用 Result 类型。
3. “优雅地处理错误”
压力:“优雅就是不抛出异常”
**回应:**优雅意味着做出适当的响应。吞掉错误并不优雅。
**行动:**抛出异常,在边界处捕获,并返回有意义的错误响应。
4. “防御性编程”
压力:“防御性代码能处理所有情况”
**回应:**防御性意味着尽早验证并在出错时失败,而不是隐藏故障。
**行动:**验证输入、断言不变量,并在违反约束时抛出异常。
危险信号——停下来重新考虑
如果你发现以下任何一种情况,就进行重构:
catch (e) { return null; }catch (e) { return { success: false }; }if (!x) return;(静默提前返回)try { } catch { }(空 catch)- 出错时返回默认值
- “Error: Internal error”(笼统捕获所有错误)
- 记录错误日志后继续执行
这些情况都意味着:让错误继续传播,或明确抛出异常。
快速参考
| 慢速失败(坏) | 快速失败(好) |
|---|---|
catch (e) { return null } | catch (e) { throw e } |
if (!user) return | if (!user) throw new NotFoundError() |
return { success: false } | throw new OperationError() |
| 笼统的“内部错误” | 具体的错误类型 |
| 吞掉错误并继续 | 让错误继续传播,并在边界处处理 |
常见借口(都不成立)
| 借口 | 事实 |
|---|---|
| “应用不应该崩溃” | 明确的错误总比隐藏的 Bug 好。 |
| “返回结果而不是抛出异常” | 调用方会忽略返回值,异常却无法被忽略。 |
| “优雅地处理错误” | 吞掉错误并不优雅。 |
| “防御性编程” | 防御性意味着验证并在出错时失败,而不是隐藏错误。 |
| “绝不让函数崩溃” | 遇到错误时崩溃,才能发现 Bug。 |
| “用户体验” | 用户更愿意看到“支付失败”,而不是静默失败。 |
核心要点
尽早失败。明确报错。在源头失败。
发生错误时:立即抛出异常并附上上下文信息。让错误传播到边界层,以便记录日志并转换为适合用户的响应。绝不吞掉错误。绝不返回默认值来掩盖失败。
评论
支持 Markdown