MCP HubMCP Hub
스킬 목록으로 돌아가기

fail-early-pattern

pjt222
업데이트됨 Yesterday
2 조회
17
2
17
GitHub에서 보기
개발aiapi

정보

이 스킬은 입력값을 조기에 검증하고 명확한 오류 메시지를 제공하여 실패-빠른 패턴을 구현하며, 가드 절, 단언 함수, 그리고 안티패턴을 다룹니다. 외부 공개용 함수 작성, 코드 리팩터링, 오류 처리 검토와 같은 시나리오를 위한 R 예제와 다중 언어 가이드라인을 포함합니다. 개발자는 오류를 묵묵히 전파하는 대신 의미 있는 맥락과 함께 즉시 실패해야 하는 견고한 함수를 구축할 때 이를 활용해야 합니다.

빠른 설치

Claude Code

추천
기본
npx skills add pjt222/agent-almanac -a claude-code
플러그인 명령대체
/plugin add https://github.com/pjt222/agent-almanac
Git 클론대체
git clone https://github.com/pjt222/agent-almanac.git ~/.claude/skills/fail-early-pattern

Claude Code에서 이 명령을 복사하여 붙여넣어 스킬을 설치하세요

문서

フェイルアーリー

何かが失敗する場合、できるだけ早く、できるだけ大きな声で、できるだけ多くのコンテキストとともに失敗すべきだ。このスキルはフェイルアーリーパターンを体系化する: システム境界で入力を検証し、ガード句を使用して悪い状態が伝播する前に拒否し、何が失敗したか、どこでなぜ修正方法に答えるエラーメッセージを書く。

使用タイミング

  • 外部入力(ユーザーデータ、APIレスポンス、ファイルコンテンツ)を受け付ける関数を書く/レビューする場合
  • CRAN申請前にパッケージ関数に入力バリデーションを追加する場合
  • エラーを出す代わりに黙って誤った結果を生成するコードをリファクタリングする場合
  • エラーハンドリング品質のプルリクエストをレビューする場合
  • 無効な引数に対して内部APIを強化する場合

入力

  • 必須: パターンを適用する関数またはモジュール
  • 必須: トラスト境界の特定(外部データが入ってくる場所)
  • 任意: リファクタリングする既存のエラーハンドリングコード
  • 任意: ターゲット言語(デフォルト: R; Python、TypeScript、Rustにも適用)

手順

ステップ1: トラスト境界を特定する

外部データがシステムに入ってくる場所をマップする。これらがバリデーションが必要なポイントだ:

  • パブリックAPI関数(Rパッケージのエクスポートされた関数)
  • ユーザー向けパラメータ
  • ファイルI/O(設定、データファイル、ユーザーアップロードの読み込み)
  • ネットワークレスポンス(APIコール、データベースクエリ)
  • 環境変数とシステム設定

検証済みのコードのみによって呼ばれる内部ヘルパー関数は、通常冗長なバリデーションを必要としない。

期待結果: 信頼されていないデータがコードに入ってくるエントリポイントのリスト。

失敗時: 境界が不明確な場合、ログや不具合レポートのエラーから後ろ向きに追跡して、不正なデータが最初に入ってきた場所を見つける。

ステップ2: エントリポイントにガード句を追加する

各パブリック関数の先頭で、作業が始まる前に入力を検証する。

R(base):

calculate_summary <- function(data, method = c("mean", "median", "trim"), trim_pct = 0.1) {
  # Guard: type check
  if (!is.data.frame(data)) {
    stop("'data' must be a data frame, not ", class(data)[[1]], call. = FALSE)
  }
  # Guard: non-empty
  if (nrow(data) == 0L) {
    stop("'data' must have at least one row", call. = FALSE)
  }
  # Guard: argument matching
  method <- match.arg(method)
  # Guard: range check
  if (!is.numeric(trim_pct) || trim_pct < 0 || trim_pct > 0.5) {
    stop("'trim_pct' must be a number between 0 and 0.5, got: ", trim_pct, call. = FALSE)
  }
  # --- All guards passed, begin real work ---
  # ...
}

R(rlang/cli — パッケージに推奨):

