Développeurs, ne réinventez pas la roue¹

¹ Sauf si vous savez très exactement ce que vous faites et que vous avez une excellente raison de le faire

Dans le développement informatique, il existe un proverbe, basé sur une version de la vie courante, et qui dit ceci :

Ne réinvente pas la roue.

Ce qui souvent signifie quelque chose comme :

Ne perds pas du temps à développer une fonctionnalité qui est déjà remplie par une bibliothèque open source que tu peux te contenter d’ajouter à ton projet.

Il y a aussi une variante qui dit :

Réinventer la roue carrée.

Et qui signifie que non seulement on a recréé quelque chose qui existe déjà, mais qu’en plus cette recréation ne fonctionne pas.

Mais comme tous les grands proverbes, citations et principes de l’informatique – au premier lieu duquel je mettrais KISS1, entre beaucoup d’autres –, sa signification réelle est peu claire et mal comprise. C’est pourquoi je vous propose une petite réflexion autour de ce proverbe et de tout ce qu’il implique.


  1. Je ne donnerai pas la signification de cette abréviation, parce que pour commencer il faudrait choisir l’une des 17 variantes à laquelle elle fait référence (ou l’une des 7 variantes selon Wikipédia EN), et même les plus courantes (« Keep it simple, stupid! » ou « Keep it simple and stupid ») peuvent donner lieu à des interprétations très différentes.

Parfois, ça n’est pas grave – ou même intéressant – de réinventer la roue

Une question de besoin

Quand on y réfléchit, ce proverbe est un peu idiot et montre une limite quasi immédiate : si le concept de roue est universel dans l’humanité, sa réalisation peut être très différente selon ce dont on a besoin. Une roue de char, de voiture ancienne ou moderne, de formule 1, de draisine, de draisienne, de VTT ou de vélo de course, d’avion de chasse – de ligne – d’épandage, de train, de fauteuil de bureau, de tracteur – sans compter toutes les roues qui ne roulent, comme une roue à aubes, un volant, un rouet, une grande roue, une roulette…

Autant de situations très différentes qui ont mené à des solutions technologiques qui partagent un concept commun, mais n’ont pas grand-chose à voir dans la réalisation.

La métaphore est très pratique parce qu’elle directement transposable dans le monde du développement informatique : des besoins différents impliquent des solutions différentes, même si elles partagent un concept commun. (Je vous laisse ici faire votre propre digression vers la notion de convergence évolutive).

On en déduit immédiatement la première raison qui poussera à développer une nouvelle solution même s’il semble en exister une qui réponde au problème.

« Réinventer la roue » peut être nécessaire s’il n’y a pas de roue adaptée ; si les solutions disponibles ne répondent pas réellement au besoin, il faut en développer une nouvelle.

La taille compte

Aucune métaphore filée ici, mais un simple constat purement informatique.

Parfois, on a une bibliothèque qui fournit une simple fonction qui serait très pratique dans le projet… mais cette bibliothèque est gigantesque – ou vient avec une myriade de dépendances. Est-ce qu’on a besoin d’alourdir sensiblement le projet, avec tout ce que ça implique1 pour éviter d’avoir à réécrire une simple fonction ?

Probablement pas.


  1. Espace disque, téléchargements, temps de compilation, d’intégration continue, de déploiement, surface d’attaque, quantité de failles potentielles, risques de régressions lors des mises à jour, consommation en ressources dans un monde où l’écologie et l’économie d’énergie deviennent de plus en plus importantes, et j’en passe…

L’immense majorité des roues réinventées sont carrées, et bloquent les projets

Un déluge de roues carrées

Douze années d’expérience – dont une partie en SSII/ESN – et de pratique du logiciel libre m’ont mené à travers beaucoup de projets de toutes tailles et de toutes natures.

