🍪🧽 Grand ménage de printemps, la refonte d’un formulaire à étape en suivant les principes SOLID
Temps de lecture : 9 minutes
Plusieurs semaines pour refacto une plateforme, n'est-ce pas ça le rêve ultime de tous les dévs ? Oui ? Non ? Peut-être ? C'est bien quand c'est fini aussi !
Depuis quelques semaines je suis en plein ménage de printemps sur la plateforme de souscription en ligne de La Nef et je vais vous raconter comment on a refacto, entre autre, un énorme formulaire à étapes en respectant les principes SOLID (teaser : ce n'était pas une mince affaire).
Au programme aujourd’hui :
Grand ménage de printemps, la refonte d’un formulaire à étape en suivant les principes SOLID par Mélanie
L’agence de recrutement pour les leads dévs RoR !
Temps de lecture : 9 minutes
Hello les petits Biscuits !
Bienvenue sur la 20ème édition de Ruby Biscuit.
Vous êtes maintenant 471 abonnés 🥳
Avant de vous laissez avec l’article du mois, je tenais à vous rappeler que vous êtes tous les bienvenus pour donner votre avis en commentaire et partager vos expériences sur les sujets que nous abordons. Vous pouvez aussi mettre un petit like ❤️ et/ou partager la newsletter à un copain ou une copine ! 😉
Bonne lecture.
Grand ménage de printemps, la refonte d’un formulaire à étape en suivant les principes SOLID
Petit mot sur La Nef avant d'aller plus loin :
La Nef est une coopérative bancaire française éthique, dédiée au financement de projets écologiques et sociaux, promouvant la transparence et l'impact positif. Nous avons développé leur plateforme de souscription en ligne en 2018. Depuis ils ont beaucoup évolué et la plateforme avec. Notamment depuis le lancement du Big Banque, une grande campagne lancée il y a un peu plus d'un an, visant à transformer La Nef en la première banque éthique indépendante de France. Et dire qu'on les a rencontrés quand ils finançaient Veja alors que personne ne connaissait encore la marque.
Revenons-en au code maintenant. Quel était notre point de départ avant le refacto :
Un formulaire complexe composé de 8 à 12 étapes dont l’ordre et la visibilité de chacune d’elles dépendent de la réponse de l’utilisateur aux précédentes. Il y avait donc un parcours "de base" mais qui allait devenir dynamique dès la complétion de la première étape.
En fonction des choix, certaines étapes pouvaient s'ajouter ou disparaître de la liste. Il était possible de revenir sur ses pas pour modifier ses choix. On avait également des sous-étapes (comme vous pouvez le voir sur l'image du breadcrumb ci-dessous).
Aujourd'hui on ne parlera pas de ces sous-étapes (ok si vous insistez vraiment pour qu'on en parle, on le fera une prochaine fois).
Enfin, chaque étape avait son propre controller et nous n'utilisions pas de librairie pour gérer ces étapes.
Le système d'étapes était géré majoritairement au sein d'un unique controller Subscriptions::ApplicationController
et d'un decorator SubscriptionDecorator
, qui se voyaient attribuer une lourde charge de responsabilités.
Les étapes étaient définies avec leur ordre dans un hash avec des conditions dispersées qui définissaient si les étapes étaient accessibles avec des prédicates. Comme ca :
STEPS = {
subscription: StepDisplay.new(
url: :edit_subscription_url,
order: 1,
predicate: nil
),
child_details: StepDisplay.new(
url: :edit_subscription_child_url,
order: 2,
predicate: ->(subscription, _, _) do
subscription.client_choice&.child?
end
),
user_details: StepDisplay.new(
url: :edit_subscription_user_details_url,
order: 3,
predicate: nil
),
documents: StepDisplay.new(
url: :edit_subscription_documents_url,
order: 4,
predicate: nil
),
payment_option: StepDisplay.new(
url: :edit_subscription_payment_url,
order: 5,
predicate: payment_option_predicate
),
sign: StepDisplay.new(
url: :subscription_sign_url,
order: 6,
predicate: nil
)
}.freeze
On avait dans le controller toute la logique pour retrouver l'étape précédente, la suivante, celle en cours et aussi pour définir si une étape était visible, complète etc. Les conditions étaient vraiment éparpillées de partout, il y en avait même un peu dans un helper et l'évolution de la plateforme nous avait contraint d'ajouter des couches supplémentaire au fur et à mesure des années.
Vous l'avez compris, c'était un vrai champ de bataille. Impossible de toucher à une étape sans en casser une autre.
Il fallait absolument revoir ce système et expliquer aux chefs de projets et au client pourquoi c'était devenu primordial.
Ils ont vite compris.
L'objectif de la refactorisation était clair : Transformer ce labyrinthe de conditions en une architecture plus compréhensible, flexible et maintenable (tiens ça ne ressemblerait pas à la définition des principes SOLID ça ?).
Il ne s'agissait pas seulement de corriger les bugs existants mais de repenser le formulaire pour faciliter l'intégration future de nouvelles étapes et conditions, sans perturber l'expérience utilisateur. En d'autres termes, nous voulions que notre travail en coulisses soit invisible pour les utilisateurs, tout en posant des fondations solides (😏) pour un avenir évolutif et sans tracas. Simple non ?
C'est à ce moment qu'on a relancé, pour la énième fois, le débat sur la gem wicked.
TL;DR : nous n'avons pas utilisé wicked.
Pourquoi pas wicked ?
Face à la nécessité de refactoriser notre formulaire complexe, la gem wicked
s'est présentée comme une option envisageable (au cas où certain(e)s d'entre vous ne connaîtraient pas, c'est la gem la plus populaire pour gérer les formulaires ou autres systèmes à étapes).
Cependant, malgré ses avantages, nous avons décidé de ne pas l'adopter.
La raison ? La gem avait déjà été utilisée sur cette plateforme puis retirée car non adaptée. L'idée de la remettre a soulevé une divergence d'opinions au sein de notre équipe technique ; pour certains, c'est une solution qui fonctionne bien et qui peut faire l'affaire, pour d'autres ce n'est qu'ajouter une couche de complexité qui rendrait notre système plus opaque. C'était l'avis, entre autre, du dév responsable de la plateforme en question. Les décisions techniques, bien qu'avant tout pragmatiques, sont parfois aussi le reflet des dynamiques d'équipe et des préférences individuelles. Nous avons donc décidé de ne pas utiliser la gem wicked.
Après plusieurs jours de R&D sur le sujet, autour d'une table avec plusieurs développeurs, nous avons pu trouver l'architecture parfaite et ça a fonctionné du premier coup. FAUX.
J'aimerais vous dire que c'est vrai, mais vous savez tous ici que ça ne se passe jamais comme ça.
Il fallait faire au plus vite et au mieux, alors on a réfléchi un peu, on avait une piste, une petite idée de ce qu'on voulait et on a itéré. That's the real life.
Alors comment avons-nous fait ? Nous avons gardé les principes SOLID en ligne de mire et nous nous sommes lancés.
🚨 Vous trouverez ici une version simplifiée de notre système d'étapes pour bien comprendre ce qu'on a fait et appuyer les explications suivantes 🚨
S : Principe de responsabilité unique.
Chaque classe ou module dans un programme doit avoir une seule raison de changer, se concentrant sur une seule tâche ou fonctionnalité.
Il suffisait simplement de se demander 40 fois par jours "ok et ça, c'est la responsabilité de qui ?"
Est ce qu'on a réussi à respecter ça ? Oui
On à créé :
Un fournisseur d'étapes (
StepsProvider
) : une classe qui fait office de chef d'orchestre pour fournir aux controllers l'ensemble des étapes du formulaire dans l'ordre et la logique de navigation (comme la détermination des étapes précédentes, suivantes et l'actuelle).Un modèle pour chaque étape : on a transformé chaque étape en classe et toutes ces étapes héritaient d'une même classe parent :
BaseStep
. Ces classes répondaient elles-mêmes aux méthodesvisible?
completed?
link
etname
.
Chacun son métier et les vaches seront bien gardées.
O : Principe Ouvert/Fermé.
Une entité (comme une classe, module, ou méthode) doit être ouverte à l'extension mais fermée à la modification. Donc on doit pouvoir ajouter une nouvelle fonctionnalité sans avoir à changer le code existant.
Est ce qu'on a réussi à respecter ça ? Oui
Avec nos classes qui héritent de BaseStep
, l'ajout d'une étape ne nécessite plus de remaniements complexes et n'impacte aucune autre étape. Cela n'implique "que" de créer une nouvelle classe pour l'étape, sa route, son controller et sa vue.
L : Le Principe de Substitution de Liskov (LSP)
Les instances de classes parentes doivent pouvoir être remplacées par des instances de classes dérivées sans altérer le fonctionnement correct du programme. En d'autres termes, avoir des objets conçus pour être interchangeables.
Est ce qu'on a réussi à respecter ça ? Oui
Tous nos modèles d'étapes respectent le même contrat (aucun modèle n'override l'initializer) et contiennent les mêmes méthodes. On peut donc interchanger les étapes à l'infini sans casser notre système.
I : Principe de ségrégation des interfaces
Suggère que les clients ne devraient pas être forcés de dépendre d'interfaces qu'ils n'utilisent pas, promouvant ainsi la création d'interfaces spécifiques plutôt qu'une interface unique et large.
Est ce qu'on a réussi à respecter ça ? Oui
Parce qu'on a découpé notre interface en plusieurs interfaces plus petites (nos étapes et le StepsProvider
) qui sont spécialisées.
D : Principe d'inversion des dépendances
Les modules de haut niveau ne devraient pas dépendre des modules de bas niveau, mais tous deux devraient dépendre d'abstractions, inversant ainsi la direction conventionnelle des dépendances.
Est ce qu'on a réussi à respecter ça ? Oui
Nos modules de hauts niveaux (Subscriptions::ApplicationController
et la vue du breadcrumb) n'ont pas connaissance de la logique métier derrière chaque étape et donc n'en sont pas dépendants. Ils utilisent une interface qui fait abstraction de cette logique métier.
Résultats des comptes
Ça fonctionne et on en est plutôt fiers ! On a réussi à corriger les bugs présents dans l'affichage et on a aujourd'hui un système d'étapes simple, maintenable et robuste. Si demain nous souhaitons ajouter une étape au formulaire, il nous suffira de créer une nouvelle classe qui hérite de BaseStep
, d'implémenter ses méthodes visible?
, link
, completed?
et name
, puis de l'ajouter à la méthode #steps_list
du StepsProvider
là où on veut qu'elle se trouve.
Enfin, comme mentionné au début de cette newsletter, chaque étape bénéficie de son propre controller et de sa propre vue, une structure que nous avons pu conserver grâce à notre approche de refactorisation. Cela nous permet de maintenir une cohérence et une modularité où chaque étape est traitée de manière autonome, renforçant ainsi la clarté et la facilité de gestion du flux utilisateur.
Dans l'idéal, si on avait eu l'opportunité de concevoir ce système dans le cadre d'un nouveau projet, nous aurions probablement opté pour une architecture centrée autour d'un unique controller. Cela aurait simplifié davantage l'ajout d'une nouvelle étape car nous n'aurions pas eu à ajouter un modèle d'étape, une route, un controller et une vue mais uniquement le modèle de l'étape qui hérite de BaseStep
. Tout serait plus générique.
Quoi qu'il en soit, repenser notre formulaire en se reposant sur un principe aussi éprouvé que celui des principes SOLID facilite grandement la tâche. Cela nous permet de ne pas faire de sortie de route ou de retomber dans des complexités inutiles.
Ce qui a été encore plus précieux, c'est l'esprit d'équipe. Travailler avec des collègues plus expérimentés et qui connaissaient moins la plateforme, a permis la prise de recul nécessaire et le respect des bonnes pratiques. Alors merci à eux pour leurs propositions de solutions élégantes et pérennes et surtout pour leur soutien.
— Mélanie
L’agence de recrutement pour les leads dévs RoR !
Comme vous le savez, derrière Ruby Biscuit, il y a Capsens 👋 , nous sommes une agence web qui fait du Ruby on Rails depuis 10 ans.
Avec le temps on s'est rendu compte que beaucoup de dévs choisissent leur entreprise un peu par hasard alors qu'ils pourraient davantage s'épanouir et se valoriser dans des structures qui leur correspondent mieux. De plus on sait à quel point les process de recrutement peuvent ne pas être adaptés à notre métier et nos profils.
Ce qui tombe super bien c'est que chez Capsens nous avons une excellente connaissance de l'écosystème RoR en France, avec un réseau d'entreprises considérable. La plupart étant des boites bien installées (+ de 5 ans), avec des équipes tech déjà présentes et qui recherchent avant tout des leads dévs et dévs séniors.
C'est pourquoi nous avons décidé de mettre à profit nos ressources pour vous aider à trouver le poste de vos rêves !
Alors tu as plusieurs années d’expériences ? Tu souhaites trouver le prochain poste de lead dév de tes rêves ?
Concrètement voilà ce qui va se passer :
Réponds à cette newsletter en te présentant en deux lignes !
Je t’envoie aussitôt notre test technique pour évaluer ta séniorité
Je te propose des créneaux pour un appel afin de faire ta connaissance et que tu me dises ce que tu cherches pour t’épanouir dans une entreprise.
Je te propose 3 entreprises qui correspondent à ton profil et tes aspirations. Pour chacune de ces entreprises :
Je me charge de te donner un max d’infos et répondre à toutes tes questions par message (horaires, ambiance, taille et séniorité de l’équipe, responsabilités, marge de manœuvre pour la négociation du salaire, localisation des bureaux, politique de télétravail, etc). Pas d’appels inutiles.
Avant de rencontrer le recruteur lui-même, je te mets en relation avec un développeur de leur équipe. Tu pourras alors te faire une idée de comment ça se passe de l’intérieur.
Enfin, le recruteur te recevra ! Il aura déjà eu toutes les informations que je lui aurai transmises sur toi ce qui vous permettra d’aller à l’essentiel !
Lance-toi, on attend ton e-mail ! Et si tu aimes déjà ton travail, ne nous contacte surtout pas ! Ou alors fais-le pour nous recommander ta boîte 😉