web-dev-qa-db-fra.com

Comment gérer la complexité accidentelle dans les projets logiciels

Lorsqu'on a demandé à Murray Gell-Mann comment Richard Feynman avait réussi à résoudre tant de problèmes difficiles, Gell-Mann a répondu que Feynman avait un algorithme:

  1. Notez le problème.
  2. Réfléchissez bien.
  3. Notez la solution.

Gell-Mann essayait d'expliquer que Feynman était un type différent de solutionneur de problèmes et qu'il n'y avait aucune idée à tirer de l'étude de ses méthodes. Je ressens un peu la même chose à propos de la gestion de la complexité dans les projets logiciels moyens/grands. Les gens qui sont bons sont intrinsèquement bons dans ce domaine et parviennent en quelque sorte à superposer et à empiler diverses abstractions pour rendre le tout gérable sans introduire de débris étrangers.

L'algorithme de Feynman est-il donc le seul moyen de gérer la complexité accidentelle ou existe-t-il des méthodes réelles que les ingénieurs logiciels peuvent appliquer de manière cohérente pour apprivoiser la complexité accidentelle?

76
davidk01

Lorsque vous voyez un bon coup, cherchez-en un meilleur.
- Emanuel Lasker, champion du monde d'échecs de 27 ans

D'après mon expérience, le plus grand moteur de complexité accidentelle est que les programmeurs s'en tiennent à la première version, juste parce que cela fonctionne. C'est quelque chose que nous pouvons apprendre de nos cours de composition en anglais. Ils construisent à temps pour passer en revue plusieurs ébauches dans leurs devoirs, intégrant les commentaires des enseignants. Les classes de programmation, pour une raison quelconque, ne le font pas.

Il existe des livres pleins de façons concrètes et objectives de reconnaître, d'articuler et de corriger le code sous-optimal: Clean Code , Working Effective with Legacy Code , et bien d'autres. De nombreux programmeurs connaissent ces techniques, mais ne prennent pas toujours le temps de les appliquer. Ils sont parfaitement capables de réduire la complexité accidentelle, ils n'ont tout simplement pas pris l'habitude d'essayer .

Une partie du problème est que nous ne voyons pas souvent la complexité intermédiaire du code des autres, à moins qu'il n'ait fait l'objet d'un examen par les pairs à un stade précoce. Le code propre semble facile à écrire, alors qu'en fait il implique généralement plusieurs brouillons. Vous écrivez le meilleur moyen qui vous vient à l'esprit dans un premier temps, remarquez les complexités inutiles qu'il introduit, puis "cherchez un meilleur coup" et refactorisez pour supprimer ces complexités. Ensuite, vous continuez à "chercher un meilleur coup" jusqu'à ce que vous ne puissiez pas en trouver un.

Cependant, vous ne mettez pas le code en revue avant tout ce taux de désabonnement, donc en apparence, il pourrait aussi bien s'agir d'un processus de type Feynman. Vous avez tendance à penser que vous ne pouvez pas tout faire comme ça, alors ne vous embêtez pas à essayer, mais la vérité est que l'auteur de ce code magnifiquement simple que vous venez de lire ne peut généralement pas tout écrire en un seul morceau comme ça non plus, ou s'ils le peuvent, c'est uniquement parce qu'ils ont de l'expérience à écrire du code similaire plusieurs fois auparavant, et peuvent maintenant voir le modèle sans les étapes intermédiaires. De toute façon, vous ne pouvez pas éviter les ébauches.

105
Karl Bielefeldt

"Les compétences en architecture logicielle ne peuvent pas être enseignées" est une erreur répandue.

