Un valor de configuración malformado debe registrarse y usar el valor por defecto
Un typo en una variable de entorno acaba de tumbar producción. No fue un mal deploy ni un bug en la lógica de negocio: alguien escribió TIMEOUT_MS=3O00 (esa es una letra O) y la siguiente llamada a int() lanzó una excepción en medio de una petición. Una capa de configuración cuyo propósito es la seguridad nunca debe convertirse en la caída. La solución es una sola regla: un valor de configuración malformado se registra en el log y degrada a un valor por defecto documentado — nunca tumba una petición.
Por qué las lecturas dispersas son una bomba de tiempo
Las variables de entorno son el lugar estándar para la configuración de despliegue — la metodología Twelve-Factor App las recomienda justamente porque cambian entre deploys sin tocar el código. Pero traen una trampa: siempre son strings. process.env de Node incluso convierte a string los valores asignados (env.test = null se lee como 'null'), y en todo lenguaje te toca parsear "3000", "true" o "0.25" por tu cuenta.
Cuando esos parseos están dispersos — un int(os.environ["TIMEOUT_MS"]) aquí, un parseFloat allá — cada punto de lectura es un punto de fallo independiente, y cada uno falla en el peor momento posible: en tiempo de petición, en producción, cuando el valor por fin se lee. Un solo valor inválido puede tumbar un pool de workers que estaba sano.
El mecanismo: un único lector tipado
Pasa cada ajuste por un único lector con cuatro responsabilidades:
- Parsear el string crudo al tipo declarado.
- Validar el resultado: acotar rangos numéricos, rechazar
infyNaN, verificar enums. - Registrar cualquier fallo en nivel
WARNINGcon el nombre de la variable, el valor crudo y el valor por defecto que se usará. - Degradar al valor por defecto documentado y seguir sirviendo.
import logging, math, os
log = logging.getLogger("config")
def read_float(name: str, default: float, lo: float, hi: float) -> float:
raw = os.environ.get(name)
if raw is None:
return default
try:
val = float(raw)
if not math.isfinite(val):
raise ValueError("non-finite")
except ValueError:
log.warning("config %s=%r malformed; using default %s", name, raw, default)
return default
if not (lo <= val <= hi):
log.warning("config %s=%s out of range [%s, %s]; clamping", name, val, lo, hi)
return min(max(val, lo), hi)
return val
RETRY_BACKOFF = read_float("RETRY_BACKOFF", default=0.5, lo=0.05, hi=30.0)
Seis líneas de política, aplicadas en todas partes: parsear, verificar finitud, registrar, degradar, acotar, devolver. La línea WARNING es toda la historia de observabilidad — un ajuste inválido se convierte en algo que buscas con grep por la mañana, no en una alerta a las 3 a.m.
Reglas prácticas
| Situación | Haz esto |
|---|---|
| Valor ausente | Valor por defecto en silencio — que falte es normal, no un error |
Valor malformado ("3O00") | Log WARNING con nombre + valor crudo, usar el valor por defecto |
| Valor fuera de rango | Registrar y acotar al límite más cercano |
inf / NaN en un float | Rechazar como malformado — pasan float() pero envenenan los cálculos |
| Secreto / credencial ausente | Fallar rápido al arrancar — una credencial por defecto es peor que un crash |
| Feature flag imposible de parsear | Tratarlo como apagado — la dirección segura, no la emocionante |
Las dos últimas filas importan: fail-safe no es fail-open. Los valores donde una suposición equivocada es peligrosa (credenciales, toggles de seguridad) deben fallar ruidosamente al arrancar, donde el radio de daño es un deploy fallido — nunca a mitad de una petición.
Trucos avanzados
- Valida al arrancar, tolera al leer. Ejecuta cada lector una vez durante el boot para que los valores malformados aparezcan de inmediato en los logs del deploy — así el fallback en runtime es la segunda red, no la primera.
- Usa una librería de esquemas cuando los ajustes se multiplican.
BaseSettingsde Pydantic lee variables de entorno, aplica validación tipada, valida incluso los valores por defecto y documenta un orden claro de prioridad de fuentes (argumentos de init → env vars → dotenv → secrets → defaults). Una clase se convierte en el lector único. - Haz del valor por defecto parte del contrato. El SRE workbook de Google propone "convertir preguntas obligatorias en opcionales" con defaults que "apliquen de forma segura y efectiva a la mayoría de los usuarios, si no a todos". Documenta el default junto al ajuste; un fallback sin documentar es solo otra sorpresa.
- Cuenta los fallbacks. Emite una métrica junto a la línea de log. Un warning es un typo; mil por minuto significan que tu tooling de deploy está enviando basura.
Recursos
- The Twelve-Factor App — Config
- Docs de Node.js — coerción a string en
process.env - Pydantic Settings — configuración tipada desde env vars, con defaults validados
- Google SRE Workbook — Configuration Design and Best Practices
loggingde Python —Logger.warning
¿Construyes sistemas de producción donde un ajuste inválido no debería convertirse en un incidente? Yeda AI los diseña, audita y entrega.