Quels changements sont trop importants pour être facilités par une conception appropriée?


11

C'est une question assez vague, mais c'est quelque chose que je n'ai jamais senti avoir reçu de réponse satisfaisante lors de la lecture sur la conception appropriée.

Généralement, lorsque vous apprenez la programmation orientée objet, l'abstraction, l'affacturage, etc., le Saint Graal de la conception - et la raison pour laquelle ils prétendent toujours que vous utilisez les techniques de développement en question - est que cela rendra votre programme "facile à changer" , "maintenable", "flexible" ou l'un des synonymes utilisés pour exprimer un tel concept à consonance productive. En marquant ivars comme privé, en divisant le code en de nombreuses petites méthodes autonomes, en gardant les interfaces générales, vous êtes censé avoir la possibilité de modifier votre programme en toute simplicité et grâce.

Pour des changements relativement petits, cela a bien fonctionné pour moi. Les modifications apportées aux structures de données internes utilisées par une classe pour augmenter les performances n'ont jamais été une difficulté majeure, pas plus que les modifications apportées à l'interface utilisateur, indépendamment de l'API, telles que la refonte d'un système de saisie de texte ou la refonte des graphiques d'un élément de gameplay. .

Tous ces changements semblent intrinsèquement autonomes. Aucun d'entre eux n'implique de modification du comportement ou de la conception du composant de votre programme en cours de modification, en ce qui concerne le code extérieur concerné. Que vous écriviez ou non de manière procédurale ou dans un style OO, avec des fonctions grandes ou petites, ce sont des changements faciles à faire même si vous n'avez qu'un design modérément bon.

Cependant, chaque fois que les changements deviennent importants et velus - c'est-à-dire les changements de l'API - aucun de mes précieux «modèles» ne vient à la rescousse. Le grand changement reste important, le code affecté reste affecté et de nombreuses heures de travail de génération de bogues m'attendent.

Voici donc ma question. Quelle est l'ampleur d'un changement que la conception appropriée prétend pouvoir faciliter? Y a-t-il une autre technique de conception, inconnue de moi ou que je n'ai pas mise en œuvre, qui rend vraiment simple la modification au son collant, ou est-ce que la promesse (que j'ai entendu faire par tant de paradigmes différents) n'est qu'une idée sympa, complètement déconnecté des vérités immuables du développement logiciel? Existe-t-il un «outil de changement» que je peux ajouter à ma ceinture d'outils?

Plus précisément, le problème auquel je suis confronté qui m'a conduit aux forums est le suivant: j'ai travaillé sur la mise en œuvre d'un langage de programmation interprété (implémenté en D, mais ce n'est pas pertinent), et j'ai décidé que les arguments de mes fermetures devraient être basé sur des mots clés, plutôt que positionnel comme ils le sont actuellement. Cela nécessite de modifier tout le code existant qui appelle des fonctions anonymes, ce qui, heureusement, est assez petit car je suis au début du développement de mon langage (<2000 lignes), mais ce serait énorme si j'avais pris cette décision à un stade ultérieur. Dans une telle situation, y a-t-il un moyen qui, grâce à une bonne prévoyance dans la conception, aurait pu faciliter cette modification, ou certains (la plupart) des changements sont-ils intrinsèquement de grande portée? Je suis curieux de savoir si cela est en quelque sorte un échec de mes propres compétences en conception - si c'est le cas, je '

Pour être clair, je ne suis en aucun cas sceptique vis-à-vis de la POO ou de tout autre modèle couramment utilisé. Pour moi, cependant, leurs vertus sont dans l'écriture originale, plutôt que dans le maintien, des bases de code. L'héritage vous permet de bien résumer les motifs répétitifs, le polymorphisme vous permet de séparer le code par fonction comprise par l'homme (quelle classe) plutôt que par effet compris par la machine (quelle branche de l' switchinstruction), et de petites fonctions autonomes vous permettent de écrire dans un style "bottom-up" très agréable. Cependant, je suis sceptique quant à leurs prétentions de flexibilité.


Je ne vois pas comment le changement dans votre langue touche autre chose que l'analyseur. Pourriez-vous clarifier cela?
scarfridge

Dans un langage de type smalltalk, il est vrai que vous n'auriez qu'à modifier l'analyseur, car il s'agirait simplement de traiter foo metarg1: bar metarg2: bazcomme foo.metarg1_metarg2_(bar, baz). Cependant, ma langue fait un changement plus important, à savoir les paramètres basés sur la liste en paramètres basés sur le dictionnaire, ce qui affecte le runtime mais pas l'analyseur, en fait, en raison des propriétés spécifiques de ma langue, je n'entrerai pas en ce moment. Comme il n'y a pas de mappage clair entre les mots-clés non ordonnés et les arguments positionnels, le runtime est le principal problème.
existe-pour tous