Dans ces projets, beaucoup de roues ont été réinventées – quelle qu’en soit la raison, ça n’est pas le sujet pour ce point – et pratiquement toutes avaient au moins l’un des défauts suivants, comparativement à des solutions standards et adaptées au problème qu’elles adressaient. Et souvent plusieurs.

  • Problèmes graves de sécurité.
  • Mauvaise gestion des cas aux limites – c’est quasiment systématique : le cas général est à peu près bien géré, mais dès qu’on s’en écarte c’est la catastrophe.
  • Mauvaises performances.
  • API peu claire, parfois devenue mensongère.
  • Utilisation peu pratique – en particulier dès que l’on sort du cas nominal exact pour lequel elles ont été prévues.
  • Documentation absente ou obsolète (interne ou externe au code).
  • Mauvaise découvrabilité d’une partie ou de tout ce qui a été réécrit – ce qui conduit à des situations ubuesques où une même fonction est réimplémentée plusieurs fois dans le même projet.

Toutes ces mauvaises réimplémentations de fonctionnalités standards ont un impact important sur le projet, augmentent sa dette technique et à terme le rende difficile à maintenir.

Plus les fonctionnalités réinventées sont des standards « de base », plus elles sont utilisées et plus il devient compliqué de s’en débarrasser, ce qui n’aide pas au rétablissement du projet.

Vous n’êtes pas seul au monde, et vos moi-du-futur et moi-du-passé sont des personnes différentes

Les gros avantages des bibliothèques tierces « standard », c’est qu’elles sont généralement bien documentées et qu’elles ont une bonne communauté qui les utilise, prête à vous aider en cas de besoin.

Ce qui n’est pas le cas de l’alternative que vous avez écrit en pensant bien faire.

En cas de problème dans le code qui implique une bibliothèque commune, un tiers – potentiellement vous, dans six mois – peut facilement se référer à la documentation officielle, ou demander de l’aide sur StackOverflow ou tout autre canal de discussion.

Mais si le problème est dans du code réinventé, il faudra faire avec ce code seul, et rien d’autre. Ce qui peut énormément compliquer la résolution du problème, je parle d’expérience.

N’oubliez pas non plus le principe de moindre surprise et les connaissances communes. Si le standard de fait pour résoudre un problème X est d’utiliser la bibliothèque Y dans le type de projet sur lequel vous travaillez, on s’attendra à ce que vous aussi l’utilisiez. Et donc une personne qui reprendrait la maintenance devra commencer par comprendre que vous n’utilisez pas le standard, puis devra comprendre ce que vous utilisez, et comment l’utiliser – ça complique énormément les choses.

C’était fourni dans le standard (ou dans une bibliothèque déjà utilisée)

Un classique, hélas très – trop – souvent croisé en conditions réelles, surtout sur des fonctions assez simples : quelqu’un a croisé une situation qui demandait quelque chose de non trivial, mais presque, et s’est empressé de l’implémenter.

Sans se rendre compte qu’il y avait déjà une méthode qui remplissait exactement son besoin dans la bibliothèque standard ou dans l’une des bibliothèques déjà présentes dans le projet, et qui le faisait mieux.

Un exemple concret

Ce problème est tellement présent que c’est littéralement une question de la démonstration du système d’entretien technique via CodinGame en Java.

La situation est la suivante : vous avez un tableau d’entrées uniques triées en entrée, vous devez implémenter une fonction qui renvoie l’index de l’objet passé en argument, avec des performances raisonnables sur de gros tableaux (> 1 million d’entrées).

L’astuce ?

L’API standard de Java possède une fonction qui répond exactement à la demande – et l’appeler est la seule façon d’avoir tous les points à la question… et pourtant beaucoup d’utilisateurs se lancent dans sa réimplémentation, avec plus ou moins de succès.

La solution standard aurait fonctionné si vous aviez lu la documentation

Ici c’est un problème qui touche plutôt les réimplémentations de grosses fonctionnalités, et qui lui est trop classique.

