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

TypeScript avancé en pratique : ce qui sépare une utilisation superficielle d'une utilisation mature

Mature TypeScript ne consiste pas à écrire plus de types, il s'agit de laisser le compilateur travailler en votre faveur.

Adopter TypeScript est simple. Bien l’utiliser est rare. La différence entre les deux choses n'apparaît pas le premier jour, elle apparaît six mois plus tard, lorsque l'équipe doit refactoriser et découvrir si les types sont des alliés ou de la décoration.

Il existe une surface TypeScript, dans laquelle tout est annoté manuellement, any apparaît chaque fois que les choses deviennent difficiles, et le compilateur n'est utilisé que pour compléter automatiquement les noms. Et il existe un TypeScript mature, dans lequel les types modélisent réellement le problème et le compilateur devient un réviseur infatigable qui détecte les erreurs avant l'exécution du code.

Ce texte concerne le deuxième. Pas comme un tutoriel, mais comme un argument sur la valeur de chaque ressource pour la santé du code et de l'équipe.

Inférence : écrivez moins pour garantir plus

Le premier réflexe de quiconque vient à TypeScript est de tout écrire. Chaque variable, chaque retour, chaque paramètre reçoit un type manuscrit. Cela semble zélé et, dans la pratique, c'est le contraire.

Une annotation manuelle excessive crée du bruit et, pire encore, crée des mensonges. Le type manuscrit peut différer de la valeur réelle, et vous disposez alors d'une annotation qui documente une intention que le code ne remplit plus. L'inférence ne ment pas : elle dérive le type de la valeur du fait.

L'usage mature fait confiance à l'inférence là où elle est fiable et réserve les annotations là où cela compte, c'est-à-dire les limites. Signature de fonction publique, contrat de module, format de données qui entre dans la périphérie du système. Essentiellement, laissez le compilateur déduire. Moins de code, moins de désaccord, plus de vérité.

Génériques : la différence entre la réutilisation du type et la perte du type

Les génériques font souvent peur car la syntaxe semble académique. Le concept est simple et profondément pratique : c'est ainsi que vous écrivez quelque chose de réutilisable sans jeter les informations de type en cours de route.

Sans génériques, la solution la plus simple pour une fonction qui fonctionne avec n'importe quoi est de taper l'entrée et la sortie comme étant trop génériques, et l'effet est que le type est perdu. Celui qui appelle la fonction récupère une valeur informe et doit deviner quoi en faire.

Avec les génériques, la relation entre l’entrée et la sortie est préservée. Une fonction qui reçoit une liste de quelque chose renvoie ce même quelque chose, et le compilateur le sait au moment de l'appel. C’est le cœur d’une réutilisation sûre : abstraire le comportement sans extraire l’information. Les équipes qui maîtrisent les génériques écrivent moins d'utilitaires en double et obtiennent une saisie semi-automatique correcte dans chacun d'eux.

Types d'utilitaires : réutiliser le format sans répéter le format

Chaque système accumule des types qui sont des variations d’autres types. La version partielle d'un objet pour un formulaire, la version sans le champ mot de passe à envoyer au client, la version en lecture seule d'une configuration.

L'approche naïve consiste à redéclarer chaque variation à la main. Le problème apparaît lorsque le type d'origine change : il y a maintenant cinq copies à mettre à jour et aucune garantie que vous vous en souveniez toutes. C'est le même piège que le code dupliqué, uniquement sous forme de caractères.

Les types utilitaires résolvent ce problème en dérivant une forme d’une autre. Lorsque le type de base change, les variations se succèdent d'elles-mêmes, car elles ont été définies en fonction de celui-ci. Cela transforme les types en un point de vérité unique et élimine cette classe subtile de bogues où le code et son type ont été mis à jour à des rythmes différents.

Rétrécissement : apprenez au compilateur à vous raisonner

La fonctionnalité la plus sous-estimée est peut-être le rétrécissement, qui est la capacité de TypeScript à restreindre un type à mesure que le flux de code progresse. Vous vérifiez si une valeur existe et, dans ce bloc, le compilateur commence à la traiter comme existante.

Cela change la façon dont l'équipe gère les cas extrêmes. Au lieu de disperser des contrôles défensifs partout et d'espérer, vous modélisez les états possibles et laissez le compilateur exiger que chacun soit traité. La valeur peut être une chose ou une autre, et le code n'est compilé que lorsque les deux chemins ont été parcourus.

L’effet culturel de cela est grand. Le nul cesse d'être une surprise de production et devient une exigence de temps d'écriture. L'équipe arrête de découvrir qu'elle a oublié un cas lorsque le client se plaint, et commence à le découvrir lorsque l'éditeur se plaint. C'est la différence entre un bug et un rappel.

La guerre contre tout

any est la soupape d'échappement de TypeScript, et comme toute soupape d'échappement, elle est nécessaire à doses minimales et destructrice à doses normales. Un any ne désactive pas seulement le type de cette variable. Il contamine tout ce qu'il touche, car tout ce qui dérive d'un any devient aussi un any.

Une base avec any étalée a le pire des deux mondes : elle paie le coût de configuration et de maintenance de TypeScript, mais perd les garanties précisément dans les points les plus risqués, c'est-à-dire là où quelqu'un ne savait pas taper et a abandonné.

Les équipes matures traitent le any comme un signe d’avertissement et non comme une solution. Lorsque le type est vraiment inconnu, il existe une option sûre : le marquer comme inconnu et forcer une vérification avant utilisation plutôt que de tout publier. La règle générale est simple : any devrait être une exception rare, justifiée et visible lors de l'examen, jamais la solution par défaut à un problème.

Modéliser le domaine : le saut le plus payant

Le niveau le plus élevé d'utilisation de TypeScript] n'est pas technique, c'est la conception. Il utilise des types pour décrire les règles métier de telle manière que l'état invalide ne puisse tout simplement pas exister.

Une commande qui peut être payée ou en attente, mais jamais les deux. Un utilisateur qui, lorsqu'il est invité, ne dispose pas encore de certaines données, et lorsqu'il est actif, il en possède nécessairement. Lorsque vous modélisez ces états sous forme de types distincts, le compilateur empêche les combinaisons impossibles avant l’exécution des tests.

Cela change la discussion de l’équipe. Au lieu de « ce champ peut-il être vide ici », la réponse est en type, explicite et vérifiable. Le domaine est documenté dans le code qui s'exécute, et non dans un document que personne ne met à jour. Pour la périphérie du système, où les données externes arrivent sans aucune garantie, cela vaut la peine de combiner cela avec la validation d'exécution, et le chemin naturel est la validation des données avec Zod, qui connecte les données réelles au type.

Additionnez tout cela et le retour n'est pas esthétique, il est opérationnel. Moins de bugs qui atteignent la production, car le compilateur les a détectés plus tôt. Une refactorisation à laquelle l'équipe est confrontée sans crainte, car casser quelque chose apparaît immédiatement. Une documentation qui ne vieillit pas, car c'est le code lui-même.

Mature TypeScript ne consiste pas à écrire plus de types. Il s'agit d'écrire les bons types aux bons endroits et de laisser le compilateur faire le travail ennuyeux de vérification de la cohérence, qui est précisément le travail que les humains font mal et que les machines font bien.

Si votre équipe utilise déjà TypeScript mais traite toujours les types comme de la bureaucratie, l'étape suivante consiste à relever la barre en matière de révision du code et à traiter la qualité des types comme faisant partie de la qualité du code. Pour voir comment ces concepts se connectent dans une architecture plus large, cela vaut la peine de se rendre sur monorepos avec TypeScript.

A lire aussi