Suite à mon dernier article sur la vérification de types automatique en Javascript, dans cet article, on parlera un peu plus du côté non typé du langage. Plus précisément, on parlera des conversions automatiques de types, un aspect du langage qui peut représenter un vrai piège pour ceux qui écrivent des front ends complexes.
Cela fait tellement longtemps qu'on utilise le Javascript pour écrire des applications complexes en front et back end qu'on a presque oublié le sens du mot script dans Javascript. D'habitude, un langage de script est utilisé pour écrire des petits programmes qui étendent la fonctionnalité d'un programme host. Par exemple des shell scripts qui rajoutent des fonctionnalités au shell Linux. Un langage de script est fait pour des petits programmes, plus proches des centaines que des milliers de lignes. En termes de design d'un langage et dans un contexte où les programmes ne sont pas faits pour être longs, on va se permettre de faire des concessions et prendre des raccourcis pour arriver à des programmes plus courts et plus faciles à lire que dans des langages de programmation conventionnels où on va demander aux développeurs de faire un effort de design (typage, architecture, gestion explicite d'exceptions etc) qu'on ne demandera pas aux utilisateurs de langages de scripting.
Dans cet article on parlera de la capacité que a le langage d'utiliser des valeurs d'un type dans un contexte où des valeurs d'un autre type sont demandés, moyennant une conversion automatique entre de l'un à l'autre.
A quoi servent les conversions automatiques de types ? A écrire des programmes plus courts.
L'exemple suivant montre du code Javascript qui récupère une valeur dans un champ texte et affiche son double dans une fenêtre popup.
On remarque qu'on a pas besoin de faire du traitement d'erreur, ni de parser ce qui est dans le textbox explicitement. Si on essaye de jouer avec ce code on verra qu'il gère assez bien des scénarios simples, comme un textbox vide ou avec une valeur non numérique, sans générer des exceptions.
Dans une application moderne, composée de plusieurs couches de code Javascript qui gèrent des aspects différents (persistence, sécurité, réseaux...), les règles choisis pour le Javascript d'il y à 20 ans ne sont pas forcèment les meilleures.
Quelques pièges à éviter
Le but de cet article n'est pas de donner une liste exhaustive de règles ou d'exceptions, on se contentera de parler des deux grosses classes de situations à éviter. En gros, il n'y a que trois types de conversions automatiques en Javascript: en string, en booléen et en nombre. Des les sous sections suivantes on montre des cas où ces conversions automatiques peuvent engendrer des problèmes.
Cas 1: Les opérateurs arithmétiques avec des types non numériques
Les opérateurs arithmétiques essayent de convertir ses inputs en nombres en appelant la fonction ToPrimitive() ce qui parfois va rendre une string. Dans le cas où on utilise l'opérateur + et un des arguments est une string, il transformera tous ses entrées en strings et les concatènera.
L'exemple le plus connu de comportement inattendu est [1,2]+[3,4] qui génère la string [1,23,4] parce que Javascript transforme les deux paramètres en "[1,2]" et "[3,4]" et les concatene.
Cas 2: Expressions booléennes avec des types non booléens
Les opérateurs booléens tels que || et && transforment ses entrés en booléens, mais retournent le valeurs avant conversion. Par exemple [1,2] || [3,4] retourne [1,2] et pas true. Parce que, même si pour exécuter l'opération binaire, Javascript est obligé de les transformer en des valeurs booléennes; la valeur du premier paramètre est retournée.
Quelques conseils rapides
Dans une application complexe, la recommandation est toujours la même: évitez d'écrire du code qui se base sur des conversions implicites.
Vous pouvez utiliser les built-ins String(), Number() et Boolean() pour convertir explicitement des valeurs en ces types respectifs. Les avantages: elles ne retournent pas d'erreur, et ont un comportement uniforme face à des valeurs particulières telles que null, undefined et NaN.
Il y a aussi d'autres astuces qui s'appliquent à des objets complexes. Par exemple, avec les dates on peut utiliser la méthode getTime() pour obtenir un number qui représente un timestamp, ou on peut aussi implémenter/utiliser la méthode valueOf() pour obtenir une valeur primitive d'un objet complexe.
Pour aller plus loin...
Pour éviter des comportements inattendus, il n'y a pas de solution magique. Dans un premier moment, il faut éviter faire des opérations sur des types inconnus à tout prix. On ne prends pas des inputs sans vérifier ses types, on n'écrit pas du code qui si repose sur le contexte d'exécution pour faire la bonne conversion. Dans un autre moment on utilisera des vérificateurs de types automatiques pour qu'il nous aident à éviter ces pièges.
Je peux vous conseiller la lecture de quelques articles qui vont vous aider à vous faire une idée des situations où les conversions de types se font automatiquement et à savoir quel est le choix fait par les moteurs Javascript dans chacune des occasions. J'espère que cela vous aidera aussi à savoir quel type de code ne pas écrire.