Article écrit par Emilie Podczaszy
Ruby est typé dynamiquement, c’est ce qui fait son charme, mais dans certaines situations cela peut s’avérer être un frein au développement si on n’a pas les bons outils pour détecter les erreurs rapidement. Qui ne s’est jamais trompé sur le type d’un paramètre ou fait une faute de syntaxe lors d’un appel de méthode un peu obscure ?
Plus vite ce genre d’inattention est détecté, moins il est couteux de le corriger.
Je vous propose donc de vous parler d’un outil qui peut être salvateur dans ces cas de figure : Sorbet.
En quelques mots, Sorbet est un vérificateur de type progressif pour Ruby. Développé par Stripe et devenu open-source en 2019, le projet est toujours en développement actif.
Cette gem fait à la fois de la vérification statique et dynamique. Elle est composée d’une interface de ligne de commande qui peut analyser un projet très rapidement, ce qui peut, par exemple, réduire le temps d’exécution des tests ou d’un système qui tourne lors de l’exécution du code.
L’aspect progressif de cette gem vient du fait qu’elle puisse être adoptée au fil du temps : par défaut rien n’est analysé. Nous pouvons donc, par exemple, développer une fonctionnalité puis ajouter le typage une fois le code solidifié. Il existe aussi plusieurs niveaux de rigueur allant du plus permissif jusqu’à interdire totalement l’usage du type undefined
.
Comment ça marche concrètement ?
Pour que Sorbet soit en mesure de vérifier le typage, il faut qu’il ait connaissance des types d’objets en entrée et sortie de chaque méthode, des types, des constantes et variables, et ce, pour toute la base de code de la bibliothèque standard de Ruby jusqu’aux différents modules du framework sans oublier les gems.
Pour cela, il existe 2 systèmes d’annotations :
- La déclaration de signature de méthodes (appelée sig) directement dans le fichier source, qui nécessite d’utiliser le module dédié fournit par Sorbet pour écrire la signature, en Ruby, au-dessus de la méthode.
# typed: true class Welcome extend T::Sig sig {params(name: String).returns(String)} def hello(name) "Hello #{name} !" end end Welcome.hello('World') # => "Hello World" Welcome.hello(42) # => Parameter 'name': Expected type String, got type Integer with value 42 (TypeError)
Cette déclaration est vérifiée aussi bien à l’exécution qu’en passant par la ligne de commande. L’accès au code source de la méthode étant obligatoire pour ce format, il est donc à privilégier pour tout le code que nous écrivons.
- La déclaration de signature dans un fichier à part, appelé RBI (Ruby Interface) spécifique à Sorbet, dont la syntaxe est identique au format précédent, mais sans avoir besoin ni de l’inclusion du module
T::Sig
, ni de l’implémentation finale de la méthode.
# welcome.rb class Welcome def hello(name) "Hello #{name} !" end end # welcome.rbi class Welcome sig {params(name: String).returns(String)} def self.hello(name); end end
Cette méthode exige de synchroniser 2 fichiers, elle est ainsi à privilégier pour toutes les dépendances du projet qui n’ont pas vocation à être modifiées régulièrement : gems, bibliothèques, etc.
Nous venons de mettre le doigt sur un inconvénient majeur : comment récupérer les signatures de toutes ces dépendances ? Lorsque nous travaillons avec un framework (Rails ou autre) et plusieurs gems, devoir chercher les signatures de chaque méthode peut vite donner le tournis. Heureusement pour nous, Sorbet propose de générer les fichiers RBI à notre place, malheureusement pour nous, il le fait assez mal. C’est ce qui m’a fait abandonner l’idée d’utiliser cette gem dans une application Rails lorsque j’avais voulu la tester. Mais depuis peu (juillet 2022), Sorbet recommande officiellement d’utiliser Tapioca pour toute la génération des RBI et a mis à jour sa documentation en conséquence. On a maintenant à portée de main des commandes fonctionnelles pour récupérer les RBI communautaires ou officiels et générer les autres fichiers manquants.
Après quelques jours d’utilisation, voici ce que j’ai noté :
Ses avantages :
- la documentation de Sorbet mise à jour avec Tapioca est claire, la mise en marche est très simple ;
- la rapidité globale qui est toujours appréciable ;
- la mise à disposition d’une extension VSCode et le support LSP pour son éditeur favori pour accéder sans effort aux définitions, autocompléter et plus encore ;
- le choix du niveau de rigueur qui permet d’intégrer Sorbet dans un projet existant au fur et à mesure.
Ses inconvénients :
- Sorbet comme Tapioca sont encore en développement et n’ont toujours pas de version stable ;
- toutes les signatures se trouvent dans un dossier supplémentaire dans le projet, qui nécessite du versionnage ;
- l’ajout de code Ruby par-dessus son code Ruby, c’est assez déroutant au début et cela n’en reste pas moins du code à maintenir, même s’il y a très peu de chance que la syntaxe change.
Et RBS dans tout ça ?
La V3 de Ruby a vu naître un tout nouveau système de typage nommé RBS, bien que celui-ci soit natif il n’est encore qu’à ses débuts et est assez limité : il ne propose pas de solution pour vérifier les annotations RBS, ni d’annotations de type au même endroit que la définition de méthode. L’équipe de Sorbet est néanmoins impliquée dans le groupe de travail Ruby 3 pour le typage statique, on peut donc espérer une compatibilité entre les deux systèmes à l’avenir.