fail-early-pattern
정보
이 스킬은 가드 절과 명확한 오류 메시지를 사용하여 입력을 검증하고 오류를 즉시 포착하는 조기 실패 패턴을 구현합니다. 함수에 입력 검증을 추가하고, 침묵하는 실패를 리팩터링하며, API를 보호하기 위한 다중 언어 지침과 R 예제를 제공합니다. 외부 입력을 받는 함수를 작성할 때, CRAN 제출을 준비할 때, 또는 풀 리퀘스트에서 코드 품질을 검토할 때 사용하세요.
빠른 설치
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-patternClaude Code에서 이 명령을 복사하여 붙여넣어 스킬을 설치하세요
문서
Fruehzeitig Scheitern
Wenn etwas scheitern wird, sollte es so frueh wie moeglich, so laut wie moeglich und mit so viel Kontext wie moeglich scheitern. Dieser Skill kodifiziert das Fail-Early-Muster: Eingaben an Systemgrenzen validieren, Guard-Klauseln verwenden, um schlechten Zustand abzulehnen bevor er sich ausbreitet, und Fehlermeldungen schreiben, die beantworten was scheiterte, wo, warum und wie man es behebt.
Wann verwenden
- Schreiben oder Pruefen von Funktionen, die externe Eingaben akzeptieren (Benutzerdaten, API-Antworten, Dateiinhalte)
- Eingabevalidierung zu Paketfunktionen hinzufuegen vor der CRAN-Einreichung
- Refaktorieren von Code, der still falsche Ergebnisse erzeugt statt Fehler zu werfen
- Pruefen von Pull Requests auf Fehlerbehandlungsqualitaet
- Absichern interner APIs gegen ungueltiger Argumente
Eingaben
- Erforderlich: Funktion oder Modul, auf das das Muster angewendet werden soll
- Erforderlich: Identifizierung von Vertrauensgrenzen (wo externe Daten eintreten)
- Optional: Bestehender Fehlerbehandlungscode zum Refaktorieren
- Optional: Zielsprache (Standard: R; gilt auch fuer Python, TypeScript, Rust)
Vorgehensweise
Schritt 1: Vertrauensgrenzen identifizieren
Kartieren, wo externe Daten ins System eintreten. Diese Punkte benoetigen Validierung:
- Oeffentliche API-Funktionen (exportierte Funktionen in einem R-Paket)
- Benutzerseitige Parameter
- Datei-E/A (Lesen von Konfigs, Datendateien, Benutzer-Uploads)
- Netzwerkantworten (API-Aufrufe, Datenbankabfragen)
- Umgebungsvariablen und Systemkonfiguration
Interne Hilfsfunktionen, die nur durch eigenen validierten Code aufgerufen werden, benoetigen generell keine redundante Validierung.
Erwartet: Eine Liste von Einstiegspunkten, an denen nicht vertrauenswuerdige Daten in den Code eintreten.
Bei Fehler: Falls Grenzen unklar sind, rueckwaerts von Fehlern in Logs oder Bug-Reports verfolgen, um herauszufinden, wo schlechte Daten zuerst eingetreten sind.
Schritt 2: Guard-Klauseln an Einstiegspunkten hinzufuegen
Eingaben am Anfang jeder oeffentlichen Funktion validieren, bevor irgendeine Arbeit beginnt.
R (base):
calculate_summary <- function(data, method = c("mean", "median", "trim"), trim_pct = 0.1) {
# Guard: Typenpruefung
if (!is.data.frame(data)) {
stop("'data' must be a data frame, not ", class(data)[[1]], call. = FALSE)
}
# Guard: Nicht leer
if (nrow(data) == 0L) {
stop("'data' must have at least one row", call. = FALSE)
}
# Guard: Argument-Abgleich
method <- match.arg(method)
# Guard: Bereichspruefung
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)
}
# --- Alle Guards bestanden, echte Arbeit beginnen ---
# ...
}
R (rlang/cli — bevorzugt fuer Pakete):
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}}.")
}
# ...
}
Allgemein (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}`);
}
// ...
}
Erwartet: Jede oeffentliche Funktion beginnt mit Guard-Klauseln, die ungueltige Eingaben ablehnen, bevor Nebeneffekte oder Berechnungen beginnen.
Bei Fehler: Falls Validierungslogik lang wird (>15 Zeilen Guards), einen validate_*-Helfer extrahieren oder stopifnot() fuer einfache Typ-Assertions verwenden.
Schritt 3: Aussagekraeftige Fehlermeldungen schreiben
Jede Fehlermeldung sollte vier Fragen beantworten:
- Was scheiterte — welcher Parameter oder welche Operation
- Wo — Funktionsname oder Kontext (automatisch mit
cli::cli_abort) - Warum — was erwartet wurde vs. was erhalten wurde
- Wie zu beheben — wenn die Loesung nicht offensichtlich ist
Gute Meldungen:
# Was + Warum (erwartet vs. tatsaechlich)
stop("'n' must be a positive integer, got: ", n, call. = FALSE)
# Was + Warum + Wie zu beheben
cli::cli_abort(c(
"{.arg config_path} does not exist: {.file {config_path}}",
"i" = "Create it with {.run create_config({.file {config_path}})}."
))
# Was + Kontext
cli::cli_abort(c(
"Column {.val {col_name}} not found in {.arg data}.",
"i" = "Available columns: {.val {names(data)}}"
))
Schlechte Meldungen:
stop("Error") # Was ist gescheitert? Keine Ahnung
stop("Invalid input") # Welche Eingabe? Was ist damit falsch?
stop(paste("Error in step", i)) # Keine handlungsrelevante Information
Erwartet: Fehlermeldungen sind selbstdokumentierend — ein Entwickler, der den Fehler zum ersten Mal sieht, kann ihn ohne Lesen des Quellcodes diagnostizieren und beheben.
Bei Fehler: Die drei juengsten Bug-Reports ueberpruefen. Falls einer das Lesen des Quellcodes erforderte, um ihn zu verstehen, muessen seine Fehlermeldungen verbessert werden.
Schritt 4: stop() gegenueber warning() bevorzugen
stop() (oder cli::cli_abort()) verwenden, wenn die Funktion kein korrektes Ergebnis erzeugen kann. warning() nur verwenden, wenn die Funktion noch ein sinnvolles Ergebnis liefern kann, aber der Aufrufer von einem Problem wissen sollte.
Faustregel: Falls ein Benutzer still eine falsche Antwort erhalten koennte, ist das ein stop(), kein warning().
# RICHTIG: stop wenn Ergebnis falsch waere
read_config <- function(path) {
if (!file.exists(path)) {
stop("Config file not found: ", path, call. = FALSE)
}
yaml::read_yaml(path)
}
# RICHTIG: warnen wenn Ergebnis noch verwendbar ist
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), ]
}
# Mit gueltigen Daten fortfahren
}
Erwartet: stop() wird fuer Bedingungen verwendet, die falsche Ergebnisse erzeugen wuerden; warning() ist fuer degradierte-aber-gueltige Ergebnisse reserviert.
Bei Fehler: Bestehende warning()-Aufrufe pruefen. Falls die Funktion nach der Warnung Unsinn zurueckgibt, auf stop() aendern.
Schritt 5: Assertions fuer interne Invarianten verwenden
Fuer Bedingungen, die "niemals passieren sollten" in korrektem Code, Assertions verwenden. Diese fangen Programmiererfehler waehrend der Entwicklung auf:
# R: stopifnot fuer interne Invarianten
process_chunk <- function(chunk, total_size) {
stopifnot(
is.list(chunk),
length(chunk) > 0,
total_size > 0
)
# ...
}
# R: explizite Assertion mit Kontext
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)
}
# ...
}
Erwartet: Interne Invarianten werden assertiert, damit Bugs sofort an der Verletzungsstelle auftreten, nicht drei Funktionsaufrufe spaeter mit einem kryptischen Fehler.
Bei Fehler: Falls stopifnot()-Meldungen zu kryptisch sind, auf explizites if/stop mit Kontext umstellen.
Schritt 6: Anti-Muster refaktorieren
Diese gaengigen Anti-Muster identifizieren und beheben:
Anti-Muster 1: Leeres tryCatch (Fehler verschlucken)
# VORHER: Fehler verschwindet still
result <- tryCatch(
parse_data(input),
error = function(e) NULL
)
# NACHHER: Protokollieren, neu werfen oder typisierter Fehler
result <- tryCatch(
parse_data(input),
error = function(e) {
cli::cli_abort("Failed to parse input: {e$message}", parent = e)
}
)
Anti-Muster 2: Standardwerte, die schlechte Eingaben verdecken
# VORHER: Aufrufer weiss nie, dass seine Eingabe ignoriert wurde
process <- function(x = 10) {
if (!is.numeric(x)) x <- 10 # ersetzt schlechte Eingabe still
x * 2
}
# NACHHER: Aufrufer ueber das Problem informieren
process <- function(x = 10) {
if (!is.numeric(x)) {
stop("'x' must be numeric, got ", class(x)[[1]], call. = FALSE)
}
x * 2
}
Anti-Muster 3: suppressWarnings als Loesung
# VORHER: Symptom verstecken statt Ursache beheben
result <- suppressWarnings(as.numeric(user_input))
# NACHHER: Explizit validieren, erwarteten Fall behandeln
if (!grepl("^-?\\d+\\.?\\d*$", user_input)) {
stop("Expected a number, got: '", user_input, "'", call. = FALSE)
}
result <- as.numeric(user_input)
Anti-Muster 4: Catch-All-Ausnahmebehandler
# VORHER: Jeder Fehler wird gleich behandelt
tryCatch(
complex_operation(),
error = function(e) message("Something went wrong")
)
# NACHHER: Spezifische Bedingungen behandeln, unerwartete propagieren lassen
tryCatch(
complex_operation(),
custom_validation_error = function(e) {
cli::cli_warn("Validation issue: {e$message}")
fallback_value
}
# Unerwartete Fehler propagieren natuerlich
)
Erwartet: Anti-Muster werden durch explizite Validierung oder spezifische Fehlerbehandlung ersetzt.
Bei Fehler: Falls das Entfernen eines tryCatch kaskadierte Fehler verursacht, hat der Upstream-Code eine Validierungsluecke. Die Quelle beheben, nicht das Symptom.
Schritt 7: Das Fail-Early-Refaktoring validieren
Die Testsuite ausfuehren, um zu bestaetigen, dass Fehlerpfade korrekt funktionieren:
# Fehlermeldungen pruefen ob sie ausgeloest werden
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")
# Pruefen ob gueltige Eingaben noch funktionieren
testthat::expect_no_error(calculate_summary(mtcars, method = "mean"))
# Vollstaendige Testsuite ausfuehren
Rscript -e "devtools::test()"
Erwartet: Alle Tests bestehen. Fehlerpfad-Tests bestaetigen, dass schlechte Eingaben die erwartete Fehlermeldung ausloesen.
Bei Fehler: Falls bestehende Tests auf stillen Fehlern beruhten (z.B. NULL bei schlechter Eingabe zurueckgeben), sie aktualisieren, um den neuen Fehler zu erwarten.
Validierung
- Jede oeffentliche Funktion validiert ihre Eingaben, bevor sie arbeitet
- Fehlermeldungen beantworten: was scheiterte, wo, warum und wie zu beheben
-
stop()wird fuer Bedingungen verwendet, die falsche Ergebnisse erzeugen -
warning()wird nur fuer degradierte-aber-gueltige Ergebnisse verwendet - Keine leeren
tryCatch-Bloecke, die Fehler still verschlucken - Kein
suppressWarnings()als Ersatz fuer ordentliche Validierung - Keine Standardwerte, die ungueltige Eingaben still verdecken
- Interne Invarianten verwenden
stopifnot()oder explizite Assertions - Fehlerpfad-Tests fuer jede Validierungs-Guard existieren
- Testsuite besteht nach Refaktorierung
Haeufige Stolperfallen
-
Zu tief validieren: An Vertrauensgrenzen validieren (oeffentliche API), nicht in jedem internen Helfer. Uebermaessige Validierung fuegt Laerm hinzu und schadet der Performance.
-
Fehlermeldungen ohne Kontext:
"Invalid input"zwingt den Aufrufer zu raten. Immer den Parameternamen, den erwarteten Typ/Bereich und den tatsaechlich erhaltenen Wert einbeziehen. -
warning() verwenden wenn stop() gemeint ist: Falls die Funktion nach der Warnung Unsinn zurueckgibt, erhaelt der Aufrufer still eine falsche Antwort.
stop()verwenden und den Aufrufer entscheiden lassen, wie damit umzugehen ist. -
Fehler in tryCatch verschlucken:
tryCatch(..., error = function(e) NULL)versteckt Bugs. Falls gefangen werden muss, mit hinzugefuegtem Kontext protokollieren oder neu werfen. -
call. = FALSE vergessen: In R schliesst
stop("msg")standardmaessig den Aufruf ein, was fuer Endbenutzer laestig ist. In benutzerseitigen Funktionencall. = FALSEverwenden.cli::cli_abort()macht dies automatisch. -
In Tests statt in Code validieren: Tests pruefen Verhalten, schuetzen aber keine Produktions-Aufrufer. Validierung gehoert in die Funktion selbst.
-
Falsches R-Binary auf Hybrid-Systemen: Unter WSL oder Docker kann
Rscripteinen plattformuebergreifenden Wrapper statt nativem R aufloesen. Mitwhich Rscript && Rscript --versionpruefen. Das native R-Binary bevorzugen (z.B./usr/local/bin/Rscriptunter Linux/WSL) fuer Zuverlaessigkeit. Fuer die R-Pfadkonfiguration siehe Setting Up Your Environment.
Verwandte Skills
write-testthat-tests- Tests schreiben, die Fehlerpfade verifizierenreview-pull-request- Code auf fehlende Validierung und stille Fehler pruefenreview-software-architecture- Fehlerbehandlungsstrategie auf Systemebene beurteilencreate-skill- neue Skills nach dem agentskills.io-Standard erstellensecurity-audit-codebase- sicherheitsfokussierter Review, der sich mit Eingabevalidierung ueberschneidet
GitHub 저장소
연관 스킬
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 에이전트 배포 또는 에이전트 워크플로우 오케스트레이션을 요청받았을 때 사용하세요.
