Les symptômes de l’over-engineering

Benjamin Dumont
Just-Tech-IT
Published in
10 min readOct 28, 2020

En tant que développeur depuis 10 ans, j’ai pu travailler sur de multiples projets dont certains portaient en eux une complexité liée à la volonté de bien faire. C’est ce type de complexité que je souhaite aborder. Cet article est purement subjectif et peut amener différents débats.

Définition

L’over-engineering (ou over-kill) est le fait de créer un programme étant plus robuste et ayant plus de fonctionnalités que nécéssaire, ce qui a pour conséquences de le rendre inutilement complexe voire non opérationnel.

L’ over-engineering peut être un incontournable quand la sécurité et la performance sont critiques et sont les enjeux du programme, mais il peut être critiqué de manière plus usuelle comme étant une perte de temps, de maintenabilité, de productivité et d’argent.

Mies Van der Rohe (architecte en bâtiment) et Richard P. Gabriel (computer scientist) proposent une approche philosophique du problème de l’over-engineering: “Less is more” (approche minimaliste favorisant le développement minimaliste de fonctionnalité) ou encore “Worse is better” (faire du moins bien peut être un plus, essai The Rise of Worse is Better, EuroPal 1990).

Le symptôme de l’anticipation

Le rôle du développeur est aussi d’anticiper les évolutions futures de l’application; de ce fait nous nous projetons souvent trop loin…en amenant de la complexité.

Par exemple, j’ai déjà dû intervenir sur une application mobile avec une base de données très complexe. De ce fait, s’il y a persistence sur l’ensemble de l’application, j’en conclue que l’application doit avoir un mode offline. En interrogeant le métier, je comprends que s’il y a un problème réseau, l’application doit retourner sur l’écran de Login… donc aucun intérêt de prévoir un mode offline. Et voici donc l’état des lieux:

  • La base de données rajoute un complexité non négligeable (car le Model Conceptuel de Données est très lourd).
  • Le MCD est dur à maintenir.
  • S’il y a évolution du MCD, il y a potentiellement un travail à faire sur la partie migration de données.
  • Pertes de performance. On persiste des données de JSON et ces données ne sont utilisées qu’à l’instant T, à chaque interaction utilisateur, les données sont mises à jour.
  • Les objets de la Base de Données remontent directement dans la vue, sans découpe en couche (sûrement par manque de temps).

Le remède: échangez avec votre Product Owner pour déterminer le réel besoin, avec à l’appui des chiffres (plus-value, temps de développement supplémentaire, standards du marché…). Si ce n’est que purement technique, votre équipe de développement, voire une autre équipe de développement peut vous indiquer si vous n’allez pas trop loin.

Le symptôme de la généricité

Souvent, on découvre qu’un composant est réutilisable, et de ce fait, on le rend générique à juste titre afin d’éviter le développement “copier/coller”. Puis on doit l’adapter dans certains cas de figure… encore et encore. Le composant générique contient donc du code spécifique correspondant à certains Use Case. Le composant peu ainsi devenir complexe et très difficile à maintenir. Je suggère dans ce cas de faire du générique par type de comportement, facilitant la lecture du code et donc la maintenance, quitte à dupliquer un peu de code. Et s’il y a bien une base commune, l’héritage est fait pour ça! La règle est simple: si vous devez faire du spécifique sur du générique, soit la notion de générique est erronée, soit il faut utiliser la puissance du langage pour garder un code simple et lisible.

Le remède: faire une code review en prenant compte toutes les remarques, notamment celles de développeurs moins expérimentés et/ou moins expérimentés sur le projet. Si votre code n’est pas compris par cette population, c’est qu’il n’est pas maintenable.

Le symptôme de l’abstraction

Un code maintenable est un code testable et testé. La découpe en couche de notre code est souvent accompagnée de différentes interfaces/protocoles pour avoir un couplage faible entre les couches et ainsi, pouvoir aisément changer une brique (même si c’est peu fréquent) mais surtout rendre notre code testable unitairement.

Le problème avec cette démarche, c’est quand on en abuse. J’ai souvent entendu des phrases du type «tu crées un objet, tu crées une interface». En appliquant cette règle, non seulement on s’éloigne de l’objectif premier à savoir le couplage faible entre les couches, mais aussi on complexifie notre code en ajoutant de l’abstraction partout. De ce fait, le code est rendu moins lisible, donc moins accessible aux néophytes.

Voici des exemples de code (en Swift) utilisant l’abstraction (via les protocoles) ou non:

Et la version avec la couche d’abstraction:

On constate bien que l’abstraction ajoute une complexité (et éventuellement une duplication). Cela ne veut pas dire qu’il ne faut pas faire d’abstraction, mais qu’il faut se poser la question sur sa nécessité. Est-ce que l’objet/service va changer et doit donc répondre à un contrat? Est-ce nécessaire pour faire mes tests unitaires? Le découpage en couches est-il respecté? Les réponses à ces questions mettront en évidence si vous avez besoin d’abstraction.

Le remède: bien définir le besoin avec les autres développeurs. La programmation orientée protocoles est très verbeuse et pas forcément facile à maîtriser aux premiers abords. Je pense qu’il faut modifier l’approche “tu crées une classe, tu crées une interface” en “si je crée une classe, celle-ci est-elle testable au sein de mon application? Dois-je faire une injection de dépendances?”. En se posant cette question, vous transformez une pratique systématique (et pas forcément réfléchie) par un besoin/nécessité informatique.

Le symptôme de l’élégance

Ou le symptôme de l’Ego trip! On a tous codé à un moment du code que l’on trouve beau mais qui n’est compréhensible que par nous.

Pour mettre en évidence le principe de l’ego trip, prenons 2 algorithmes faisant le fameux FizzBuzz (donc le même besoin fonctionnel):

Choisissez l’algorithme que vous préférez entre:

ou

L’exemple est extrême, toutefois on remarque que pour un besoin simple, on peut dériver et développer du code peu lisible/illisible.

Ces exemples sont tirés de ce site, n’hésitez pas à le visiter, il y en a pléthore.

Le remède: montrez votre codes à votre équipe de développeur (via des Pull Requests ou des Code Review). Ils vous limiteront dans vos excès.

Le symptôme de la performance

Oui, la performance est un sujet au coeur de notre métier. Cependant, il y a performance et performance. Par soucis de performance, par méconnaissance de fonctionnalités avancées de notre SDK ou par méconnaissance de notre matériel (nous n’avons pas les même contraintes de performances qu’il y a quelques années), nous sommes souvent amenés à faire du code plus complexe alors que l’enjeu ne le nécessité pas.

Le remède: voici les questions à se poser:

  • Est-ce que la performance est importante? Si non, prenez la méthode la plus simple.
  • Est-ce que même si mon code est moins performant, est-ce ressenti par l’utilisateur? Si non, prenez la méthode la plus simple.
  • Est-ce que la latence ressentie par l’utilisateur est-elle vraiment une gène pour lui? Si non, prenez la méthode la plus simple.

Si une des réponse est oui, codez avec des enjeux de performance et reposez-vous les mêmes questions pour voir si ça nécessite encore une amélioration (si possible).

Le symptôme de la disponibilité

Ne pas avoir le temps pour tenir une deadline nous entraine dans un mode “Fast & Dirty”: afin de répondre à une contrainte de deadline forte, la qualité passe malheureusement souvent à la trappe. Mais l’inverse est aussi possible: si nous n’avons pas un objectif daté (par exemple la fin d’un sprint), il peut arriver au développeur d’en faire trop, et de cumuler l’ensemble des symptômes déjà décrit. Le “parfait” étant souvent l’ennemi du “bien”, celui-ci peut mettre en évidence le problème d’ego du développeur.

Le remède: A l’échelle du projet, l’idéal selon moi est d’avoir un planning co-construit entre les différents acteurs du projet avec une estimation des charges cohérentes (vous pensez Scrum & Sprint?). Pour avoir des charges cohérentes, il faut à la fois de l’intuition, de l’expérience technique et de l’expérience sur le projet. Le poker planning est là pour vous aider (ou toute autre pratique permettant des estimations collectives) :). Et le fait de co-construire un planning permet aussi de partager les enjeux entre les différents acteurs et donc d’éviter les problème d’ego-trip.

Le symptôme du théoricien ou de l’architecte

Lors de nouveaux projets, il peut être très utile d’avoir un avis extérieur sur comment démarrer le projet, notamment d’un point de vue architecture. De ce fait, dans l’IT nous faisons souvent appel à un architecte logiciel pour nous orienter vers un choix technique. Cependant, il faut que ce choix technique soit compris et partagé par l’ensemble de l’équipe sinon, les développeurs risquent de perdre leur motivation pour coder quelque chose qui ne leur correspond pas voire qui peut être trop complexe.

Le remède: comme indiqué précédemment, l’architecte est là pour nous orienter, pas pour décider. Il faut que la décision finale incombe aux personnes qui vont développer le projet et le maintenir car:

  • ce sont eux les acteurs
  • ils connaissent le contexte projet
  • ils connaissent le code existant
  • ils sont les plus aptes à estimer la charge de travail