calculate_summary <- function(data, method = c("mean", "median", "trim"), trim_pct = 0.1) {
  rlang::check_required(data)
  if (!is.data.frame(data)) {
    cli::cli_abort("{.arg data} must be a data frame, not {.cls {class(data)}}.")
  }
  if (nrow(data) == 0L) {
    cli::cli_abort("{.arg data} must have at least one row.")
  }
  method <- rlang::arg_match(method)
  if (!is.numeric(trim_pct) || trim_pct < 0 || trim_pct > 0.5) {
    cli::cli_abort("{.arg trim_pct} must be between 0 and 0.5, not {.val {trim_pct}}.")
  }
  # ...
}

一般(TypeScript):

function calculateSummary(data: DataFrame, method: Method, trimPct: number): Summary {
  if (data.rows.length === 0) {
    throw new Error(`data must have at least one row`);
  }
  if (trimPct < 0 || trimPct > 0.5) {
    throw new RangeError(`trimPct must be between 0 and 0.5, got: ${trimPct}`);
  }
  // ...
}

期待結果: すべてのパブリック関数が、副作用や計算の前に無効な入力を拒否するガード句で始まる。

失敗時: バリデーションロジックが長くなりすぎる場合(ガードが15行以上)、validate_* ヘルパーを抽出するか、単純な型アサーションには stopifnot() を使用する。

ステップ3: 意味のあるエラーメッセージを書く

すべてのエラーメッセージは4つの質問に答えるべきだ:

  1. 何が失敗したか — どのパラメータまたは操作
  2. どこで — 関数名またはコンテキスト(cli::cli_abort で自動)
  3. なぜ — 期待されたものと受け取ったものの比較
  4. 修正方法 — 修正が非自明な場合

良いメッセージ:

# What + Why (expected vs. actual)
stop("'n' must be a positive integer, got: ", n, call. = FALSE)

# What + Why + How to fix
cli::cli_abort(c(
  "{.arg config_path} does not exist: {.file {config_path}}",
  "i" = "Create it with {.run create_config({.file {config_path}})}."
))

# What + context
cli::cli_abort(c(
  "Column {.val {col_name}} not found in {.arg data}.",
  "i" = "Available columns: {.val {names(data)}}"
))

悪いメッセージ:

stop("Error")                    # 何が失敗した? わからない
stop("Invalid input")           # どの入力? 何が問題?
stop(paste("Error in step", i)) # 実行可能な情報なし

期待結果: エラーメッセージが自己文書化されている — エラーを初めて見た開発者がソースコードを読まずに診断して修正できる。

失敗時: 最近の3つの不具合レポートを確認する。ソースコードを読む必要があった場合、そのエラーメッセージは改善が必要だ。

ステップ4: warning()よりstop()を優先する

関数が正しい結果を生成できない場合は stop()(または cli::cli_abort())を使用する。関数がまだ意味のある結果を生成できるが呼び出し元が懸念を知るべき場合にのみ warning() を使用する。

経験則: ユーザーが黙って誤った回答を得る可能性がある場合、それは stop() であり、warning() ではない。

# CORRECT: stop when result would be wrong
read_config <- function(path) {
  if (!file.exists(path)) {
    stop("Config file not found: ", path, call. = FALSE)
  }
  yaml::read_yaml(path)
}

# CORRECT: warn when result is still usable
summarize_data <- function(data) {
  if (any(is.na(data$value))) {
    warning(sum(is.na(data$value)), " NA values dropped from 'value' column", call. = FALSE)
    data <- data[!is.na(data$value), ]
  }
  # proceed with valid data
}

期待結果: stop() が誤った結果を生成する条件に使用され、warning() が劣化しているが有効な結果に予約されている。

失敗時: 既存の warning() 呼び出しを監査する。警告後に関数がナンセンスを返す場合、stop() に変更する。

ステップ5: 内部不変条件にアサーションを使用する

「正しいコードでは絶対に起こらない」条件にはアサーションを使用する。これらは開発中のプログラマーエラーをキャッチする:

# R: stopifnot for internal invariants
process_chunk <- function(chunk, total_size) {
  stopifnot(
    is.list(chunk),
    length(chunk) > 0,
    total_size > 0
  )
  # ...
}

# R: explicit assertion with context
merge_results <- function(left, right) {
  if (ncol(left) != ncol(right)) {
    stop("Internal error: column count mismatch (", ncol(left), " vs ", ncol(right),
         "). This is a bug — please report it.", call. = FALSE)
  }
  # ...
}

