Non importa quanto una cosa sia strettamente accoppiata all'altra se quell'altra cosa non cambia mai. Ho trovato generalmente più produttivo nel corso degli anni concentrarmi sulla ricerca di meno motivi per cui le cose cambiano, cercare stabilità, piuttosto che renderle più facili da cambiare cercando di ottenere la forma più libera possibile di accoppiamento.
Il disaccoppiamento l' ho trovato molto utile, al punto che a volte preferisco la modesta duplicazione del codice per disaccoppiare i pacchetti. Come esempio di base, ho potuto scegliere di utilizzare la mia libreria matematica per implementare una libreria di immagini. Non ho duplicato alcune funzioni matematiche di base che erano banali da copiare.
Ora la mia libreria di immagini è completamente indipendente dalla libreria matematica in un modo in cui, indipendentemente dal tipo di modifiche apportate alla mia libreria matematica, non influirà sulla libreria immagini. Questo sta mettendo innanzitutto la stabilità. La libreria di immagini ora è più stabile, come se avesse drasticamente meno motivi per cambiare, poiché è disaccoppiata da qualsiasi altra libreria che potrebbe cambiare (oltre alla libreria standard C che si spera non dovrebbe mai cambiare). Come bonus è anche facile da implementare quando si tratta solo di una libreria autonoma che non richiede l'inserimento di un sacco di altre librerie per costruirlo e usarlo.
La stabilità mi è molto utile. Mi piace creare una raccolta di codice ben collaudato che abbia sempre meno ragioni per cambiare in futuro. Non è un sogno irrealizzabile; Ho il codice C che sto usando e usando di nuovo dalla fine degli anni '80, che da allora non è cambiato affatto. È certamente roba di basso livello come il codice orientato ai pixel e la geometria, mentre molte delle mie cose di livello superiore sono diventate obsolete, ma è qualcosa che aiuta ancora molto ad avere in giro. Ciò significa quasi sempre una biblioteca che si basa su sempre meno cose, se non addirittura nulla di esterno. L'affidabilità aumenta se il tuo software dipende sempre più da basi stabili che trovano poche o nessuna ragione per cambiare. Un minor numero di parti mobili è davvero bello, anche se in pratica le parti mobili sono molto più numerose rispetto alle parti stabili.
L'accoppiamento libero è nella stessa vena, ma trovo spesso che l'accoppiamento lento sia molto meno stabile di nessun accoppiamento. A meno che tu non stia lavorando in un team con progettisti e client di interfaccia di gran lunga superiori che non cambiano idea di quanto io abbia mai lavorato, anche le interfacce pure spesso trovano ragioni per cambiare in modi che causano ancora rotture a cascata nel codice. L'idea che la stabilità possa essere raggiunta dirigendo le dipendenze verso l'astratto piuttosto che verso il concreto è utile solo se la progettazione dell'interfaccia è più semplice da ottenere al primo avvio rispetto all'implementazione. Lo trovo spesso capovolto dove uno sviluppatore potrebbe aver creato un'implementazione molto buona, se non meravigliosa, dati i requisiti di progettazione che pensavano di soddisfare, solo per scoprire in futuro che i requisiti di progettazione cambiano completamente.
Quindi mi piace favorire la stabilità e il disaccoppiamento completo in modo da poter almeno dire con sicurezza: "Questa piccola libreria isolata che è stata utilizzata per anni e protetta da test approfonditi non ha quasi alcuna probabilità di richiedere cambiamenti, indipendentemente da ciò che accade nel caotico mondo esterno ". Mi dà una piccola fetta di sanità mentale, indipendentemente dal tipo di modifiche progettuali richieste all'esterno.
Accoppiamento e stabilità, esempio ECS
Adoro anche i sistemi entità-componente e introducono molti accoppiamenti stretti perché il sistema dalle dipendenze dei componenti accede e manipola direttamente i dati grezzi, in questo modo:
Tutte le dipendenze qui sono piuttosto strette poiché i componenti espongono solo dati grezzi. Le dipendenze non fluiscono verso le astrazioni, stanno fluendo verso i dati grezzi, il che significa che ogni sistema ha la massima quantità di conoscenza possibile su ogni tipo di componente a cui richiedono l'accesso. I componenti non hanno funzionalità con tutti i sistemi che accedono e manomettono i dati grezzi. Tuttavia, è molto facile ragionare su un sistema come questo poiché è così piatto. Se una trama risulta sfigata, allora sai immediatamente con questo sistema che solo il sistema di rendering e pittura accede ai componenti della trama, e probabilmente puoi escludere rapidamente il sistema di rendering poiché legge solo trame concettualmente.
Nel frattempo un'alternativa vagamente accoppiata potrebbe essere questa:
... con tutte le dipendenze che fluiscono verso funzioni astratte, non dati, e ogni singola cosa in quel diagramma espone un'interfaccia pubblica e una funzionalità propria. Qui tutte le dipendenze potrebbero essere molto allentate. Gli oggetti potrebbero anche non dipendere direttamente l'uno dall'altro e interagire tra loro attraverso interfacce pure. Tuttavia è molto difficile ragionare su questo sistema, specialmente se qualcosa va storto, dato il complesso groviglio di interazioni. Ci saranno anche più interazioni (più accoppiamento, anche se più libero) rispetto all'ECS perché le entità devono conoscere i componenti che aggregano, anche se conoscono solo l'interfaccia pubblica astratta reciproca.
Inoltre, se ci sono modifiche di progettazione a qualcosa, si ottengono più rotture a cascata rispetto all'ECS e in genere ci saranno più motivi e tentazioni per le modifiche di progettazione poiché ogni singola cosa sta cercando di fornire un'interfaccia e un'astrazione orientate agli oggetti. Ciò viene immediatamente con l'idea che ogni singola piccola cosa cercherà di imporre vincoli e limitazioni al design, e questi vincoli sono spesso ciò che garantisce cambiamenti nel design. La funzionalità è molto più limitata e deve fare molte più ipotesi di progettazione rispetto ai dati non elaborati.
Ho trovato in pratica il suddetto tipo di sistema ECS "piatto" per essere molto più facile da ragionare rispetto anche ai sistemi più accoppiati con una complessa ragnatela di dipendenze sciolte e, soprattutto per me, trovo così poche ragioni affinché la versione ECS debba mai cambiare qualsiasi componente esistente poiché i componenti da cui dipendeva non hanno alcuna responsabilità se non quella di fornire i dati appropriati necessari al funzionamento dei sistemi. Confronta la difficoltà di progettare un'interfaccia pura IMotion
e un oggetto di movimento concreto che implementa quell'interfaccia che fornisce funzionalità sofisticate mentre cerca di mantenere invarianti su dati privati rispetto a un componente di movimento che deve solo fornire dati grezzi rilevanti per risolvere il problema e non si preoccupa di funzionalità.
La funzionalità è molto più difficile da ottenere rispetto ai dati, motivo per cui penso che sia spesso preferibile orientare il flusso di dipendenze verso i dati. Dopo tutto, quante librerie di vettori / matrici ci sono là fuori? Quanti di loro usano la stessa identica rappresentazione dei dati e differiscono solo leggermente per funzionalità? Innumerevoli, eppure ne abbiamo ancora così tanti nonostante identiche rappresentazioni di dati perché vogliamo sottili differenze di funzionalità. Quante librerie di immagini ci sono là fuori? Quanti di essi rappresentano i pixel in un modo diverso e unico? Quasi nessuno, e ancora una volta dimostra che la funzionalità è molto più instabile e soggetta a modifiche di progettazione rispetto ai dati in molti scenari. Ovviamente a un certo punto abbiamo bisogno di funzionalità, ma è possibile progettare sistemi in cui la maggior parte delle dipendenze fluisce verso i dati, e non verso astrazioni o funzionalità in generale. Ciò darebbe priorità alla stabilità rispetto all'accoppiamento.
Le funzioni più stabili che abbia mai scritto (il tipo che ho usato e riutilizzato dalla fine degli anni '80 senza doverle cambiare affatto) erano tutte quelle che si basavano su dati grezzi, come una funzione geometrica che ha appena accettato una serie di float e numeri interi, non quelli che dipendono da un Mesh
oggetto o IMesh
un'interfaccia complessi , o una moltiplicazione vettoriale / matrice che dipendeva float[]
o da double[]
cui dipendeva FancyMatrixObjectWhichWillRequireDesignChangesNextYearAndDeprecateWhatWeUse
.