Programmation par contrat en ruby
+ transcription
Mardi 12 décembre j’ai présenté un court sujet aux HumanTalks Grenoble. Il s’agissait de présenter rapidement le principe de programmation par contrat et surtout comment l’utiliser avec un langage très dynamique, ruby. Le choix de ruby est presque à contre courant de la programmation par contrat, qu’on associe facilement avec des langages plus strictes tels Ada.
Si vous voulez avoir directement les slides, vous pouvez les retrouver à la fin de l’article.
Il est question de supprimer un maximum de bug.
Pour ça on a déjà plein de solutions de tests. Mais qui dit test dit “vérifier que le programme écrit fait bien ce qu’on lui demande”
Donc comment augmenter la qualité du code avant même les tests ?
Et oui, c’est pas tout jeune. Etiez-vous déjà né d’ailleurs ?
La programmation par contrat se trouve beaucoup dans l’aviation, le féroviaire, le spatial, le militaire, etc. Tout un tas de domaines où on va parler de criticité du logiciel, de vies.
Les 3 grands concepts de la programmation par contrat
La précondition s’applique sur les paramètres en entrée
La postcondition sur les valeurs en sortie
Je vous ai prévu un exemple hyper complexe pour bien comprendre
Trop facile, n’est pas ? Pourquoi vouloir rajouter des contrats sur un tel code ? Il n’y a aucun problème dans ce code, c’est vrai. Qui s’arrête ici si on lui demande un tel code ?
hum…
Oops. A noter que c’est la méthode écrite qui va lever l’exception, vous ne saurez pas forcément qui a passé la mauvaise valeur, d’où elle vient.
Programmation défensive FTW!
Bon ben voilà, c’était pas si compliqué
…
-_- Et oui, il va falloir rajouter plein d’autres cas à vérifier
En voilà des questions qu’elles sont primordiales
Dans les exemples précédent tout est inversé. Si la méthode retourne
nil
quand une mauvaise valeur est entrée c’est l’appelant qui va la recevoir et doit se débrouiller avec. Et si la valeur en entrée est mauvaise, c’est à la méthode qui effectue le traitement qui doit s’en occuper.
Et si au lieu de corriger les problèmes ont ne les laissaient juste pas rentrer ?
Indiquons les “types” (ce ne sont pas nécessairements des types mais des contraintes) sur les entrées et sorties
Comme vous pouvez le voir, c’est pas juste un type. C’est un type + une contrainte
Un exception est levée. Comme avant ? Pas exactement, l’exception est levée au moment de l’appel, pas dans le corps de la méthode.
Idem ici (et pour tout ce qui ne correspond pas à la contrainte)
Comme dit, l’exception est au moment de l’appel. La responsabilité de fournir la donnée correcte incombe donc à l’appelant, ce n’est plus à la méthode de le gérer
Et si on s’amusait avec la sortie ?
Encore une exception de levée. Dans le corps de la méthode.
L’exception est levée dans la méthode, pas dans le code appelant. C’est donc à la méthode de respecter son propre contrat et non à l’appelant de faire avec une mauvaise donnée reçue.
Imaginons qu’on veuille ce comportement. Pourquoi ? Parce que ;-)
Le plus simple, rajoutons de la logique dans la méthode
Ca marche, certes
Mais grace à cette lib de contrat, on peut maintenant faire de l’overloading de méthode, en matchant sur les contrats. Suivant la valeur du paramètre, une méthode ou une autre va être exécutée -> Code plus clair, plus lisible, plus simple
Ok, on a joué avec un positif. Il y a autre chose de dispo ?
On peut explicitement dire qu’on ne veut pas de paramètres, et qu’on retourne n’importe quoi
On peut aussi dire que notre tableau ne doit contenir que des
String
. Pas d’autres types pas denil
. Ou spécifier notreHash
. Ou tiens, un tableau qui contient desHash
chacun contenant une clé avec le symbole:date
et une valeur numérique.Expressif, simple, clair, lisible
En ruby on peut nommer les arguments. Ca permet de faire
connect("sqsc", user: current_user.name", password: input.value)
Ici on indique que
host
est optionnel (et la valeur par défaut est dans la méthode) mais aussi quepassword
est une chaine de caractères avec au moins 8 caractères
Bien évidemment ca fonctionne aussi sur des fonctions
Et sur du duck typing. Tout objet ayant une méthode
parent_user
sera valide. Et associé avec de l’overloading on va gérer et renvoyer une erreur propre si ce n’est pas le cas. Encore une fois, clair, très concis, beaucoup plus lisible et utilisable.
La bibliothèque utilisée ici
Plus que de la documentation, on a une documentation exécutable et donc forcément à jour. Il peut néanmoins y avoir un coût à l’exécution mais on peut désactiver en production par exemple. Et par certains côtés ont s’éloigne un peu de l’idiomatique ruby, mais je pense que ca vaut le coût !
Bref, allez tester !
Si vous préférez voici la version pdf.