Il arrive que l’on essaie d’utiliser une bibliothèque, qu’on la trouve trop complexe ou mal fichue, et qu’on décide de la réimplémenter, après une étude rapide ou un essai de quelques heures, « parce que c’est encore plus simple comme ça ». Ce qui donne naissance à un monstre qui finira par couter des dizaines de fois plus cher qu’un investissement à comprendre et utiliser correctement ladite bibliothèque.

L’exemple typique dans le monde de Java, c’est les innombrables entreprises qui ont réimplémenté leur propre ORM après avoir regardé et parfois essayé Hibernate. Je n’en ai jamais croisé un seul qui arrive à la cheville d’Hibernate ; en général c’est des usines à gaz avec de la génération de code et des API infiniment trop verbeuses qui rendent toute « requête » illisible et impossible à maintenir en un temps raisonnable. La documentation d’Hibernate a longtemps été atroce, c’est un fait ; mais l’effort fait pour la comprendre et utiliser correctement toutes les fonctionnalités de ce très puissant produit aurait toujours été très rentable. En fait, l’effort pour remplacer ce genre d’outil est généralement tel qu’il aurait été plus rentable de comprendre l’outil et réécrire complètement sa documentation.

Comment savoir si je dois importer une bibliothèque ou réinventer la roue ?

Un rappel sur ce qui est important dans un projet informatique

On ne fait pas un projet pour l’amour du code1. Un projet informatique doit remplir un besoin, et il doit le faire bien – c’est-à-dire qu’il doit répondre au besoin des utilisateurs de façon robuste, maintenable, avec des performances acceptables.

La conséquence, c’est qu’il n’y a pas tellement de place là-dedans pour du code qui n’existerait que pour faire plaisir au développeur qui l’aurait conçu. Non, le code doit être le plus efficace possible dans les missions susnommées ; et la valeur ajoutée du développeur est dans la réponse au besoin de l’utilisateur.

On en déduit directement que tout ce qui permet au développeur de se concentrer sur ces problèmes est le bienvenu. Le développeur doit choisir ses outils dans ce but : toute énergie perdue à se battre contre des outils inadaptés ou à en créer inutilement est de l’énergie perdue pour le projet.

Ici, je pourrais partir sur une grande dissertation sur les principes KISS, la gestion de projet en général et les considérations métaphysiques qui découlent de tout ça – mais ça n’est pas le sujet de ce billet. Non, le sujet de ce billet, c’est le suivant :

Est-ce que je implémenter ce truc moi-même ou prendre une bibliothèque – et laquelle ?

  1. Ne prenez pas la première bibliothèque venue « parce qu’elle a l’air de faire le boulot » ou « Parce que tout le monde l’utilise ».
  2. Étudiez votre besoin, présent et prévisible. Vraiment.
  3. Vérifiez que vous n’avez pas déjà un outil pour y répondre dans votre projet.
  4. Regardez ce qu’il y a comme bibliothèque pour y répondre.
  5. Vérifiez que les candidats sont sérieux en regard de la responsabilité que vous lui délèguerez et des contraintes projet : suivi des mises à jour, qualité de la documentation, communauté, problèmes connus…
  6. Vérifiez les impacts de cette bibliothèque (sa taille, entre autres).
  7. Enfin, et seulement maintenant, vous pouvez prendre votre décision : si vous pouvez utiliser une bibliothèque, vous savez laquelle et pourquoi ; si vous devez réimplémenter une fonctionnalité, vous savez aussi pourquoi.

Le temps passé à tout ça dépend de la complexité et de la criticité de la fonctionnalité en question : quelque chose de presque trivial peut se suffire d’une recherche rapide sur Internet ; mais vous devriez réfléchir encore avant de vous attaquer à cette implémentation maison d’un système cryptographique…

Dois-je remplacer ma bibliothèque par une implémentation personnalisée de la fonctionnalité ?

Tout dépend de pourquoi.