S’il y a donc désaccord avec l’architecte, c’est à l’équipe de développement de choisir la méthode à adopter en connaissance de cause (car l’architecte est censé donner les avantages/inconvénients de chaque méthode). Une autre façon de faire serait éventuellement de solliciter l’avis d’une nouvelle personne externe afin de trancher ou d’alimenter les arguments des uns et des autres. N’hésitez pas non plus à faire intervenir des profils plus junior car leur regard neuf sur les problèmes permettra de faire ressortir des questionnements plus pragmatiques, plus terre à terre, ou forcera les seniors à justifier les décisions prises tout le long du projet.

Le symptôme de la couverture de code

Avant de traiter ce symptôme, je précise qu’il faut faire des tests unitaires et/ou fonctionnels. C’est impératif pour faire une application maintenable et mesurer les éventuelles régressions. Cependant, faire du test unitaire est chronophage et le code testé doit avoir du sens en terme de non régression. Car en effet, il est facile de tricher sur la couverture de code, il suffit de faire un test unitaire qui passe dans la méthode, sans tester les différents cas de figure possible (comme une valeur null par exemple). Donc si la cible, c’est uniquement la couverture de code, on perd le sens d’empêcher la régression.

Le remède:

  • Déterminez le code qui est important à tester. Dans une architecture DDD par exemple, la couche Domaine est importante à tester car elle contient la logique métier, ainsi que la couche Présentation car c’est ce que voit le client. Quant à la couche Repository (récupération des données), les tests la concernant sont moins importants car souvent dépourvu de logique et non visible par l’utilisateur. Cela ne veut pas dire qu’il ne faut pas la tester, mais, selon le contexte du projet, il faut prioriser les tests sur ce qui apporte de la valeur ajoutée au client et ce qui est le plus susceptible d’évoluer.
  • Dans l’écriture de vos tests, il faut tester des valeurs qui ont du sens: les valeurs null, les bonnes valeurs, des mauvaises valeurs, des valeurs impossibles (à l’instant T, car l’application peu évoluer) et les valeurs limites aux bornes (si applicable). Le rituel nommé 3 amigos ou Example Mapping permet à la fois de partager le besoin/problème, de s’accorder sur le vocabulaire mais aussi de définir ces tests avec l’ensemble des représentants de l’équipe (Product Owner, Testeur et Développeur).
  • Si le temps vous manque, préférez des tests fonctionnels (Behavior Drivent Development) car cela représente réellement ce qu’attends l’utilisateur final
  • Pour résumer, préférez du code ciblé bien testé plutôt que la couverture de code uniquement.

Le symptôme du boy-scout

La règle du boy-scout est la suivante: “Pushez un code plus propre que vous ne l’avez pullé”. En d’autres termes, lorsque vous développez, ne vous contentez pas de coder votre feature proprement; il se peut que du code aux alentours soit moins propre (par exemple, le code appelant votre feature, du code non appelé par la feature mais présent dans la même classe…). Encore une fois, c’est une très bonne pratique mais il ne faut pas qu’elle soit extrême (ou se lancer dans des refactor éternels) car cela peut dérouter l’équipe qui a l’habitude de travailler avec ce Legacy Code et que le code parfait n’existe pas.

Le remède: essayer de garder la logique initiale du code lorsque vous le réfactorez. Par exemple, gardez le naming (ou s’il n’a pas/plus de sens, échangez avec votre équipe pour en trouver un nouveau), ne changez pas toute la mécanique du code existant s’il n’y a pas de changement de fonctionnalité… Si le refactor est léger, soumettez votre code en Pull Request, elle devrait être facilement acceptée. Si au contraire, le refactor est lourd (à comprendre, à lire ou en terme de lignes de code), prévoyez une Technical Story estimée pour donner de la valeur à la dette technique qui vient d’être identifiée et une fois le développement terminé, préférez une Code Review en présentiel afin de débattre et déterminer si vous n’avez pas été un peu trop loin. De plus, faites attention à bien prévenir la qualification et le métier de vos changement car vous êtes tous dans le même bateau: refactorer du code entraîne forcément une phase de recette pour s’assurer la non régression (et vous pouvez alléger cette phase de recette grâce à des tests en amont).

Le mot de la fin

Des principes simples de développeur doivent rester au coeur de notre métier: KISS (Keep It Stupidly Simple!) et YAGNI (You Ain’t Gonna Need It). Clean Code/Software Craftmanship peut aussi vous guider pour rendre votre code plus propre. Bref, codez simple tout en répondant à des besoins métier et en respectant des critères de qualité et de lisibilité pour que vos collègues (ou successeurs) n’aient pas de difficulté à maintenir votre code!

Merci à Nicolas Linard et à Antoine Blancke pour la relecture et les échanges.

--

--