La gestion des erreurs en Typescript

By Thomas Gonzalez|Posted 07 Juin, 2022

La gestion des erreurs en Typescript

Introduction

Après s’être intéressé aux objets Error en JavaScript, poursuivons avec la gestion des erreurs en TypeScript.

TypeScript est un langage qui intervient pendant l’écriture du code. Il ajoute un ensemble de fonctionnalités et de contraintes, qui permettent d’aboutir à un code de meilleur qualité, et une meilleure compréhension de ce dernier, à la fois pour l’IDE et à la fois pour les développeurs.

Cependant, l’intégration est limitée car le code exécuté au final reste du Javascript. Je vais vous décrire quelques une des méthodes utilisés pour gérer les erreurs en TypeScript et comment cela peux vous aider.

Un exemple pour mieux comprendre le problème

Imaginons une méthode permettant de récupérer les messages d’un utilisateur en utilisant un id. 

On décide de renvoyer une exception si son identifiant n’est pas valide.

Le code ci-dessus posera plusieurs problèmes :

  • Les autres développeurs utilisant la fonction getUserMessages devront penser à mettre la fonction getUserMessages dans un bloc try/catch.
 
  • throw n’est pas typesafe. Les erreurs récupérées dans un bloc catch sont soit de type any, soit de type unknown. En effet, cela est cohérent avec le fait que JavaScript permet de renvoyer des exceptions de n’importe quel type. Tout cela peut rendre TypeScript moins efficient.
 
  • TypeScript ne peut dire si le code est “sûr”, c’est-à-dire qu’il ne produira pas d’erreurs d’exécution (runtime errors). Une erreur peut donc survenir et en chercher la cause pourra être ardue et frustrant.
 
  • Cet exemple de code rend la réalisation d’une documentation à jour plus difficile, car il faudra “renseigner manuellement les erreurs”. Autrement dit, votre outil de génération de documentation n’indiquera pas automatiquement que le code n’est pas “sûr”.
 

Il n’existe pas de solution parfaite, mais certains patterns de gestions d’erreurs permettent d’éviter les points mentionnées plus haut.

Comprendre les différents types d’erreurs

Avant de s’attaquer aux traitements des erreurs, il peut être intéressant de distinguer les types d’erreurs auxquels on fait face :

  • Les erreurs prévisibles et connues : une connexion à une base de données qui échouent, un appel HTTP qui retourne une erreur… Les développeurs les anticipent en écrivant leur code, et ils prévoient parfois des solutions permettant de les éviter.
 
  • Les erreurs des développeurs : la complexité de l’environnement, la maîtrise partielle d’un élément de la stack technique, ou tout simplement un oubli ou une maladresse peuvent mener à ce genre d’erreurs. Dit plus simplement, ce sont des bugs.

Les approches pour traiter les erreurs en Typescript

L’utilisation de null

Une première idée consisterait à utiliser null en type de retour, dès qu’il y une erreur est survenue. Par exemple, une fonction pourrait renvoyer un résultat nulle en cas d’erreur.

Utiliser null apporte les inconvénients suivants :

  • L’utilisation de getUserAccountCreds est limitée. Comment l’intégrer dans une autre fonction ? Comment réagir en cas d’erreur de cette dernière ?
 
  • L’utilisateur de la fonction ne connaîtra pas la cause de ce qui a provoqué l’erreur.
 
  • Comment distingué une erreur d’une method qui parfois retourne null

Try/catch , throw…

Une autre idée, un peu comme l’exemple d’introduction serait d’utiliser des blocs try/catch.

Les limites des blocs try/catch :

  • La codebase peut devenir illisible et difficilement compréhensible.
 
  • La distinction entre les “erreurs prévisibles” et les “erreurs de développeurs” devient confuse. Les blocs try/catch les capturent toutes, ce qui provoquera des effets de bords, et rendra le débogage plus compliqué.

Utiliser des objets de type Result

Cette solution provient de la programmation fonctionnelle, et elle notamment utilisée en Kotlin, Rust, ou encore Swift. Le principe est simple : un objet Result contiendra le résultat d’une opération. 

Soit une erreur en cas d’échec, soit un objet (ou une primitive) correspondant au résultat attendu.

Différentes librairies permettent d’utiliser ces objets en TypeScript. Par exemple : ts-results, neverthrow. On se basera sur neverthrow pour les explications à venir.

Voici la signature de l’objet Result. C’est ce que retournera désormais chaque fonction dont l’exécution est incertaine :

En reprenant l’exemple précédent, la signature de la fonction getUserMessages devient alors :

On testera ensuite le résultat retourné comme ci-dessous :

neverthrow fournit 4 fonctions wrapper : err, errAsync, ok et okAsync, qui permettent de retourner un résultat attendu (une valeur s’est correctement déroulée ou une erreur.). 

On peut donc transformer la fonction comme cela :

On peut ajouter une fonction de gestion d’erreur personnalisée à une autre fonction existante, pour éviter d’avoir à traiter les éventuelles erreurs avec un bloc try/catch. 

Par exemple, on peut créer une version “sûr” de JSON.parse :

Il est possible dans la même logique en utilisant ResultAsync de gérer les erreurs que les promesses sont susceptibles de renvoyer :

Combiner à des types personalisées, on arrive à quelque chose d’intéressant. Par exemple pour la gestion d’un agenda :

Faut-il abandonner définitivement le try/catch ?

Loin de la. L’utilisation de try/catch se fait juste plus modéré. Mais il y a des avantages à s’en passer parfois :

  • Il est plus simple de produire une documentation (meilleure intégration des erreurs)
 
  • Pas de mauvaise surprise lorsque les développeurs liront votre documentation de manière transversale et se rendront compte sur le fait qu’il exécute du code “unsafe”.
 
  • En utilisant une approche basée sur des objets Result, il est plus facile de produire une API facile et plus conviviale à utilisé.
 

A l’inverse, utiliser des try/catch peut s’avérer utile :

  • Si l’on souhaite effectivement faire remonter une erreur critique.
 
  • Lorsque l’on utilise des librairies externes.
 

Si ce genre d’approche vous intéresse en TypeScript (c’est aussi possible en JavaScript), vous pouvez consulter la page github de neverthrow (https://github.com/supermacro/neverthrow)

Conclusion

La gestion des erreurs est extrêmement importante pour toute application. Vous devez toujours fournir à vos utilisateurs des informations suffisantes sur les erreurs qui se sont produites dans leur application. 

Les différentes erreurs ont une gravité différente et il est utile de savoir quelle est l’erreur exacte qui s’est produite afin de pouvoir la traiter correctement.

Leave a Reply

Your email address will not be published. Required fields are marked *