đȘđ§œ 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 đ