### TL;DR
*L'objectif de ce document est d'exposer les bénéfices liés à l'intégration de Typescript dans nos projets.*
- *Nous avons rencontrés trop de problèmes en production qui aurait pu être évités simplement, avec du typage*
- *Typescript apporte beaucoup d'avantages : Sécuriser la maintenance, améliorer la productivité sur le long terme, renforcer la conception, expliciter et homogénéiser le code*
- *On peut aussi lui voir des inconvénients : maintenance des types nécessaire et difficultés de débogages*
- *Nous pensons que la mise en place de Typescript serait moins chronophage que la maintenance d'un code non typé*
- *Des stratégies existent pour échelonner l'intégration de Typescript*
## Motivation
Après de longues discussions et échanges entre nous autour de Typescript, nous nous sommes rendus compte des avantages que Georges pouvait tirer de son utilisation.
Ce sentiment a récemment été renforcé par différents problèmes que nous avons eu en production et qui auraient été facilement détectables en amont avec typescript (propriété inexistante sur un objet, appel de fonction avec une signature fausse, etc).
Si les tests sont censés résoudre une partie de ces problèmes, force est de constater que ça ne marche pas toujours et que certaines erreurs sont assez fourbes.
Nous avons donc décidé d'écrire ce RFD dans l'optique d'ouvrir et d'officialiser le dialogue sur ce sujet à l'ensemble de l'équipe technique.
Les différents points que vous trouverez ci-dessous sont tirés de notre expérience passée, de notre (modeste) expérience sur Georges mais aussi d'échanges avec différents développeurs ayant utilisé TS.
L'espoir est que cela nous permettra de faire évoluer la base de code de l'entreprise bien au-delà de la taille de notre base de code actuelle, tout en gardant le code facile à comprendre et maintenir.
## Avantages attendus
<!--
### Réduire le nombre d'erreurs au runtime
Comme dit en introduction, on remarque qu'un bon nombre d'erreurs qu'on observe dans Georges sont de type `TypeError: cannot read property foo of undefined`.
Avoir un système de types permettrait de détecter les erreurs potentielles directement à l'écriture du code.
Par exemple, on pourra limiter le risque de lire `args.maSuperVariable` au lieu de `args.maVariable` (plusieurs bugs en prod de ce type ces derniers mois).
-->
### Sécuriser la maintenance
Avoir un système de typage peut nous faciliter et aider à sécuriser la maintenance.
_Sécuriser, mais bien évidemment en **complément** des tests unitaires que l'on a déjà l'habitude de faire._
Devoir modifier la signature d'une fonction est une situation courante. Par exemple, lors de la mise en place de l'exercice décalé, préciser une année fiscale ne suffisait plus pour déterminer la période fiscale. La signature de plusieurs fonctions a alors dûe être modifiée.
Avec notre base de code en JS, comment s'assurer que tous les appelants de la fonction modifiée ont correctement été mis à jour ? TypeScript nous apporte une sécurité supplémentaire dans ce cas précis, et à un coût ridicule :
Prenons par exemple ce changement, dans lequel on rajoute la date de fin d'exercice fiscal pour pouvoir créer une OD à la bonne date :
```diff
- personalReintegrationTransaction(asset, lastFiscalYearOpen) {
+ personalReintegrationTransaction(asset, lastFiscalYearOpen, endDate) {
```
Il faut donc changer tous les appels à cette fonction pour lui fournir cette fameuse date. Mais que se passe-t-il si l'on oublie ce paramètre ? _Rien._
Cet appel est totalement valide et `endDate` sera simplement `undefined` dans le corps de la fonction :
```js
personalReintegrationTransaction(asset, lastFiscalYearOpen)
```
`moment(undefined)` donne la date courante, et par conséquent toutes les OD sont créées à la date du jour, et donc très souvent en dehors de l'exercice fiscal pour lequel cette OD était destinée. Il faut donc prévoir un script de correctifs pour replacer ces OD aux bonnes dates, avec toutes les conséquences que cela implique (notamment quand l'exercice est déjà clôturé).
***Comment TypeScript aurait pu éviter le problème ?***
Même sans _aucune_ annotation de type, cette erreur aurait été détectée, rien qu'en se basant sur le nombre de paramètres.
Cet exemple, bien que trivial, montre que TypeScript est là pour nous **aider**, pas pour ralentir notre cycle de développement. Par défaut, le compilateur est déjà capable de nous donner des retours utiles, alors imaginez si l'on aide le compilateur en lui précisant les types attendus et les formats de données !
À mesure que Georges grandit, et par extension son équipe, il sera de plus en plus difficile pour nous de s'assurer que de tels changements n'introduisent pas de bugs. ***TypeScript ne résoudra pas tous les problèmes, mais peut nous aider à en prévenir beaucoup.***
D'après une estimation d'Airbnb sur leurs bugs rencontrés en production avant leur migration vers TS, **38% des bugs seraient évitables avec TS**.
Cette sécurité supplémentaire nous permettrait par ailleurs d'appliquer la technique du "boyscout" avec plus d'assurance et de sérénité, réduisant ainsi les barrières pour des petits refactorings courants pouvant améliorer la qualité globale du code.
<!--
Si on veut généraliser la technique de crafter dite "du boy scout", avoir un typage nous permettrait de refactorer plus souvent avec plus de confiance.
En plus de ça, on aurait moins besoin de fouiller dans les implémentations des fonctions pour savoir comment s'en servir.
En précisant la signature des fonctions de manière exhaustive (types des paramètres et de retour), le contrat serait beaucoup plus clair à comprendre.
De plus, les chagements de l'implémentation d'une fonction seront plus sûres dans la mesure où TS va garantir que les paramètres sont correctements passés et que le retour est cohérent avec ce que les différents appels existants attendent.
Quand on doit manipuler un objet, on sait comment il est constitué et donc comment l'utiliser. Si on veut créer un objet du même type, on sait les attributs qui sont obligatoires pour ne pas casser une autre fonction qui l'utiliserait.
En conclusion les type améliorent la facilité de refactorer le code et sont l’une des meilleurs formes de documentation qui soit.
-->
### Productivité
Bien que cette idée puisse paraître contre intuitive parce qu'il faut définir et maintenir des types, nous pensons qu'elle peut au contraire fluidifier les développements.
Détecter les erreurs potentielles plus tôt dans la feedback loop évite d'avoir à exécuter le code en testant l'app trop souvent, en faisant des aller-retours entre le navigateur et les sources.
On réduit alors le nombre de changements de contexte qui réduisent la productivité.
Par ailleurs, les IDE s'intègrent extrêmement bien avec TS en fournissant de l'autocomplétion et l'affichage d'erreurs instantanément dans les sources. Ce qui fait qu'il n'est pas nécessaire d'exécuter le compilateur TypeScript manuellement pour savoir si l'on a fait des erreurs (comme pour ESLint).
On alors l'assurance d'avoir une documentation à jour avec l'implémentation, utilisable par les IDE.
<details>
<summary>Petit exemple avec les amortissements</summary>
Prenons le cas de cette fonction permettant de calculer les plus-value et moins-value d'une immobilisation :
```js
function calculateGainLoss(args)
```
Aujourd'hui, il est difficile de dire quels sont les arguments attendus et quel est le retour de cette fonction.
Dans ce cas, on doit regarder l'implémentation pour comprendre comment se servir de la fonction.
Voilà comment est-ce que l'on pourrait typer cette fonction :
1. Tout d'abord, on définit ce qu'est un `asset` :
```ts
type Asset = {
id: string
date: Date
user: UserInfo // { user: { _id: string } }
amortizations: Amortization[]
// --snip--
}
type Amortization = {
year: number
amount_in_cents: number
personal_kilometer_option_amount_in_cents: number
// --snip--
}
```
2. Ensuite, on peut mettre à jour la signature de la fonction :
```ts
function calculateGainLoss({
asset: Asset,
date: Date,
amount: number,
shortTermOnly: boolean,
startDate: Date,
endDate: Date,
}): { gainOrLossSTInCents: number, gainOrLossLTInCents: number }
```
Avoir une fonction bien définie comme ici nous permettra de faciliter les futurs refactos lorsqu'elles seront nécessaires, car le compilateur pourra vérifier que tous les paramètres sont passés correctement.
D'ailleurs cela tombe bien, rajouter une notion de startDate et endDate pour gérer les utilisateur en exercice décalé.
Par exemple, on peut imaginer que donner une `startDate` et `endDate` pour un exercice fiscal est une chose courante avec les exercices décalés, pas uniquement dans ce contexte.
On peut donc définir un nouveau type qui encapsule ces valeurs :
```ts
type ExerciseDates = {
startDate: Date
endDate: Date
}
```
et changer la définition de la fonction comme ceci :
```diff
function calculateGainLoss({
asset: Asset,
date: Date,
amount: number,
shortTermOnly: boolean,
- startDate: Date,
- endDate: Date,
+ exerciseDates: ExerciseDates,
}): { gainOrLossSTInCents: number, gainOrLossLTInCents: number }
```
Grâce une définition explicite des types, on peut donner un sens aux paramètres au-delà des types par défaut, rendant ainsi notre signature plus compréhensible et notre interface plus claire.maintenant ainsi le code en état de marche
</details>
### _Program to interfaces, not implementation_
Attention, on ne parle pas forcément du mot-clé `Interface` du langage, mais plutôt du _concept_.
Au moment de l'écriture d'une nouvelle fonction, il peut être utile de se poser la question des pré-conditions (les paramètres et leurs types) et des post-conditions (quoi retourner et sous quel format). Cela constitue donc en quelque sorte l'interface, ou la signature de cette fonction.
En précisant le contrat d'une fonction avec des annotations de types cela nous force à réflechir en amont, et espérons-le, à faire moins de suppositions sur le contexte des appelants.
L'implémentation est alors en quelque sorte "libérée" du contexte des appelants, il suffit alors de satisfaire la signature. Cela pourra plus nous inciter à découpler nos différentes briques.
Au final, cette idée reprend un peu celle du TDD : d'abord **quoi**, ensuite **comment**.
### Rationalisation des types
Avec ou sans TS, on crée et on consomme des types au quotidien, par exemple une variable `asset` sous-entend la présence d'un certain nombre de propriétés sur cet objet.
TypeScript n'introduit pas la notion de types, **il les explicite**, il peut donc vérifier automatiquement leur utilisation.
Comme JS permet de créer des objets très facilement sans avoir besoin de définir leur schéma, il est plus difficile de maintenir des formats de données cohérents dans toute l'application.
Expliciter ces types nous inciterait plus à réutiliser d'autres schémas de données existants, et nous permettrait ainsi d'avoir des formats de données plus homogènes.
Mais nous ne faisons pas ça seulement pour faire plaisir à TypeScript, c'est aussi une aide précieuse **pour nous**. Il y aura moins besoin de regarder l'implémentation d'une fonction pour savoir comment s'en servir, moins besoin de regarder le schéma d'une collection pour savoir quels attributs il est possible d'accéder... l'auto-complétion nous le proposera immédiatement.
<!--
// Pas sûr que ce soit nécessaire ^^
// ça fait limite passif agressif non ? :p
*Qui est aujourd'hui capable de définir le type d'un objet ***asset*** dans georges, sans avoir à fouiller dans le schéma de la collection ?*
-->
<!--
### Moins de programmation défensive
On remarque que dans beaucoup de fonctions on fait des checks sur les paramètres pour s'assurer que les paramètres sont bien conformes à ce qui est attendu.
Par exemple en lançant des erreurs si ce n'est pas le cas, ou en utilisant `_.get` pour au contraire les éviter.
Plutôt que de prendre des mesures correctives dans ces situations
TODO VICTOR : Je ne suis pas forcément d'accord avec ça. Quand on fait ça on se protège plus de ce qui peut se trouver en base.
-->
<!--
### Interfaces
TS apporte les interfaces, qui est un outil qui nous permettrait d'ajouter une couche d'isolation entre les implémentations et les clients, pour avoir du code un peu plus modulaire.
-->
<!--
Ça fait un peu argument d'autorité non ? Genre il faut utiliser parce que c'est microsoft et il a beaucoup de stars...
### Une technologie solide
TypeScript est un langage développé et supporté par Microsoft, avec certains membres de leur équipe ayant travaillé sur sur le développement sur langage C#.
De plus en plus de modules externes travaillent pour avoir une excellente interopérabilité avec TypeScript, et certains frameworks Node font même de TS leur langage d'utilisation par défaut (y compris Vue 3).
_Stats GitHub sur TypeScript : 59k stars, 2 millions de repos l'utilisant_
-->
## Inconvénients
### Maintenance des types
On doit écrire des types, donc le développement d'une tâche peut-être rallongé. Mais comme on l'a vu pendant le BBL clean code, est-ce qu'on privilégie la rapidité d'écriture ou la maintenance dans le long terme ?
Avec l'utilisation de TS, nous introduisons une complexité supplémentaire en utilisant un langage plus avancé que le JavaScript simple, mais nous sommes toutefois convaincus qu'en augmentant un peu la complexité du langage d'implémentation, nous pouvons réduire considérablement la complexité globale de la base de code.
### Duplication des déclarations de types
En fonction de la stack technique dans l'application et des librairies utilisées, on risque de se retrouver à devoir définir plusieurs fois les types, notamment en ce qui concerne la base de données. Avec un setup basique avec Mongoose par exemple, il sera probable de devoir définir à la fois le schéma de la collection et la déclaration de type pour TypeScript.
Bien sûr, avec un peu de travail, il semble possible de mutualiser ces définitions pour avoir une seule source de vérité.
Par ailleurs, si on utilise GraphQL et Apollo, il semblerait qu'il y ait un très bon support TS et auquel cas cela nous faciliterait énormément le travail.
### Débogage au runtime
La mise en place de TS (ou de n'importe quel transpileur) implique un débogage moins clair. En effet, le code JS qui est finalement exécuté est différent du code réellement écrit. Cela affectera notamment le débogage (par exemple dans le navigateur ou avec des breakpoints), mais aussi le monitoring (logs et Sentry).
Ce problème pourra être résolu encore une fois avec un peu de travail en utilisant les source maps qui permettent de lier les fichiers transpilés à leurs sources. [node-source-map-support](https://github.com/evanw/node-source-map-support) semble être un outil très prometteur pour nous aider.
## Mise en place possible
Il est totalement possible d'adopter petit à petit TypeScript. Nous pensons qu'il serait le plus utile dans les fonctions métier, celles qui calculent les soldes de comptes, ou encore dans le calcul des amortissements.
Dans les faits, l'inférence de types de TS marche très bien, à tel point que dans la majorité des cas donner une signature exhaustive aux fonctions peut être suffisant (paramètres et types de retour).
On peut rappeler que la mise en place de TS est **sans risque** pour nos utilisateurs : 0 dépendances au runtime, TS n'est **qu'un outil de dev**.
TypeScript n'est que du JavaScript avec plus de features.
Deux modes d'adoption semblent possibles :
**Adopter TS tôt dans le cadre de la refonte**
Le moment semble idéal pour commencer à utiliser TS. Les modules réécrits pourront bénéficier d'un typage dès le début, ce qui pourrait aussi faciliter le travail.
**Adopter incrémentalement TS dans une base de code existante**
Au contraire, il est également possible d'ajouter TypeScript sur un code existant. Pour cela, on pourra s'armer de _code mods_ (comme Airbnb l'a fait) pour automatiser autant que possible l'ajout de types dans le code. On pourra tout à fait faire cohabiter des fichiers JS et TS, typés ou non au sein de la même appli.
<!--
Je pense pas qu'on en ait besoin, je pense que tout le monde en a déjà fait plus ou moins
## Montée en compétence
-->
<!--
Peut-être faire des exemples au fur et à mesure de nos arguments pour les illustrer ?
## Les avantages de TS par l'exemple
-->
## FAQ
Cette partie rassemble l'ensemble des objections qu'on a pu avoir à l'utilisation de TS lors des différents échanges *café* :coffee:. L'objectif est d'y apporter une réponse pour pouvoir lancer des discussions sur les points qui peuvent être bloquants.
### :no_good: Je ne veux pas faire de Java / Angular / C#
Nous non plus !
TypeScript nous laisse cette flexibilité de choisir jusqu'à quel point on veut utiliser les features du langage. C'est juste du JavaScript avec des features supplémentaires, libre à l'équipe de s'imposer des règles et des limites quand à son utilisation.
TypeScript est très souvent lié dans les esprits à Angular ou Nest qui utilisent énormément les features avancées (comme les annotations pour faire de l'injection de dépendances). Mais il est tout à fait possible d'en faire un usage beaucoup plus simple tout en profitant de ses principaux avantages : productivité et sécurité.
### :pencil: Maintenir les types demandera trop de travail
C'est vrai que ça demandera du travail, mais c'est nécessaire pour assurer la bonne cohérence du code et des formats de données dans toute l'application.
En contrepartie de cet effort, les refactorings et la detection d'erreurs seront plus rapides.
### :bug: Ça ne va pas empêcher d'avoir des bugs
Oui, c'est certain.
L'utilisation de Typescript ne permet en aucun cas de faire disparaitre l'intégralité des bugs d'une application. En revanche, TS permet de detecter facilement des bugs, qui sont souvent assez simple, mais qui peuvent tout de même causé de gros problème dans l'intégrité des données de nos clients.
C'est donc un gain de temps en correction/migrations des données.
### :mega: Pas besoin de TS si on nomme bien les fonctions et les variables
C'est certain qu'il faut avoir de bons noms de variables, aussi explicites que possible. Mais un nom ne peut pas traduire à lui tout seul son format de données (ses propriétés et ses sous-types). Dans dans le cas d'une fonction, le nom de peut pas traduire l'ensemble de son contrat de manière exhaustive.
Par ailleurs, l'intérêt de TypeScript réside dans le fait que l'on puisse vérifier de manière automatique que les formats de données ainsi que les contrats soient respectés. Même un excellent nommage demandera à ce qu'un humain relise le code pour vérifier qu'il soit correct sur ces aspects.
### :package: Il y a une étape de transpilation supplémentaire
Oui, mais il est rare de devoir exécuter le compilateur TypeScript à la main pour faire les vérifications. Dans les faits, les IDE sont très bien intégrés avec TS et affichent les problèmes dès qu'ils sont écrits.
Dans la CI, on peut espérer que cela prenne autant de temps qu'un linter.
On pourrait en revanche rencontrer des difficultés à mettre en place TS si jamais on essayait de l'intégrer dans un framework tel que Meteor.
## :popcorn: :book:
[Retour d'expérience de Airbnb sur leur migration de JS vers TS](https://www.youtube.com/watch?v=P-J9Eg7hJwE)
[et aussi de Slack sur leur application PC](https://slack.engineering/typescript-at-slack-a81307fa288d)
<!--
// Rajouter un exemple avec Georgette pour le calcul des lignes et une définition pour `context` (a l e d cet objet de ses morts)
// Lisibilité du code
// j'ai l'impression que pour beaucoup c'est un outil qui fait perdre beaucoup de temps et qui est lourd à utiliser. Alors qu'au final c'est très simple (sauf quand on veut volontairement faire des choses tricky). De plus, définir ses types permets d'avoir une réflexion en amont et écrire du code moins vite. Dixit manu: Un bon développeur est un développeur qui réfléchi plus qu'il ne code. (c'était un truc comme ça aha)
// C'est seulement un outil de développement et on peut en sortir facilement si on veut
// Force a structurer le code
Des bouts d'articles que je trouve cool :
----------
I. TypeScript est un choix sans risque
Avant toute chose, TypeScript n'est pas un choix dangereux pour l'équipe de développeurs qui envisage de l'adopter.
En effet, le nouveau langage fait sien le slogan de CoffeeScript : « It's just JavaScript ». Le compilateur TypeScript n'ajoute aucune dépendance à une bibliothèque JavaScript.
----------
"Entre parenthèses je tiens à insister sur quelque chose de fondamental : les bonnes pratiques en JavaScript, dont les closures et la programmation asynchrone par callbacks, restent de bonnes pratiques en TypeScript. La syntaxe des classes ne retire rien, elle s'ajoute."
--------------------
TypeScript simplifies JavaScript code, making it easier to read and debug.
TypeScript is open source.
TypeScript provides highly productive development tools for JavaScript IDEs and practices, like static checking.
TypeScript makes code easier to read and understand.
With TypeScript, we can make a huge improvement over plain JavaScript.
TypeScript gives us all the benefits of ES6 (ECMAScript 6), plus more productivity.
TypeScript can help us to avoid painful bugs that developers commonly run into when writing JavaScript by type checking the code.
Powerful type system, including generics.
TypeScript is nothing but JavaScript with some additional features.
Structural, rather than nominal.
TypeScript code can be compiled as per ES5 and ES6 standards to support the latest browser.
Aligned with ECMAScript for compatibility.
Starts and ends with JavaScript.
Supports static typing.
TypeScript will save developers time.
TypeScript is a superset of ES3, ES5, and ES6.
---------------
Exemple assez simple :
Aujourd'hui on a une fonction getAllAssetAmortizations permettant de récupérer l'ensemble des valeurs d'amortissement d'un asset.
Il est assez clair de comprendre le sens metier de ce que renvoie la fonction mais on est obligé de remonter dans cette dernière (et peut être encore plus haut) si on veut connaitre la structure de l'objet qui est renvoyé.
```ts
const allAmortizations = getAllAssetAmortizations({ asset, fiscalYear, amortizationEndDate, isAlreadySold }))
```
En dessous, en rajoutant un type qui prend littérallement 10 secondes à écrire (mais plus à le reflechir en amont, je suis d'accord) on peut savoir directement le structure de l'objet, en passant la souris sur la variable. Premier avantage: de la documentation pas cher....
```ts
type AssetAmortizations {
amortizationRate: string,
previousAmortization: number,
currentAmortization: number,
};
const assetAmortizations: AssetAmortizations = assetAmortizations({ asset, fiscalYear, amortizationEndDate, isAlreadySold }))
```
Si demain on a besoin d'avoir la valeur residuel d'un amortissement dans cette objet, alors il suffit de modifier le type :
```ts
type AssetAmortizations {
amortizationRate: string,
previousAmortization: number,
currentAmortization: number,
residualValue: number,
};
```
Et la, typescript montrera une erreur dans toutes les fonctions qui retournent ce type d'objet.
//Plein de détails à rajouter dans mes explications, j'ai la flemme d'écrire je te les dirai en vrai déja aha
Le but c'est d'avoir un exemple qui montre qu'une refacto galère sans TS devient très simple et sans risque.
-->