TypeScript
Qualidade de Software
Arquitetura
Boas Práticas
Liderança Técnica

TypeScript avanzado en la práctica: lo que separa el uso superficial del uso maduro

TypeScript maduro no se trata de escribir más tipos, se trata de dejar que el compilador trabaje a su favor.

Adoptar TypeScript es fácil. Usarlo bien es raro. La diferencia entre las dos cosas no aparece el primer día, aparece seis meses después, cuando el equipo necesita refactorizar y descubrir si los tipos son aliados o decoración.

Hay una superficie TypeScript, en la que todo está anotado manualmente, any aparece cuando las cosas se ponen difíciles y el compilador solo se usa para autocompletar nombres. Y existe un TypeScript maduro, en el que los tipos realmente modelan el problema y el compilador se convierte en un revisor incansable que detecta errores antes de que se ejecute el código.

Este texto trata sobre el segundo. No como un tutorial, sino como un argumento sobre el valor de cada recurso para la salud del código y del equipo.

Inferencia: escribe menos para asegurar más

El primer instinto de cualquiera que llega a TypeScript es escribirlo todo. Cada variable, cada retorno, cada parámetro recibe un tipo escrito a mano. Esto parece celoso y en la práctica es todo lo contrario.

El exceso de anotaciones manuales genera ruido y, peor aún, mentiras. El tipo escrito a mano puede diferir del valor real y luego tiene una anotación que documenta una intención que el código ya no cumple. La inferencia no miente: deriva el tipo del valor del hecho.

El uso maduro confía en la inferencia cuando es confiable y reserva anotaciones donde importa, que son los límites. Firma de función pública, contrato de módulo, formato de datos que ingresa al borde del sistema. En esencia, deje que el compilador infiera. Menos código, menos desacuerdo, más verdad.

Genéricos: la diferencia entre reutilizar tipos y perderlos

Los genéricos suelen dar miedo porque la sintaxis parece académica. El concepto es simple y profundamente práctico: es cómo escribir algo reutilizable sin desechar información tipográfica en el camino.

Sin genéricos, la salida más fácil para una función que funciona con cualquier cosa es escribir la entrada y la salida como demasiado genéricas, y el efecto es que el tipo se pierde. Quien llama a la función obtiene un valor informe y tiene que adivinar qué hacer con él.

Con los genéricos, se preserva la relación entre entrada y salida. Una función que recibe una lista de algo devuelve ese mismo algo, y el compilador lo sabe en el momento de la llamada. Este es el corazón de la reutilización segura: abstraer comportamiento sin abstraer información. Los equipos que dominan los genéricos escriben menos utilidades duplicadas y obtienen el autocompletado correcto en todas ellas.

Tipos de utilidad: reutilizar formato sin repetir formato

Cada sistema acumula tipos que son variaciones de otros tipos. La versión parcial de un objeto para un formulario, la versión sin el campo de contraseña para enviar al cliente, la versión de solo lectura de una configuración.

El enfoque ingenuo es redeclarar cada variación a mano. El problema aparece cuando cambia el tipo original: ahora hay cinco copias para actualizar y no hay garantía de que las recuerdes todas. Es la misma trampa que el código duplicado, solo que en formato tipográfico.

Los tipos utilitarios resuelven esto derivando una forma de otra. Cuando el tipo base cambia, las variaciones siguen solas, porque fueron definidas en base a él. Esto convierte los tipos en un único punto de verdad y elimina esa clase sutil de error en la que el código y su tipo se han actualizado a diferentes velocidades.

Estrechamiento: enséñele al compilador a razonar con usted

Quizás la característica más subestimada es la reducción, que es la capacidad de TypeScript para limitar un tipo a medida que avanza el flujo de código. Comprueba si existe un valor y, dentro de ese bloque, el compilador comienza a tratarlo como si existiera.

Esto cambia la forma en que el equipo maneja el caso límite. En lugar de esparcir comprobaciones defensivas por todas partes y esperar, modela los posibles estados y deja que el compilador exija que se maneje cada uno de ellos. El valor puede ser una cosa u otra, y el código sólo se compila cuando se han recorrido ambos caminos.

El efecto cultural de esto es grande. Lo nulo deja de ser una sorpresa de producción y se convierte en un requisito para el tiempo de escritura. El equipo deja de descubrir que olvidaron un caso cuando el cliente se queja y comienza a descubrirlo cuando el editor se queja. Es la diferencia entre un error y un recordatorio.

La guerra contra cualquier

any es la válvula de escape de TypeScript, y como cualquier válvula de escape, es necesaria en dosis mínimas y destructiva en dosis normales. Un any no simplemente desactiva el tipo de esa variable. Contamina todo lo que toca, porque cualquier cosa derivada de un any también se convierte en un any.

Una base con any repartidos tiene lo peor de ambos mundos: paga el coste de configuración y mantenimiento de TypeScript, pero pierde las garantías precisamente en los puntos más riesgosos, que es donde alguien no supo teclear y desistió.

Los equipos maduros tratan el any como una señal de advertencia, no como una solución. Cuando el tipo es realmente desconocido, existe la opción segura de marcarlo como desconocido y forzar una verificación antes de usarlo en lugar de revelarlo todo. La regla general es sencilla: any debería ser una rara excepción, justificada y visible en la revisión, nunca la solución predeterminada a un problema.

Modelando el dominio: el salto que más paga

El nivel más alto de uso de TypeScript] no es técnico, es diseño. Se utilizan tipos para describir reglas comerciales de tal manera que el estado no válido simplemente no puede existir.

Un pedido que puede estar pagado o pendiente, pero nunca ambas cosas. Un usuario que al ser invitado aún no tiene ciertos datos, y al estar activo necesariamente los tiene. Cuando modela estos estados como tipos distintos, el compilador evita combinaciones imposibles antes de ejecutar cualquier prueba.

Esto cambia la discusión del equipo. En lugar de "¿puede este campo estar vacío aquí?", la respuesta está escrita, es explícita y verificable. El dominio está documentado en el código que se ejecuta, no en un documento que nadie actualiza. Para el borde del sistema, donde los datos externos llegan sin ninguna garantía, vale la pena combinar esto con la validación en tiempo de ejecución, y el camino natural es validación de datos con Zod, que conecta los datos reales al tipo.

Suma todo y la devolución no es estética, es operativa. Menos errores que llegan a producción, porque el compilador los detectó antes. Refactorización que el equipo afronta sin miedo, porque romper algo aparece inmediatamente. Documentación que no envejece, porque es el código mismo.

Mature TypeScript no se trata de escribir más tipos. Se trata de escribir los tipos correctos en los lugares correctos y dejar que el compilador haga el aburrido trabajo de verificar la coherencia, que es precisamente el trabajo que los humanos hacen mal y las máquinas hacen bien.

Si su equipo ya usa TypeScript pero aún trata los tipos como burocracia, el siguiente paso es elevar el nivel de revisión del código y tratar la calidad de los tipos como parte de la calidad del código. Para ver cómo estos conceptos se conectan en una arquitectura más amplia, vale la pena dirigirse a monorepos con TypeScript.

Lea también