Parfois le besoin ou le contexte ont évolué et quelque chose qui fonctionnait bien ne marche plus. C’est alors le moment de refaire une passe d’évaluation (telle qu’au paragraphe précédent), qui peut déboucher sur un changement d’outil, une nouvelle implémentation, ou la décision de ne rien changer. Mais sans réflexion, aucune décision correcte n’est possible.

Par contre, « Je ne comprends plus ce que fait ma bibliothèque » n’est jamais une bonne raison de la virer. C’est un classique (là encore, je pense à Hibernate en Java, mais pas seulement), mais c’est surtout la garantie de se créer d’autres problèmes ensuite. Soit on a mal compris le problème, auquel cas on peut se diriger vers une mauvaise solution ; soit on a mal compris l’outil – ça arrive – et on ne peut pas l’exploiter correctement.

La seule attitude valable face à une situation où l’outil nous échappe, c’est de réussir à comprendre au moins pourquoi il nous échappe (même si on ne parvient pas à comprendre l’outil en lui-même).


  1. Sauf cas particulier, comme des projets d’étude, de test de langage, etc. qui ne rentrent par conception pas dans le cadre de ce billet.

Et donc :

En conclusion

Ne réinventez pas la roue !

Sauf si vous savez très exactement ce que vous faites et que vous avez une excellente raison de le faire.

Dans ce cas, documentez-le.


Icône d’après Fred.th, CC BY-SA 3.0.

11 commentaires

Salut.

Dans les raisons légitimes qui peuvent expliquer qu’une roue n’est pas adaptée, on peut aussi mentionner :

  • Lorsqu’intégrer la roue demande presque autant de boilerplate que de code qui l’utilise, ce qui peut arriver typiquement lorsque l’on intègre cette roue derrière une interface (et un pattern Adapter) pour se garder la possibilité de la remplacer plus tard.
  • Lorsque la roue en question nous dépossède du contrôle sur quelque-chose que l’on a besoin de contrôler par ailleurs (exemple simpliste : une bibliothèque qui ne nous permet pas d’utiliser notre pool de connexions, ou qui nous impose sa propre implémentation).
  • Lorsque aucune roue pré-existante ne correspond aux standards de qualité du projet que l’on développe (je repense à un parseur TNEF sur le Pypi qui contenait des sys.exit(0)).
+2 -0

Pour le premier cas, de mon point de vue ça se discute, parce que le boilerplate est moins sensible, moins complexe, nécessite moins de maintenance et celle-ci est plus simple que du code avec un vrai impact fonctionnel. Donc sans plus de détails ça ressemble à un cas où il est intéressant de passer par une bibliothèque externe. On pourrait disserter de l’utilité des adaptateurs, mais ça serait un gros hors sujet (en résumé : découplez, mais ne créez pas de convertisseurs ou d’interfaces qui ne seraient là que "au cas où").

Les deux autres points fort partie du processus d’évaluation des bibliothèques.

Sinon je précise que le message de ce billet, c’est bien de ne pas réinventer la roue, et que vous pouvez (devriez ?) conserver ce réflexe dans vos projets. Mais que c’est une action qui doit être réfléchie.

Merci pour ce billet.

On ne fait pas un projet pour l’amour du code. Un projet informatique doit remplir un besoin, et il doit le faire bien – c’est-à-dire qu’il doit répondre au besoin des utilisateurs de façon robuste, maintenable, avec des performances acceptables.

C’est ce que j’essaie d’expliquer à certaines personnes et ce que j’aurais expliqué à Ge0 de 20 ans si je l’avais en face de moi aujourd’hui.

C’est même ce que certaines personnes avaient essayé de m’expliquer à cet âge-là et que je n’avais pas écouté pour des raisons que je connaissais très bien à cette époque : elles faisaient des énormités en code qui les faisait perdre en crédibilité à mes yeux, et il était impossible pour moi de les écouter. C’était même viscéral au point que je fisse tout le contraire de ce qu’elles faisaient.

