fail-early-pattern
关于
This skill implements the fail-fast pattern by validating inputs early and providing clear error messages, covering guard clauses, assertion functions, and anti-patterns. It includes R examples and multi-language guidance for scenarios like writing external-facing functions, refactoring code, or reviewing error handling. Developers should use it when building robust functions that need to fail immediately with meaningful context instead of silently propagating errors.
快速安装
Claude Code
推荐npx skills add pjt222/agent-almanac -a claude-code/plugin add https://github.com/pjt222/agent-almanacgit 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つの質問に答えるべきだ:
- 何が失敗したか — どのパラメータまたは操作
- どこで — 関数名またはコンテキスト(
cli::cli_abortで自動) - なぜ — 期待されたものと受け取ったものの比較
- 修正方法 — 修正が非自明な場合
良いメッセージ:
# 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 仓库
相关推荐技能
qmd
开发这是一个本地搜索和索引的CLI工具,支持BM25、向量搜索和重排序功能。开发者可以用它快速索引本地文件(如Markdown文档)并进行混合搜索,特别适合代码库或文档的本地检索。它还提供MCP模式,能轻松集成到Claude开发环境中使用。
subagent-driven-development
开发该Skill用于在当前会话中执行包含独立任务的实施计划,它会为每个任务分派一个全新的子代理并在任务间进行代码审查。这种"全新子代理+任务间审查"的模式既能保障代码质量,又能实现快速迭代。适合需要在当前会话中连续执行独立任务,并希望在每个任务后都有质量把关的开发场景。
mcporter
开发mcporter Skill 让开发者能在Claude中直接管理和调用MCP服务器。它支持列出可用服务器、调用工具、处理OAuth认证以及管理服务器守护进程。开发者可以通过命令行式交互快速执行`mcporter list`查看服务器,或使用`mcporter call`直接调用工具,简化了MCP工作流程。
adk-deployment-specialist
开发这是一个用于部署和编排Google Vertex AI ADK智能体的Claude Skill,专为构建生产级多智能体系统而设计。它支持通过A2A协议进行智能体通信,提供代码执行沙箱和记忆库功能,并能处理智能体发现与任务提交。当开发者需要部署ADK智能体或编排多智能体协作时,可使用此Skill来简化Vertex AI Agent Engine的部署流程。
