Peu importe à quel point une chose est étroitement couplée à une autre si cette autre chose ne change jamais. J'ai trouvé généralement plus productif au fil des ans de se concentrer sur la recherche de moins de raisons pour que les choses changent, pour rechercher la stabilité, que pour les rendre plus faciles à changer en essayant d'obtenir la forme de couplage la plus lâche possible.
Le découplage s'est avéré très utile, au point que je préfère parfois une duplication de code modeste pour découpler des packages. Comme exemple de base, j'avais le choix d'utiliser ma bibliothèque de mathématiques pour implémenter une bibliothèque d'images. Je ne l'ai pas fait et j'ai simplement dupliqué certaines fonctions mathématiques de base qui étaient triviales à copier.
Maintenant, ma bibliothèque d'images est complètement indépendante de la bibliothèque de mathématiques d'une manière où, peu importe le type de modifications que j'apporte à ma bibliothèque de mathématiques, cela n'affectera pas la bibliothèque d'images. C'est mettre la stabilité avant tout. La bibliothèque d'images est maintenant plus stable, car elle a considérablement moins de raisons de changer, car elle est dissociée de toute autre bibliothèque qui pourrait changer (à part la bibliothèque standard C qui, espérons-le, ne devrait jamais changer). En prime, il est également facile à déployer quand il s'agit simplement d'une bibliothèque autonome qui ne nécessite pas de tirer un tas d'autres bibliothèques pour la construire et l'utiliser.
La stabilité m'est très utile. J'aime construire une collection de code bien testé qui a de moins en moins de raisons de changer à l'avenir. Ce n'est pas un rêve de pipe; J'ai du code C que j'utilise et utilise depuis la fin des années 80, qui n'a pas changé du tout depuis. Il s'agit certes de trucs de bas niveau comme du code orienté pixel et lié à la géométrie alors que beaucoup de mes trucs de haut niveau sont devenus obsolètes, mais c'est quelque chose qui aide toujours beaucoup à avoir autour. Cela signifie presque toujours une bibliothèque qui repose sur de moins en moins de choses, voire rien d'extérieur. La fiabilité monte et monte si votre logiciel dépend de plus en plus de fondations stables qui trouvent peu ou pas de raisons de changer. Moins de pièces mobiles est vraiment agréable, même si en pratique les pièces mobiles sont beaucoup plus nombreuses que les pièces stables.
Le couplage lâche est dans la même veine, mais je trouve souvent que le couplage lâche est tellement moins stable que pas de couplage. À moins que vous ne travailliez dans une équipe avec des concepteurs d'interface et des clients bien supérieurs qui ne changent pas d'avis que je n'ai jamais travaillé, même les interfaces pures trouvent souvent des raisons de changer de manière à provoquer des ruptures en cascade dans le code. Cette idée selon laquelle la stabilité peut être obtenue en dirigeant les dépendances vers l'abstrait plutôt que vers le concret n'est utile que si la conception de l'interface est plus facile à corriger la première fois que l'implémentation. Je trouve souvent inversé le cas où un développeur aurait pu créer une très bonne, sinon merveilleuse, implémentation compte tenu des exigences de conception qu'il pensait devoir remplir, pour constater à l'avenir que les exigences de conception changeaient complètement.
J'aime donc privilégier la stabilité et le découplage complet pour pouvoir dire au moins en toute confiance: "Cette petite bibliothèque isolée qui a été utilisée pendant des années et sécurisée par des tests approfondis n'a presque aucune probabilité d'exiger des changements quoi qu'il se passe dans le monde extérieur chaotique . " Cela me donne une petite tranche de raison quel que soit le type de changements de conception qui sont nécessaires à l'extérieur.
Couplage et stabilité, exemple ECS
J'adore également les systèmes d'entité-composant et ils introduisent beaucoup de couplage étroit parce que les dépendances du système aux composants accèdent et manipulent directement les données brutes, comme ceci:
Toutes les dépendances ici sont assez étroites puisque les composants exposent simplement les données brutes. Les dépendances ne se dirigent pas vers des abstractions, elles se dirigent vers des données brutes, ce qui signifie que chaque système possède le maximum de connaissances possible sur chaque type de composant auquel il demande d'accéder. Les composants n'ont aucune fonctionnalité avec tous les systèmes accédant et altérant les données brutes. Cependant, il est très facile de raisonner sur un système comme celui-ci car il est si plat. Si une texture apparaît visqueuse, alors vous savez immédiatement avec ce système que seul le système de rendu et de peinture accède aux composants de texture, et vous pouvez probablement rapidement éliminer le système de rendu car il ne lit que des textures conceptuellement.
Pendant ce temps, une alternative faiblement couplée pourrait être la suivante:
... avec toutes les dépendances vers des fonctions abstraites, pas des données, et chaque élément de ce diagramme exposant une interface publique et des fonctionnalités qui lui sont propres. Ici, toutes les dépendances peuvent être très lâches. Les objets peuvent même ne pas dépendre directement les uns des autres et interagir les uns avec les autres via des interfaces pures. Il est toujours très difficile de raisonner sur ce système, surtout si quelque chose se passe mal, étant donné l'enchevêtrement complexe des interactions. Il y aura également plus d'interactions (plus de couplage, quoique plus lâches) que l'ECS car les entités doivent connaître les composants qu'elles agrègent, même si elles ne connaissent que l'interface publique abstraite de l'autre.
De plus, s'il y a des modifications de conception sur quoi que ce soit, vous obtenez plus de ruptures en cascade que l'ECS, et il y aura généralement plus de raisons et de tentations pour les modifications de conception car chaque chose essaie de fournir une interface et une abstraction orientées objet agréables. Cela vient immédiatement avec l'idée que chaque petite chose essaiera d'imposer des contraintes et des limitations à la conception, et ces contraintes sont souvent ce qui justifie des changements de conception. La fonctionnalité est beaucoup plus limitée et doit faire beaucoup plus d'hypothèses de conception que les données brutes.
J'ai trouvé dans la pratique que le type de système ECS "plat" ci-dessus était tellement plus facile à raisonner que même les systèmes les plus lâchement couplés avec une toile d'araignée complexe de dépendances lâches et, plus important pour moi, je trouve si peu de raisons pour que la version ECS ait besoin de changer les composants existants, car les composants dépendants n'ont aucune responsabilité, sauf de fournir les données appropriées nécessaires au fonctionnement des systèmes. Comparez la difficulté de concevoir une IMotion
interface pure et un objet de mouvement concret implémentant cette interface qui fournit des fonctionnalités sophistiquées tout en essayant de maintenir des invariants sur les données privées par rapport à un composant de mouvement qui n'a besoin que de fournir des données brutes pertinentes pour résoudre le problème et ne se soucie pas de fonctionnalité.
La fonctionnalité est tellement plus difficile à obtenir correctement que les données, c'est pourquoi je pense qu'il est souvent préférable de diriger le flux des dépendances vers les données. Après tout, combien de bibliothèques vectorielles / matricielles existe-t-il? Combien d'entre eux utilisent exactement la même représentation des données et ne diffèrent que subtilement par leur fonctionnalité? D'innombrables, et pourtant nous en avons encore tellement malgré des représentations de données identiques car nous voulons de subtiles différences de fonctionnalités. Combien de bibliothèques d'images existe-t-il? Combien d'entre eux représentent des pixels d'une manière différente et unique? Presque aucun, et montrant à nouveau que la fonctionnalité est beaucoup plus instable et sujette aux changements de conception que les données dans de nombreux scénarios. Bien sûr, à un moment donné, nous avons besoin de fonctionnalités, mais vous pouvez concevoir des systèmes où la majeure partie des dépendances se dirigent vers les données, et non vers des abstractions ou des fonctionnalités en général. Ce serait prioriser la stabilité au-dessus du couplage.
Les fonctions les plus stables que j'ai jamais écrites (celles que j'utilise et réutilise depuis la fin des années 80 sans avoir à les changer du tout) étaient toutes celles qui s'appuyaient sur des données brutes, comme une fonction de géométrie qui acceptait simplement un tableau de des flottants et des entiers, pas ceux qui dépendent d'un Mesh
objet ou d'une IMesh
interface complexe , ou la multiplication vectorielle / matrice qui dépendait juste de, float[]
ou double[]
pas celle qui dépendait FancyMatrixObjectWhichWillRequireDesignChangesNextYearAndDeprecateWhatWeUse
.