Maintenant, ma question pour toi : comment équilibrer le côté « amour du code » que certains développeurs (moi y compris) ressentent parce que développer peut être une activité créative, et le côté « je dois répondre au besoin de l’utilisateur » ?

C’est une question que j’ai commencé à me poser, parce qu’aujourd’hui je suis davantage centré sur le besoin de mes utilisateurs, mais ça me déprimerait de faire quelque chose que je déteste d’un point de vue technique (au hasard, bosser sur Python 2 pour prendre un cas extrême, mais je suppose que tu vois l’idée).

Tu en penses quoi ?

C’est ce que j’essaie d’expliquer à certaines personnes et ce que j’aurais expliqué à Ge0 de 20 ans si je l’avais en face de moi aujourd’hui.

Ge0

Haha, il aurait fallu que je l’explique aussi au jeune SpaceFox…

Maintenant, ma question pour toi : comment équilibrer le côté « amour du code » que certains développeurs (moi y compris) ressentent parce que développer peut être une activité créative, et le côté « je dois répondre au besoin de l’utilisateur » ?

C’est une question que j’ai commencé à me poser, parce qu’aujourd’hui je suis davantage centré sur le besoin de mes utilisateurs, mais ça me déprimerait de faire quelque chose que je déteste d’un point de vue technique (au hasard, bosser sur Python 2 pour prendre un cas extrême, mais je suppose que tu vois l’idée).

Tu en penses quoi ?

Ge0

Il y a plusieurs possibilités, comme :

  • Chercher un travail dont je sais qu’il y aura des problèmes techniquement intéressants à résoudre. Même si ça ne sera pas 100 % du boulot (parce que ça n’est jamais le cas), ça peut remplir une bonne partie de ce besoin de « code créatif ».
  • Idem, mais avec des projets open-source qui ont besoin de contribution.
  • Faire des projets-jouets pour tester des trucs.

Bonjour,

Le moins qu’on puisse dire, c’est que vous vous répondez fort bien entre vous deux, Nohar et Spacefox. Merci pour ce billet.

J’ai souvent été, et je suis encore parfois pour mes projets perso, dans le wah j’y comprends rien je vais recoder ce truc moi-même ça ira plus vite / mieux; pour le regretter un temps plus tard. Au niveau pro ce n’est pas (encore ?) moi qui décide quoi choisir ou ne pas choisir, c’est peut-être mieux comme ça.

Je retiendrais quand même surtout:

  1. Ne prenez pas la première bibliothèque venue « parce qu’elle a l’air de faire le boulot » ou « Parce que tout le monde l’utilise ».
    Oui, d’abord, essayez-la, éventuellement sur un petit projet test, pour voir si vraiment ça fait ce dont vous avez besoin, et si vraiment ça vous simplifie la vie

  2. Vérifiez que vous n’avez pas déjà un outil pour y répondre dans votre projet.
    Oui, il y a bien plus de packages dans la bibliothèque standard de Java et de modules Python fournis d’office que vous imaginez… et c’est pareil avec tous les langages modernes. je renvois mon moi du passé refaire des thread pool à ma sauce parce qu’utiliser ce que propose java.util.concurrent n’est pas immédiatement évident, ou encore réimplémenter parce que je ne savais tout simplement pas que ça existait. Donc, explorez la bibliothèque standard ! Faites-le de temps en temps car ça évolue au fil des versions, et ne le faites pas uniquement quand vous cherchez quelque chose de précis. Je vous assure que c’est très instructif ! A noter que parfois, c’est même des bibliothèques open source très influentes qui finissent par se retrouver en partie inclus en standard… je pense à boost en C++.

  3. Vérifiez les impacts de cette bibliothèque (sa taille, entre autres).
    JE crois que c’est surtout le monde du front-end qui aurait besoin d’une cure d’amaigrissement. Coucou l’éditeur de texte qui fait plusieurs centaines de Mo parce qu’il embarque un browser complet, ou le module npm qui sert juste à savoir si un nombre est impair.

