Guía: Cuándo usar TypeScript en tus desarrollos web
José M. Alarcón 🌐
Posted on September 11, 2020
Artículo original publicado en el blog de campusMVP
Abróchate el cinturón. En esta guía, comparamos cuándo es absolutamente vital utilizar TypeScript, el lenguaje de programación de tipado estricto, y cuándo tiene sentido atenerse a JavaScript "puro".
Nota : este artículo es una traducción del artículo When To Use TypeScript - A Detailed Guide Through Common Scenarios de Khalil Stemmler traducido con su permiso expreso. Puedes seguir las discusiones que se generaron sobre el mismo en Hackernews y en Reddit.
¿Has oído hablar de ese pequeño lenguaje de programación llamado TypeScript? Ya sabes, el que hizo Microsoft... ¿El que está explotando en popularidad?
Tal vez seas como yo, un verdadero purista de JavaScript. Me iba bien con React y Node sin tipos. Los Prop Types y la validación Joi me han tratado muy bien, gracias.
Tal vez cediste ante TypeScript y le diste una oportunidad. Empezaste a trastear con él. Tal vez lo llegaste a odiar porque te recordaba a Java. Tal vez te irritaste por no poder lograr mucha productividad desde el primer momento.
Estas fueron algunas de mis sensaciones iniciales cuando empecé con TypeScript.
En realidad no le vi las ventajas... hasta que empecé a sufrir cosas muy molestas. Cosas como que los "builds" no fallaban cuando debían, código con errores y faltas tipográficas que se abrían paso de alguna manera en el código de producción y que empezaron a afectarme. Además, a medida que las exigencias de mi proyecto comenzaban a hacerse más complejas, me resultaba cada vez más difícil plasmar mis diseños de una manera realmente orientada a objetos limpia y clara.
9 meses después de usar TypeScript, he creado nuevas funcionalidades en aplicaciones Angular para mis clientes, he comenzado a compilar el Front-End React/Redux de Univjobs con TypeScript, y he portado todos los servicios de backend de Univjobs a TypeScript desde Node.js con JavaScript "puro", refactorizando de paso enormes cantidades de código durante el proceso.
En este artículo, echaremos un vistazo a algunos de los escenarios más comunes e identificaremos cuándo podría ser vital usar TypeScript , y cuándo probablemente podríamos prescindir de él y seguir con JavaScript puro.
Por qué este debate es hoy más importante que nunca
He llegado a la trascendental conclusión de que, dependiendo de tu situación, contexto, proyecto, nivel de habilidad y otros factores, resulta extremadamente arriesgado que tu proyecto NO esté programado con TypeScript hoy en día.
El espacio del Front-End, por mencionar uno de los posibles, se está volviendo cada vez más complejo. Ciertas características que antes se consideraban de vanguardia, ahora los usuarios las esperan como algo estándar.
Por ejemplo, casi siempre es de esperar que tu aplicación siga funcionando sin conexión en alguna medida; y cuando los usuarios ESTÁN conectados, también suelen tener la expectativa de recibir notificaciones en tiempo real sin tener que actualizar la página.
Estas son algunas de las exigencias más importantes (y definitivamente no poco realistas) en 2019.
Antes de sumergirnos en diferentes escenarios, deberíamos hablar de las tres categorías de problemas de software realmente complejos que debemos resolver.
3 Categorías de problemas difíciles de resolver en software
En términos generales, hay 3:
- El Problema del Sistema de Alto Rendimiento.
- El Problema del Sistema Embebido.
- El Problema del Dominio Complejo.
1. El Problema del Sistema de Alto Rendimiento
Hablemos de Twitter.
Twitter es en realidad un concepto muy simple.
Te registras, escribes tuits, te gustan los tuits de otras personas y eso es prácticamente todo.
Si Twitter es tan sencillito, ¿por qué no podría haberlo hecho otra persona?
Es evidente que el verdadero reto para Twitter no es tanto "lo que hace", sino "cómo consigue hacer lo que hace".
Twitter tiene el desafío único de servir las peticiones de aproximadamente 500 millones de usuarios cada día.
La dificultad que soluciona Twitter es en realidad un problema de rendimiento.
Cuando el reto es el rendimiento, el hecho de que utilicemos o no un lenguaje fuertemente tipado es menos importante.
2. El Problema del Sistema Embebido
Un sistema embebido es una combinación de hardware y software informático, con el fin de permitir el control de los aspectos mecánicos o eléctricos de un sistema.
La mayoría de los sistemas que utilizamos hoy en día están construidos sobre una capa de código muy compleja que, si no está programada inicialmente en C o C++, suele compilar a estos lenguajes.
La programación en estos lenguajes no es para "flojos".
En C, no existen los objetos; y a nosotros como humanos nos gustan los objetos porque podemos entenderlos fácilmente. C es procedimental y esto hace que el código que tenemos que desarrollar en este lenguaje sea más difícil de mantener de forma limpia. Estos problemas también exigen tener conocimiento de detalles de bajo nivel.
C++ hace que la vida sea mucho mejor ya que está orientado a objetos, pero el reto sigue siendo la interacción fundamental con los detalles de hardware de bajo nivel.
Debido a que realmente no tenemos demasiada elección sobre los lenguajes que usamos para estos problemas, es irrelevante discutir aquí sobre TypeScript.
3. El Problema de Dominio Complejo
En el caso de algunos problemas, el desafío consiste no tanto en escalar en términos de manejar más peticiones, sino en escalar en términos del tamaño de la base de código.
Las empresas tienen problemas complejos para resolver en la vida real. En estas empresas, los mayores retos de ingeniería del software suelen ser:
- Ser capaz de separar lógicamente (a través de dominios) partes de ese monolito en aplicaciones más pequeñas. Y luego, físicamente (a través de microservicios para contextos vinculados), dividirlos para que se puedan asignar equipos de trabajo para mantenerlos.
- Gestionar la integración y sincronización entre estas aplicaciones
- Modelar los conceptos de dominio y resolver realmente los problemas del dominio
- Creación de un lenguaje ubicuo (que abarque todos los aspectos) para que sea compartido por desarrolladores y los expertos en los dominios.
- No perderse en las cantidades masivas de código escrito y ralentizarse hasta el punto en que se hace imposible añadir nuevas funciones sin romper las existentes.
He descrito esencialmente los tipos de problemas que resuelve el diseño orientado por dominio (Domain Driven Design). Para este tipo de proyectos, ni siquiera pensarías en no usar un lenguaje fuertemente tipado como TypeScript.
JavaScript orientado a objetos
En el caso de problemas con dominios complejos , si no se escoge TypeScript y, en su lugar, se escoge JavaScript, se requerirá un esfuerzo adicional para que los resultados sean satisfactorios. No sólo tendrás que estar más que cómodo con tus habilidades de modelado de objetos en JavaScript puro, sino que también tendrás que saber cómo utilizar los 4 principios de la programación orientada a objetos (encapsulación, abstracción, herencia y polimorfismo).
Esto puede ser difícil de hacer. JavaScript no tiene de forma nativa conceptos como interfaces y clases abstractas, y la herencia por prototipos puede resultar complicada a veces.
La "segregación de interfaces" de los principios de diseño de SOLID no es fácil de conseguir con JavaScript puro.
El uso de JavaScript por sí solo también requeriría un cierto nivel de disciplina como desarrollador para mantener el código limpio, y esto es vital una vez que la base de código sea lo suficientemente grande. Y también es importante asegurarse de que todo el equipo de programación comparte la misma disciplina, la misma experiencia y el mismo nivel de conocimientos sobre cómo implementar patrones de diseño comunes en JavaScript. Si no, es necesario orientarlos.
En proyectos orientados a dominios como este, el gran beneficio de usar un lenguaje fuertemente tipado no consiste tanto en expresar lo que se puede hacer , sino más bien en usar encapsulación y ocultación de información para reducir la posibilidad de errores limitando lo que los objetos del dominio realmente pueden hacer.
Se puede prescindir de esto en la parte Front-End, pero en mi opinión es un requisito de lenguaje indispensable para el backend. También es la razón por la que cambié mis servicios de backend de Node.js a TypeScript.
Hay una razón por la que a TypeScript se le llama "JavaScript que escala".
De entre las tres categorías de problemas de software difíciles, sólo el problema del dominio complejo es aquel en el que TypeScript es absolutamente imprescindible.
Además de esto, existen otros factores que pueden determinar cuándo es mejor utilizar TypeScript para los proyectos JavaScript.
Tamaño del código
El tamaño del código generalmente se relaciona con el problema del dominio complejo, donde una base de código grande significa un dominio complejo, pero no siempre es así.
Cuando la cantidad de código que tiene un proyecto alcanza un cierto tamaño, se hace más difícil llevar un registro de todo lo que existe, y es más fácil terminar reimplementando algo ya programado.
La duplicación es el enemigo de un software bien diseñado y estable.
Esto se acentúa especialmente cuando los nuevos desarrolladores comienzan a programar sobre una base de código ya de por sí grande.
El auto-completado de Visual Studio Code e Intellisense ayudan a navegar a través de grandes proyectos. Funciona muy bien con TypeScript, pero es algo más limitado con JavaScript.
Para proyectos que permanecerán simples y pequeños , o si se sabe que con el tiempo serán desechados, estaría menos inclinado en recomendar TypeScript como requisito.
Software de producción frente a proyectos personales
El software que está en producción es el código que importa , o el código por el que se puede tener problemas si no funciona. También es un código para el que has hecho pruebas. Por regla general, si a uno le preocupa el código, necesita que le hagan pruebas unitarias.
Pero si no te importa, no hagas testing.
Los proyectos personales se explican por sí solos. Se puede hacer lo que se quiera. No hay ningún compromiso profesional para mantener estándares de calidad.
Es bueno atreverse a hacer cosas. Hacer cosas pequeñas, hacer cosas grandes.
Tal vez algún día se te haga fastidioso si un proyecto personal se convierte en un proyecto profesional y que algún día se convertirá en software de producción, ya que tendrá errores debido a que no se le hicieron pruebas... 🙃 no es como si me hubiera pasado a mí ni nada de eso....
Ausencia de pruebas unitarias
No siempre es posible hacer pruebas para todo, porque, bueno, la vida es así...
En este caso, diría que, si no se realizan pruebas unitarias, lo mejor que se puede hacer es una comprobación en tiempo de compilación con TypeScript. Después de eso, si se está usando React, lo mejor es usar la comprobación en tiempo de ejecución con Prop Types.
Sin embargo, la inspección en tiempo de compilación no es una alternativa a las pruebas unitarias. Lo bueno es que las pruebas unitarias se pueden escribir en cualquier lenguaje, por lo que el argumento para TypeScript aquí es irrelevante. Lo importante es que las pruebas estén programadas y que tengamos confianza en nuestro código.
Startups
Sin duda, lo mejor es usar lo que ayude a ser más productivo.
En este momento, el lenguaje que se elija importa mucho menos.
Lo más importante que se debe hacer es validar el producto.
Elegir un lenguaje (Java, por ejemplo) o una herramienta (como Kubernetes) que pueda ayudar a escalar en el futuro, sin estar totalmente familiarizado con ella y con la necesidad de pasar tiempo aprendiendo, puede o no ser la mejor opción en el caso de un startup.
Dependiendo de en qué fase se encuentre la empresa, lo más importante que se debe hacer es intentar ser productivo.
En el famoso artículo de Paul Graham , The Python Paradox, la idea principal es que los programadores novatos sólo deben utilizar una tecnología que maximice su productividad.
En general, en este caso, se puede usar cualquier cosa con la que uno esté más cómodo: tipado o no tipado. Siempre se puede refactorizar hacia un mejor diseño una vez que se sabe que se ha creado algo que las personas realmente quieren.
Trabajo en equipo
Dependiendo del tamaño de los equipos y de los frameworks que se utilicen, utilizar TypeScript puede salir muy bien o fatal.
Equipos grandes
Cuando los equipos son lo suficientemente grandes (porque los problemas son lo suficientemente grandes), es una buena razón para usar un framework que nos deje poca libertad de opciones, como Angular para el front-end y TypeScript para el backend.
La razón por la cual el uso de un framework "con opiniones" es beneficioso es porque se limita el número de formas posibles en que las personas pueden llegar a hacer algo. En Angular, hay casi una manera principal de añadir un Route Guard, usar inyección de dependencias, conectar rutas, hacer Lazy-Loading y crear formularios reactivos.
La gran ventaja es que la API está bien especificada.
Con TypeScript, se ahorra mucho tiempo y se hace más eficiente la comunicación.
La capacidad de determinar rápidamente los argumentos necesarios y su tipo de retorno para cualquier método, o la capacidad de describir explícitamente la intención del programa a través de variables públicas, privadas y protegidas es increíblemente útil.
Sí, algo de todo esto también es posible con JavaScript, pero es un lío.
Comunicar patrones e implementar principios de diseño
No sólo eso, sino que los patrones de diseño , las soluciones a los problemas más comunes que ocurren en el software, se comunican más fácilmente a través de lenguajes explícitos fuertemente tipados.
A continuación se muestra un ejemplo de un patrón común en JavaScript. Fíjate a ver si puedes identificar qué es:
class AudioDevice {
constructor () {
this.isPlaying = false;
this.currentTrack = null;
}
play (track) {
this.currentTrack = track;
this.isPlaying = true;
this.handlePlayCurrentAudioTrack();
}
handlePlayCurrentAudioTrack () {
throw new Error(`Subclasss responsibility error`)
}
}
class Boombox extends AudioDevice {
constructor () {
super()
}
handlePlayCurrentAudioTrack () {
// Play through the boombox speakers
}
}
class IPod extends AudioDevice {
constructor () {
super()
}
handlePlayCurrentAudioTrack () {
// Ensure headphones are plugged in
// Play through the ipod
}
}
const AudioDeviceType = {
Boombox: 'Boombox',
IPod: 'Ipod'
}
const AudioDeviceFactory = {
create: (deviceType) => {
switch (deviceType) {
case AudioDeviceType.Boombox:
return new Boombox();
case AudioDeviceType.IPod:
return new IPod();
default:
return null;
}
}
}
const boombox = AudioDeviceFactory
.create(AudioDeviceType.Boombox);
const ipod = AudioDeviceFactory
.create(AudioDeviceType.IPod);
Si adivinaste el Patrón "Factoría Abstracta" , estás en lo cierto. Dependiendo de tu familiaridad con el patrón, podría no haber sido tan obvio.
Veámoslo ahora implementado en TypeScript. Observa cuánta más intención podemos significar sobre AudioDevice
en este lenguaje:
abstract class AudioDevice {
protected isPlaying: boolean = false;
protected currentTrack: ITrack = null;
constructor () {
}
play (track: ITrack) : void {
this.currentTrack = track;
this.isPlaying = true;
this.handlePlayCurrentAudioTrack();
}
abstract handlePlayCurrentAudioTrack () : void;
}
Mejoras inmediatas
- Sabemos que la clase es abstracta de inmediato. En el ejemplo de JavaScript era necesario investigar un poco.
-
AudioDevice
puede ser instanciado en el ejemplo de JavaScript. Esto es malo, queríamos queAudioDevice
fuera una clase abstracta. Y las clases abstractas no deberían poder ser instanciadas, sólo están destinadas a ser subclasificadas e implementadas por clases concretas. Esta limitación se establece correctamente en el ejemplo de TypeScript. - Hemos señalado el ámbito de las variables.
- En este ejemplo,
currentTrack
se refiere a una interfaz. Según el principio de diseño de la inversión de la dependencia, siempre debemos depender de las abstracciones, no de las concreciones. Esto no es posible en la implementación con JavaScript. - También hemos señalado que cualquier subclase de
AudioDevice
necesitará implementar elhandlePlayCurrentAudioTrack
. En el ejemplo de JavaScript, expusimos la posibilidad de que alguien introdujera errores de ejecución intentando ejecutar el método desde la clase abstracta ilegal o desde la implementación de la clase concreta no completa.
Conclusión : si se trabaja en un equipo grande y se necesita minimizar las posibles formas en las que alguien podría hacer un mal uso del código, TypeScript es una buena forma de ayudar a corregirlo.
Equipos más pequeños y estilos de programación
Los equipos más pequeños son mucho más fáciles de manejar en lo que respecta a estilos de programación y comunicación. Junto con las herramientas de comprobación de reglas (linting), las discusiones frecuentes sobre cómo se harán las cosas y los pre-commit hooks, creo que los equipos pequeños pueden tener mucho éxito sin TypeScript.
Creo que el éxito es una ecuación que incluye el tamaño de la base de código y el tamaño del equipo.
A medida que crece la base de código, el equipo puede encontrar que necesita contar con algo de ayuda del propio lenguaje para recordar dónde están las cosas y cómo deberían estar.
A medida que el equipo crece, pueden encontrar que necesitan más reglas y restricciones para asegurar la coherencia del estilo y evitar la duplicación de código.
Frameworks
React y Angular
Mucho de lo que me atrae a mí y a otros desarrolladores de React es la capacidad de escribir código como quieras y de forma elegante/inteligente.
Es cierto que React te hace mejor programador de JavaScript porque te obliga a enfocar los problemas de forma diferente, te obliga a ser consciente de cómo funciona este vínculo en JavaScript y te permite crear componentes grandes a partir de otros pequeños.
React también te permite tener un poco de estilo propio. Y debido a la cantidad de formas en que se puede implementar cualquier tarea, lo más frecuente es que se programen aplicaciones en React.js "puro" cuando:
- La base de código es pequeña
- Sólo lo programa una persona
Pero yo lo compilaría con TypeScript cuando:
- Lo estén programando más de 3 personas, o
- se espera que la base de código llegue a ser muy grande
También opcionalmente usaría Angular por la misma razón por la que compilaría React con TypeScript.
Conclusión
En conclusión, estas son mis opiniones personales sobre cuándo TypeScript es absolutamente necesario y os invito a estar en desacuerdo con cualquiera de ellas.
Esto es lo que me ha funcionado en el pasado a la hora de decidir si utilizo TypeScript. Sin embargo, hoy en día, como he visto la luz, no es mucho más difícil para mí usar TypeScript que JavaScript puro, ya que me siento igual de cómodo con ambos y preferiría la seguridad del tipado.
Lo último que quiero decir es lo siguiente:
Siempre se puede empezar a utilizar TypeScript gradualmente
Empieza gradualmente añadiendo TypeScript y ts-node a tu package.json
y utilizando la opción allowjs: true
, en tu archivo tsconfig
.
Así es cómo, con el paso del tiempo, migré todas mis aplicaciones Node.js a TypeScript.
Los errores de tiempo de compilación son mejores que los de tiempo de ejecución
Eso no admite discusión. Si la detección de errores en el código de producción es especialmente importante para ti, TypeScript te ayudará a minimizar muchos de ellos.
Si estás en disposición de aprenderlo, apréndelo: hace maravillas con tus habilidades de diseño de software
Dependiendo de dónde estés en tu vida y en tu trayectoria profesional, es posible que no tengas tiempo para aprender y formarte. Si tienes tiempo, te recomiendo que empieces por aprender sobre los principios de diseño SOLID y los patrones de diseño de software. Esta es la manera más rápida de subir de nivel como desarrollador junior, en mi sincera opinión.
Posted on September 11, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.