Ruby est un langage à typage dynamique. On ne peut pas fixer le type des signatures de méthodes, des variables locales, ou autre. Et oui, c'est précisément ce que des outils comme Sorbet et RBS cherchent à résoudre.
Après des années à écrire du Ruby/Rails je pense que de bonnes conventions de nommage offrent 80 % des avantages du typage statique. Pas de gems, pas d'annotations de types, pas d'étape de build. Juste de la rigueur.
Convention over configuration (même pour les noms de variables)
L'une des choses que j'aime le plus dans Rails, c'est sa philosophie de « convention over configuration ». On s'appuie déjà sur le nommage partout : noms de modèles, noms de tables, routes. Pourquoi ne pas appliquer la même rigueur à nos variables ?
Jetons un oeil à cette méthode :
def invite_to_party(user)
# ...
endPersonne qui lit ce code ne s'attend à ce que user soit une String ou un Integer. Tout le monde comprend que c'est une instance de User. C'est presque trop simple à mentionner, mais réfléchissons-y : cette minuscule convention fait déjà le travail d'une annotation de type. Aucun outillage requis, juste des conventions de nommage.
Encoder les types dans les noms de variables
On peut appliquer ce fonctionnement bien au-delà des instances de modèles. Dès qu'un type n'est pas évident dans le contexte, on peut l'encoder directement dans le nom — par exemple avec le suffixe _h pour les hashes. Disons que vous récupérez des attributs utilisateur depuis une API JSON externe :
response = Faraday.get("https://api.mypartner.com/api/v1/users/17")
user_h = JSON.parse(response.body)
# => { "first_name" => "John", "last_name" => "Doe" }Ce petit suffixe _h change tout. Désormais, partout où user_h est passé dans la codebase, chaque développeur comprend deux choses en un coup d'œil : il contient des données utilisateur, et c'est un Hash — pas une instance du modèle User. Aucune confusion, plus besoin de remonter la stack pour trouver où elle est instanciée.
On peut étendre ce pattern à d'autres types : user_ids pour un tableau d'IDs, user_json pour du JSON brut, etc. L'idée est d'encoder le type dans le nom pour que le code se documente lui-même.
Nommer les clés étrangères
Cette approche a particulièrement de sens avec les associations ActiveRecord. Prenez la relation classique Book/Author :
class Book < ApplicationRecord
belongs_to :author, foreign_key: :author_id
end
class Author < ApplicationRecord
has_many :books
endSimple. Mais imaginons maintenant que l'auteur soit en réalité une entrée de la table users. C'est là que beaucoup de codebases commencent à devenir confuses — on voit author_id et on n'a aucune idée qu'il pointe vers un User. Mais si on l'écrit de façon un peu plus explicite :
class Book < ApplicationRecord
belongs_to :author_user, foreign_key: :author_user_id, class_name: "User"
endEn intégrant le nom de la classe dans l'association et la clé étrangère, on ne laisse aucune place au doute. Aucune ambiguité, on sait immédiatement qu'author_user_id référence la table users.
Le nommage comme système de types
Régulièrement, la même conversation refait surface : « Ruby a besoin de typage statique. Regardez ce que TypeScript a fait pour JavaScript — ça sauvera Ruby aussi. » Et bien sûr, TypeScript a changé la donne pour JS. Mais Ruby n'est pas JavaScript.
Le typage statique a un coût — en verbosité, en surcharge d'outillage. Et ce coût est nettement plus difficile à justifier en Ruby, où le duck typing et la métaprogrammation ne sont pas des cas particuliers, mais des principes de conception fondamentaux. Annoter des types par-dessus un langage conçu pour être fluide et expressif revient souvent à aller à contre-courant.
Les bonnes conventions de nommage ne détecteront pas tous les bugs que le typage statique détecte. Mais elles sont gratuites, sans friction, et fonctionnent avec Ruby plutôt que d'essayer d'en faire quelque chose qu'il n'est pas.
(Et si votre projet grandit au point où Sorbet devient vraiment justifié — au moins, vos noms de variables seront déjà impeccables.)