Comment éviter d'itérer sans fin à travers des conceptions tout aussi sous-optimales?

10

Donc, probablement comme beaucoup, je me retrouve souvent confronté à des maux de tête avec des problèmes de conception dans lesquels, par exemple, il existe un modèle / approche de conception qui semble s'adapter intuitivement au problème et présente les avantages souhaités. Très souvent, il y a une mise en garde qui rend difficile la mise en œuvre du modèle / de l'approche sans une sorte de travail autour qui annule alors l'avantage du modèle / de l'approche. Je peux très facilement finir par itérer à travers de nombreux modèles / approches, car il est prévisible que presque tous ont des mises en garde très importantes dans des situations du monde réel dans lesquelles il n'y a tout simplement pas de solution facile.


Exemple:

Je vais vous donner un exemple hypothétique basé vaguement sur un vrai que j'ai récemment rencontré. Supposons que je souhaite utiliser la composition plutôt que l'héritage, car les hiérarchies d'héritage ont nui à l'évolutivité du code dans le passé. Je pourrais refactoriser le code mais trouver ensuite qu'il existe certains contextes où la superclasse / classe de base a simplement besoin d'appeler des fonctionnalités sur la sous-classe, malgré les tentatives pour l'éviter.

La meilleure approche suivante semble implémenter un modèle à moitié délégué / observateur et un modèle à moitié composition pour que la superclasse puisse déléguer le comportement ou pour que la sous-classe puisse observer les événements de la superclasse. Ensuite, la classe est moins évolutive et maintenable car il n'est pas clair comment elle doit être étendue, il est également difficile d'étendre les écouteurs / délégués existants. De plus, les informations ne sont pas bien cachées car on commence à avoir besoin de connaître l'implémentation pour voir comment étendre la superclasse (sauf si vous utilisez très largement les commentaires).

Donc, après cela, je pourrais simplement choisir d'utiliser simplement des observateurs ou des délégués pour échapper aux inconvénients liés à la confusion des approches. Cependant, cela vient avec ses propres problèmes. Par exemple, je pourrais trouver que je finis par avoir besoin d'observateurs ou de délégués pour une quantité croissante de comportements jusqu'à ce que je finisse par avoir besoin d'observateurs / délégués pour pratiquement tous les comportements. Une option pourrait être d'avoir un seul gros écouteur / délégué pour tout le comportement, mais la classe d'implémentation se retrouve avec beaucoup de méthodes vides, etc.

Ensuite, je pourrais essayer une autre approche, mais il y a tout autant de problèmes avec cela. Ensuite, le suivant et le suivant, etc.


Ce processus itératif devient très difficile lorsque chaque approche semble avoir autant de problèmes que les autres et conduit à une sorte de paralysie des décisions de conception . Il est également difficile d'accepter que le code finira également par poser problème, quel que soit le modèle de conception ou l'approche utilisé. Si je me retrouve dans cette situation, cela signifie-t-il que le problème lui-même doit être repensé? Que font les autres lorsqu'ils rencontrent cette situation?

Edit: Il semble y avoir un certain nombre d'interprétations de la question que je veux clarifier:

  • J'ai complètement exclu la POO car il s'avère que ce n'est pas réellement spécifique à la POO, et il est trop facile de mal interpréter certains des commentaires que j'ai faits en passant à propos de la POO.
  • Certains ont prétendu que je devrais adopter une approche itérative et essayer différents modèles, ou que je devrais rejeter un modèle lorsqu'il cesse de fonctionner. C'est le processus auquel je comptais me référer en premier lieu. Je pensais que c'était clair à partir de l'exemple mais j'aurais pu le rendre plus clair, j'ai donc édité la question pour le faire.
Jonathan
la source
3
Ne "souscrivez pas à la philosophie OO". C'est un outil, pas une décision de vie majeure. Utilisez-le quand cela aide et ne l'utilisez pas quand ce n'est pas le cas.
user253751
Si vous êtes coincé à penser en termes de certains modèles, essayez peut-être d'écrire un projet modérément complexe en C, il sera intéressant de découvrir tout ce que vous pouvez faire sans ces modèles.
user253751
2
Oh C a des motifs. Ce ne sont que des motifs très différents. :)
candied_orange
J'ai éliminé la plupart de ces interprétations erronées dans l'édition
Jonathan
Il est normal que ces problèmes apparaissent de temps en temps. Les faire se produire souvent ne devrait pas être le cas. Très probablement, vous utilisez le mauvais paradigme de programmation pour le problème ou vos conceptions sont un mélange de paradigmes aux mauvais endroits.
Dunk

Réponses:

8