Il est facile de comprendre pourquoi beaucoup de gens y croient (ceux qui sont bons dans ce domaine veulent croire qu'ils sont mystiquement spéciaux, et ceux qui ne veulent pas croire que ce n'est pas de leur faute s'ils ne le sont pas.) a néanmoins tort; la compétence est juste un peu plus intensive en pratique que les autres compétences logicielles (par exemple, comprendre les boucles, gérer les pointeurs, etc.)

Je crois fermement que la construction de grands systèmes est susceptible de se répéter et d'apprendre de l'expérience de la même manière que devenir un grand musicien ou un orateur public est: un minimum de talent est une condition préalable, mais ce n'est pas un minimum déprimant qui est hors de portée de la plupart des pratiquants.

Gérer la complexité est une compétence que vous acquérez en grande partie en essayant et en échouant plusieurs fois. C'est juste que les nombreuses directives générales que la communauté a découvert pour la programmation à grande échelle (utiliser des couches, lutter contre la duplication partout où elle se lève la tête, adhérer religieusement à 0/1/infini ...) ne sont pas aussi évidemment correctes et nécessaires à un débutant jusqu'à ce qu'ils programment réellement quelque chose de grand. Tant que vous n'avez pas été mordu par une duplication qui a causé des problèmes quelques mois plus tard, vous ne pouvez tout simplement pas "comprendre" l'importance de ces principes.

47
Kilian Foth

Pensée pragmatique par Andy Hunt résout ce problème. Il se réfère au modèle Dreyfus, selon lequel il existe 5 niveaux de compétence dans diverses compétences. Les novices (étape 1) ont besoin d'instructions précises pour pouvoir faire quelque chose correctement. Les experts (étape 5), au contraire, peuvent appliquer des schémas généraux à un problème donné. Citant le livre,

Il est souvent difficile pour les experts d'expliquer leurs actions avec un niveau de détail très fin; beaucoup de leurs réponses sont si bien pratiquées qu'elles deviennent des actions préconscientes. Leur vaste expérience est exploitée par des zones précurseurs non verbales du cerveau, ce qui les rend difficiles à observer et difficiles à articuler.

Quand les experts font leur truc, cela semble presque magique pour le reste d'entre nous: d'étranges incantations, une perspicacité qui semble surgir de nulle part et une capacité apparemment étrange à connaître la bonne réponse lorsque le reste d'entre nous ne sont même pas si sûrs sur la question. Ce n'est pas magique, bien sûr, mais la façon dont les experts perçoivent le monde, comment ils résolvent les problèmes, les modèles mentaux qu'ils utilisent, etc., sont tous très différents des non-experts.

Cette règle générale de voir (et par conséquent d'éviter) différents problèmes peut être appliquée spécifiquement à la question de la complexité accidentelle. Avoir un ensemble de règles donné ne suffit pas pour éviter ce problème. Il y aura toujours une situation qui ne sera pas couverte par ces règles. Nous devons acquérir de l'expérience pour pouvoir prévoir les problèmes ou trouver des solutions. L'expérience est quelque chose qui ne peut pas être enseigné, elle ne peut être acquise qu'en essayant, échouant ou réussissant constamment et en apprenant des erreurs.

Cette question de Workplace est pertinente et IMHO serait intéressant à lire dans ce contexte.

23
superM

Vous ne le précisez pas, mais la "complexité accidentelle" est définie comme une complexité qui n'est pas inhérente au problème, par rapport à la complexité "essentielle". Les techniques requises pour le "domptage" dépendront de votre point de départ. Ce qui suit se réfère principalement aux systèmes qui ont déjà acquis une complexité inutile.

J'ai de l'expérience dans un certain nombre de grands projets pluriannuels où la composante "accidentelle" l'emportait largement sur l'aspect "essentiel", et aussi ceux où elle ne l'était pas.

En fait, l'algorithme de Feynman s'applique dans une certaine mesure, mais cela ne signifie pas que "penser vraiment fort" ne signifie que de la magie qui ne peut pas être codifiée.

Je trouve qu'il y a deux approches à adopter. Prenez-les tous les deux - ce ne sont pas des alternatives. L'une consiste à y remédier au coup par coup et l'autre consiste à effectuer une refonte majeure. Alors, certes, "notez le problème". Cela pourrait prendre la forme d'un audit du système - les modules de code, leur état (odeur, niveau de test automatisé, combien de personnes prétendent le comprendre), l'architecture globale (il y en a une, même si elle "a des problèmes" ), état des besoins, etc. etc.

C'est la nature de la complexité "accidentelle" qu'il n'y a pas un seul problème qui doit être résolu. Vous devez donc trier. Où cela fait-il mal - en termes de capacité à maintenir le système et à faire progresser son développement? Peut-être que du code sent vraiment mauvais, mais n'est pas la priorité absolue et que la correction peut être faite pour attendre. D'un autre côté, il peut y avoir du code qui retournera rapidement le temps passé au refactoring.

Définissez un plan pour une meilleure architecture et essayez de vous assurer que le nouveau travail est conforme à ce plan - c'est l'approche incrémentale.

Également, expliquez le coût des problèmes et utilisez-le pour construire une analyse de rentabilisation pour justifier un refactor. L'essentiel ici est qu'un système bien architecturé peut être beaucoup plus robuste et testable, ce qui entraîne un temps beaucoup plus court (coût et calendrier) pour mettre en œuvre le changement - cela a une valeur réelle.

Un remaniement majeur vient dans la catégorie "réfléchissez bien" - vous devez bien faire les choses. C'est là qu'avoir un "Feynman" (enfin, une petite fraction de l'un serait bien) porte ses fruits. Un remaniement majeur qui n'aboutit pas à une meilleure architecture peut être un désastre. Les réécritures complètes du système sont connues pour cela.

Implicite dans toute approche est de savoir comment distinguer "accidentel" de "essentiel" - c'est-à-dire que vous devez avoir un grand architecte (ou une équipe d'architectes) qui comprend vraiment le système et son objectif.

Cela dit, l'essentiel pour moi est tests automatisés. Si vous en avez assez, votre système est sous contrôle. Sinon. . .

4
Keith

" Tout doit être aussi simple que possible, mais pas plus simple."
- attribué à Albert Einstein

Permettez-moi d'esquisser mon algorithme personnel pour gérer la complexité accidentelle.

  1. Écrivez une histoire d'utilisateur ou un cas d'utilisation. Passez en revue avec le propriétaire du produit.
  2. Écrivez un test d'intégration qui échoue car la fonctionnalité n'est pas là. Vérifiez auprès de l'AQ ou de l'ingénieur principal s'il existe une telle chose dans votre équipe.
  3. Écrivez des tests unitaires pour certaines classes qui pourraient réussir le test d'intégration.
  4. Écrivez l'implémentation minimal pour les classes qui réussissent les tests unitaires.
  5. Passez en revue les tests unitaires et la mise en œuvre avec un collègue développeur. Passez à l'étape 3.

Toute la magie du design se trouverait à l'étape 3: comment mettre en place ces classes? Cela devient la même question que: comment imaginez-vous que vous avez une solution à votre problème avant d'avoir une solution à votre problème?

Remarquablement, juste imaginer que vous avez la solution semble être l'une des principales recommandations des personnes qui écrivent sur la résolution de problèmes (appelée "vœu pieux" par Abelson et Sussman dans Structure et interprétation des programmes informatiques et "travailler en arrière" dans Polya Comment le résoudre )

En revanche, tout le monde n'a pas le même " goût pour les solutions imaginées": il existe des solutions que vous seul trouvez élégantes, et d'autres plus compréhensibles par un public plus large. C'est pourquoi vous devez passer en revue votre code avec vos collègues développeurs: pas tant pour régler les performances, mais pour convenir de solutions comprises. Habituellement, cela conduit à une refonte et, après quelques itérations, à un code bien meilleur.

Si vous vous en tenez à écrire des implémentations minimal pour réussir vos tests et écrire des tests qui sont compris par beaucoup de gens, vous devriez terminer avec une base de code où seulement irréductible la complexité demeure.

3
logc

Complexité accidentelle

La question initiale (paraphrasée) était:

Comment les architectes gèrent-ils la complexité accidentelle des projets logiciels?

Une complexité accidentelle survient lorsque ceux qui dirigent un projet choisissent d'ajouter des technologies uniques, et que la stratégie globale des architectes originaux du projet n'avait pas l'intention à apporter au projet. Pour cette raison, il est important d'enregistrer le raisonnement derrière le choix de la stratégie.

La complexité accidentelle peut être évitée par un leadership qui s'en tient à sa stratégie d'origine jusqu'à ce qu'un écart délibéré de cette stratégie devienne apparemment nécessaire.

Éviter la complexité inutile

Sur la base du corps de la question, je la reformulerais comme ceci:

Comment les architectes gèrent-ils la complexité des projets logiciels?

Cette reformulation est plus à propos du corps de la question, où l'algorithme de Feynman a ensuite été introduit, fournissant un contexte qui propose que les meilleurs architectes, lorsqu'ils sont confrontés à un problème, aient une gestalt à partir de laquelle ils construisent ensuite habilement une solution, et que le reste d'entre nous ne peut pas espérer apprendre cela. Avoir une gestalt de compréhension dépend de l'intelligence du sujet et de sa volonté d'apprendre les caractéristiques des options architecturales qui pourraient entrer dans son champ d'application.

Le processus de planification du projet utiliserait l'apprentissage de l'organisation pour dresser une liste des exigences du projet, puis tenter de construire une liste de toutes les options possibles, puis réconcilier les options avec les exigences. La gestalt de l'expert lui permet de le faire rapidement et peut-être avec peu de travail évident, ce qui semble lui venir facilement.

Je vous soumets que cela lui vient à cause de sa préparation. Pour avoir la gestalt de l'expert, il faut connaître toutes vos options, et la prévoyance de fournir une solution simple qui tient compte des besoins futurs prévus qu'il est déterminé que le projet devrait prévoir, ainsi que la flexibilité de s'adapter aux besoins changeants des le projet. La préparation de Feynman était qu'il avait une compréhension approfondie de diverses approches en mathématiques et en physique théoriques et appliquées. Il était naturellement curieux et assez brillant pour donner un sens aux choses qu'il avait découvertes sur le monde naturel qui l'entourait.

L'architecte expert en technologie aura une curiosité similaire, s'appuyant sur une compréhension approfondie des fondamentaux ainsi qu'une large exposition à une grande diversité de technologies. Il (ou elle) aura la sagesse de s'appuyer sur les stratégies qui ont réussi dans tous les domaines (tels que Principes de programmation Unix ) et celles qui s'appliquent à des domaines spécifiques (tels que conception modèles et guides de style ). Il ne connaît peut-être pas intimement toutes les ressources, mais il saura où les trouver.

Construire la solution

Ce niveau de connaissances, de compréhension et de sagesse peut être tiré de l'expérience et de l'éducation, mais nécessite de l'intelligence et de l'activité mentale pour élaborer une solution stratégique gestaltiste qui fonctionne ensemble de manière à éviter une complexité accidentelle et inutile. Il faut que l'expert rassemble ces principes fondamentaux; ce sont les travailleurs du savoir que Drucker prévoyait lors de la création du terme.

Retour aux questions finales spécifiques:

Des méthodes spécifiques pour apprivoiser la complexité accidentelle peuvent être trouvées dans les types de sources suivants.

Suivre les principes de la programmation Unix vous permettra de créer des programmes modulaires simples qui fonctionnent bien et sont robustes avec des interfaces communes. Les modèles de conception suivants vous aideront à construire des algorithmes complexes qui ne sont pas plus complexes que nécessaire. Les guides de style suivants garantiront que votre code est lisible, maintenable et optimal pour la langue dans laquelle votre code est écrit. Les experts auront internalisé bon nombre des principes contenus dans ces ressources et seront en mesure de les rassembler de manière cohérente et transparente.

2
Aaron Hall

Cela a peut-être été une question difficile il y a quelques années, mais il n'est plus difficile aujourd'hui à l'OMI d'éliminer la complexité accidentelle.

Ce que Kent Becksa a dit de lui-même, à un moment donné: "Je ne suis pas un bon programmeur; je suis juste un bon programmeur avec de bonnes habitudes."

Deux choses méritent d'être soulignées, OMI: il se considère comme un programmeur, pas un architecte, et il se concentre sur les habitudes, pas sur la connaissance.

La manière de Feynman de résoudre des problèmes difficiles est la seule façon de le faire. La description n'est pas forcément très facile à comprendre, je vais donc la disséquer. La tête de Feynman n'était pas seulement pleine de connaissances, elle était aussi pleine de compétences pour appliquer ces connaissances. Lorsque vous avez à la fois les connaissances et les compétences nécessaires pour l'utiliser, résoudre un problème difficile n'est ni difficile ni facile. C'est le seul résultat possible.

Il y a une façon complètement non magique d'écrire du code propre, qui ne contient pas de complexité accidentelle, et elle est principalement similaire à ce que Feynman a fait: acquérir toutes les connaissances requises, s'entraîner à s'habituer à le mettre au travail, plutôt que de simplement le cacher. dans un coin de votre cerveau, puis écrivez du code propre.

Maintenant, de nombreux programmeurs ne sont même pas au courant de toutes les connaissances requises pour écrire du code propre. Les programmeurs plus jeunes ont tendance à ignorer les connaissances sur les algorithmes et les structures de données, et la plupart des programmeurs plus âgés ont tendance à l'oublier. Ou une grande notation O et une analyse de complexité. Les programmeurs plus âgés ont tendance à ignorer les modèles ou les odeurs de code - ou même à ne pas savoir qu'ils existent. La plupart des programmeurs de toute génération, même s'ils connaissent les modèles, ne se souviennent jamais du moment exact à utiliser et des composants des pilotes. Peu de programmeurs de toute génération évaluent constamment leur code par rapport aux principes SOLID. De nombreux programmeurs mélangent tous les niveaux d'abstraction possibles partout. Je ne connais pas de collègue programmeur pour le moment. , pour évaluer constamment son code par rapport aux stenches décrits par Fowler dans son livre de refactoring. Bien que certains projets utilisent un outil de métrique, la métrique la plus utilisée est la complexité, d'une sorte ou d'une autre, tandis que deux autres métriques - couplage et cohésion - sont à un dans une large mesure ignorés, même s'ils sont très importants pour un code propre. Un autre aspect que presque tout le monde ignore est la charge cognitive. Peu de programmeurs traitent les tests unitaires comme de la documentation, et encore moins sont conscients que les tests unitaires difficiles à écrire ou à nommer sont une autre puanteur de code , ce qui indique généralement une mauvaise factorisation. Une petite minorité est consciente du mantra de la conception pilotée par le domaine pour garder le modèle de code et le modèle de domaine métier aussi proches que possible l'un de l'autre, car les écarts sont liés à cr créer des problèmes sur la route. Tous ces éléments doivent être pris en compte, tout le temps, si vous voulez que votre code soit propre. Et bien d'autres dont je ne me souviens pas en ce moment.

Vous voulez écrire du code propre? Aucune magie n'est requise. Allez simplement apprendre tout ce qui est nécessaire, puis utilisez-le pour évaluer la propreté de votre code et refactorisez jusqu'à ce que vous soyez satisfait. Et continuez à apprendre - le logiciel est encore un domaine jeune, et de nouvelles perspectives et connaissances sont acquises à un rythme rapide.

0
user625488