Réponses:


13

Parfois, un changement est suffisamment important pour que vous deviez concevoir un chemin de migration. Même si les points de départ et d'arrivée sont bien conçus, vous ne pouvez souvent pas simplement changer de dinde froide. Un grand nombre de concepteurs par ailleurs bons ne parviennent pas à concevoir de bons chemins de migration.

C'est pourquoi je pense que chaque programmeur devrait faire un logiciel d'écriture standard pour les environnements de production 24/7. Il y a quelque chose à propos des pertes citées dans l'ordre de votre salaire annuel par minute qui vous motive à apprendre à écrire un code de migration robuste.

Ce que les gens font naturellement pour un grand changement est d'extraire l'ancienne méthode, de la mettre de la nouvelle manière, puis de passer quelques jours à corriger un bazillion d'erreurs de compilation, jusqu'à ce que votre code soit enfin à nouveau compilé afin que vous puissiez commencer les tests. Étant donné que le code n'a pas été testable pendant deux jours, vous avez soudainement un énorme gâchis de bogues intriqués à trier.

Pour un changement de conception important comme le passage d'arguments positionnels à des mots clés, un bon chemin de migration serait d'ajouter d'abord la prise en charge des arguments mots clés sans supprimer la prise en charge positionnelle . Ensuite, vous modifiez méthodiquement le code appelant pour utiliser des arguments de mots clés. C'est similaire à la façon dont vous vous y êtes pris auparavant, sauf que cette fois, vous pouvez tester au fur et à mesure, car le code d'appel inchangé fonctionne toujours. Parce que vos modifications sont plus petites entre les tests, les bogues sont plus faciles à corriger plus tôt. Ensuite, lorsque tout le code appelant a été modifié, il est trivial de supprimer le support positionnel.

C'est pourquoi les API publiées publiquement déconseillent les anciennes méthodes au lieu de les supprimer. Il fournit un chemin de migration transparent pour appeler du code. Cela peut sembler plus de travail de prendre en charge les deux pendant un certain temps, mais vous gagnez du temps dans les tests.

Donc, pour répondre à votre question telle que formulée à l'origine, il n'y a presque pas de changements trop importants pour être facilités par une conception appropriée. Si votre modification semble trop importante, il vous suffit de concevoir des étapes intermédiaires temporaires pour que votre code puisse migrer.


+1 pour prendre en charge le nouveau cas et l'ancien cas afin que vous puissiez vous retirer progressivement.
Erik Reppen

9

Je vous recommande de lire l' essai Big Ball of Mud .

Fondamentalement, le fait que la conception a tendance à se détériorer au fur et à mesure que vous progressez dans le développement et que vous devez consacrer du travail à contenir la complexité. La complexité elle-même ne peut pas être éliminée, seulement contenue, car elle est inhérente au problème que vous essayez de résoudre.

Cela conduit aux grands principes du développement agile. Les deux plus pertinents sont «vous n'en aurez pas besoin» vous disant de ne pas préparer la conception pour les fonctionnalités que vous n'êtes pas encore sur le point de mettre en œuvre, car vous avez peu de chances de réussir la conception de toute façon et de «refactoriser sans pitié» vous dire de travailler sur le maintien de l'intégrité du code tout au long du projet.

Il semble que la réponse est qu'aucun design n'est capable de faciliter un changement au-delà d'exemples artificiels conçus spécifiquement pour montrer que le design est plus fort qu'il ne l'est réellement.

D'un côté, vous devriez être sceptique quant à la programmation orientée objet. C'est un excellent outil dans de nombreux contextes, mais vous devez être conscient de ses limites. En particulier, une mauvaise utilisation de l'héritage peut conduire à un code incroyablement alambiqué. Bien sûr, vous devriez également être sceptique à propos de toute autre technique de conception. Chacun a ses utilisations et chacun a ses points faibles. Il n'y a pas de solution miracle.


1
+1: le nouveau design initial est rarement réussi. Les conceptions réussies sont soit des récapitulations de conceptions éprouvées, soit raffinées de manière itérative.
kevin cline

1
+1 vous devrait être sceptique de tous les concepts de programmation, que par la critique objective ne nous appliquons nos compétences analytiques pour trouver la bonne solution plutôt que d' une solution .
Jimmy Hoffa

4

En termes généraux, il y a des "morceaux de code" (fonctions, méthodes, objets, etc.) et des "interfaces entre des morceaux de code" (API, déclarations de fonctions, peu importe; y compris le comportement).

Si un morceau de code peut être modifié sans changer l'interface dont dépendent d'autres morceaux de code, alors le changement sera plus facile (et peu importe si vous utilisez la POO ou non).

