El guard que nunca se disparó

Durante dos versiones, @nestjslatam/ddd-lib tuvo una validación que no validaba nada. Compilaba, los tests pasaban en verde, y la causa cabe en una línea.

El cambio

// 2.x
public isValid(): boolean { ... }

// 3.x
public get isValid(): boolean { ... }

Un cambio razonable. isValid describe un estado, no una acción, y un getter lo dice mejor. Estaba anunciado en el CHANGELOG como cambio incompatible.

El código que quedó

if (!name.isValid) {          // ← sobre 2.x, esto evalúa la FUNCIÓN
  throw new BrokenRulesException(...);
}

En 2.x, name.isValid es una referencia a función. Una función es siempre truthy. Así que !name.isValid es siempre false, y el cuerpo del if no se ejecuta jamás.

La fábrica devuelve todos los objetos que le pidas, válidos o no. Sin error. Sin aviso. Sin nada que aparezca en un log.

Por qué no lo cogió el compilador

Porque !fn es TypeScript perfectamente legal. Negar una función da un booleano; el tipo es correcto. El compilador no tiene forma de saber que querías negar el resultado de llamarla.

Con strictNullChecks tampoco salta: no hay nada nulo. Con ESLint tampoco, salvo que tengas activada @typescript-eslint/no-unnecessary-condition, que requiere información de tipos y casi nadie enciende porque es lenta.

La simetría del error

EscribesSobre 2.x (método)Sobre 3.x (getter)
if (!obj.isValid)Nunca se dispara, en silencioCorrecto
if (!obj.isValid())CorrectoTypeError al ejecutar

La migración de 2.x a 3.x rompe ruidosamente: intentas invocar un booleano y revienta. Eso es bueno — un fallo escandaloso es un fallo que se arregla.

La dirección peligrosa es la otra: código escrito con sintaxis de getter corriendo sobre una versión de método. Ahí no revienta nada. Simplemente deja de validar.

Qué hicimos

Un validate que lee tus tipos instalados. npx ddd validate no lleva una tabla de compatibilidad; parsea los .d.ts de tu node_modules y comprueba tu código contra la API que realmente tienes.

Convertimos el README en un test. El ejemplo de inicio rápido es ahora un fichero .spec.ts que corre en CI. Si deja de compilar contra la versión publicada, el build se pone rojo antes de que nadie lo copie.

Lo que nos llevamos

Un booleano y una función que devuelve booleano son intercambiables en un contexto de verdad, y ésa es la trampa. El lenguaje no distingue «el valor» de «la cosa que produce el valor» cuando lo único que haces es preguntar si es cierto.

Cada vez que un cambio de API convierte un método en propiedad —o al revés— estás creando esta trampa para todos tus consumidores. Merece una regla de lint, un validate, o como mínimo un párrafo en mayúsculas en el CHANGELOG.

Leave a Comment