Quand j'arrive à une décision difficile comme ça, je me pose généralement trois questions:

  1. Quels sont les avantages et les inconvénients de toutes les solutions disponibles?

  2. Y a-t-il une solution que je n'ai pas encore envisagée?

  3. Plus important encore:
    quelles sont exactement mes exigences? Pas les exigences sur papier, les vraies fondamentales?
    Puis-je en quelque sorte reformuler le problème / modifier ses exigences, afin qu'il permette une solution simple et directe?
    Puis-je représenter mes données d'une manière différente, afin de permettre une solution aussi simple et directe?

    Ce sont les questions sur les principes fondamentaux du problème perçu. Il se peut que vous essayiez de résoudre le mauvais problème. Votre problème peut avoir des exigences spécifiques qui permettent une solution beaucoup plus simple que le cas général. Interrogez votre formulation de votre problème!

Je trouve très important de bien réfléchir aux trois questions avant de continuer. Faites une promenade, arpentez votre bureau, faites tout ce qu'il faut pour vraiment réfléchir aux réponses à ces questions. Cependant, répondre à ces questions ne devrait pas prendre trop de temps. Les délais appropriés peuvent varier de 15 minutes à quelque chose comme une semaine, ils dépendent de la gravité des solutions que vous avez déjà trouvées et de son impact sur l'ensemble.

La valeur de cette approche est que vous trouverez parfois des solutions étonnamment bonnes. Solutions élégantes. Les solutions valent bien le temps que vous avez investi pour répondre à ces trois questions. Et vous ne trouverez pas ces solutions, si vous entrez juste la prochaine itération immédiatement.

Bien sûr, parfois aucune bonne solution ne semble exister. Dans ce cas, vous êtes coincé avec vos réponses à la première question, et le bien est tout simplement le moins mauvais. Dans ce cas, l'intérêt de prendre le temps de répondre à ces questions est que vous évitez éventuellement les itérations qui sont vouées à l'échec. Assurez-vous simplement de revenir au codage dans un délai approprié.

cmaster - réintégrer monica
la source
Je pourrais bien marquer cela comme la réponse, cela a le plus de sens pour moi et il semble y avoir un consensus sur la simple réévaluation du problème lui-même.
Jonathan
J'aime aussi la façon dont vous l'avez divisé en étapes, donc il y a une sorte de procédure souple pour évaluer la situation.
Jonathan
OP, je vois que vous êtes coincé dans la mécanique de la solution de code, alors oui, vous pourriez aussi bien marquer cette réponse. Le meilleur code que nous ayons écrit était après une analyse très approfondie des exigences, des exigences dérivées, des diagrammes de classes, des interactions de classes, etc. Dieu merci, nous avons résisté à tous les chahuts des sièges bon marché (lire la gestion et les collègues): "Vous passons trop de temps dans la conception "," dépêchez-vous et obtenez le codage! "," c'est trop de cours! ", etc. Une fois que nous y sommes arrivés, le codage était une joie - plus qu'un plaisir. Objectivement, c'était le meilleur code de tout le projet.
radarbob
13

Tout d'abord - les motifs sont des abstractions utiles, la fin ne doit pas être uniquement du design, et encore moins du design OO.

Deuxièmement - l'OO moderne est suffisamment intelligent pour savoir que tout n'est pas un objet. Parfois, l'utilisation de simples anciennes fonctions, ou même de certains scripts de style impératifs, donnera une meilleure solution à certains problèmes.

Passons maintenant à la viande des choses:

Cela devient très difficile lorsque chaque approche semble avoir autant de problèmes que les autres.

Pourquoi? Lorsque vous avez un tas d'options similaires, votre décision devrait être plus facile! Vous ne perdrez pas grand-chose en choisissant la «mauvaise» option. Et vraiment, le code n'est pas fixe. Essayez quelque chose, voyez si c'est bon. Itérer .

Il est également difficile d'accepter que le code deviendra très problématique quel que soit le modèle de conception ou l'approche utilisé.

Noix dures. Problèmes difficiles - les problèmes mathématiques réels sont tout simplement difficiles. Difficile à prouver. Il n'y a littéralement pas de bonne solution pour eux. Et il s'avère que les problèmes faciles n'ont pas vraiment de valeur.

Mais méfiez-vous. Trop souvent, j'ai vu des gens frustrés par aucune bonne option parce qu'ils sont bloqués à regarder le problème d'une certaine manière, ou qu'ils ont réduit leurs responsabilités d'une manière qui n'est pas naturelle au problème en question. "Pas de bonne option" peut donner l'impression que votre approche présente un problème fondamental.

Il en résulte une perte de motivation car le code «propre» cesse parfois d'être une option.