期待結果: 内部不変条件がアサートされ、3つの関数呼び出し後に不可解なエラーではなく、違反サイトですぐにバグが表面化する。

失敗時: stopifnot() メッセージが不可解すぎる場合、コンテキストを含む明示的な if/stop に切り替える。

ステップ6: アンチパターンをリファクタリングする

これらの一般的なアンチパターンを特定して修正する:

アンチパターン1: 空のtryCatch(エラーを飲み込む)

# BEFORE: Error silently disappears
result <- tryCatch(
  parse_data(input),
  error = function(e) NULL
)

# AFTER: Log, re-throw, or return a typed error
result <- tryCatch(
  parse_data(input),
  error = function(e) {
    cli::cli_abort("Failed to parse input: {e$message}", parent = e)
  }
)

アンチパターン2: 悪い入力を隠すデフォルト値

# BEFORE: Caller never knows their input was ignored
process <- function(x = 10) {
  if (!is.numeric(x)) x <- 10  # silently replaces bad input
  x * 2
}

# AFTER: Tell the caller about the problem
process <- function(x = 10) {
  if (!is.numeric(x)) {
    stop("'x' must be numeric, got ", class(x)[[1]], call. = FALSE)
  }
  x * 2
}

アンチパターン3: suppressWarningsを修正として使用

# BEFORE: Hiding the symptom instead of fixing the cause
result <- suppressWarnings(as.numeric(user_input))

# AFTER: Validate explicitly, handle the expected case
if (!grepl("^-?\\d+\\.?\\d*$", user_input)) {
  stop("Expected a number, got: '", user_input, "'", call. = FALSE)
}
result <- as.numeric(user_input)

アンチパターン4: キャッチオール例外ハンドラー

# BEFORE: Every error treated the same
tryCatch(
  complex_operation(),
  error = function(e) message("Something went wrong")
)

# AFTER: Handle specific conditions, let unexpected ones propagate
tryCatch(
  complex_operation(),
  custom_validation_error = function(e) {
    cli::cli_warn("Validation issue: {e$message}")
    fallback_value
  }
  # Unexpected errors propagate naturally
)

期待結果: アンチパターンが明示的なバリデーションまたは特定のエラーハンドリングに置き換えられる。

失敗時: tryCatch を削除すると連鎖的な失敗が発生する場合、上流のコードにバリデーションギャップがある。症状ではなく原因を修正する。

ステップ7: フェイルアーリーリファクタリングを検証する

テストスイートを実行してエラーパスが正しく機能することを確認する:

# Verify error messages are triggered
testthat::expect_error(calculate_summary("not_a_df"), "must be a data frame")
testthat::expect_error(calculate_summary(data.frame()), "at least one row")
testthat::expect_error(calculate_summary(mtcars, trim_pct = 2), "between 0 and 0.5")

# Verify valid inputs still work
testthat::expect_no_error(calculate_summary(mtcars, method = "mean"))
# Run full test suite
Rscript -e "devtools::test()"

期待結果: すべてのテストがパスする。エラーパスのテストが、悪い入力が期待されるエラーメッセージをトリガーすることを確認する。

失敗時: 既存のテストが黙った失敗(例: 悪い入力でNULLを返す)に依存していた場合、新しいエラーを期待するように更新する。

バリデーション

  • すべてのパブリック関数が作業前に入力を検証する
  • エラーメッセージが答える: 何が失敗したか、どこで、なぜ、修正方法
  • stop() が誤った結果を生成する条件に使用されている
  • warning() が劣化しているが有効な結果にのみ使用されている
  • エラーを黙って飲み込む空の tryCatch ブロックがない
  • 適切なバリデーションの代替として suppressWarnings() が使用されていない
  • 無効な入力を黙って隠すデフォルト値がない
  • 内部不変条件が stopifnot() または明示的なアサーションを使用する
  • 各バリデーションガードのエラーパステストが存在する
  • リファクタリング後にテストスイートがパスする

