La codebase de LiveMentor a 6 ans. Et alors ?

Romain Vigo Benia
LiveMentor Product
Published in
8 min readJun 19, 2018

Bonjour ! Je m’appelle Romain et je suis lead dev chez LiveMentor , l’école en ligne des entrepreneurs. Nous aidons des porteurs de projet à passer à l’action et à changer de vie. Nous les coachons durant plusieurs mois, en leur transmettant des compétences concrètes comme la création de site web, le copywriting ou encore le dropshipping.

C’est quoi ce bordel ? Je n’y comprends rien ! Ça a été codé avec les pieds… je vais tout réécrire ça sera beaucoup plus simple !

En tant que dev, beaucoup d’entre nous avons déjà vécu ce genre de remarques. Bien souvent lors de l’arrivée d’un nouveau membre d’équipe.

Si l’on omet l’approche malsaine de dénigrement de code en production et le côté “code hero”, la phase naturelle qui suit c’est que l’auteur de ce commentaire convainc les bonnes personnes qu’une refonte d’une partie majeure du projet, voir de son entièreté, est nécessaire. Avance rapide de quelques semaines/mois: la refonte est toujours en cours, le development de nouvelles fonctionnalités a été stoppé. La refonte prend deux fois plus de temps qu’initialement prévue. Entre temps le specifications ont changés et le produit a évolué: la nouvelle version ne voit pas le jour.

L’exemple le plus iconique d’une refonte totale qui n’a pas fonctionné est sûrement celui de Netscape qui est passé directement de la version 4.0 à 6.0. Trois années se sont écoulées entre les deux versions. Trois années pendant lesquelles Netscape a travaillé sur une refonte totale de leur navigateur qui n’a jamais vu le jour (la version 5.0). Trois années pendant lesquelles aucune nouvelle fonctionnalité majeur n’a été fournie aux utilisateurs. Trois années pendant lesquelles Netscape n’a cessé de perdre des parts de marché au profit d’Internet Explorer (et oui, c’était une autre époque !).

Le premier commit de notre principal repository date de Mars 2012.

LiveMentor fête ses 6 ans.

Dans le milieu des startups, 6 ans c’est long ! Nous avons connu un pivot et de multiples évolutions de nos services. Nous sommes passé au travers de multiples versions majeures de notre framework (Rails). Plus de 22 personnes ont collaborés sur notre projet principal, avec des niveaux d’experience et de programmation très hétérogènes. Beaucoup d’entre nous sommes montés en compétence sur cette codebase.

Autant dire que nous nous sommes posé la question de refonte totale du projet à de nombreuses occasions.

On a connu plus original comme message de premier commit :)

Il existe de nombreuses bonnes raisons de se lancer dans une refonte totale d’un projet.

Par exemple, la stack historiquement utilisée peut ne pas permettre l’élaboration de nouvelles fonctionnalités souhaitées ou ne permet pas un passage à l’échelle efficient. Ou encore un projet peut radicalement changer de scope et nécessite la mise en place d’une toute nouvelle DSL.

Malheureusement, bien trop souvent, la refonte d’un projet technique est un aveu d’échec et est la seule solution envisageable lorsqu’un projet se trouve dans une impasse témoignant d’une code base très coûteuse à faire évoluer résultante d’années de mauvaises pratiques.

It’s funny ‘cause it’s true

C’est donc avec fierté et un brin de malice que je m’amuse souvent à dire que je travaille sur une “vieille codebase”. J’explique vite par la suite que la codebase a beau avoir été créée il y a longtemps, elle n’en n’est pas moins moderne pour autant. Les lignes de codes qui n’ont pas été mises à jour au cours de ces dernières années peuvent se compter sur les doigts de la main.

Alors, pourquoi n’avons-nous jamais eu à faire de refonte totale ?

La solution tient en deux points: une bonne hygiène de travail et une architecture logicielle flexible aux changements.

L’une des composantes principales de cette hygiène de travail étant la gestion de notre dette technique.

La dette technique est un magnifique outil qui permet de prendre de jolis raccourcis et d’avoir une grande vélocité de développement. Seulement, comme toute dette, si nous ne prenons pas le temps de la rembourser, elle viendra tôt au tard nous hanter. Ça n’a pas toujours été le cas mais, cela fait maintenant quelques temps que nous maintenons à jour une liste de notre dette technique avec toutes les features ou choix architecturaux sur lesquels il faut repasser.

Nous faisons ensuite en sorte de nous libérer du temps sur notre roadmap pour adresser les éléments les plus urgents de cette liste. L’une des difficultés majeures est que seules les équipes techniques ont une vision globale de cette dette. C’est leur rôle d‘alerter et de faire de la pédagogie auprès des autres équipes pour expliquer pourquoi, par exemple, les deux prochaines semaines de développement seront dédiées à un refactor du module de tchat utilisé par des milliers d’élèves chaque mois, totalement transparent pour ces derniers, mais nécessaire pour tenir la charge des mois qui arrivent.

Une couverture de tests convenable est une bonne corde à rajouter à son arc.

Il y a de nombreux bienfaits à écrire des tests. L’un d’entre eux est qu’ils favorisent un comportement d’amélioration incrémentale: lorsqu’un développeur repasse sur du code existant pour ajouter une nouvelle fonctionnalité, il en profite pour refactorer si besoin le code qui entre dans le scope de ces changements. Cela n’est possible que si l’on est confiant que les changements apportés n’entraine aucune régression. Lorsque l’on part d’une code base existante avec un objectif de couverture de tests (80% par exemple), il peut être assez frustrant de suivre le taux de couverture global du projet car ce dernier n’évolue que lentement comparé aux efforts fournis pour ajouter des tests.

