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.
Réponses:
Quand j'arrive à une décision difficile comme ça, je me pose généralement trois questions:
Quels sont les avantages et les inconvénients de toutes les solutions disponibles?
Y a-t-il une solution que je n'ai pas encore envisagée?
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é.
la source
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:
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 .
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.
Le parfait est l'ennemi du bien. Faites travailler quelque chose, puis refactorisez-le.
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.
la source
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).
la source
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
la source
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!".
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.
la source
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.
la source