Journal Lutter contre l'overengineering

Posté par . Licence CC by-sa
50
23
mai
2016

Problématique

Lors du développement d’un logiciel (ça existe dans d’autres domaine de l’ingénierie), il arrive « facilement » que l’on fasse de l’overengineering. On entend généralement par là le fait de rendre le programme plus complexe que nécessaire pour effectuer sa tâche. C’est quelque par l’inverse du principe KISS bien connu. Voir la page wikipedia.

L’overengineering pose problème car en favorisant un critère de qualité logiciel, il nuit aux autres. Par exemple on peut faire de l’overengineering pour avoir des performances optimales et nuire à la maintenabilité du logiciel voir à sa robustesse. Il est aussi possible d’utiliser des structures trop complexes pour améliorer la maintenabilité et rendre le logiciel plus lent et/ou plus complexe.

S’il y a bien sûr pleins de cas triviaux où discriminer l’overengineering du design correct est simple, ce n’est pas forcément toujours le cas. C’est en fait généralement plus simple à faire après coup qu’avant… Ce qui rend ce sujet complexe c’est aussi qu’il dépend du logiciel : selon ce qu’il fait et comment il est développé les critères de qualités ont plus ou moins d’importance (vous pouvez vouloir garder un logiciel simple pour augmenter vos chances d’avoir des contributeurs ou vouloir avoir de la performance à tous prix car votre logiciel ne sert à rien s’il répond trop tard).

Comment lutter contre ?

La dictature

La meilleure façon de lutter contre ça consiste probablement à avoir de l’expérience. Avoir vu des cas d’overengineering permet de plus facilement comprendre ce qui est important à quel moment. Dans le monde du logiciel libre, ce rôle incombe aux gentils dictateurs de chaque projet qui doivent avoir une vision claire de ce que doit devenir leur projet. Dans le domaine, Linus Torvald pour Linux s’illustre régulièrement.

Une information est sortie récemment à ce sujet. Il semble que les créateurs de certains projets à succès (Rails, Django…) n’aiment pas particulièrement le développement ce qui pousse naturellement à être minimaliste (cf : Les créateurs de Django, PHP et Rails n’étaient pas des passionnés du code).

Le collectif

Une autre façon qui peut aider à lutter contre l’overengineering consiste à multiplier le nombre d’avis différent sur le code. Le fait de faire des revues de code peut permettre de mettre le doigt sur ces problèmes. Mais il faut réunir AMHA un certain nombre de critères :

  • avoir des avis différents : une équipe de gros bœufs du code qui adorent la métaprgrammation et la création de DSL vont avoir tendance apprécier d’utiliser ces solutions même si elles ne sont pas nécessaires
  • faire des revues au plus tôt : faire relire son travail avant de l’avoir fini car remettre en cause le design après l’avoir terminé est contre productif (par exemple avec des relectures à 30 % du travail terminé)

La structure

Le fait d’organiser un projet sur des temps données permet aussi de limiter l’overengineering. Savoir que l’on a 1 jour pour faire une tâche donnée pousse à commencer par implémenter la version naïve qui marche (mais peut être moins élégante) pour ensuite utiliser le reste du temps à l’amélioration de ce dernier. Cette amélioration sera donc limitée juste par le temps.

C’est une façon qui présente le risque inverse de pousser à utiliser des solutions à la va vite pour tenter de tenir les délais. Il y a un juste milieu à trouver.

Et vous ?