Une métrique intéressante que nous utilisons chez LiveMentor, c’est le taux de couverture du nouveau code ajouté. C’est tout de même plus motivant de savoir que les changements que je viens de faire sont couverts à 100% plutôt que de savoir que cela fait évoluer la couverture totale de 0.1% !

CodeClimate fournit ici la couverture de test de la pull request (100%) ainsi que de l’intégralité de la code base (56%)

Maintenir les dépendances à jour, tâche laborieuse mais ô combien importante !

Il n’y a rien de plus frustrant que de commencer à résoudre un problème en utilisant une dépendance déjà présente dans un projet, de tomber sur un comportement étrange ou un message d’erreur vague, pour finir par se rendre compte que la version utilisée est vieille de 3 ans et que la documentation n’est même plus disponible… L’argument choc que beaucoup de parents utilisent pour faire ranger leur chambre à leur enfant “plus tu le fais souvent, moins ça prend de temps” fait entièrement sens ici.

Bien que le moi adolescent n’a jamais voulu tester cette stratégie, le moi adulte l’a mise en place. Concrètement, chez LiveMentor, notre maman est personnifiée par un reminder Slack qui nous rappelle une fois par mois de mettre nos dépendances à jour. La première update a été compliquée et chronophage, celles qui ont suivi, beaucoup moins.

Le reminder mensuel pour nous rappeler de mettre à jours nos gems (dépendances)

La première chose que je fais lorsque je crée un nouveau projet, c’est de mettre en place un style guide

Même si c’est un projet perso et que je suis seul dessus. Couplé à un linter, ce style guide me permet de garder une certaine homogénéité dans la façon d’écrire mon code. C’est encore plus important quand plusieurs personnes travaillent sur le même projet.

Après tout il est souvent plus difficile de lire du code que de l’écrire. Éliminer le bruit apporté par des syntaxes différentes entre plusieurs développeurs est déjà un grand pas en avant. Le process de code review en est grandement simplifié car nous pouvons nous concentrer sur du feedback pertinent plutôt que des détails syntaxiques.

En Ruby, il existe Rubocop, un super linter qui vient avec un ensemble de règles prédéfinies. Comme pour n’importe quelle méthode, il ne suffit pas de l’appliquer bêtement. Dans notre équipe nous l’avons adapté à notre sauce. Si un membre de l’équipe souhaite faire un changement (comme désactiver le calcul de compléxité cyclomatique), on en parle tous ensemble et, si l’on est d’accord , on modifie la configuration de notre linter.

Les changements apportés aux règles par défaut de Rubocop

Toutes ces bonnes pratiques ne serviraient pas à grand chose si l’architecture logicielle sur laquelle se base un projet ne tient pas la route.

Chez LiveMentor, on se challenge régulièrement les uns et les autres sur ce sujet au cours de discussions passionnées et animées. Quoi qu’il en soit, nous tombons toujours d’accord sur ces deux règles :

  • La modularité est reine
  • KISS / Keep It Simple Stupid (“garde ça simple, idiot”)

Bien que travaillant sur un monolithe, cela ne veut pas dire que tout composant logique doit pouvoir converser avec n’importe quel autre composant. Par exemple, pendant longtemps, notre module d’inscription en formation était fortement couplé à notre module de paiement. Lorsque nous mettions à jour le module d’inscription, bien souvent cela impactait le module de paiement.

La solution a été de mettre en place de l’indépendance entre ces deux modules en utilisant une façade (design pattern) et quelques services objects. Un bon exercice mental que je me force à faire lorsque je code, c’est de considérer chaque composant de notre monolithe comme des APIs externes avec lesquelles je ne peux communiquer qu’au travers de façades. Cela m’évite de succomber à la tentation d’aller chercher l’information directement en base de données ou d’utiliser des méthodes internes aux autres composants.

Une autre règle, facile à appliquer est de toujours se poser la question suivante avant de mettre du code en production:

Est-ce que je peux éclater ma logique en sous parties ?

L’entropie tend à augmenter en fonction du nombre de lignes de codes. Nous sommes de simples être humains qui raisonnons beaucoup mieux sur des problèmes dont la portée est limitée. Garder des classes courtes ou encore éclater de longues méthodes en sous méthodes sont de bonnes pratiques à suivre. En plus de réduire la complexité du code, cela permet d’écrire des tests plus facilement et de naturellement respecter le principe de responsabilité unique.

Un monolithe “microservices ready”

Au niveau architecture logicielle, l’une des conférences qui nous a beaucoup inspirée dernièrement et dans laquelle nous avons retrouvé beaucoup de nos valeurs est celle d’Arnaud LeMaire, intelligemment intitulé “Un monolithe microservices ready”

la conférence qui m’a le plus plu ces derniers mois !

Même si nous sommes fiers de l’état actuel du projet, nous sommes d’éternels insatisfaits et n’avons en tête que les futures améliorations à apporter à notre plateforme.

Alors, si toi aussi tu aimes travailler sur de vielles codebases, LiveMentor recrute des développeurs Ruby full-stack !

--

--