J'ai récemment lu un site Web sur le développement de code propre (je ne mets pas de lien ici car il n'est pas en anglais).
Un des principes annoncés par ce site est le principe ouvert fermé : chaque composant logiciel doit être ouvert pour extension et fermé pour modification. Par exemple, lorsque nous avons implémenté et testé une classe, nous ne devons la modifier que pour corriger des bogues ou ajouter de nouvelles fonctionnalités (par exemple de nouvelles méthodes qui n'influencent pas celles existantes). La fonctionnalité et l'implémentation existantes ne doivent pas être modifiées.
J'applique normalement ce principe en définissant une interface I
et une classe d'implémentation correspondante A
. Lorsque la classe A
est devenue stable (implémentée et testée), je ne la modifie normalement pas trop (éventuellement, pas du tout), c'est-à-dire
- Si de nouvelles exigences arrivent (par exemple des performances ou une implémentation totalement nouvelle de l'interface) qui nécessitent de gros changements dans le code, j'écris une nouvelle implémentation
B
et continue à l'utiliserA
tant qu'elleB
n'est pas mature. Une foisB
arrivé à maturité, il suffit de changer la façon dont ilI
est instancié. - Si les nouvelles exigences suggèrent également une modification de l'interface, je définis une nouvelle interface
I'
et une nouvelle implémentationA'
. AlorsI
,A
sont congelés et restent la mise en œuvre du système de production aussi longtemps queI'
etA'
ne sont pas assez stables pour les remplacer.
Ainsi, au vu de ces observations, j'ai été un peu surpris que la page Web suggère alors l'utilisation de refactorings complexes , "... car il n'est pas possible d'écrire du code directement dans sa forme finale".
N'y a-t-il pas une contradiction / conflit entre l'application du principe ouvert / fermé et la suggestion de l'utilisation de refactorings complexes comme meilleure pratique? Ou l'idée ici est que l'on peut utiliser des refactorings complexes pendant le développement d'une classe A
, mais quand cette classe a été testée avec succès, elle devrait être gelée?
Le principe Open-Closed est davantage un indicateur de la qualité de la conception de votre logiciel ; pas un principe à suivre littéralement. C'est également un principe qui nous aide à ne pas modifier accidentellement les interfaces existantes (classes et méthodes que vous appelez et comment vous vous attendez à ce qu'elles fonctionnent).
L'objectif est d'écrire des logiciels de qualité. L'une de ces qualités est l'extensibilité. Cela signifie qu'il est facile d'ajouter, de supprimer et de modifier le code, ces changements ayant tendance à être limités à autant de classes existantes que possible. L'ajout de nouveau code est moins risqué que le changement de code existant, donc à cet égard, Open-Closed est une bonne chose à faire. Mais de quel code parlons-nous exactement? Le crime de violation de l'OC est beaucoup moins important lorsque vous pouvez ajouter de nouvelles méthodes à une classe au lieu de devoir modifier celles existantes.
OC est fractal . Il s'applique à toutes les profondeurs de votre conception. Tout le monde suppose qu'il n'est appliqué qu'au niveau de la classe. Mais elle est également applicable au niveau méthode ou au niveau assemblage.
Une violation trop fréquente du CO au niveau approprié suggère qu'il est peut-être temps de refactoriser . Le «niveau approprié» est un appel au jugement qui a tout à voir avec votre conception globale.
Suivre Open-Closed signifie littéralement que le nombre de classes va exploser. Vous allez créer des «I» majuscules inutilement. Vous vous retrouverez avec des bits de fonctionnalité répartis entre les classes et vous devrez ensuite écrire beaucoup plus de code pour tout câbler. À un moment donné, vous comprendrez qu'il aurait été préférable de changer la classe d'origine.
la source
Le principe Open-Closed semble être un principe qui est apparu avant que TDD ne soit plus répandu. L'idée étant qu'il est risqué de refactoriser le code parce que vous pourriez casser quelque chose, il est donc plus sûr de laisser le code existant tel quel et de simplement l'ajouter. En l'absence de tests, cela a du sens. L'inconvénient de cette approche est l'atrophie du code. Chaque fois que vous étendez une classe plutôt que de la refactoriser, vous vous retrouvez avec une couche supplémentaire. Vous boulonnez simplement le code sur le dessus. Chaque fois que vous ajoutez plus de code, vous augmentez les risques de duplication. Imaginer; il y a un service dans ma base de code que je veux utiliser, je trouve qu'il n'a pas ce que je veux donc je crée une nouvelle classe pour l'étendre et inclure mes nouvelles fonctionnalités. Un autre développeur arrive plus tard et souhaite également utiliser le même service. Malheureusement, ils ne le font pas t réaliser que ma version étendue existe. Ils codent par rapport à l'implémentation d'origine, mais ils ont également besoin d'une des fonctionnalités que j'ai codées. Au lieu d'utiliser ma version, ils étendent désormais également l'implémentation et ajoutent la nouvelle fonctionnalité. Nous avons maintenant 3 classes, l'original et deux nouvelles versions qui ont des fonctionnalités dupliquées. Suivez le principe ouvert / fermé et cette duplication continuera de s'accumuler pendant la durée de vie du projet, menant à une base de code inutilement complexe.
Avec un système bien testé, il n'est pas nécessaire de subir cette atrophie de code, vous pouvez refactoriser en toute sécurité le code permettant à votre conception d'assimiler de nouvelles exigences plutôt que de devoir continuellement boulonner le nouveau code. Ce style de développement est appelé conception émergente et conduit à des bases de code qui sont capables de rester en bonne forme pendant toute leur durée de vie plutôt que de collecter progressivement la croûte.
la source
Dans les mots du profane:
A. Le principe d'O / C signifie que la spécialisation doit être effectuée en étendant, et non en modifiant une classe pour répondre à des besoins spécialisés.
B. L'ajout de fonctionnalités manquantes (non spécialisées) signifie que la conception n'était pas terminée et que vous devez l'ajouter à la classe de base, évidemment sans violer le contrat. Je pense que cela ne viole pas le principe.
C. La refactorisation ne viole pas le principe.
Lorsqu'un dessin arrive à maturité , disons, après un certain temps de production:
la source
Pour moi, le principe ouvert-fermé est une ligne directrice, pas une règle stricte et rapide.
En ce qui concerne la partie ouverte du principe, les classes finales en Java et les classes en C ++ avec tous les constructeurs déclarés privés violent la partie ouverte du principe ouvert-fermé. Il existe de bons cas d'utilisation solides (remarque: solide, pas SOLIDE) pour les classes finales. La conception d'extensibilité est importante. Cependant, cela demande beaucoup de prévoyance et d'efforts, et vous contournez toujours la ligne de violation de YAGNI (vous n'en aurez pas besoin) et injectez l'odeur de code de généralité spéculative. Les composants logiciels clés doivent-ils être ouverts pour extension? Oui. Tout? Non. C'est en soi une généralité spéculative.
En ce qui concerne la partie fermée, lors du passage de la version 2.0 à 2.1 à 2.2 à 2.3 de certains produits, ne pas modifier le comportement est une bonne idée. Les utilisateurs n'aiment vraiment pas que chaque version mineure casse leur propre code. Cependant, en cours de route, on constate souvent que l'implémentation initiale de la version 2.0 a été fondamentalement rompue ou que les contraintes externes qui limitaient la conception initiale ne s'appliquent plus. Êtes-vous souriant et le supportez-vous et maintenez-vous cette conception dans la version 3.0, ou rendez-vous 3.0 non rétrocompatible à certains égards? La rétrocompatibilité peut être une énorme contrainte. Les limites des versions majeures sont l'endroit où la rupture de la compatibilité descendante est acceptable. Vous devez être conscient que cela pourrait perturber vos utilisateurs. Il doit y avoir de bonnes raisons pour lesquelles cette rupture avec le passé est nécessaire.
la source
La refactorisation, par définition, change la structure du code sans changer le comportement. Ainsi, lorsque vous refactorisez, vous n'ajoutez pas de nouvelles fonctionnalités.
Ce que vous avez fait comme exemple pour le principe Open Close semble OK. Ce principe consiste à étendre le code existant avec de nouvelles fonctionnalités.
Cependant, ne vous trompez pas. Je n'implique pas que vous ne devriez faire que des fonctionnalités ou ne refactoriser que de gros morceaux de données. La façon la plus courante de programmer consiste à faire un peu de fonctionnalité plutôt qu'à faire immédiatement un peu de refactoring (combiné avec des tests bien sûr pour vous assurer que vous n'avez changé aucun comportement). Un refactoring complexe ne signifie pas un «gros» refactoring, cela signifie appliquer des techniques de refactoring compliquées et bien pensées.
À propos des principes SOLID. Ce sont vraiment de bonnes directives pour le développement de logiciels, mais ce ne sont pas des règles religieuses à suivre aveuglément. Parfois, plusieurs fois, après avoir ajouté une deuxième et une troisième et une nième fonctionnalités, vous réalisez que votre conception initiale, même si elle respecte l'Open-Close, ne respecte pas d'autres principes ou exigences logicielles. Il y a des points dans l'évolution d'une conception et d'un logiciel où des changements plus complexes doivent être effectués. Il s'agit de trouver et de réaliser ces problèmes le plus rapidement possible et d'appliquer au mieux les techniques de refactoring.
La conception parfaite n'existe pas. Il n’existe pas une telle conception qui puisse et devrait respecter tous les principes ou modèles existants. C'est coder l'utopie.
J'espère que cette réponse vous a aidé dans votre dilemme. N'hésitez pas à demander des clarifications si besoin.
la source
B
et, lorsque celle-ci est prête, remplacer l'ancienne implémentationA
par la nouvelle implémentationB
(c'est une utilisation des interfaces).A
Le code de '' peut servir de base auB
code de '' et ensuite je peux utiliser le refactoring sur leB
code pendant son développement, mais je pense que leA
code déjà testé devrait rester figé.B
est construit sur du codeA
en tant qu'évolution deA
, alors, lorsqu'ilB
est publié, ilA
doit être supprimé et ne plus jamais être utilisé. Les clients qui utilisaient auparavant utiliserontA
simplementB
sans connaître le changement, car l'interfaceI
n'a pas été modifiée (peut-être un peu du principe de substitution de Liskov ici? ... le L de SOLID)Selon ma compréhension - si vous ajoutez de nouvelles méthodes à la classe existante, cela ne cassera pas l'OCP. cependant je suis un peu confus avec l'ajout de nouvelles variables dans la classe. Mais si vous modifiez la méthode et les paramètres existants dans la méthode existante, cela cassera sûrement l'OCP, car le code est déjà testé et passé si nous changeons intentionnellement la méthode [lorsque les exigences changent], ce sera un problème.
la source