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 default | La intención | Lo que provocaba |
|---|---|---|
| Categoría → una fija | Que la vista siempre tenga algo que mostrar | Ofrecer lo equivocado y ensuciar la métrica |
| Saldo de puntos → un número fijo | Que la tarjeta nunca quede vacía | Prometer un premio inventado cuando el servicio falla |
| País → uno fijo | Formatear fechas sin romper | Fechas y formatos equivocados para el resto de los países |
| Marca desconocida → sitio principal | Que siempre haya un link válido | Mandar al usuario de una marca asociada al sitio de otra empresa |
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
- 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.
- 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.
- 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. - 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í
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
- Que tenga nombre.
DEFAULT_SEARCH_WINDOW_DAYS = 30en vez de un30en 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. - 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.
- 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
Hay 5 problemas en este cambio. Tocá la línea donde creas que está.
| 12 | 12 | ||
| 13 | 13 | ||
| 14 | |||
| 14 | |||
| 15 | |||
BloqueaLa categoría inventada viaja a la analítica Cuando el servicio no manda la categoría, el usuario ve «general» y el evento de analítica registra «general». El equipo que mira las métricas ve demanda de una categoría que nadie eligió, y decide sobre eso. Un default de presentación nunca tiene que entrar a un evento: ahí va el valor real o nada. | |||
| 16 | |||
BloqueaUn precio de 0 es un precio válido Poner 0 cuando el precio no llegó es indistinguible de un producto gratis. Y si en algún momento aparece un botón de compra, es un producto que se puede comprar a cero. Cuando el dato falta, la tarjeta no puede mostrar un precio: tiene que mostrar que no lo sabe. | |||
| 17 | |||
SugerenciaEl operador elegido convierte un 0 legítimo en 0 igual, y esconde otra cosa Con || cualquier valor falso cae al default, así que stock 0 y stock ausente terminan iguales. Acá el número coincide y el significado no: uno es «sin stock» y el otro es «no sé». Con ?? se distinguen, y eso hace visible que faltaba decidir qué mostrar en cada caso. | |||
| 18 | |||
| 19 | |||
| 15 | 20 | ||
| 16 | 21 | ||
| 17 | |||
| 22 | |||
PreguntaUn nombre por defecto es el que menos molesta y el que más confunde Mostrar «Producto» es defendible si la tarjeta tiene que seguir en pie. Lo que hay que decidir explícitamente es si esta tarjeta debería existir cuando no hay datos, o si corresponde no renderizarla y reportar el caso. | |||
| 18 | 23 | ||
| 19 | 24 | ||
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?
Práctica
BloqueaEl objeto vacío apaga todos los errores de golpe
Con ?? {} ninguna lectura de abajo explota, y por eso ninguna avisa. Una respuesta sin producto deja de ser un caso de error y pasa a ser una tarjeta con datos inventados que se ve perfecta. Es el default más caro de todos: el que garantiza que nadie se entere.