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 でエラーを隠さないでください。不正な状態を伝播させないでください。失敗した時点で失敗させましょう。破損したデータを抱えたまま、3 層後で失敗させてはいけません。

使用する場面

  • エラー処理コードを書くとき
  • エラーをキャッチしてデフォルト値を返そうとしているとき
  • あらゆる場所に「防御的な」null チェックを追加するとき
  • すべてを 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!
  }
}

問題点:

  • 呼び出し元は何が失敗したのかを把握できません
  • スタックトレースが失われます
  • バグが「内部エラー」として隠れてしまいます
  • 実際の失敗を把握できません

正しいパターン:速やかに失敗させる

失敗した時点でスローします。エラーは伝播させましょう:

// ✅ 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' });
  }
}

失敗を遅らせることが危険な理由

問題影響
隠れたバグエラーが「動かなかった」で済まされます
コンテキストの喪失スタックトレースには原因ではなく catch が表示されます
状態の破損不正なデータが伝播します
デバッグの悪夢実際にはどこで失敗したのでしょうか?
気付かれないデータ損失操作が失敗してもアプリは続行します

速やかに失敗させるテクニック

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-all)
  • エラーをログに記録するだけで、処理を続行する

これらはすべて、エラーを伝播させるか、明示的にスローすべきことを意味します。

クイックリファレンス

失敗を遅らせる(悪い例)速やかに失敗させる(良い例)
catch (e) { return null }catch (e) { throw e }
if (!user) returnif (!user) throw new NotFoundError()
return { success: false }throw new OperationError()
汎用的な「内部エラー」具体的なエラー型
握りつぶして続行する伝播させて境界で処理する

よくある正当化(すべて誤り)

言い訳実際のところ
「アプリはクラッシュしてはいけない」明確なエラーのほうが、隠れたバグよりも適切です。
「スローする代わりに値を返す」呼び出し元は戻り値を無視します。スローされたエラーは無視できません。
「エラーを適切に処理する」握りつぶすのは適切な処理ではありません。
「防御的プログラミング」防御的であるとは、検証して失敗させることであり、隠すことではありません。
「関数を絶対にクラッシュさせない」エラー時にクラッシュさせることでバグを発見できます。
「ユーザー体験」ユーザーは、失敗をひそかに隠されるよりも「決済に失敗しました」と知らされることを望みます。

結論

速やかに失敗し、明確に失敗し、発生源で失敗してください。

エラーが発生したら、コンテキストを添えて直ちに例外をスローしてください。エラーは、ログに記録してユーザーに適した応答に変換できる境界まで伝播させてください。決して握りつぶさないでください。失敗を隠すためにデフォルト値を返してはいけません。

コメント