+2 -0

Donc, explorez la bibliothèque standard !

Même hors de toute considération sur la réinvention de la roue, c’est une activité super instructive, surtout lorsque l’on ne se borne pas à la doc mais que l’on va également voir comment c’est implémenté (et testé).

Je ne connais pas de façon plus efficace d’apprendre les idiomatismes et les bonnes pratiques d’un langage.

+4 -0

des besoins différents impliquent des solutions différentes, même si elles partagent un concept commun. (Je vous laisse ici faire votre propre digression vers la notion de convergence évolutive).

Je voulais noter au passage que les deux phénomènes (convergences de solutions logicielles et convergences évolutives) n’ont en fait rien à voir. Dans le premier cas, on met volontairement au point une solution à un problème donné, parfois (lire souvent) en s’inspirant de solutions existantes à des problèmes proches. Dans le second cas, des traits similaires apparaissent indépendamment sans aucune contrainte de répondre à un besoin et sont ensuite sélectionnés par des contraintes similaires. Ce serait comme écrire des logiciels au pif (cf les singes avec leurs machines à écrire), en mettre une partie sur Github et sur bitbucket, faire des commits au pif (la plupart vont simplement casser le code existant) en ne gardant que les branches qui sont utilisées par des tiers, et constater que certains projets sur les deux plateformes offrent des solutions similaires avec un design logiciel similaire. De manière plus intéressante, comme cette stratégie évolutive explore l’ensemble des codes possibles en brute-force, on va aussi trouver des solutions complètements différentes au même problème.

La différence fondamentale est que dans le cas du logiciel, on produit du code avec l’intention de répondre à un besoin identifié. Si ce besoin a déjà été croisé, de prêt ou de loin, on va généralement pas trop se fatiguer à réfléchir à un design complètement différent. On réutilise une roue existante ou le concept de roue. On peut s’éloigner plus ou moins, réfléchir à la bonne roue à prendre, mais on reste sur de la roue. Lorsqu’on est là pour produire du code "métier" pour répondre à un besoin, c’est une stratégie qui minimise les risques de passer des plombes à trouver une autre solution. Le revers de la médaille, c’est qu’elle minimise aussi les chances de tomber sur une stratégie différente mais bien meilleure à long terme.

Il ne faut pas oublier de prendre du recul de temps en temps et se demander si finalement, on pourrait pas utiliser autre chose de complètement différent à la place (genre faire un maglev pour se déplacer). Là où la nature fait du brute-force pour explorer les réponses aux contraintes environnementales (et arrive de temps en temps sur les même solutions), on doit sortir volontairement de nos zones de confort déjà trouvées et aller chercher d’autres coins confortables dans l’espace des solutions logicielles (sinon on reste coincés sur des solutions qui sont toutes essentiellement les mêmes).

La différence fondamentale est que dans le cas du logiciel, on produit du code avec l’intention de répondre à un besoin identifié. Si ce besoin a déjà été croisé, de prêt ou de loin, on va généralement pas trop se fatiguer à réfléchir à un design complètement différent. On réutilise une roue existante ou le concept de roue. On peut s’éloigner plus ou moins, réfléchir à la bonne roue à prendre, mais on reste sur de la roue. Lorsqu’on est là pour produire du code "métier" pour répondre à un besoin, c’est une stratégie qui minimise les risques de passer des plombes à trouver une autre solution. Le revers de la médaille, c’est qu’elle minimise aussi les chances de tomber sur une stratégie différente mais bien meilleure à long terme.

À mes yeux, il y a une dissonance fondamentale entre ce raisonnement et la réalité du terrain (et c’est d’ailleurs la même raison qui explique que je sois pas spécialement emballé par l’idée défendue dans ce billet). Cela vient du fait que le besoin (et a fortiori la façon d’y répondre) n’est pas toujours connu a priori, et qu’il est très souvent amené à évoluer dans le temps.

