Pourquoi de nombreux développeurs de logiciels enfreignent-ils le principe d'ouverture / fermeture en modifiant plusieurs éléments, tels que le changement de nom de fonctions, qui endommageront l'application après la mise à niveau?
Cette question me vient à l’esprit après les versions rapide et continue de la bibliothèque React .
À chaque courte période, je remarque de nombreux changements dans la syntaxe, les noms de composants, etc.
Exemple dans la prochaine version de React :
Nouveaux avertissements de dépréciation
Le plus gros changement est que nous avons extrait React.PropTypes et React.createClass dans leurs propres packages. Les deux sont toujours accessibles via l'objet principal React, mais leur utilisation consignera un avertissement de dépréciation unique sur la console en mode de développement. Cela permettra d’optimiser la taille du code à l’avenir.
Ces avertissements n'affecteront pas le comportement de votre application. Cependant, nous sommes conscients qu’ils peuvent être frustrants, en particulier si vous utilisez un framework de test qui traite console.error comme un échec.
- Ces changements sont-ils considérés comme une violation de ce principe?
- En tant que débutant à quelque chose comme React , comment puis-je l'apprendre avec ces changements rapides dans la bibliothèque (c'est tellement frustrant)?
la source
Réponses:
La réponse de IMHO JacquesB, bien que contenant beaucoup de vérité, montre une incompréhension fondamentale de l'OCP. Pour être juste, votre question exprime déjà ce malentendu. Les fonctions renommées annulent la compatibilité ascendante , mais pas l’OCP. Si la rupture de la compatibilité vous semble nécessaire (ou le maintien de deux versions du même composant pour ne pas rompre la compatibilité), l'OCP était déjà cassé avant!
Comme Jörg W Mittag l'a déjà mentionné dans ses commentaires, le principe ne dit pas "vous ne pouvez pas modifier le comportement d'un composant" - vous devez essayer de concevoir des composants de manière à ce qu'ils soient ouverts pour être réutilisés (ou étendus) de plusieurs façons, sans nécessiter de modification. Cela peut être fait en fournissant les "points d'extension" appropriés, ou, comme mentionné par @AntP, "en décomposant une structure classe / fonction au point où chaque point d'extension naturel est présent par défaut". IMHO suite à l'OCP n'a rien de commun avec "garder l'ancienne version inchangée pour des raisons de compatibilité ascendante" ! Ou, citant le commentaire de @ DerekElkin ci-dessous:
Les bons programmeurs utilisent leur expérience pour concevoir des composants en gardant à l'esprit les "bons" points d'extension (ou - mieux - de manière qu'aucun point d'extension artificiel ne soit nécessaire). Toutefois, pour procéder correctement et sans ingénierie inutile, vous devez au préalable savoir à quoi peuvent ressembler les futurs cas d'utilisation de votre composant. Même les programmeurs expérimentés ne peuvent pas regarder vers l'avenir et connaître à l'avance toutes les exigences à venir. C’est pourquoi il est parfois nécessaire de ne pas respecter la compatibilité ascendante - quel que soit le nombre de points d’extension de votre composant ou la qualité de sa conformité avec l’OCP en ce qui concerne certains types d’exigences, il y aura toujours une exigence qui ne peut pas être mise en œuvre facilement sans modification. le composant.
la source
Le principe d'ouverture / fermeture présente des avantages, mais il présente également de graves inconvénients.
En théorie, le principe résout le problème de la compatibilité ascendante en créant un code "ouvert à l'extension mais fermé à la modification". Si une classe a de nouvelles exigences, vous ne modifiez jamais le code source de la classe elle-même, mais créez une sous-classe qui remplace uniquement les membres appropriés nécessaires pour modifier le comportement. Tout le code écrit avec la version d'origine de la classe n'est donc pas affecté, vous pouvez donc être sûr que votre modification n'a pas altéré le code existant.
En réalité, vous vous retrouvez facilement avec du code gonflé et un désordre déroutant de classes obsolètes. S'il n'est pas possible de modifier certains comportements d'un composant par le biais d'une extension, vous devez fournir une nouvelle variante du composant avec le comportement souhaité et conserver l'ancienne version inchangée pour des raisons de compatibilité avec les versions antérieures.
Supposons que vous découvriez un défaut de conception fondamental dans une classe de base dont beaucoup de classes héritent. Supposons que l'erreur est due à un type de champ privé incorrect. Vous ne pouvez pas résoudre ce problème en remplaçant un membre. Fondamentalement, vous devez remplacer la classe entière, ce qui signifie que vous finissez par s’étendre
Object
pour fournir une classe de base alternative. Maintenant, vous devez également fournir des alternatives à toutes les sous-classes, aboutissant ainsi à une hiérarchie d’objets dupliquée, une hiérarchie défectueuse, une version améliorée. . Mais vous ne pouvez pas supprimer la hiérarchie défectueuse (puisque la suppression de code est une modification), tous les futurs clients seront exposés aux deux hiérarchies.Maintenant, la réponse théorique à ce problème est "juste le concevoir correctement la première fois". Si le code est parfaitement décomposé, sans aucune faille ni erreur, et conçu avec des points d'extension préparés pour toutes les modifications éventuelles des exigences, vous évitez le désordre. Mais en réalité, tout le monde fait des erreurs et personne ne peut prédire l'avenir parfaitement.
Prenez quelque chose comme le framework .NET: il contient toujours l'ensemble des classes de collection qui ont été conçues avant l'introduction des génériques il y a plus de dix ans. C’est certes un avantage en termes de compatibilité ascendante (vous pouvez mettre à niveau le framework sans rien réécrire), mais elle le gonfle également et offre aux développeurs un large éventail d’options dont beaucoup sont simplement obsolètes.
Apparemment, les développeurs de React ont estimé que le coût et la complexité du code coûtaient à eux seuls de ne pas suivre strictement le principe d'ouverture / fermeture.
L'alternative pragmatique à l'ouverture / la fermeture est une dépréciation contrôlée. Plutôt que de supprimer la compatibilité ascendante dans une seule version, les anciens composants sont conservés pendant un cycle de publication, mais les clients sont informés via des avertissements du compilateur que l'ancienne approche sera supprimée dans une version ultérieure. Cela donne aux clients le temps de modifier le code. Cela semble être l'approche de React dans ce cas.
(Mon interprétation du principe est basée sur le principe d' ouverture / fermeture de Robert C. Martin)
la source
J'appellerais le principe ouvert / fermé un idéal. Comme tous les idéaux, il accorde peu de considération aux réalités du développement logiciel. En outre, comme pour tous les idéaux, il est impossible de l’atteindre réellement dans la pratique - on s’efforce simplement d’approcher de cet idéal du mieux possible.
L’autre partie de l’histoire est connue sous le nom de menottes dorées. Les menottes dorées sont ce que vous obtenez lorsque vous vous esclave trop du principe d'ouverture / fermeture. Les menottes dorées sont ce qui se produit lorsque votre produit, qui ne rompt jamais avec la compatibilité en arrière, ne peut pas grandir parce que trop d'erreurs ont été commises.
Un exemple célèbre de cela se trouve dans le gestionnaire de mémoire Windows 95. Dans le cadre de la commercialisation de Windows 95, il était indiqué que toutes les applications Windows 3.1 fonctionneraient sous Windows 95. Microsoft a en fait acquis des licences pour des milliers de programmes afin de les tester sous Windows 95. L'un des problèmes était Sim City. En fait, Sim City avait un bogue qui l’écrivait dans une mémoire non allouée. Dans Windows 3.1, sans un "bon" gestionnaire de mémoire, il s'agissait d'un faux pas mineur. Toutefois, dans Windows 95, le gestionnaire de mémoire intercepterait cela et provoquerait une erreur de segmentation. La solution? Sous Windows 95, si le nom de votre application est
simcity.exe
, le système d'exploitation assouplira les contraintes du gestionnaire de mémoire afin d'éviter toute erreur de segmentation!Le véritable problème derrière cet idéal réside dans les concepts bien définis de produits et de services. Personne ne fait vraiment l'un ou l'autre. Tout se situe quelque part dans la zone grise entre les deux. Si vous envisagez une approche orientée produit, ouvrir / fermer semble être un idéal idéal. Vos produits sont fiables. Cependant, en ce qui concerne les services, l'histoire change. Il est facile de montrer qu'avec le principe ouvert / fermé, la quantité de fonctionnalités que votre équipe doit prendre en charge doit approcher l'infini de manière asymptotique, car vous ne pouvez jamais nettoyer les anciennes fonctionnalités. Cela signifie que votre équipe de développement doit prendre en charge de plus en plus de code chaque année. Finalement, vous atteignez un point de rupture.
La plupart des logiciels actuels, en particulier ceux de source ouverte, suivent une version assouplie commune du principe ouvert / fermé. Il est très courant de voir ouvert / fermé suivre servilement pour les versions mineures, mais abandonné pour les versions majeures. Par exemple, Python 2.7 contient de nombreux "mauvais choix" des jours Python 2.0 et 2.1, mais Python 3.0 les a tous balayés. ( En outre, le passage de Windows 95 codebase pour Windows NT codebase quand ils ont sorti Windows 2000 a brisé toutes sortes de choses, mais il ne signifie que nous ne devons traiter avec un gestionnaire de contrôle de la mémoire du nom de l' application pour décider comportement!)
la source
La réponse de Doc Brown est la plus exacte, les autres réponses illustrent des malentendus concernant le principe de fermeture.
Pour exprimer explicitement le malentendu, il semble y avoir une croyance que l'OCP signifie que vous ne devriez pas faire des changements en arrière incompatibles (ou même des changements ou quelque chose le long de ces lignes.) L'OCP est sur la conception de composants de sorte que vous n'avez pas besoin de apportez-leur des modifications pour étendre leurs fonctionnalités, que ces modifications soient compatibles avec les versions antérieures ou non. Outre l'ajout de fonctionnalités, vous pouvez apporter des modifications à un composant, qu'il soit rétro-compatible (par exemple, refactoring ou optimisation) ou rétro-compatible (par exemple, en dépréciant et en supprimant des fonctionnalités). Le fait que vous apportiez ces modifications ne signifie pas que votre composant a violé l’OCP (et ne signifie certainement pas que vous violent l’OCP).
En réalité, il ne s'agit pas du tout de code source. Une déclaration plus abstraite et pertinente de l'OCP est la suivante: "un composant devrait permettre une extension sans qu'il soit nécessaire de violer ses limites d'abstraction". J'irais plus loin en disant qu'une interprétation plus moderne est: "un composant doit imposer ses limites d'abstraction tout en permettant une extension". Même dans l'article de Bob Martin sur l'OCP, alors qu'il décrit "le code source comme étant inviolable", il commence ensuite à parler d'encapsulation, qui n'a rien à voir avec la modification du code source et tout ce qui concerne l'abstraction. limites.
La prémisse erronée de la question est donc que l'OCP est (censé être) un guide sur les évolutions d'une base de code. L'OCP est généralement sloganisé comme "un composant doit être ouvert aux extensions et fermé aux modifications par les consommateurs". Fondamentalement, si un consommateur d'un composant souhaite ajouter des fonctionnalités au composant, il devrait pouvoir étendre l'ancien composant à un nouveau avec les fonctionnalités supplémentaires, mais ne devrait pas pouvoir modifier l'ancien composant.
L'OCP ne dit rien sur le créateur d'un composant qui modifie ou supprime des fonctionnalités. L'OCP ne préconise pas de maintenir la compatibilité des bogues pour toujours. En tant que créateur, vous ne violez pas le panneau de commande en modifiant ou même en supprimant un composant. Vous, ou plutôt les composants que vous avez écrits, violez le panneau de commande si le seul moyen pour les utilisateurs d’ajouter des fonctionnalités à vos composants est de le muter, par exemple en appliquant des correctifs monkeyou avoir accès au code source et recompiler. Dans de nombreux cas, aucune de ces options ne constitue une option pour le consommateur, ce qui signifie que si votre composant n'est pas "ouvert à l'extension", il n'a pas de chance. Ils ne peuvent tout simplement pas utiliser votre composant pour leurs besoins. L'OCP recommande de ne pas placer les consommateurs de votre bibliothèque dans cette position, du moins en ce qui concerne une catégorie identifiable d'extensions. Même lorsque des modifications peuvent être apportées au code source ou même à la copie principale du code source, il est préférable de "prétendre" que vous ne pouvez pas le modifier car cela pourrait avoir de nombreuses conséquences négatives.
Donc, pour répondre à vos questions: non, ce ne sont pas des violations de l'OCP. Aucune modification apportée par un auteur ne peut constituer une violation du PCO car ce dernier n'est pas une propriété des changements. Toutefois, les modifications peuvent créer des violations du protocole OCP et peuvent être motivées par des défaillances du protocole OCP dans les versions précédentes de la base de code. L'OCP est une propriété d'un morceau de code particulier, pas l'histoire évolutive d'une base de code.
En revanche, la compatibilité ascendante est une propriété d’un changement de code. Cela n'a aucun sens de dire qu'une partie du code est ou non compatible avec les versions antérieures. Il est logique de parler de la compatibilité ascendante de certains codes par rapport à des codes plus anciens. Par conséquent, il n’a jamais de sens de parler de la première version d’un code compatible ou non avec la version antérieure. La première copie de code peut satisfaire ou ne pas satisfaire à l'OCP, et en général, nous pouvons déterminer si un code satisfait à l'OCP sans faire référence à aucune version historique du code.
En ce qui concerne votre dernière question, on peut dire que StackExchange est généralement hors sujet en tant qu’opinion, mais les technologies, et en particulier JavaScript, sont les bienvenues. En effet, ces dernières années, le phénomène que vous décrivez a été appelé fatigue JavaScript . (N'hésitez pas à chercher sur Google plusieurs autres articles, certains satiriques, qui abordent ce sujet sous plusieurs angles.)
la source
private
ou non. Si un auteur crée uneprivate
méthodepublic
plus tard, cela ne signifie pas qu'il a violé le contrôle d'accès, (1/2)private
avant. "Supprimer un composant publié est clairement un changement décisif", est un non séquentiel. Que les composants de la nouvelle version satisfassent l'OCP ou non, vous n'avez pas besoin de l'historique de la base de code pour le déterminer. Par votre logique, je ne pourrais jamais écrire de code qui satisfasse à l'OCP. Vous associez une compatibilité ascendante, une propriété de modifications de code, à l'OCP, une propriété de code. Votre commentaire a autant de sens que de dire que quicksort n'est pas rétrocompatible. (2/2)