Le parfait est l'ennemi du bien. Faites travailler quelque chose, puis refactorisez-le.

Existe-t-il des approches de conception qui peuvent aider à minimiser ce problème? Existe-t-il une pratique acceptée pour ce que l'on devrait faire dans une situation comme celle-ci?

Comme je l'ai mentionné, le développement itératif minimise généralement ce problème. Une fois que quelque chose fonctionne, vous connaissez mieux le problème qui vous met en meilleure position pour le résoudre. Vous avez un code réel à regarder et à évaluer, pas une conception abstraite qui ne semble pas correcte.

Telastyn
la source
Je pensais juste que je devrais dire que j'ai clarifié certaines erreurs d'interprétation lors de l'édition. Je fais généralement l'approche "get it working" et refactor. Cependant, cela finit souvent par conduire à beaucoup de dettes techniques selon mon expérience. Par exemple, si je fais juste fonctionner quelque chose, mais que moi-même ou d'autres construisons dessus, certaines de ces choses peuvent avoir des dépendances inévitables qui nécessitent également une tonne de refactoring. Bien sûr, vous ne pouvez pas toujours le savoir à l'avance. Mais si vous savez déjà que l'approche est imparfaite, faut-il la faire fonctionner puis la refaire de manière itérative avant de s'appuyer sur le code?
Jonathan
@Jonathan - tous les modèles sont une série de compromis. Oui, vous devez répéter immédiatement si le design est défectueux et vous avez un moyen clair de l'améliorer. S'il n'y a pas d'amélioration nette, arrête de baiser.
Telastyn
"ils ont réduit leurs responsabilités d'une manière qui n'est pas naturelle au problème en cours. Aucune bonne option ne peut être une odeur qu'il y a quelque chose de fondamentalement mauvais dans votre approche." Je parierais sur celui-ci. Certains développeurs ne rencontrent jamais les problèmes décrits par OP et d'autres semblent le faire assez fréquemment. Souvent, la solution n'est pas facile à voir lorsqu'elle est posée par le développeur d'origine, car ils ont déjà biaisé la conception dans la façon dont ils encadrent le problème. Parfois, la seule façon de trouver la solution la plus évidente est de recommencer à zéro en fonction des exigences de conception.
Dunk
8

La situation que vous décrivez ressemble à une approche ascendante. Vous prenez une feuille, essayez de la réparer et découvrez qu'elle est connectée à une branche qui elle-même est également connectée à une autre branche, etc.

C'est comme essayer de construire une voiture à partir d'un pneu.

Ce dont vous avez besoin, c'est de prendre du recul et de voir la situation dans son ensemble. Comment cette feuille se situe-t-elle dans la conception globale? Est-ce toujours d'actualité et correct?

À quoi ressemblerait le module si vous le conceviez et l'implémentiez à partir de zéro? À quelle distance de cet "idéal" se trouve votre implémentation actuelle.