Si un morceau de code ne peut pas être modifié sans changer l'interface dont dépendent d'autres morceaux de code, alors le changement sera plus difficile (et peu importe si vous utilisez la POO ou non).

Les principaux avantages de la POO sont que les "interfaces" publiques sont clairement marquées (par exemple les méthodes publiques d'un objet) et qu'un autre code ne peut pas utiliser les interfaces internes (par exemple les méthodes privées de l'objet). Parce que les interfaces publiques sont clairement marquées, les gens ont tendance à être plus prudents lors de la conception de ces interfaces publiques (ce qui permet d'éviter les changements les plus difficiles). Parce que les interfaces internes ne peuvent pas être utilisées par d'autres codes, vous pouvez les modifier sans vous soucier des autres codes en fonction d'eux.


Un autre avantage de la POO est que l'encapsulation des données avec la logique qui les opère conduit à des interfaces publiques plus petites (si c'est bien fait).
Michael Borgwardt

2

Il n'y a pas de méthodologie de conception qui puisse vous éviter les tracas d'un grand changement d'exigences / portée. Il est tout simplement impossible d'imaginer et de tenir compte de tous les changements possibles qui pourraient être envisagés à une date ultérieure.

Lorsqu'un bon design ne vous aide est pour vous aider à comprendre comment toutes les parties emboîtent et ce qui sera affecté par la modification proposée.
De plus, une bonne conception peut limiter les pièces qui sont affectées par la plupart des modifications proposées.

En bref, tous les changements sont facilités par une bonne conception, mais une bonne conception ne peut pas transformer un gros changement en petit. (un mauvais / aucun design d'un autre côté peut transformer n'importe quel petit changement en un gros.)


1

Étant donné que la conception est destinée à faire passer un projet de développement de zéro feuille vierge à un produit final fiable et fonctionnel (au moins la conception pour un), et c'est le plus grand changement possible à un projet, je doute qu'il existe une telle chose un changement trop important à gérer.

Au contraire, c'est l'inverse. Certains changements sont trop modestes pour être gênés par une quelconque "conception" ou réflexion. Des corrections de bugs simples, par exemple.

N'oubliez pas que les modèles de conception doivent aider à réfléchir clairement et à trouver de bonnes solutions de conception aux situations courantes, comme la création d'un grand nombre de petits objets de type identique. Aucune liste de modèles n'est complète. Vous et tous les autres, vous aurez des problèmes de conception qui ne sont pas courants, qui ne conviendront à aucun pigeonnier. Tenter de faire chaque petit pas dans un processus de développement de logiciels selon un modèle officiel ou un autre, est une manière trop religieuse de faire les choses et conduit à des gâchis incontrôlables.

Tout travail dans un logiciel est naturellement générateur de bogues. Les joueurs de football se blessent. Les musiciens ont des callosités ou des spasmes labiaux selon leur instrument. (Si vous obtenez des spasmes labiaux en jouant de la guitare, vous vous trompez.) Les développeurs de logiciels ont des bugs. Nous aimons trouver des moyens de réduire leur taux de ponte, mais ce ne sera jamais nul.


3
La position de départ peut également être négative. Ne sous-estimez pas le pouvoir de Legacy :)
Maglob

Concevoir un projet à partir de zéro est la partie la plus facile. Mais même si vous arrivez à démarrer un nouveau projet à partir de zéro, ce qui est rare en soi, vous ne profiterez de la construction à partir de rien que les premiers jours. La première décision de conception initiale s'est révélée erronée et les choses ne commenceront qu'à partir de là. La conception à partir de zéro est totalement hors de propos dans la pratique.
Jan Hudec

1

Le coût des changements de balayage est souvent proportionnel à la taille de la base de code. Par conséquent, la réduction de la taille de la base de code doit toujours être l'un des objectifs de toute conception appropriée.

Dans votre exemple, une conception appropriée vous a déjà facilité la tâche: avoir à effectuer des modifications sur une base de code de 2000 lignes, au lieu de 20000 ou 200000 lignes de base de code.

Une conception appropriée réduit les changements de balayage, mais ne les élimine pas.

Les modifications radicales sont assez facilement automatisables si elles sont correctement prises en charge par les outils d'analyse de langage. Une modification de refactorisation globale peut être appliquée à la base de code entière dans un style de recherche et remplacement (tout en respectant correctement les règles de langue). Le programmeur doit seulement faire un jugement oui / non pour chaque coup de recherche.


+1 pour la suggestion d'automatisation. Je pense que je vais vraiment l'utiliser pour beaucoup de mes fonctions de bibliothèque qui appellent des fermetures - c'est une simple question de détecter quelles fonctions prennent des blocs et de les modifier pour avoir un paramètre de symbole supplémentaire pour le nom de l'argument pour passer la valeur.
existe-pour tout
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.