MCP HubMCP Hub
Volver a habilidades

fail-early-pattern

pjt222
Actualizado Yesterday
4 vistas
17
2
17
Ver en GitHub
Desarrolloaiapi

Acerca de

Esta habilidad ayuda a los desarrolladores a implementar el patrón de fallo rápido mediante la validación temprana de entradas y la provisión de mensajes de error significativos. Se centra en el uso de cláusulas de guarda, funciones de aserción y en evitar fallos silenciosos, incluyendo ejemplos en lenguaje R y orientación multilingüe. Úsela al escribir funciones que acepten entradas externas, al refactorizar código propenso a errores o al fortalecer la validación de parámetros en APIs.

Instalación rápida

Claude Code

Recomendado
Principal
npx skills add pjt222/agent-almanac -a claude-code
Comando PluginAlternativo
/plugin add https://github.com/pjt222/agent-almanac
Git CloneAlternativo
git clone https://github.com/pjt222/agent-almanac.git ~/.claude/skills/fail-early-pattern

Copia y pega este comando en Claude Code para instalar esta habilidad

Documentación

尽早失败

如果某件事会失败,它应该尽早失败、尽可能响亮地失败、携带尽可能多的上下文。此技能固化了尽早失败模式:在系统边界验证输入,使用守卫子句在错误状态传播之前拒绝它,以及编写能回答什么失败了、在哪里为什么以及如何修复的错误消息。

适用场景

  • 编写或审查接受外部输入(用户数据、API 响应、文件内容)的函数
  • 在 CRAN 提交前为包函数添加输入验证
  • 重构静默产生错误结果而非报错的代码
  • 审查 pull request 的错误处理质量
  • 强化内部 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) {
  # 守卫:类型检查
  if (!is.data.frame(data)) {
    stop("'data' must be a data frame, not ", class(data)[[1]], call. = FALSE)
  }
  # 守卫:非空
  if (nrow(data) == 0L) {
    stop("'data' must have at least one row", call. = FALSE)
  }
  # 守卫:参数匹配
  method <- match.arg(method)
  # 守卫:范围检查
  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)
  }
  # --- 所有守卫通过,开始实际工作 ---
  # ...
}

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 步:编写有意义的错误消息

每条错误消息应回答四个问题:

  1. 什么失败了——哪个参数或操作
  2. 在哪里——函数名或上下文(使用 cli::cli_abort 自动提供)
  3. 为什么——期望的是什么,实际收到了什么
  4. 如何修复——当修复方法不明显时

好的消息:

# 什么 + 为什么(期望 vs. 实际)
stop("'n' must be a positive integer, got: ", n, call. = FALSE)

# 什么 + 为什么 + 如何修复
cli::cli_abort(c(
  "{.arg config_path} does not exist: {.file {config_path}}",
  "i" = "Create it with {.run create_config({.file {config_path}})}."
))

# 什么 + 上下文
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)) # 没有可操作的信息

预期结果: 错误消息是自文档化的——第一次看到错误的开发者无需阅读源代码就能诊断和修复。

失败处理: 审查最近三个错误报告。若有任何一个需要阅读源代码才能理解,其错误消息需要改进。

第 4 步:优先使用 stop() 而非 warning()

当函数无法产生正确结果时使用 stop()(或 cli::cli_abort())。仅当函数仍能产生有意义的结果但调用者应了解某问题时使用 warning()

经验法则: 若用户可能静默地得到错误答案,那就是 stop(),而非 warning()

# 正确:结果会错时使用 stop
read_config <- function(path) {
  if (!file.exists(path)) {
    stop("Config file not found: ", path, call. = FALSE)
  }
  yaml::read_yaml(path)
}

# 正确:结果仍可用时使用 warn
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), ]
  }
  # 继续处理有效数据
}

预期结果: stop() 用于会产生错误结果的条件;warning() 保留用于降级但有效的结果。

失败处理: 审计现有 warning() 调用。若函数在警告后返回无意义结果,改为 stop()

第 5 步:使用断言处理内部不变量

对于"正确代码中不应该发生"的条件,使用断言。这些可以在开发期间捕获程序员错误:

# R:使用 stopifnot 处理内部不变量
process_chunk <- function(chunk, total_size) {
  stopifnot(
    is.list(chunk),
    length(chunk) > 0,
    total_size > 0
  )
  # ...
}

# R:带上下文的显式断言
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)
  }
  # ...
}

预期结果: 内部不变量被断言,这样 bug 能在违规位置立即浮现,而非三个函数调用之后出现神秘错误。

失败处理:stopifnot() 消息太隐晦,改为带上下文的显式 if/stop

第 6 步:重构反模式

识别并修复以下常见反模式:

反模式 1:空的 tryCatch(吞噬错误)

# 之前:错误静默消失
result <- tryCatch(
  parse_data(input),
  error = function(e) NULL
)

# 之后:记录日志、重新抛出或返回类型化错误
result <- tryCatch(
  parse_data(input),
  error = function(e) {
    cli::cli_abort("Failed to parse input: {e$message}", parent = e)
  }
)

反模式 2:用默认值掩盖错误输入

# 之前:调用者永远不知道其输入被忽略了
process <- function(x = 10) {
  if (!is.numeric(x)) x <- 10  # 静默替换错误输入
  x * 2
}

# 之后:告知调用者问题所在
process <- function(x = 10) {
  if (!is.numeric(x)) {
    stop("'x' must be numeric, got ", class(x)[[1]], call. = FALSE)
  }
  x * 2
}

反模式 3:将 suppressWarnings 当作修复方法

# 之前:掩盖症状而不是修复原因
result <- suppressWarnings(as.numeric(user_input))

# 之后:显式验证,处理预期情况
if (!grepl("^-?\\d+\\.?\\d*$", user_input)) {
  stop("Expected a number, got: '", user_input, "'", call. = FALSE)
}
result <- as.numeric(user_input)

反模式 4:通用异常处理器

# 之前:所有错误都一样处理
tryCatch(
  complex_operation(),
  error = function(e) message("Something went wrong")
)

# 之后:处理特定条件,让意外的传播
tryCatch(
  complex_operation(),
  custom_validation_error = function(e) {
    cli::cli_warn("Validation issue: {e$message}")
    fallback_value
  }
  # 意外错误自然传播
)

预期结果: 反模式被替换为显式验证或特定错误处理。

失败处理: 若删除 tryCatch 导致级联失败,上游代码存在验证缺口。修复源头,而非症状。

第 7 步:验证尽早失败重构

运行测试套件以确认错误路径正常工作:

# 验证错误消息被触发
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")

# 验证有效输入仍然正常工作
testthat::expect_no_error(calculate_summary(mtcars, method = "mean"))
# 运行完整测试套件
Rscript -e "devtools::test()"

预期结果: 所有测试通过。错误路径测试确认错误输入触发了预期的错误消息。

失败处理: 若现有测试依赖于静默失败(例如对错误输入返回 NULL),更新它们以期待新的错误。

验证清单

  • 每个公共函数在开始工作前验证其输入
  • 错误消息回答:什么失败了、在哪里、为什么、以及如何修复
  • stop() 用于会产生错误结果的条件
  • warning() 仅用于降级但有效的结果
  • 无空的 tryCatch 块静默吞噬错误
  • 无将 suppressWarnings() 用作适当验证替代品的情况
  • 无静默掩盖无效输入的默认值
  • 内部不变量使用 stopifnot() 或显式断言
  • 每个验证守卫都有对应的错误路径测试
  • 重构后测试套件通过

常见问题

  • 验证太深:在信任边界(公共 API)验证,而不是在每个内部辅助函数中。过度验证增加噪音,影响性能。

  • 缺少上下文的错误消息"Invalid input" 迫使调用者猜测。始终包含参数名称、期望的类型/范围和实际收到的值。

  • 用 warning() 代替 stop():若函数在警告后返回无意义结果,调用者会静默得到错误答案。使用 stop() 让调用者决定如何处理。

  • 在 tryCatch 中吞噬错误tryCatch(..., error = function(e) NULL) 隐藏 bug。若必须捕获,记录日志或重新抛出并添加上下文。

  • 忘记 call. = FALSE:在 R 中,stop("msg") 默认包含调用信息,对最终用户来说很嘈杂。在面向用户的函数中使用 call. = FALSEcli::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 - 与输入验证重叠的安全审查

Repositorio GitHub

pjt222/agent-almanac
Ruta: i18n/zh-CN/skills/fail-early-pattern
0
agentsagentskillsai-assisted-developmentclaude-codeskillsteams

Habilidades relacionadas

qmd

Desarrollo

qmd es una herramienta CLI de búsqueda e indexación local que permite a los desarrolladores indexar y buscar en archivos locales mediante búsqueda híbrida que combina BM25, embeddings vectoriales y reranking. Es compatible tanto con uso desde la línea de comandos como con modo MCP (Model Context Protocol) para integración con Claude. La herramienta utiliza Ollama para los embeddings y almacena los índices localmente, lo que la hace ideal para buscar documentación o bases de código directamente desde la terminal.

Ver habilidad

subagent-driven-development

Desarrollo

Esta habilidad ejecuta planes de implementación asignando un nuevo subagente para cada tarea independiente, con revisión de código entre tareas. Permite una iteración rápida mientras mantiene controles de calidad a través de este proceso de revisión. Úsala cuando trabajes en tareas mayormente independientes dentro de la misma sesión para garantizar un progreso continuo con verificaciones de calidad integradas.

Ver habilidad

mcporter

Desarrollo

La habilidad mcporter permite a los desarrolladores gestionar y llamar servidores del Protocolo de Contexto de Modelo (MCP) directamente desde Claude. Proporciona comandos para listar servidores disponibles, llamar a sus herramientas con argumentos, y manejar la autenticación y el ciclo de vida del daemon. Utiliza esta habilidad para integrar y probar la funcionalidad de servidores MCP en tu flujo de trabajo de desarrollo.

Ver habilidad

adk-deployment-specialist

Desarrollo

Esta habilidad despliega y orquesta agentes Vertex AI ADK utilizando el protocolo A2A, gestionando el descubrimiento de AgentCard, el envío de tareas y soportando herramientas como el Sandbox de Ejecución de Código y el Banco de Memoria. Permite construir sistemas multiagente con patrones de orquestación secuencial, paralela o en bucle en Python, Java o Go. Úsela cuando se le solicite desplegar agentes ADK u orquestar flujos de trabajo de agentes en Google Cloud.

Ver habilidad