Ne vous trompez pas de type

Simplifiez votre TypeScript : pourquoi l'utilisation de Record<K,V> est préférable aux types intermédiaires superflus pour un code plus maintenable et robuste.
Loin de moi, l'idée de lyncher un confrère qui a voulu bien faire en rédigeant un article sur le typage Typescript. Ce dernier a voulu partager une optimisation de code dans un article Medium afin de réduire l'impact d'une PR qui s'étendait sur plusieurs fichiers. J'ai d'autant plus peur quand je vois la centaine de commentaires qui acclament cet article, j'espère vraiment que ce sont des bots parce que das le cas contraire, on a du soucis à se faire sur nos projets ! Je rédige donc cet article afin de vous montrer une approche plus pratique.
Quelque part, je comprends le besoin initial, on a tous, la phobie du merge conflict quand on travaille dans une grosse équipe, donc on fait le maximum pour réduire l'impact.
Pourtant, son approche ne me parait pas judicieuse et je vais vous expliquer pourquoi.
Disclaimer: N'allez surtout pas le harceler dans les commentaires, je pense qu'il a voulu bien faire et aider la communauté. Malheureusement, son manque d'expérience peut coûter cher à terme si vous optez pour son approche.
Je ne vais pas revenir sur son état de code initial qui était justement ce qu'il cherchait à optimiser, mais directement vous montrer le résultat final après refacto.
Voici le typage proposé
L'élémet qui nous intéresse, c'est la clé "reactions" dans le type FinalResponse.
// FinalResponse.ts
import { Reaction } from './Reaction'
type AllowedReactions =
| 'likes'
| 'unicorns'
| 'explodingHeads'
| 'raisedHands'
| 'fire'
export type ReactionMap = {
[key in AllowedReactions]: Reaction
}
export type FinalResponse = {
totalScore: number
headingsPenalty: number
sentencesPenalty: number
charactersPenalty: number
wordsPenalty: number
headings: string[]
sentences: string[]
words: string[]
links: { href: string; text: string }[]
exceeded: {
exceededSentences: string[]
repeatedWords: { word: string; count: number }[]
}
reactions: ReactionMap
}
Pourtant quand je lis cette approche, je trouve cela complexe et pas optimisé, la création d'un type supplémentaire ReactionMap me semble superflu.
J'aurais procédé de la sorte:
// FinalResponse.ts
import { Reaction } from './Reaction'
type AllowedReactions =
| 'likes'
| 'unicorns'
| 'explodingHeads'
| 'raisedHands'
| 'fire'
export type FinalResponse = {
totalScore: number
headingsPenalty: number
sentencesPenalty: number
charactersPenalty: number
wordsPenalty: number
headings: string[]
sentences: string[]
words: string[]
links: { href: string; text: string }[]
exceeded: {
exceededSentences: string[]
repeatedWords: { word: string; count: number }[]
}
reactions: Record<AllowedReactions, Reaction>
}
Pourquoi privilégier un typage simple et lisible ?
- Premièrement, il faut réduire au maximum le nombre de types intermédiaires (surtout quand on peut s'en passer)
- Et parce que lorsque l'on na moins de types à maintenir et que chacun de ces types est correctement utilisé, cela réduit le scope du projet et le rend simple.
J'applique avec beaucoup de ferveur l'approche de Dijkstra qui dit que la simplicité est un prédicat de la fiabilité.
Ce n'est que lorsque vous manipulez des choses simples, que vous créez du code fiable, robuste!
N'essayez pas de produire quelque chose de confus et difficile à lire. Certains pensent que le code complexe est synonyme de maitrise, mais je suis persuadé qu'il reflète surtout une incompréhension de l'auteur.
FAQ
À quoi sert `Record<K, V>` en TypeScript ?
Record<K, V> est un utilitaire TypeScript intégré qui permet de typer un objet dont les clés appartiennent à un ensemble défini K et les valeurs à un type V. Il remplace avantageusement un type mappé manuel comme { [key in AllowedReactions]: Reaction }.
Quand est-il justifié de créer un type intermédiaire comme ReactionMap ?
Un type intermédiaire se justifie quand il est réutilisé à plusieurs endroits dans le projet ou qu'il apporte une sémantique métier forte. S'il n'est utilisé qu'à un seul endroit, il alourdit inutilement la base de code sans apporter de valeur.
Est-ce que supprimer des types intermédiaires peut vraiment rendre le code plus robuste ?
Oui, moins il y a de types à maintenir, moins il y a de risques d'incohérence ou d'oubli de mise à jour lors d'une évolution. C'est l'idée défendue par Dijkstra : la simplicité est un prédicat de la fiabilité.
Ce conseil s'applique-t-il à tous les types mappés ou seulement à certains cas ?
Il s'applique surtout aux types mappés simples qui n'expriment rien de plus que ce que Record<K, V> exprime déjà. Dès qu'un type mappé contient une logique conditionnelle ou des transformations complexes, le créer explicitement reste pertinent.
Comment savoir si mon TypeScript est trop complexe ?
Une bonne heuristique est de se demander si un développeur qui découvre le code peut comprendre un type sans avoir à naviguer dans d'autres fichiers. Si la réponse est non, c'est souvent le signe qu'une simplification est possible.

Alexandre P.
Développeur passionné depuis plus de 20 ans, j'ai une appétence particulière pour les défis techniques et changer de technologie ne me fait pas froid aux yeux.
Poursuivre la lecture