よくある落とし穴

  • 深すぎる場所でのバリデーション: トラスト境界(パブリックAPI)でバリデーションを行い、すべての内部ヘルパーではない。過剰なバリデーションはノイズを追加しパフォーマンスを損なう。

  • コンテキストのないエラーメッセージ: "Invalid input" は呼び出し元に推測を強いる。常にパラメータ名、期待される型/範囲、受け取った実際の値を含める。

  • stop()の代わりにwarning()を使用: 警告後に関数がゴミを返す場合、呼び出し元は黙って誤った回答を得る。stop() を使用して呼び出し元が処理方法を決めるようにする。

  • tryCatchでエラーを飲み込む: tryCatch(..., error = function(e) NULL) がバグを隠す。キャッチする必要がある場合、コンテキストを追加してログに記録するか再スローする。

  • call. = FALSEを忘れる: Rでは stop("msg") はデフォルトでコールを含み、エンドユーザーにはノイズになる。ユーザー向け関数では call. = FALSE を使用する。cli::cli_abort() はこれを自動的に行う。

  • コードではなくテストでバリデーション: テストは動作を確認するが、本番の呼び出し元を保護しない。バリデーションは関数自体に属する。

  • ハイブリッドシステムでの誤った R バイナリ:WSL や Docker では、Rscript がネイティブ R の代わりにクロスプラットフォームラッパーに解決される場合があります。which Rscript && Rscript --version で確認してください。信頼性のために、ネイティブ R バイナリ(例:Linux/WSL では /usr/local/bin/Rscript)を優先してください。R パス設定については Setting Up Your Environment を参照してください。

関連スキル

  • write-testthat-tests - エラーパスを確認するテストを書く
  • review-pull-request - 欠けているバリデーションと黙った失敗のコードをレビューする
  • review-software-architecture - システムレベルでエラーハンドリング戦略を評価する
  • create-skill - agentskills.io標準に従って新しいスキルを作成する
  • security-audit-codebase - 入力バリデーションと重複するセキュリティ重視のレビュー

GitHub 저장소

pjt222/agent-almanac
경로: i18n/ja/skills/fail-early-pattern
0
agentsagentskillsai-assisted-developmentclaude-codeskillsteams

연관 스킬

qmd

개발

qmd는 BM25, 벡터 임베딩, 재순위화를 결합한 하이브리드 검색을 통해 로컬 파일을 색인화하고 검색할 수 있는 로컬 검색 및 색인화 CLI 도구입니다. 명령줄 사용과 Claude 통합을 위한 MCP(Model Context Protocol) 모드를 모두 지원합니다. 이 도구는 임베딩에 Ollama를 사용하고 색인을 로컬에 저장하여 터미널에서 직접 문서나 코드베이스를 검색하는 데 이상적입니다.

스킬 보기

subagent-driven-development

개발

이 스킬은 각 독립적인 작업마다 새로운 하위 에이전트를 배치하고 작업 사이에 코드 리뷰를 진행하여 구현 계획을 실행합니다. 이 리뷰 프로세스를 통해 품질 게이트를 유지하면서 빠른 반복 작업을 가능하게 합니다. 동일한 세션 내에서 대부분 독립적인 작업을 진행할 때 내장된 품질 검증과 함께 지속적인 진행을 보장하기 위해 사용하세요.

스킬 보기

mcporter

개발

mcporter 스킬은 개발자가 Claude에서 직접 Model Context Protocol(MCP) 서버를 관리하고 호출할 수 있도록 합니다. 이 스킬은 사용 가능한 서버를 나열하고, 인수를 사용해 해당 서버의 도구를 호출하며, 인증 및 데몬 생명주기를 처리하는 명령어를 제공합니다. 개발 워크플로우에서 MCP 서버 기능을 통합하고 테스트할 때 이 스킬을 사용하세요.

스킬 보기

adk-deployment-specialist

개발

이 스킬은 A2A 프로토콜을 사용하여 Vertex AI ADK 에이전트를 배포하고 오케스트레이션하며, AgentCard 검색, 작업 제출, 코드 실행 샌드박스 및 메모리 뱅크와 같은 지원 도구를 관리합니다. Python, Java 또는 Go 언어로 순차, 병렬 또는 루프 오케스트레이션 패턴을 갖춘 다중 에이전트 시스템 구축을 가능하게 합니다. Google Cloud에서 ADK 에이전트 배포 또는 에이전트 워크플로우 오케스트레이션을 요청받았을 때 사용하세요.

스킬 보기