Ce phénomène a plusieurs causes :

  • Quand le logiciel est censé être utilisé directement par des humains, qui ne savent pas forcément ce dont ils ont besoin au départ (encore moins le formuler), mais qui peuvent s’adapter et discuter quand on leur met quelque chose entre les mains,
  • Quand la méthode de développement est purement itérative et repose sur l’alternance entre découverte et réponse au besoin.

Dans ce genre de cas, l’idée n’est pas tant de fournir dès le départ une solution à un besoin, mais bien un outil qui va assister les utilisateurs dans l’accomplissement d’une tâche ou d’une autre, en cherchant à maximiser la "valeur" ("l’utilité", le "temps d’utilisateur gagné") ajoutée à chaque itération (ce qui est distinct des priorités énoncées dans le billet, d’ailleurs).

Dans ce genre de cadre, la formulation du besoin se précise (ou peut même changer du tout au tout) à mesure que progresse notre maîtrise des problématiques auxquelles doit répondre le logiciel. Maîtrise qui peut naître notamment du travail déjà réalisé et qui peut impliquer que l’on découvre que l’on reposait jusqu’à présent sur une hypothèse qui est (ou est devenue) fausse.

Une formulation que j’aime beaucoup et qui résume tout cela, c’est que l’ingénierie logicielle, c’est finalement "de la programmation intégrée dans le temps".

Là où se situe la limite de tout ce raisonnement à propos de la réinvention de la roue, selon moi, c’est qu’on a très facilement tendance à ne pas tenir compte de ce changement dans le temps dans nos raisonnements, et à continuer de réfléchir comme si l’on avait un cahier des charges auquel les éléments ne peuvent que s’ajouter, comme si tout découlait d’une compréhension parfaite de la problématique dès le début du développement.

C’est sûrement vrai dans certains domaines du développement, mais pas du tout le mien, où je réfléchis quotidiennement à la facilité avec laquelle je peux péter et refaire entièrement des pans entiers de mon système au pied levé, du jour au lendemain, parce que telle feature dégage du jeu, ou parce qu’à l’usage on finit par se rendre compte qu’on a fait fausse route avec nos hypothèses précédentes.

+1 -0

Dans ce genre de cas, l’idée n’est pas tant de fournir dès le départ une solution à un besoin, mais bien un outil qui va assister les utilisateurs dans l’accomplissement d’une tâche ou d’une autre, en cherchant à maximiser la "valeur" ("l’utilité", le "temps d’utilisateur gagné") ajoutée à chaque itération (ce qui est distinct des priorités énoncées dans le billet, d’ailleurs).

Il suffit de définir le besoin de cette façon (plutôt que le besoin comme étant celui formulé par le client), et on se retrouve à dire la même chose, en fait. Peu importe que la cible (en terme de fonctionnalités) soit mouvante, le logiciel est quand même designé volontairement de façon à répondre au besoin qui est d’une part un ensemble de fonctionnalités à un instant donné, et d’autre part pouvoir évoluer lorsque les fonctionnalités changent/la charge que l’on veut pouvoir gérer grimpe/etc.

Là où se situe la limite de tout ce raisonnement à propos de la réinvention de la roue, selon moi, c’est qu’on a très facilement tendance à ne pas tenir compte de ce changement dans le temps dans nos raisonnements, et à continuer de réfléchir comme si l’on avait un cahier des charges auquel les éléments ne peuvent que s’ajouter, comme si tout découlait d’une compréhension parfaite de la problématique dès le début du développement.