Et toi nal' ? Comment gères-tu ce sujet ? Je sais qu’il y a par ici des amoureux du code qui ont une grande maîtrise du langage qu’ils utilisent. Comment faites-vous pour ne pas trop en faire ?

  • # ça ne vaut pas que pour le code

    Posté par (page perso) . Évalué à 10.

    Je ne code pas mais je rencontre les mêmes problématiques dans le système. Faire qqch de "simple" au regard du besoin est un vrai travail et nécessite une vraie recherche car, quoiqu'on en pense, c'est très rarement la solution la plus simple qui vient en 1er à l'esprit. La solution qui arrive le plus vite à l'esprit est la solution qu'on a le plus l'habitude de mettre en place… autrement dit, la plus "confortable".
    A partir de là, on peut se retrouver avec des horreurs à gérer (et/ou reprendre) car le mec qui était là avant avait une notion du "confortable" complètement dingue ou alors il a voulu se faire plaisir et a monté une cathédrale pour un client qui avait besoin d'un 2 pièces cuisine…

    Après, il ne faut pas tomber dans l'excès inverse et proposer des solutions simplistes qui peuvent répondre au besoin techniquement à court terme mais pas au niveau de la gestion des risques ou du cycle de vie du projet.

    En tout cas, c'est pas évident que faire comprendre à un client que la solution tellement simple et tellement évidente qu'on lui soumet est le fruit d'un vrai boulot qui vaut cher.

  • # Un chouia simpliste ?

    Posté par (page perso) . Évalué à 10.

    Tu ne parles pas non plus du principal souci, à savoir les divers facteurs humains, en estimant que c'est évident de décider qu'un projet est "over engineeré". Les gens sont rarement d'accord sur le sujet et y a plusieurs raisons pour ça.

    Par exemple, les gens vont avoir tendance à sur estimer leur capacité (effet dunning kruger), et donc à voir de l'over enginering la ou il y a juste de leur part un manque de compréhension.

    Ou voir du coté des choses qu'on a à peine commencé à explorer, comme l'effet Ikea (https://en.wikipedia.org/wiki/IKEA_effect), et comment les gens vont préférer ce qu'ils ont montés et dans lequel ils ont investis du temps.

    De surcroit, tu ne parles pas du fait que tout le monde n'a pas forcément la même idée de ce qui doit être fait, en partie parce que les spécifications sont souvent flous, et changeantes, et que certaines personnes vont avoir le sentiment qu'on va vouloir faire d'autres choses plus tard et qu'il faut prendre ça en compte

    Exemple, je déploie un site statique. Je fait un script de 5 lignes qui buildent tout. Parfait. Ensuite, on veut refaire ça. Du coup, mon script commence à avoir des paramétres, et je fait un role ansible pour déployer. Et puis, on commence à utiliser parfois des submodules, et parfois pas. Etc. Résultat, maintenant, mon script fait 243 lignes.

    Pour le cas de base, il est clairement trop complexe. Pour tout les cas de bases des autres sites, il est pas encore suffisant, vu que les demandes ont évolués, et que je me retrouve à refactoriser les demandes dans 1 script.

    Et pourtant, je pense pas que ça soit de l'over engineering, par rapport à l'alternative d'avoir 15 scripts presque semblables, voir pire, un logiciel pour produire les 15 scripts simples.

    Et tu dit qu'un logiciel compliqué ne va pas avoir de contributions, mais c'est non évident. Au contraire, si le logiciel ne réponds pas aux besoins, il ne va pas attirer des gens pour s'en occuper.

    Prends par exemple apache. Le système de module le rends plus complexe qu'un logiciel sans le même système. Mais pourtant, c'est ce qui permet d'avoir des contributions externes.

    Donc non, l'over engineering, c'est comme la sécurité, c'est pas un état binaire, ça dépend des besoins qui doivent être précisés.

    • [^] # Re: Un chouia simpliste ?

      Posté par . Évalué à 6. Dernière modification le 23/05/16 à 13:54.

      Tu ne parles pas non plus du principal souci, à savoir les divers facteurs humains, en estimant que c’est évident de décider qu’un projet est "over engineeré". Les gens sont rarement d’accord sur le sujet et y a plusieurs raisons pour ça.

      J’ai peut-être pas assez mis l’emphase dessus, mais quand je dis que ça dépend de chaque projet et de comment il est développé c’est un peu ce que je voulais dire.

      Par exemple, les gens vont avoir tendance à sur estimer leur capacité (effet dunning kruger), et donc à voir de l’over enginering là ou il y a juste de leur part un manque de compréhension.

      Tu as aussi l’effet inverse.

      Donc non, l'over engineering, c'est comme la sécurité, c'est pas un état binaire, ça dépend des besoins qui doivent être précisés.

      Je pensais que mon premier paragraphe était là justement pour montrer ce fait.


      Et pourtant, je pense pas que ça soit de l'over engineering, par rapport à l'alternative d'avoir 15 scripts presque semblables, voir pire, un logiciel pour produire les 15 scripts simples.

      Ce n’est pas parce que tu rends ton outil plus sophistiqué qu’il l’est trop. C’est plus subtil que ça.

      Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

    • [^] # Re: Un chouia simpliste ?

      Posté par . Évalué à 9.

      en partie parce que les spécifications sont souvent flous, et changeantes, et que certaines personnes vont avoir le sentiment qu'on va vouloir faire d'autres choses plus tard et qu'il faut prendre ça en compte

      C'est que je rencontre le plus souvent perso.
      Un de mes gars me présente un truc super complique qui fait plus que ce que ça devrait, "comme ca plus tard, si on veut faire truc, ca le fait déjà." Ca me fait penser à Astérix "si vous voulez mettre un escalier, vous aurez déjà la porte!".

      Ma réponse typique c'est "ca résoud pas un problème qu'on a, d'une part, et d'autre part, on ne résoud pas maintenant un potentiel futur problème". Les deux sont une perte de temps, surtout le résoudre un problème futur non spécifié qu'on va pas forcément vouloir résoudre un jour (les priorités, ça change avec le sens du vent).
      Je les renvoie en général bosser sur un truc plus simple, qui est soit facile à évoluer, soit facile à virer et réécrire pour un truc plus compliqué.
      "The simplest thing that gets the job done" que je leur dit.

      Linuxfr, le portail francais du logiciel libre et du neo nazisme.

      • [^] # Re: Un chouia simpliste ?

        Posté par . Évalué à 7.

        Surtout que la plus part du temps, c'est plus simple de réécrire le code que de jongler avec la "souplesse" de l'architecture complexe, qui de toute façon ne serait jamais assez souple pour les besoins futurs.

        "La première sécurité est la liberté"

        • [^] # Re: Un chouia simpliste ?

          Posté par (page perso) . Évalué à 7.

          Gérer avec souplesse les besoins futurs est aujourd'hui possible avec assez peu d'overengineering :
          entretien d'embauche: utilisation du machine learning avec des concepts qui me dépassent un peu pour résoudre un fizz buzz - un commentateur suggère qu'il aurait été plus efficace de faire appel aux algorithmes génétiques.

          Sérieux, la logique business pure en informatique/typographie/carrelage/cuisine/etc, c'est bien pour les gens qui n'aiment pas leur métier, mais laissez une chance à la belle ouvrage d'exister.

          • [^] # Re: Un chouia simpliste ?

            Posté par . Évalué à 4.

            J'ai compris qu'un beau code n'est pas un code ultra générique, mais un code qui se lit comme une histoire. J'ai compris ça en lisant du code de hocwp, je ne sais pas si il est encore sur le site. C'était juste limpide. Un code comme ça, peut vivre longtemps.

            En informatique, ce n'est pas le code l'important, mais la fonction a réaliser, la maintenabilité, l'absence de bug, la vitesse d’exécution.

            J'ai vu une fois du beau code bien générique d'un code "professionnel". Il s'agissait de faire des filtres numériques (type O = I(n)a + I(n-1)*b +I(n+2)*c…), c'était généralisé avec une gestion de liste et des fonctions utilitaire. Or la plus part des filtres était d'ordre 2, ce qui aurait du faire des équations du genre "return ab+c*d;"…

            "La première sécurité est la liberté"

            • [^] # Re: Un chouia simpliste ?

              Posté par . Évalué à 4.

              En informatique, ce n'est pas le code l'important, mais la fonction a réaliser, la maintenabilité, l'absence de bug, la vitesse d’exécution.

              L'overengineering c'est aussi favorisé l'un de ses composants au détriement des autres alors que le projet ne le demande pas. Un projet peut avoir besoin de performance sans compromis quitte à n'embaucher que des experts (donc favoriser la performance sur la maintenabilité), là où un autre projet préférera avoir une meilleure maintenabilité sans avoir vraiment besoin de performance.

              Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

              • [^] # Re: Un chouia simpliste ?

                Posté par . Évalué à 3.

                Je parlais de performance à haut niveau. Souvent, les personnes veulent du C++ pour aller vite, alors que c'est leur architecture de message pourri réseau (round trip) qui pose problème, ou des appels systèmes trop nombreux, ou un nombre de refresh pas du tout maitrisé, etc…

                C'est toujours le même problème : avec un code de "hack", on peut aller 10x plus vite, avec le bon algo 1000x.

                Je répondais surtout à une personne qui voulait faire du "beau code", et un logiciel rapide sera toujours plus apprécié qu'un logiciel lent.

                "La première sécurité est la liberté"

  • # Moi aussi !

    Posté par . Évalué à 5.

    Hello,

    Moi aussi j'ai été victime de l'over-engineering : soit de librairies que j'utilise pour développer, soit parce-que je tombe moi même dedans.

    Dans la communauté zope3 par exemple on le rencontrait surtout dans la volonté d'être absolument (trop) générique, soit dans la prise en compte des use-cases soit dans l'interchangeabilité des composants (qui est une des philosophies de zope3).

    Chez moi la tentation la plus forte qui porte à ce défaut est le fait de vouloir un code trop élégant (qui pousse au bout la logique formelle), ou la prise en compte de trop de use-cases (vouloir être futur proof, ce qui en pratique ne marche pas si souvent…).

    Je pense aussi que la multiplicité de styles et de caractères dans une équipe de programmation, ainsi que l'ouverture d'esprit et humilité des développeurs peut beaucoup aider.

    • [^] # Re: Moi aussi !

      Posté par (page perso) . Évalué à 6.

      par exemple on le rencontrait surtout dans la volonté d'être absolument (trop) générique

      C'est vraiment un point fondamental dans le développement : à partir de quel moment la volonté d'écrire un code générique nuit à la compréhension et même parfois à la verbosité du code. Lorsqu'on souhaite unir des choses trop différentes sous la même architecture/API, on se retrouve parfois coincé avec une grosse usine à gaz alors que les intentions étaient pures au départ.

      • [^] # Re: Moi aussi !

        Posté par . Évalué à 4.

        Quand tu commences à écrire du code en plus, au lieu d'en effacer, tu va dans le mauvais sens. C'est un peu le principe de la factorisation.

        "La première sécurité est la liberté"

        • [^] # Re: Moi aussi !

          Posté par . Évalué à 2.

          Mais c'est du hill climbing :) tu peux te retrouver coincé dans un minimum local et rater une solution plus économe.

          • [^] # Re: Moi aussi !

            Posté par . Évalué à 2.

            Oui, mais à la fin, tu dois avoir moins de code, et ne pas le garder juste parce que tu t'es fait chier à l'écrire.

            "La première sécurité est la liberté"

        • [^] # Re: Moi aussi !

          Posté par . Évalué à 6.

          Quand tu commences à écrire du code en plus, au lieu d'en effacer, tu va dans le mauvais sens. C'est un peu le principe de la factorisation.

          Je ne suis pas d'accord et j'ai un exemple : https://github.com/wordpress-mobile/WordPress-Android/blob/develop/WordPress/src/main/java/org/xmlrpc/android/XMLRPCSerializer.java#L69

          Cette méthode fais une centaine de ligne et mélange toutes les stratégies de sérialisation. AMHA elle a besoin de refactoring pour séparer chacune de ses stratégies dans des fonctions séparée et cette méthode-ci ne garder que la logique de « comment on sélectionne la bonne stratégie ? ». Cela réduira considérablement sa complexité cyclomatique et permettra de tester chaque stratégie de manière séparée. Mais on va fatalement écrire plus de chose en faisant ça.

          Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

    • [^] # Re: Moi aussi !

      Posté par . Évalué à 5.

      Je pense aussi que la multiplicité de styles et de caractères dans une équipe de programmation, ainsi que l'ouverture d'esprit et humilité des développeurs peut beaucoup aider.

      C'est vraiment quelque chose à gérer et le fait de faire des revues de code/design à 30% de la tâche aide. Plus tu t’investis dans une solution, moins tu sera enclin à la remettre en cause.

      Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

  • # java ?

    Posté par . Évalué à 0. Dernière modification le 23/05/16 à 15:14.

    Comment faites-vous pour ne pas trop en faire ?

    Tu devrais t'adresser plus particulièrement aux développeurs java qui ont juste inventé le concept. L'infection s'est propagée chez les autres, C# en tête.

    Désolé les gars mais quand je vois une classe qui :

    1. utilise des setter/getter à tire-larigot quand bien souvent un attribut suffit.
    2. hérite de classes multiples
    3. qui implémentent elles mêmes des interfaces

    Je me dit que le mec n'est pas assez efficace ou vaut bien faire trop tôt, de la même manière qu'il ne faut pas optimiser son code trop tôt.

    Je préfère le concept de l'implémentation naïve, cité dans le journal :

    1. je crée ma classe jusqu'à temps qu'elle fasse le job de manière propre mais sans prise de tête.
    2. je recode de manière plus chiadée (héritage, interface…) uniquement si : moi ou un de mes collègues risque de l'utiliser plus tard.

    À celui qui :

    1. crée une classe.
    2. crée l'interface sans savoir si ça va servir.
    3. crée une classe de base qui implémente son interface parce que c'est dans la logique.
    4. modifie sa classe en (1) pour qu'elle hérite de (3).

    Tout ça pour une fonctionnalité sur laquelle il est le seul à travailler et dont il n'a aucune visibilité (one-shoot ?). Si c'est pas de l'overengineering…

    • [^] # Re: java ?

      Posté par . Évalué à 10.

      Tu devrais t'adresser plus particulièrement aux développeurs java

      Désolé les gars mais quand je vois une classe qui hérite de classes multiples

      Troll échoué !

    • [^] # Re: java ?

      Posté par . Évalué à 6.

      Je ne crois sincèrement pas que ce soit si simple.

      moi ou un de mes collègues risque de l'utiliser plus tard

      Comment évalue-tu ce risque ?

      Ce ne sont pas que des questions de classes :

      • quand décide-tu qu'un DSL est nécessaire ?
      • comment est-ce que tu choisi d'utiliser ton système de type ? Comment choisi-tu si tu garde des chaînes de caractères ou des enum quand tu as une chaîne qui présente un choix limité et connu de cas ? De même pour les types fantomes pour les langages fonctionnels ? Ou la métaprogrammation pour le C++ ?
      • comment choisi de passer par des pointeurs de fonctions plutôt que d'avoir des if entre plusieurs fonctions ?

      Si c'est pas de l'overengineering…

      Tu ne semble pas comprendre la subtilité du problème. Sais-tu que c'est réellement un sujet sur le quel Linus se bat régulièrement ? (il envoie bouler violemment les gens qui lui propose du code trop sophistiqué) Je ne crois pas qu'on puisse prendre de haut comme ça les gens sans chercher à comprendre.

      Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

      • [^] # Re: java ?

        Posté par . Évalué à 4. Dernière modification le 23/05/16 à 15:56.

        Tu ne semble pas comprendre la subtilité du problème.

        Je ne suis pas expert du noyau pour autant je comprends l'overengineering comme étant "utiliser un bazooka pour tuer une mouche". En ce sens, je sais reconnaître les extrêmes pour un projet donné et un langage que je maîtrise: un code trop complexe pour ce qui doit être fait et un trop simpliste qui ne supporte pas léger changement sans tout casser.

        Ou alors l'overengineering n'est réservée qu'aux ingénieurs et je suis effectivement trop bête pour comprendre la subtilité.

        • [^] # Re: java ?

          Posté par . Évalué à 3.

          Je ne suis pas expert du noyau pour autant je comprends l'overengineering comme étant "utiliser un bazooka pour tuer une mouche".

          Les extrêmes c'est une chose, il y a ensuite tous les autres cas moins clairs. Vois-le comme : « Comment fais-tu pour t'assurer d'utiliser le meilleur outil pour un travail donné ? »

          Ou alors l'overengineering n'est réservée qu'aux ingénieurs et je suis effectivement trop bête pour comprendre la subtilité.

          Cool, on est que lundi :)

          Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

          • [^] # Re: java ?

            Posté par . Évalué à 2.

            Les extrêmes c'est une chose, il y a ensuite tous les autres cas moins clairs.

            Je n'ai jamais dit le contraire, je suis parti d'un cas particulier qui pour moi était l'overengineering flagrant.

      • [^] # Re: java ?

        Posté par . Évalué à 6.

        Sais-tu que c'est réellement un sujet sur le quel Linus se bat régulièrement ?

        C'est vrai que Git avec sa prise en main simplissime, sa conception irréprochable, ses commandes redondantes en est l'archétype …

    • [^] # Re: java ?

      Posté par (page perso) . Évalué à 9.

      utilise des setter/getter à tire-larigot quand bien souvent un attribut suffit.
      hérite de classes multiples
      qui implémentent elles mêmes des interfaces

      Je ne suis pas javaïste, je suis CSharpiste (donc un langage contaminé pour reprendre tes propos).

      Je me base sur les projets sur lesquels je suis. Des projets assez gros, durant plusieurs années, avec des changements etc… Sur lesquels plusieurs personnes peuvent travailler. Et bien franchement, j'ai presque envie de dire que c'est la base :
      1) les accesseurs, c'est tout simplement génial d'un point de vue maintenabilité du code (et aussi encapsulation). Tu peux modifier derrière l'implémentation sans aucun soucis, seule ta classe est concernée, pas le code l'utilisant. Avec les attributs, non (sans compter que tu n'as plus l'encapsulation)
      2) hérite de classes multiples. Bon, je vais faire le mec qui a compris que tu voulais dire "implémente des interfaces multiples" car la plupart des langages ne gèrent pas l'héritage multiple. Il faut que tu m'expliques le problème par rapport à cela.
      3) l'objectif d'une interface c'est d'être implémentée. Sans ça, cette notion ne sert strictement à rien. Et je ne vois pas en quoi c'est gênant. Pareil, il faut que tu m'expliques.

      De mon ressentis, c'est que tu n'es pas au bon niveau. Pour toi, faire de l'overengineering, c'est utiliser des concepts de base d'un langage (héritage, interface). Non, c'est pas ça. L'overengineering, c'est de te dire : tiens, j'ai besoin d'une classe qui va gérer mes objets, en me permettant de les sérialiser/désérialiser vers/depuis un fichier, et comme j'en aurais sans doute besoin dans le futur, que je puisse lire/écrire directement depuis/vers un fichier compressé (un zip, un 7z, un rar, un bz2, un gz, un xz, pardon à ceux que j'ai oublié) tout en gérant un historique comme git pourrait le faire pour pouvoir revenir en arrière.

      L'overengineering consiste à vouloir faire plus que le besoin actuel, parce que, potentiellement, ça pourra servir plus tard. Du coup, tu vas, par exemple, complexifier ton API pour pouvoir tenir compte de ces besoins alors qu'ils… n'existent pas (encore ?) ! Et c'est là qu'est véritablement la problématique : savoir identifier les besoins qui existeront dans un futur plus ou moins proche. Car ne pas tenir compte d'un besoin potentiel peut avoir l'effet pervers de devoir mettre tout un code à la benne car l'implémentation initiale n'est tout simplement pas adaptée aux nouvelles exigences et qu'il faut mieux repartir de zéro.

      Pour ma part, pour éviter l'overengineering, je fais une analyse de risque : quelle est la probabilité que telle fonctionnalité soit requise ou non ? Si c'est sûr et certain dans un avenir proche, alors j'essai d'en tenir compte dans l'implémentation que je suis en train de réaliser pour éviter de devoir tout recoder dans 2 mois (attention, je ne la code pas ! J'essai juste de faire en sorte qu'elle soit codable sans tout réécrire). Si c'est pas sur, même dans un avenir lointain, je passe mon chemin et n'en tient pas compte.

      Cette approche ne résout pas tout. Il m'est déjà arrivé de devoir recoder complètement certaines parties car les besoins avaient tellement évolués qu'il n'y avait pas le choix, et que prédire ces besoins étaient tout simplement infaisable (à moins de disposer d'une boule de cristal xD).

      • [^] # Re: java ?

        Posté par . Évalué à 1. Dernière modification le 23/05/16 à 16:58.

        Je ne suis pas javaïste, je suis CSharpiste (donc un langage contaminé pour reprendre tes propos).

        Pas de bol pour toi alors si dans les quelques langages dont, je ne suis pas au bon niveau, c'est le ressenti que j'ai.

        Et bien franchement, j'ai presque envie de dire que c'est la base… Et je ne vois pas en quoi c'est gênant. Pareil, il faut que tu m'expliques.

        C'est donc que tu n'as lu entièrement mon premier post :

        2.je recode de manière plus chiadée (héritage, interface…) uniquement si : moi ou un de mes collègues risque de l'utiliser plus tard.

        Je n'ai rien contre l'utilisation des concepts de base d'un langage d'autant plus si tu travailles à plusieurs et que tu dois exposer des fonctionnalités. Pour autant, je ne vois pas en quoi c'est mal d'utiliser directement une classe sans devoir SYSTEMATIQUEMENT interface/implement/extend.

        • [^] # Re: java ?

          Posté par (page perso) . Évalué à 4.

          Je n'ai rien contre l'utilisation des concepts de base d'un langage d'autant plus si tu travailles à plusieurs et que tu dois exposer des fonctionnalités. Pour autant, je ne vois pas en quoi c'est mal d'utiliser directement une classe sans devoir SYSTEMATIQUEMENT interface/implement/extend.

          Je crois que l'incompréhension réside ici. Je n'ai jamais vu quelqu'un faire cela systématiquement (que ce soit en java ou en C# d'ailleurs). Après, je suis entièrement d'accord avec toi : faire cela pour toutes les classes est une hérésie et est complètement contre productif.

          Maintenant, tel que tu as écrit ton premier commentaire, on à l'impression que c'est l'utilisation des interfaces qui te pose soucis, alors qu'en réalité, c'est l'utilisation systématique.

          • [^] # Re: java ?

            Posté par (page perso) . Évalué à 5.

            Je n'ai jamais vu quelqu'un faire cela systématiquement (que ce soit en java ou en C# d'ailleurs).

            Moi si, pour les couches de service et de DAO dans de "vieux" projets Spring. Bon, c'est quand même très has-been dans la scène java new age Akka / Play / Groovy et compagnie.

      • [^] # Re: java ?

        Posté par . Évalué à 7. Dernière modification le 24/05/16 à 08:44.

        les accesseurs, c'est tout simplement génial d'un point de vue maintenabilité du code

        Ouais, mais non, pas en Java. Les guetter and setter n'encapsulent rien dans 90% des cas, ils sont justes auto générés et retournent la valeur, point. C'est du bruit, et ca masque le vrai code, celui qui est pas généré par une regex.
        Pour les appelant, ça fait une montagne de code en plus (foo.bar est plus sympa à lire que foo.getBar(), et me lance pas sur is vs get pour les boolean vs Boolean).
        Surtout sur des créations/copies d'objets ou t'assignes 10 fields d'affilée.

        Tu met des properties privées, et des getter/setter qui retourne les valeur, et c'est cool t'es oriente objet parce que t'accedes pas aux champs directement.
        Tu peux faire la même chose avec des properties, des vrais, avec un gain de lisibilité certain.

        Le fait que tout soit une fonction a aussi tendance à cacher le fait qu'un getter peut avoir un side effect/être cher à calculer, et ca c'est mal. si t'as un side effect notable, ça devrait être une fonction, pas get/set.

        Exemple enswift, langage décent avec des vrai properties (il me semble que c# à un truc du genre aussi, mais je connais pas assez pour dire).

        public class foo {
            var bar: String
        }
        

        2 jours plus tard, faut faire qq chose quand bar est change:

        public class foo {
            var bar: String{
                didSet {
                  print("zomfgwtfbbq")
                 }
            }
        }
        

        Les appelants ont un code clair et lisible, et toi tu gardes ta souplesse.

        Linuxfr, le portail francais du logiciel libre et du neo nazisme.

        • [^] # Re: java ?

          Posté par . Évalué à 5.

          Ouais, mais non, pas en Java. Les guetter and setter n'encapsulent rien dans 90% des cas, ils sont justes auto générés et retournent la valeur, point.

          Non ils retournent la référence, c'est là le vrai problème à mon avis.

          foo.bar est plus sympa à lire que foo.getBar()

          On est dans la subtilité, là. Juste une question de joli.

          Surtout sur des créations/copies d'objets ou t'assignes 10 fields d'affilée.

          Le problème n'est peut être pas dans les setters. Sauf cas particulier, je considère qu'il y a 2 grandes classes d'objet (il y en a d'autre comme les énumération par exemple, mais elles sont bien plus rare) :

          • les beans, qui ne portent que de la donnée, en principe j'essaie qu'ils soient immutables (donc avec le(s) constructeur(s) qui va bien des getter (d'objets aussi immutables) et pas de setter) ou si vraiment la construction se fait en plusieurs fois j'utilise des setters de type builder qui te permettent d'écrire foo.setBar("toto").setBidule(42)
          • les objets « de traitement » dont les données internes doivent rester interne. Donc le constructeur permet de lui injecter ses dépendances et c'est tout.

          Exemple enswift, langage décent avec des vrai properties (il me semble que c# à un truc du genre aussi, mais je connais pas assez pour dire).

          Euh… Si je comprends bien ton exemple, tu va faire un print à chaque affectation. Donc tu as un effet de bord lors de l'utilisation de foo.bar = "toto". C'est totalement opposé à ce que tu affirme juste au dessus :

          Le fait que tout soit une fonction a aussi tendance à cacher le fait qu'un getter peut avoir un side effect/être cher à calculer, et ca c'est mal. si t'as un side effect notable, ça devrait être une fonction, pas get/set.

          Je trouve au contraire qu'il est bien plus logique d'imaginer avec un effet de bord sur foo.setBar() (ou foo.bar(String)) que sur foo.bar = "".

          Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

        • [^] # Re: java ?

          Posté par (page perso) . Évalué à 3. Dernière modification le 24/05/16 à 09:30.

          Ouais, mais non, pas en Java. Les guetter and setter n'encapsulent rien dans 90% des cas, ils sont justes auto générés et retournent la valeur, point. C'est du bruit, et ca masque le vrai code, celui qui est pas généré par une regex.
          Pour les appelant, ça fait une montagne de code en plus (foo.bar est plus sympa à lire que foo.getBar(), et me lance pas sur is vs get pour les boolean vs Boolean).
          Surtout sur des créations/copies d'objets ou t'assignes 10 fields d'affilée.

          Sauf que tu ne contredis pas ce que je dis. Je parle de maintenabilité, tu me parles de lisibilité. Ca augmente bien la maintenabilité du code, en stabilisant mieux l'API. Tu as plus de possibilité d'évolution si tu as un getter qu'un attribut, car tu peux très bien alors changer le comportement sans avoir à modifier le code qui t'appelle.

          Le fait que tout soit une fonction a aussi tendance à cacher le fait qu'un getter peut avoir un side effect/être cher à calculer, et ca c'est mal. si t'as un side effect notable, ça devrait être une fonction, pas get/set.

          Ca par contre, c'est tout à fait vrai ! C'est bien pour cela que je n'utilise pas de getter dans ce cas. De même, si un getter retourne une référence, il faut que la référence soit la même d'un appel à l'autre (tant que la valeur n'a pas été modifiée).

          Exemple enswift, langage décent avec des vrai properties (il me semble que c# à un truc du genre aussi, mais je connais pas assez pour dire).

          Oui, C# dispose de cela. Cela s'appelle des propriétés, et cela s'utilise comme un attribut

          class foo {
            string bar {
               get;
               private set;
            }
          }
          

          En C#, tu peux même donner une visibilité différente au set et au get (par exemple, pour ne permettre l'initialisation de la propriété qu'au sein même de la classe). Tu peux avoir une implémentation par défaut (qui agit comme un attribut classique, cas de l'exemple ci-dessus), ou alors avoir une implémentation personnalisée. Il y a aussi tout un sucre syntaxique autour et également de petites subtilités entre propriétés et attributs, mais cela dépasse le cadre d'un simple commentaire.

          • [^] # Re: java ?

            Posté par . Évalué à 2.

            Le fait que tout soit une fonction a aussi tendance à cacher le fait qu'un getter peut avoir un side effect/être cher à calculer, et ca c'est mal. si t'as un side effect notable, ça devrait être une fonction, pas get/set.

            Ca par contre, c'est tout à fait vrai ! C'est bien pour cela que je n'utilise pas de getter dans ce cas. De même, si un getter retourne une référence, il faut que la référence soit la même d'un appel à l'autre (tant que la valeur n'a pas été modifiée).

            Pour les cas comme ça, j'utilise un get "special". Par exemple, si le get va chercher la valeur en SQL (donc ça a un coût), je l'appel getValueFromDB, si c'est un calcul, getValueFromCalc. ça casse un peu (carrement en fait) la logique de masquer l'implémentation, mais ça évite les soucies de perf. Dans tout les cas je le spécifie bien explicitement dans la doc.

            • [^] # Re: java ?

              Posté par . Évalué à 2.

              ça casse un peu (carrement en fait) la logique de masquer l'implémentation

              Comme tu dis. Pour ça qu’à faire ce genre de chose je préfère complètement supprimer la notion de getter dans le nom pour buildValue() par exemple et non ne pas faire de distinction entre, accès à la base ou juste une partie calculatoire, l’utilisateur a juste besoin de savoir qu’il va faire quelque chose de plus coûteux qu’une lecture de variable.

              Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

    • [^] # Re: java ?

      Posté par . Évalué à 2.

      Je suis tout à fait sur la même longueur d'onde. J'ai fait du C/C++ pendant 15 ans et je fait du java depuis presque aussi longtemps et ce m'a le plus marqué, c'est la tentation, en java d'abstraire l'abstraction de l'abstraction … et cela a fini par contaminer les autres langages.

      • [^] # Re: java ?

        Posté par . Évalué à 3.

        Je ne comprends pas. Java EE et certaines vielles API de Java (je n’ai pas en tête d’API de ce type qui soient encore vraiment utilisées1) sont effectivement trop sophistiquée pour ce qu’elles font. Mais en quoi est-ce que ça doit « contaminer » d’une part ton code, d’autre part ton code dans d’autres langage ? Les gens qui font du Vertx, du Sparkframework, jooq… ils ont utilisé de la magie noire qui t’es impossible ?

        Tu ne t’es jamais posé la question d’utiliser des choses un peu pointues en C++ comme la programmation template (pour faire autre chose que de la généricité) ou des pointeurs de fonctions pour rendre ton code plus facile à faire évoluer (mais plus complexe à aborder) ?

        En Haskell personne ne se demande jusqu’à quel niveau pousser le système de type ?


        1. après je ne connais pas tout le JDK :) 

        Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

        • [^] # Re: java ?

          Posté par . Évalué à 2.

          Non, c'est la philosophie de l'abstraction et du générique à tout prix qui à contaminé les autres langages même ceux qui ne sont pas prévus pour.

  • # chi va piano va sano

    Posté par (page perso) . Évalué à 10.

    Ma version de "comment limiter l'over-engineering"

    Définir le niveau d'engineering logiciel attendu

    Tout comme un logiciel s'adresse à un public, un code source s'adresse à un public de développeurs.
    Est-ce que l'équipe qui développe a vocation à être composée uniquement d'expert du language / de la technologie ? Est-ce que l'équipe est pluri-disciplinaire ? Est-ce que des débutants voire - allez soyons fous, des non-développeurs doivent intervenir et/ou comprendre le code source ?

    Exemple : lorsque j'interviens sur des projets en Django, je refuse a priori d'utiliser les signaux. Pourquoi ? Parce que dans 90% des cas, c'est un mécanisme qui n'est pas nécessaire (on peut le remplacer par un appel explicite), et dans 100% des cas ça complique la compréhension du code. Certes, c'est un beau concept ; mais on peut faire la même chose avec des outils simples, alors pourquoi s'en priver ?

    Autre exemple : je veux que le code développé par mon équipe soit autant que possible compréhensible par un débutant. Ce n'est pas toujours le cas, mais souvent cela limite la complexité autorisée et finalement, le résultat est le même (je ne travaille pas sur des sujets algorithmiques).

    Favoriser la compréhension vs la "beauté" du code

    Le fameux "DRY" (don't repeat yourself - ne te répète pas), les abstractions, la méta-programmation… cela rend le code aussi compliqué à comprendre/maintenir qu'un code monolithique développé par des débutants.

    La factorisation doit intervenir quand elle apporte un réel plus, et quand elle ne nuit pas à la compréhension du code.

    Une séance de debug qui nécessite de passer de plus de 3/4 fonctions/méthodes pour comprendre un mécanisme est significative d'un code trop complexe.

    Avoir une bonne vision du projet, et la communiquer clairement

    Avoir une bonne vision du projet permet de décider des développements nécessitant de l'anticipation (en terme d'architecture) et ceux qui n'en nécessitent pas. C'est parfois clair pour le décideur / chef de projet / responsable du développement, mais il faut aussi que les développeurs le sachent. Et quand ça ne l'est pas pour les responsables, deux possibilités : soit ils cherchent l'info et la trouve et on retombe dans le cas précédent, soit l'anticipation n'est en général pas une bonne idée.

    Ne pas optimiser le code

    Souvent je vois des développeurs qui anticipent des problématiques de performance. Genre "Ah oui mais dans ce cas-là on peut améliorer la perf en faisant ceci ou cela". Résultat : un algorithme avec 2 cas de figure au lieu d'un. Sans intelligence supplémentaire, juste une pseudo-optimisation… qui bien souvent n'est pas un goulot d'étranglement.

    Quand on ne sait pas quels sont les goulots d'étranglement, on n'optimise pas.

    L'optimisation de code / algorithme est un travail de spécialiste. Si ton boulot n'est pas celui-ci, alors produits du code non optimisé ; tu l'optimiseras plus tard si c'est vraiment nécessaire.

    Ecrire du code ready-to-refactor

    Les développeurs veulent souvent faire un truc "nickel" dès le début. Hors, comme on ne sait pas comment le code va évoluer, on est tenté d'implémenter toute sortes d'abstractions et de flexibilités qui compliquent la compréhension du code.

    Le refactoring fait partie du travail quotidien d'un développeur ; la difficulté est de produire du code "rapidement fonctionnel, mais évolutif via refactoring". Comme ce que dit Fransceco un peu plus haut.

    Souvent cela passe par faire un code propre, "sauf là, à cet endroit c'est vraiment dégueu mais c'est là qu'on va refactoriser par la suite" pour le rendre plus souple.

    C'est un peu comme les "fusibles" dans la structure des voitures haut de gamme : les grosses berlines sont conçues pour "casser à certains endroits" de manière à garder l'habitacle et ses passagers sains et saufs. Dans le code c'est pareil : il faut anticiper ce qui peut/doit être cassé et garder les algorithmes dont la complexité immédiate est nécessaire aussi monolithiques que possibles.

    Mais en vrai, une bonne vieille dictature…

    Au final, je pense qu'un bon dictateur sera un excellent moyen de limiter l'over-engineering. Et un bon dictateur sera souvent un développeur pas trop passionné de code, ou en tout cas sensible aux problématiques business/délais/coûts tout autant que développement.

    • [^] # Re: chi va piano va sano

      Posté par (page perso) . Évalué à 8.

      Plus je réfléchis à ce que j'ai écrit (et à la réflexion qui m'y a amené) et plus je me dis que le vrai concept dans ce que je décris c'est vraiment le fait d'écrire du code "ready-to-refactor".

      On a pensé à l'archi logicielle géniale, mais comme c'est pas un besoin explicite, on ne l'implémente pas mais on identifie l'endroit où ça doit/peut être refactorisé dans le code. Au minimum, on "centralise" le code crade.

      Produire du code "ready-to-refactor" c'est aussi, par exemple ne pas implémenter les fonctionnalités "qu'on aimerait" mais qui :

      1. ne sont pas demandées dès maintenant
      2. ne remettent pas en cause l'architecture actuelle

      Bref, le ready-to-refactor, c'est bon, mangez-en !

      • [^] # Re: chi va piano va sano

        Posté par . Évalué à 4.

        Plus je réfléchis à ce que j'ai écrit (et à la réflexion qui m'y a amené) et plus je me dis que le vrai concept dans ce que je décris c'est vraiment le fait d'écrire du code "ready-to-refactor".

        J’ai un peu la même vision que toi sur tous les points abordé dans ton premier commentaire, l’optimisation précoce est un exemple symptomatique.

        Pour ma part quand j’écris du code, j’essaie qu’à chaque fois que je me dis : « Ici on pourrait faire ça, ça ou ça se serait plus propre » et que je laisse du code sale (typiquement du code dupliqué) je le note dans le TODO… Mais effectivement si tu veux que ça avance tu passes à une autre portion du code au lieu de passer du temps à trouver l’ultime et parfaite construction…

        • [^] # Re: chi va piano va sano

          Posté par . Évalué à 6.

          et que je laisse du code sale (typiquement du code dupliqué)

          DRY s'applique aux idées et non au code, voir http://dearjunior.blogspot.fr/2012/03/dry-is-about-ideas-not-text.html.

          Ca peut être une stratégie de garder de la duplication initialement tant que les concepts ne sont pas clairement identifiés. Souvent, on est a un stade où il est pas encore clair si deux choses sont intrinsèquement les mêmes ou si la ressemblance est juste une coïncide, possiblement temporaire. Compartimenter ces différents concepts et les blinder pour qu'ils existent indépendamment peut être un très bon choix (voir le l'article que j'ai donné plus bas). Au pire si les deux concepts sont réellement identiques, et avec beaucoup beaucoup de discipline, on pourra les fusionner à la prochaine itération.

          Par contre, si il est déjà identifié qu'on a un unique concept et plusieurs implémentations qui se baladent c'est juste du boulot mal fait. Par ce qu'il est bien connu que ton futur toi aura aura plus de temps libre que ton toi actuel pour nettoyer le caca, et qu'entre temps tout ne va pas se dégrader à vitesse grand V, koufffff.

          En complément, la présentation Sometimes a Controller is just a Controller est assez intéressante pour réfléchir à ces sujets. On y discute justement les différentes approches individuelles, d'équipes etc. Ce n'est pas uniquement sur ce sujet, mais ça vaut très largement les 42 minutes investies.

      • [^] # Re: chi va piano va sano

        Posté par . Évalué à 5.

        Je pense que tu retrouveras pas mal de tes arguments de manière assez structurée dans cet article : http://programmingisterrible.com/post/139222674273/write-code-that-is-easy-to-delete-not-easy-to

        C'est le genre de sujet sur lequel je challenge pas mal les juniors qui rejoignent mes équipes. Savoir visualiser le monde selon plusieurs point de vues, et découvrir le point de vue assez peu enseigné qu'on passe notre temps écrire de la merde par ce qu'on ne sait pas ce qu'on fait et que chercher à limiter / compartimenter les dégâts peut être une bonne approche. Ça fait parti des outils d'architecture.

        C'est assez vrai quand on est proche du e-business, et de moins en moins vrai quand on rentre dans l'Infra ou on a une démarche beaucoup plus d'ingénierie.

      • [^] # Re: chi va piano va sano

        Posté par . Évalué à 4.

        J'ai un peu de mal avec ta notion de « ready-to-refactor », pour moi l'unique prérequis pour un refactor c'est d'avoir un minimum de couverture par des tests unitaires (de ma modeste expérience, j'ai appris que je ne savais pas faire de refactoring sans avoir de test…).

        Quels sont les caractéristiques de ce genre de code pour toi ?

        Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

        • [^] # Re: chi va piano va sano

          Posté par (page perso) . Évalué à 3.

          Les tests sont un pré-requis pour refactoriser en toute sereinité. Mais parfois tu refactorises sans tests (tu te fais quelques sueurs, mais bon, ça fait partie du métier;)

          Du code ready-to-refactor, pour moi c'est :

          • du code propre et correctement conçu là où les algorithmes sont clairs et ne sont pas destinés a priori à être refactorisés
          • du code plus ou moins propre là où tu as identifié des pistes d'amélioration mais que tu ne les implémentes pas car pas dans l'objectif et générant plus de travail. Ce code sera avantageusement commenté avec des TODO, des HACK ou des FIXME citant auteur, date, et refactoring imaginés.
          • tu n'as jamais une couverture de test exhaustive de tous les cas possibles et imaginables ; les parties de code identifiées comme "ready-to-refactor" seront mieux testées pour qu'au moment du refactoring on ne se pose pas de question sur le comportement attendu.
          • le code "ready-to-refactor" sera idéalement encapsulé dans des fonctions ou des méthodes statiques. Ceci permet de localiser le refactoring et de définir un périmètre de test clair.
          • tu ne fais pas de méta-programmation sur ce code car c'est plus compliqué à comprendre et vu que c'est destiné à être refactorisé…
          • tu ne fais pas d'optimisation quelle qu'elle soit. (si tu as besoin d'améliorer les perfs, soit tu fais ton refactoring à ce moment-là, soit tu mets un gros cache moche par dessus "en attendant"… mais tu l'identifies comme tel)

          Exemples de cas compliqués à refactoriser :

          • si le code que tu veux/dois refactoriser est disséminé un peu partout dans tes sources, ça devient difficile à refactoriser car tu risques d'oublier des endroits ou des cas de figure.
          • si ce que tu veux refactoriser ce sont des classes complètes, le problème c'est qu'une classe embarque un état + un comportement, c'est donc plus compliqué qu'une "simple fonction" qui n'embarque qu'un comportement (sous réserve que tu n'utilises pas des variables globales, statiques ou assimilés)
          • [^] # Re: chi va piano va sano

            Posté par (page perso) . Évalué à 2.

            En fait du code "ready-to-refactor", c'est du code pour lequel tu as déjà en tête la solution technique pour l'adapter aux problématiques qui vont arriver dans les prochaines semaines ou les prochains mois, mais que tu n'as juste pas à t'occuper de cela dès maintenant (donc tu n'implémentes pas cette complexité supplémentaire qui te retarderait en développement, mais également en tests et en gestion des effets de bords).

          • [^] # Re: chi va piano va sano

            Posté par . Évalué à 3.

            si ce que tu veux refactoriser ce sont des classes complètes, le problème c'est qu'une classe embarque un état + un comportement, c'est donc plus compliqué qu'une "simple fonction" qui n'embarque qu'un comportement (sous réserve que tu n'utilises pas des variables globales, statiques ou assimilés)

            Donc ce que tu entends c'est que le code « r2r » est une fonction pure donc son comportement est simple (pas d'effet de bord) et facilement testable. C'est pratique quand c'est le cas, mais il arrive que ça ne soit pas possible à obtenir (dans un temps limité au moins).

            donc tu n'implémentes pas cette complexité supplémentaire qui te retarderait en développement, mais également en tests et en gestion des effets de bords

            Il y a aussi des techniques qui permettent de préparer les nouveautés avec un sur coût en terme de temps de dev négligeable. Je pense particulièrement aux cas où personnellement, je fais une sorte de data-driven programming : un cas trivial c'est pour implémenter différent cas au lieu d'avoir une structure de contrôle if/else if/else ou un switch, je place chaque partie du code dans un dictionnaire condition => fonction. Ajouter des cas consiste à ajouter le cas à l'initialisation de l'objet et le test qui va avec (c'est plus déclaratif et ça réduit la complexité cyclomatique).

            Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

        • [^] # Re: chi va piano va sano

          Posté par (page perso) . Évalué à 3.

          J'ai un peu de mal avec ta notion de « ready-to-refactor », pour moi l'unique prérequis pour un refactor c'est d'avoir un minimum de couverture par des tests unitaires (de ma modeste expérience, j'ai appris que je ne savais pas faire de refactoring sans avoir de test…).

          Je soutiens complètement ton point de vue et corrobore ton expérience. J'ai récemment fait un refactoring (un réusinage? une refonte?) d'une application NodeJS relativement complexe et les tests unitaires (automatisés cela va sans dire) m'ont guidé pendant tout les processus. Un point intéressant de langages au typage fort comme OCaml est qu'ils sont très utiles pour réaliser un refactoring: on commence par changer le “haut niveau” de son programme et on suit les répercussions de ce changement à travers les erreurs de compilation pour arriver à un programme qui marche. L'avantage est qu'on a besoin de beaucoup moins de tests unitaires (on peut presque s'en tenir aux tests fonctionnels qui testeraient par exemple l'API d'un service) ce qui réduit la maintenance du logiciel (les tests font aussi l'objet de maintenance!).

          • [^] # Re: chi va piano va sano

            Posté par . Évalué à 2.

            je suis d'accord à fond pour le typage fort qui aide à refactoriser. je code un projet perso en haskell, j'ai fait plusieurs fois de grosses refactorisations que je n'aurais jamais osé faire dans un autre langage, car je n'aurais pas eu l'aide du système de types qui me tape sur les doigts à chaque fois que je fais une connerie.

      • [^] # Re: chi va piano va sano

        Posté par . Évalué à 3.

        Pour arriver un peu à la même chose, je raisonne en cas de testes. Dans un monde rêvé, où tous les cas seraient testés, combien de tests cela fait ? Cela détermine le nombre d'état possible du code, et autant de problèmes potentiels, surtout après modification (non régression plus complexe).

        Cela permet de se rendre compte de la qualité d'un code "stateless", ce qui évite une grande quantité de teste finalement inutile. Cela permet de séparer très clairement les données du logiciel, des données temporaires inutiles. Cela permet d'identifier des "états stables" qui permet d'y retourner en cas de plantage ou d'exception non prévu, voir d'avoir un "reset()" propre, qui évite de recréer toujours le même objet. Cela permet de limiter les données du logiciel aux stricts nécessaire aux traitements, ce qui évite de trainer toutes les entrées et ne plus savoir ce qui est valide ou non, modifier ou non, etc… Pour faire cela, cela impose de gérer les erreurs avant le traitement, cela simplifie le traitement qui considère les entrées juste, et cela permet de faire de très joli message d'erreur, car on est très proche de l'entrée de pipeline de traitement (on dispose de tous les fichiers et leur numéro de ligne).

        "La première sécurité est la liberté"

        • [^] # Re: chi va piano va sano

          Posté par . Évalué à 3.

          Pour arriver un peu à la même chose, je raisonne en cas de testes. Dans un monde rêvé, où tous les cas seraient testés, combien de tests cela fait ? Cela détermine le nombre d'état possible du code, et autant de problèmes potentiels, surtout après modification (non régression plus complexe).

          C'est ce qui se cache derrière la complexité cyclomatique. Je te conseil d'utiliser un outil qui le mesure pour toi par fonction (comme tout indicateur, ça n'est pas parfait, mais c'est pas mal).

          Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

          • [^] # Re: chi va piano va sano

            Posté par . Évalué à 2.

            Non la complexité cyclomatique simplifie trop le problème. Elle compte juste le nombre de branche dans une fonction, or le contenu de chaque test peut être indépendant. La différence, c'est entre n tests et 2n.

            Elle ne permet pas non plus de calculer les dépendances entre fonction (couverture d'instance). Typiquement une fonction qui en appel une autre. C'est le nombre total de chemin de l'executable qui compte pas vraiment, la somme des chemins de l'ensemble de chaque fonction sans tenir compte des appels.

            Ensuite, c'est surtout les "états" différents qui importent, plus encore que les chemins pour y arriver (même si c'est lié). Dans une machine d'état, c'est le nombre d'état qui compte, plus que la complexité des conditions de transition.

            "La première sécurité est la liberté"

            • [^] # Re: chi va piano va sano

              Posté par . Évalué à 3.

              Non la complexité cyclomatique simplifie trop le problème.

              Oui, elle a surtout l'intérêt d'être mesurable :)

              Elle compte juste le nombre de branche dans une fonction, or le contenu de chaque test peut être indépendant.

              Indépendant, je ne suis pas d'accord. Elle te permet d'évaluer la quantité de tests pour avoir la couverture de chemin, il reste la couverture des ensembles de données d'entrée pour chaque paramètre.

              Elle ne permet pas non plus de calculer les dépendances entre fonction (couverture d'instance). Typiquement une fonction qui en appel une autre. C'est le nombre total de chemin de l'executable qui compte pas vraiment, la somme des chemins de l'ensemble de chaque fonction sans tenir compte des appels.

              Il y a un mot en trop ?

              Si tu dis que l'important c'est le nombre de chemin de l'exécutable alors, c'est que tu parle de tests d'intégration ou fonctionnel et ton nombre de chemin explose naturellement avec l'ajout de fonctionnalité, mais pour un même nombre de chemins (ou une augmentation pas significativement grande) tu a des designs qui sont meilleurs que d'autres.

              Ensuite, c'est surtout les "états" différents qui importent, plus encore que les chemins pour y arriver (même si c'est lié). Dans une machine d'état, c'est le nombre d'état qui compte, plus que la complexité des conditions de transition.

              Si on parle de tests unitaire, tu les fais en boite blanche et tu peux considérer chaque champ de ton objet comme des paramètres de ta méthode.

              Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

    • [^] # Re: chi va piano va sano

      Posté par . Évalué à 3.

      garder les algorithmes dont la complexité immédiate est nécessaire aussi monolithiques que possibles

      Personnellement pour cette partie là j'ai tendance à utiliser un peu d'abstraction, non pas pour rendre le code particulièrement souple, mais pour le rendre lisible. Mon objectif pour ce genre de chose, c'est de partir de « comment est-ce que je voudrais écrire cette logique ? » et de me créer les outils qui permettront de l'écrire de cette façon. Ça permet d'essayer d'avoir un endroit où l'on a que la logique métier sans s’embarrasser des contraintes purement technique et de garder cette partie là concise (on lit l'algo sans naviguer dans le code, on navigue dans le code quand on veut voir chaque partie de l'algo).

      Bien sûr il ne faut pas créer un DSL pour ça, juste avoir le bon niveau d'abstraction (c'est vraiment au jugé).

      Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

      • [^] # Re: chi va piano va sano

        Posté par (page perso) . Évalué à 3.

        C'est ce que j'appelle garder l'algo monolithique. La gestion des erreurs, les cas particuliers etc ne font pas partie de l'algo à proprement parler.

        Sur ce genre de developpement, énormément de personnes vont trouver que le code est trop complexe et le découper en embarquant une partie de l'algo dans la sous-fonction. Le découpage du code doit cependant se faire autant que possible pour que l'algo soit lisible d'une traite. On externalisera uniquement les "details d'implementation".

  • # L'expérience avec des langages “frustes” est très salutaire

    Posté par (page perso) . Évalué à 7.

    Les langages de haut niveau sont plein d'abstractions et les langages objets semblent donner les outils pour créer des systèmes d'une grande complexité, il est tentant de pousser l'utilisation de ces traits à leur limite! Expérimenter, après tout, fait partie du processus d'apprentissage.

    Dans mon parcours de programmeur j'ai beaucoup pêché dans la direction de l'over-engineering, surtout dans ma période d'apprentissage. Puis, un beau jour, j'ai réalisé que je mettais beaucoup moins de temps à écrire des prototypes fonctionnels en utilisant le shell ou le C qu'en utilisant des langages plus sophistiqués. En principe, ce devrait être le contraire! Les langages sophistiqués devraient me permettre de développer plus rapidement, c'est donc que je faisais quelque chose de travers… Lorsqu'on se pose la question dans ces termes, on trouve facilement la réponse: là où mes programmes shell ou C se contentaient d'écrire leurs diagnostics sur stderr mes autres programmes implémentaient des systèmes complexe de propagation de messages d'erreur, là où en cas d'erreur mes programmes en C ou en shell se contentaient de quitter après avoir fait un ménage succinct, mes autres programmes implémentaient des stratégies complexes d'analyse d'erreur et de récupération…

    Je vois donc mon expérience dans des langages frustes comme le C, le shell ou l'assembleur comme une expérience très salutaire qui m'a poussé à développer des techniques de programmation rapides, efficaces et robustes, que j'ai pu reprendre à bon compte dans des environnements plus sophistiqués pour me concentrer sur les tâches que doit accomplir le programme.

  • # En gros

    Posté par (page perso) . Évalué à 6.

    1. Je 'design' en gardant en tête ce qui n'est pas demandé mais risque de l'être.
    2. Je code uniquement ce qui est demandé, de sorte que les changements nécessaires pour gérer l'imprévu soient minimaux.

    Ca marche bien, mais ça demande de l'expérience.

  • # TDD + Pairing

    Posté par . Évalué à 0.

    Dans mon équipe nous pratiquons systématiquement le TDD et le plus souvent possible le pairing.

    En TDD, on se place du point de vue du code utilisateur de ce qui est en cours de dev, cela permet d'éviter le syndrome de l'api difficile à utiliser qui n'a aucun utilisateur.

    De plus, comme on a pas envie que l'écriture des tests soit compliquée (fainéantise légendaire du developpeur, je te salue) cela à tendance à créer des apis simples et utiles.

    Le pairing par-dessus ajoute la seconde approche pour avoir l'alerte qu'on en fait trop, mais aussi avoir le courage d'introduire plus de design pour la maintenabilité quand nécessaire.

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.