Atlasingeniería

Code review en la prácticaCambios de comportamiento que el diff no muestraTema 2

Defaults silenciosos: el valor «por las dudas» que tapa un bug

Defaultear a una categoría, a un país o a un saldo inventado parece defensivo, pero convierte una falla ruidosa en una mentira consistente. Cuatro casos reales de review y el criterio para decidir entre un valor por defecto, no hacer nada o fallar.

Hay una línea que aparece en todos los repositorios y que nadie escribe con mala intención:

const category = response.category || FALLBACK_CATEGORY;

Se pone para que la pantalla no se rompa si el dato no llega. Y funciona: la pantalla no se rompe. Lo que pasa es que ahora, cuando el dato no llega, el usuario ve una categoría que nadie pidió, el evento de analítica registra esa categoría, y el equipo que mira las métricas ve una demanda que no existe.

Cuatro defaults reales y qué escondía cada uno

Todos estos son comentarios que recibí en revisiones, sobre código mío, en una aplicación con varias marcas y países:

El defaultLa intenciónLo que provocaba
Categoría → una fijaQue la vista siempre tenga algo que mostrarOfrecer lo equivocado y ensuciar la métrica
Saldo de puntos → un número fijoQue la tarjeta nunca quede vacíaPrometer un premio inventado cuando el servicio falla
País → uno fijoFormatear fechas sin romperFechas y formatos equivocados para el resto de los países
Marca desconocida → sitio principalQue siempre haya un link válidoMandar al usuario de una marca asociada al sitio de otra empresa
En los cuatro casos el sistema sigue funcionando. Eso es exactamente el problema.

El comentario que más me marcó fue el más corto: «¿por qué ponemos un valor por defecto? Si no tiene puntos, no hacemos nada.» Ahí está todo el criterio. El default no es la única alternativa a romperse: casi siempre existe la opción de no mostrar nada, que es honesta y no requiere inventar.

El árbol de decisión

Ante un dato que puede no venir, hay cuatro salidas posibles, y son cuatro decisiones distintas de producto, no de código.

Qué hacer cuando el dato falta

  1. Fallar fuerte, si seguir sin ese dato produce algo incorrecto que el usuario no puede detectar: un precio, un total, una moneda, un identificador de cuenta.
  2. No mostrar nada, si la funcionalidad es un agregado: una tarjeta de puntos, un cartel, una recomendación. Es la opción más subestimada y casi siempre la correcta en la interfaz.
  3. Usar un default declarado, si existe un valor de negocio legítimo y documentado. La prueba es que alguien de producto pueda defenderlo en una frase, y que quede en una constante con nombre, no en un || en medio de la función.
  4. Pedir el dato explícitamente, si la función siempre lo necesita: que sea un parámetro obligatorio y que el problema aparezca en compilación, no en producción.

Antes de seguir, predecí

Una pantalla de puntos recibe una respuesta vacía del servicio. ¿Qué conviene hacer?

Cómo se escribe un default defendible

Cuando la decisión es legítimamente usar un valor por defecto, la forma importa tanto como el valor.

Tres condiciones

  1. Que tenga nombre. DEFAULT_SEARCH_WINDOW_DAYS = 30 en vez de un 30 en medio de una condición. El nombre es lo que permite buscarlo cuando haya que cambiarlo, y lo que evita que se escriban tres defaults distintos para lo mismo.
  2. Que esté en un solo lugar. El mismo valor decidido en el front y en el back se separa en cuanto alguien cambie uno. El default vive donde vive la lógica de negocio: el backend.
  3. Que se pueda distinguir del valor real. Si el valor por defecto termina en una métrica o en un log, que el registro diga que fue un default. Es la diferencia entre investigar un incidente con datos y hacerlo con datos inventados.
Más a fondo · nivel seniorFallar rápido no siempre es fallar al usuario

«Fallar fuerte» no significa mostrar una pantalla de error. Significa que el problema sea visible para quien puede arreglarlo: una excepción capturada arriba, un registro con nivel de error, una alerta. El usuario puede ver simplemente una pantalla sin esa sección. La diferencia con el default silencioso es que alguien se entera.

Encontrar los defaults en un diff

src/products/product-card.tsx+7−2

Hay 5 problemas en este cambio. Tocá la línea donde creas que está.

@@ -12,8 +12,16 @@ export const ProductCard = ({ response }: Props) => {
1212
1313
14
14
15
16
17
18
19
1520
1621
17
22
1823
1924

Cinco líneas, cinco decisiones que nadie tomó a propósito, y ningún error en ningún lado.

Cuándo un default está bien

Cierre

Autoevaluación

¿Lo entendiste?

¿Cuál es el principal costo de un default silencioso?
Llega un identificador de marca que no está en la lista. ¿Qué es más seguro?
¿Cuándo es defendible un valor por defecto?