C’est un travers qui me semble complètement orthogonal à la notion de réinventer la roue ou non. Le fait que les besoins changent dans le temps veut simplement dire qu’il faut évaluer si les roues qu’on a sont les bonnes. Comme quand on met des pneus neiges en montagne ou qu’en ingénierie on redimensionne les roues/les pneus si on se rend compte que le véhicule est plus lourd que prévu. Quand tu parles dans ton billet de virer testify pour utiliser les outils de tests de base de Go, je vois un conducteur F1 qui met des pneus lisses quand il se rend compte que finalement il ne pleut pas plutôt que quelqu’un qui réinvente la roue.

Alors certes, il suffit de formaliser le besoin avec celui de pouvoir faire facilement bouger les requirements, et ça donne le fameux "il faut faire du générique-modulaire-évolutif"… Que je n’ai jamais vu fonctionner dans la pratique.

Le soucis principal, c’est qu’il faut prendre en compte des coûts qu’on oublie très facilement d’identifier. Comme :

  • Celui de changer de solution,
  • Celui de dégager une dépendance qui ne répond plus au besoin (l’ajout étant souvent négligeable),

Dans les faits, il est également impossible de tout ouvrir à l’évolution : parfois, il est préférable de se dire que "quand ça bougera, on y verra plus clair, et il sera peut-être moins coûteux de refaire from scratch plutôt que de faire évoluer".

Il ne faut pas oublier qu’à chaque instant, on ne dispose aussi que de notre expérience plus ou moins limitée, même si elle est croissante, du problème.

Certes à formaliser ça ne semble pas la mort, mais tout ça me fait dire que dans la pratique, les forces qui entrent en jeu sont beaucoup plus complexes que celles sous-entendues ici, et qu’une fois que l’on sort des cas triviaux du style "ne pas refaire ses propres algos de crypto, ne pas réinventer un protocole quand un standard existe et qu’il est déjà implémenté", on peut se retrouver très vite à rajouter un surcoût et un frein considérables à notre capacité d’adaptation, soit des éléments de dette technique potentiellement bien plus coûteux que ceux cités dans le billet.

Certes, sur le papier, "y a qu’à l’anticiper", sauf que dans la pratique ce n’est pas toujours possible. Par expérience j’aurais même envie de dire que ça ne l’est presque jamais.

PS: Au cas où, je précise ce que j’en déduis.

Cela signifie qu’il est impossible dans la pratique de "savoir très exactement ce que l’on fait".

Partant de là, selon le billet il faudrait prendre "le réflexe" de toujours réutiliser du code existant. Or lorsque l’on sort des cas triviaux (où la "roue" en question peut vivre bien gentiment isolée à une extrémité de l’arbre des dépendances du projet) ce "réflexe" peut avoir des conséquences à long terme bien plus coûteuses que faire l’inverse (partir sur le plus simple de manière à explorer/se confronter aux problématiques jusqu’à décider que l’on gagnerait en temps et en efforts à intégrer une solution tierce), a fortiori lorsque le code est raisonnablement bien architecturé et régulièrement remis en question.

Ce que j’en conclus, c’est que c’est un mauvais "réflexe" à prendre (un réflexe étant quelque chose que l’on fait automatiquement sans réfléchir). Et qu’il est préférable d’enjoindre les développeurs :

  1. À réfléchir en premier lieu sur la portée (et l’annulabilité ou corrigeabilité) de leurs choix,
  2. À régulièrement remettre ces choix en question pour s’assurer qu’ils sont toujours valables, et corriger le cap le plus tôt possible le cas échéant.

Autrement dit faire un effort de réflexion constant pour maintenir la qualité de la codebase plutôt que reposer sur la sagesse populaire quand il s’agit de la faire évoluer.

+0 -0

Haha, il aurait fallu que je l’explique aussi au jeune SpaceFox…

Tu viens de faire tomber un mythe, là !

Non, plus sérieusement, c’est le genre de message qui rassure au fond, mais quand même, je dois concéder que depuis le jour où j’ai lu un de tes premiers messages qui stipulait que « celui qui utilise addslashes() mérite de se faire hacker », je me suis dit d’emblée « ce gars-là il sait déjà tout ».

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte