Yeda AI Tips · #106

English

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:

  1. Parsear el string crudo al tipo declarado.
  2. Validar el resultado: acotar rangos numéricos, rechazar inf y NaN, verificar enums.
  3. Registrar cualquier fallo en nivel WARNING con el nombre de la variable, el valor crudo y el valor por defecto que se usará.
  4. 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ónHaz esto
Valor ausenteValor 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 rangoRegistrar y acotar al límite más cercano
inf / NaN en un floatRechazar como malformado — pasan float() pero envenenan los cálculos
Secreto / credencial ausenteFallar rápido al arrancar — una credencial por defecto es peor que un crash
Feature flag imposible de parsearTratarlo 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

Recursos

Read this article in English

¿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.

Habla con nosotros · Lee el blog