mcpField
F
fail-fast

fail-fast

github.com/yanko-belov开发1349 次安装40 周前更新
代码仓库

关于 fail-fast

fail-fast 是一项编程技能,主张在错误发生处明确暴露错误,而不是用默认值或静默处理掩盖问题,适用于编写和审查错误处理代码的开发者。

主要功能

  1. 01尽早验证输入
  2. 02断言不变量
  3. 03传播意外错误
  4. 04在边界处处理错误

适用场景

  1. 01审查错误处理代码
  2. 02替换静默回退行为
  3. 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) returnif (!user) throw new NotFoundError()
return { success: false }throw new OperationError()
笼统的“内部错误”具体的错误类型
吞掉错误并继续让错误继续传播,并在边界处处理

常见借口(都不成立)

借口事实
“应用不应该崩溃”明确的错误总比隐藏的 Bug 好。
“返回结果而不是抛出异常”调用方会忽略返回值,异常却无法被忽略。
“优雅地处理错误”吞掉错误并不优雅。
“防御性编程”防御性意味着验证并在出错时失败,而不是隐藏错误。
“绝不让函数崩溃”遇到错误时崩溃,才能发现 Bug。
“用户体验”用户更愿意看到“支付失败”,而不是静默失败。

核心要点

尽早失败。明确报错。在源头失败。

发生错误时:立即抛出异常并附上上下文信息。让错误传播到边界层,以便记录日志并转换为适合用户的响应。绝不吞掉错误。绝不返回默认值来掩盖失败。

评论

支持 Markdown