De cette façon, vous avez une plus grande image de ce vers quoi travailler. (Ou si vous décidez que c'est trop de travail, quels sont les problèmes).

Pieter B
la source
4

Votre exemple décrit une situation qui survient généralement avec des morceaux de code hérités plus volumineux et lorsque vous essayez de faire un refactoring «trop volumineux».

Le meilleur conseil que je puisse vous donner pour cette situation est:

  • n'essayez pas d'atteindre votre objectif principal en un seul "big bang" ,

  • Apprenez à améliorer votre code en petites étapes!

Bien sûr, c'est plus facile à écrire qu'à faire, alors comment y parvenir en réalité? Eh bien, vous avez besoin de pratique et d'expérience, cela dépend beaucoup du cas, et il n'y a pas de règle stricte disant "faites ceci ou cela" qui convient à chaque cas. Mais permettez-moi d'utiliser votre exemple hypothétique. la "composition sur héritage" n'est pas un objectif de conception tout ou rien, c'est un candidat parfait à réaliser en plusieurs petites étapes.

Disons que vous avez remarqué que "composition sur héritage" est le bon outil pour le cas. Il doit y avoir des indications pour que ce soit un objectif raisonnable, sinon vous ne l'auriez pas choisi. Supposons donc qu'il y ait beaucoup de fonctionnalités dans la superclasse qui est juste "appelée" à partir des sous-classes, donc cette fonctionnalité est un candidat pour ne pas rester dans cette superclasse.

Si vous remarquez que vous ne pouvez pas supprimer immédiatement la superclasse des sous-classes, vous pouvez commencer par refactoriser la superclasse en composants plus petits encapsulant les fonctionnalités mentionnées ci-dessus. Commencez par les fruits les plus bas, extrayez d'abord des composants plus simples, ce qui rendra déjà votre superclasse moins complexe. Plus la superclasse est petite, plus les refactorisations supplémentaires seront faciles. Utilisez ces composants des sous-classes ainsi que de la super-classe.

Si vous êtes chanceux, le code restant dans la superclasse deviendra si simple tout au long de ce processus que vous pourrez supprimer la superclasse des sous-classes sans aucun problème supplémentaire. Ou, vous remarquerez que le maintien de la superclasse n'est plus un problème, car vous avez déjà extrait suffisamment de code dans des composants que vous souhaitez réutiliser sans héritage.

Si vous ne savez pas par où commencer, parce que vous ne savez pas si une refactorisation se révélera simple, parfois la meilleure approche consiste à effectuer une refactorisation de zéro .

Bien sûr, votre situation réelle pourrait être plus compliquée. Alors apprenez, accumulez de l'expérience et soyez patient, il faut des années pour réussir. Il y a deux livres que je peux recommander ici, peut-être les trouvez-vous utiles:

  • Refactoring by Fowler: décrit un catalogue complet de très petits refactorings.

  • Travailler efficacement avec le code hérité de Feathers: donne d'excellents conseils sur la façon de traiter de gros morceaux de code mal conçu et de le rendre plus testable en petites étapes

Doc Brown
la source
1
Ceci est également très utile. Un petit refactoring par étapes ou par scratch est une bonne idée car il devient alors quelque chose qui peut être progressivement complété avec d'autres travaux sans trop entraver. Je souhaite pouvoir approuver plusieurs réponses!
Jonathan
1

Parfois, les deux meilleurs principes de conception sont KISS * et YAGNI **. Ne ressentez pas le besoin d'encombrer tous les motifs de conception connus dans un programme qui n'a qu'à imprimer "Hello world!".

 * Keep It Simple, Stupid
 ** You Ain't Gonna Need It

Modifier après la mise à jour des questions (et refléter ce que Pieter B dit dans une certaine mesure):

Parfois, vous prenez tôt une décision architecturale qui vous amène à une conception particulière qui mène à toutes sortes de laideurs lorsque vous essayez de la mettre en œuvre. Malheureusement, à ce stade, la «bonne» solution est de prendre du recul et de trouver comment vous êtes arrivé à cette position. Si vous ne voyez pas encore la réponse, reculez jusqu'à ce que vous le fassiez.

Mais si le travail à faire est hors de proportion, il faut une décision pragmatique pour trouver la solution la moins laide au problème.

Simon B
la source
J'ai clarifié certains de ces points dans l'édition, mais en général, je me réfère exactement à ces problèmes où il n'y a pas de solution simple qui soit disponible.
Jonathan
Ah bien oui, il semble y avoir un consensus sur le recul et la réévaluation du problème. C'est une bonne idée.
Jonathan
1

Quand je suis dans cette situation, la première chose que je fais est d'arrêter. Je passe à un autre problème et y travaille pendant un certain temps. Peut-être une heure, peut-être un jour, peut-être plus. Ce n'est pas toujours une option, mais mon subconscient va travailler sur des choses tandis que mon cerveau conscient fait quelque chose de plus productif. Finalement, j'y reviens avec de nouveaux yeux et réessaye.

Une autre chose que je fais est de demander à quelqu'un de plus intelligent que moi. Cela peut prendre la forme d'une demande sur Stack Exchange, de la lecture d'un article sur le Web sur le sujet ou d'une demande à un collègue qui a plus d'expérience dans ce domaine. Souvent, la méthode qui, selon moi, est la bonne approche se révèle complètement fausse pour ce que j'essaie de faire. J'ai mal décrit certains aspects du problème et il ne correspond pas vraiment au modèle que je pense qu'il fait. Lorsque cela se produit, demander à quelqu'un d'autre de dire: «Vous savez, cela ressemble plus à ...» peut être d'une grande aide.

La conception ou le débogage par confession est lié à ce qui précède. Vous allez voir un collègue et vous dites: «Je vais vous dire le problème que j'ai, puis je vais vous expliquer les solutions que j'ai. Vous soulignez les problèmes de chaque approche et suggérez d'autres approches . " Souvent, avant même que l'autre personne ne parle, comme je l'explique, je commence à réaliser qu'un chemin qui semblait égal aux autres est en fait bien meilleur ou pire que ce que je pensais à l'origine. La conversation qui en résulte peut soit renforcer cette perception, soit signaler de nouvelles choses auxquelles je n'ai pas pensé.

Alors TL; DR: Faites une pause, ne forcez pas, demandez de l'aide.

user1118321
la source