executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
fail-early-patternAplica el patrón de fallo temprano (fail-fast) para detectar y reportar errores en el punto más temprano posible. Cubre la validación de entradas con cláusulas de guarda, mensajes de error significativos, funciones de aserción y anti-patrones que silenciosamente consumen fallos. Ejemplos principales en R con orientación general/polígota. Usar al escribir fu…
Independent GitHub snapshot · Automated classification · Original documentation governs
Si algo va a fallar, debe fallar lo antes posible, lo más ruidosamente posible, con tanto contexto como sea posible. Esta habilidad codifica el patrón de fallo temprano: validar entradas en los límites del sistema, usar cláusulas de guarda para rechazar estados inválidos antes de que se propaguen, y escribir mensajes de error que respondan qué falló, dónde, por qué y cómo solucionarlo.
Mapear dónde los datos externos entran al sistema. Estos son los puntos que necesitan validación:
Las funciones auxiliares internas llamadas solo por tu propio código validado generalmente no necesitan validación redundante.
Esperado: Una lista de puntos de entrada donde los datos no confiables cruzan hacia tu código.
En caso de fallo: Si los límites no están claros, rastrear hacia atrás desde los errores en los registros o informes de errores para encontrar dónde entraron primero los datos incorrectos.
Validar las entradas al inicio de cada función pública, antes de que comience cualquier trabajo.
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 — preferido para paquetes):
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}}.")
}
# ...
}
General (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}`);
}
// ...
}
Esperado: Cada función pública abre con cláusulas de guarda que rechazan entradas inválidas antes de cualquier efecto secundario o cálculo.
En caso de fallo: Si la lógica de validación se alarga (>15 líneas de guardas), extraer un auxiliar validate_* o usar stopifnot() para aserciones de tipo simples.
Cada mensaje de error debe responder cuatro preguntas:
cli::cli_abort)Buenos mensajes:
# 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)}}"
))
Malos mensajes:
stop("Error") # What failed? No idea
stop("Invalid input") # Which input? What's wrong with it?
stop(paste("Error in step", i)) # No actionable information
Esperado: Los mensajes de error se documentan por sí mismos — un desarrollador que ve el error por primera vez puede diagnosticarlo y solucionarlo sin leer el código fuente.
En caso de fallo: Revisar los tres informes de errores más recientes. Si alguno requirió leer el código fuente para entenderlo, sus mensajes de error necesitan mejoras.
Usar stop() (o cli::cli_abort()) cuando la función no puede producir un resultado correcto. Usar warning() solo cuando la función aún puede producir un resultado significativo pero el llamador debe saber sobre una preocupación.
Regla general: Si un usuario pudiera obtener silenciosamente una respuesta incorrecta, eso es un stop(), no un 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
}
Esperado: stop() se usa para condiciones que producirían resultados incorrectos; warning() se reserva para resultados degradados pero válidos.
En caso de fallo: Auditar las llamadas a warning() existentes. Si la función devuelve algo sin sentido después de la advertencia, cambiarlo a stop().
Para condiciones que "nunca deben suceder" en código correcto, usar aserciones. Estas detectan errores del programador durante el desarrollo:
# 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)
}
# ...
}
Esperado: Los invariantes internos se afirman para que los errores surjan inmediatamente en el sitio de violación, no tres llamadas de función después con un error críptico.
En caso de fallo: Si los mensajes de stopifnot() son demasiado crípticos, cambiar a if/stop explícito con contexto.
Identificar y corregir estos anti-patrones comunes:
Anti-patrón 1: tryCatch vacío (consumir errores silenciosamente)
# 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)
}
)
Anti-patrón 2: Valores por defecto enmascarando entradas incorrectas
# 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
}
Anti-patrón 3: suppressWarnings como solución
# 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*quot;, user_input)) {
stop("Expected a number, got: '", user_input, "'", call. = FALSE)
}
result <- as.numeric(user_input)
Anti-patrón 4: Manejadores de excepción que capturan todo
# 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
)
Esperado: Los anti-patrones se reemplazan con validación explícita o manejo de errores específico.
En caso de fallo: Si eliminar un tryCatch causa fallos en cascada, el código aguas arriba tiene una brecha de validación. Corregir la fuente, no el síntoma.
Ejecutar el conjunto de pruebas para confirmar que las rutas de error funcionan correctamente:
# 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()"
Esperado: Todas las pruebas pasan. Las pruebas de ruta de error confirman que las entradas incorrectas desencadenan el mensaje de error esperado.
En caso de fallo: Si las pruebas existentes dependían de fallos silenciosos (p.ej., devolver NULL en entrada incorrecta), actualizarlas para esperar el nuevo error.
stop() se usa para condiciones que producen resultados incorrectoswarning() se usa solo para resultados degradados pero válidostryCatch vacíos que consumen errores silenciosamentesuppressWarnings() usado como sustituto de la validación adecuadastopifnot() o aserciones explícitasValidar demasiado profundo: Validar en los límites de confianza (API pública), no en cada auxiliar interno. La sobre-validación añade ruido y perjudica el rendimiento.
Mensajes de error sin contexto: "Invalid input" obliga al llamador a adivinar. Incluir siempre el nombre del parámetro, el tipo/rango esperado y el valor real recibido.
Usar warning() cuando se quiere decir stop(): Si la función devuelve basura después de la advertencia, el llamador obtiene una respuesta incorrecta silenciosamente. Usar stop() y dejar que el llamador decida cómo manejarlo.
Consumir errores en tryCatch: tryCatch(..., error = function(e) NULL) oculta errores. Si debes capturar, registrar o relanzar con contexto añadido.
Olvidar call. = FALSE: En R, stop("msg") incluye la llamada por defecto, lo que es ruidoso para los usuarios finales. Usar call. = FALSE en funciones de cara al usuario. cli::cli_abort() hace esto automáticamente.
Validar en pruebas en lugar de en código: Las pruebas verifican el comportamiento pero no protegen a los llamadores en producción. La validación pertenece a la función misma.
Binario R incorrecto en sistemas híbridos: En WSL o Docker, Rscript puede resolverse a un contenedor multiplataforma en lugar de R nativo. Comprueba con which Rscript && Rscript --version. Prefiere el binario R nativo (p. ej., /usr/local/bin/Rscript en Linux/WSL) para mayor fiabilidad. Consulta Setting Up Your Environment para la configuración de la ruta de R.
write-testthat-tests - escribir pruebas que verifiquen las rutas de errorreview-pull-request - revisar código para detectar validación faltante y fallos silenciososreview-software-architecture - evaluar la estrategia de manejo de errores a nivel de sistemacreate-skill - crear nuevas habilidades siguiendo el estándar agentskills.iosecurity-audit-codebase - revisión orientada a la seguridad que se superpone con la validación de entradas