L'accoppiamento lento senza casi d'uso è un anti-schema?


22

L'accoppiamento lento è, per alcuni sviluppatori, il santo graal di software ben progettato. È certamente positivo quando rende il codice più flessibile di fronte ai cambiamenti che potrebbero verificarsi nel prossimo futuro, o evita la duplicazione del codice.

D'altro canto, gli sforzi per accoppiare liberamente i componenti aumentano la quantità di riferimento indiretto in un programma, aumentando così la sua complessità, rendendo spesso più difficile la comprensione e spesso rendendola meno efficiente.

Consideri un focus sull'accoppiamento libero senza alcun caso d'uso per l'accoppiamento lento (come evitare la duplicazione del codice o la pianificazione di cambiamenti che potrebbero verificarsi in un futuro prevedibile) come un anti-schema? L'accoppiamento lento può cadere sotto l'ombrello di YAGNI?


1
La maggior parte dei vantaggi ha un costo. Utilizzare se il vantaggio supera il costo.
LennyProgrammers,

4
hai dimenticato il rovescio della medaglia allentata, e cioè l' high cohesionuna senza l'altra è uno spreco di sforzi e la mancanza fondamentale di comprensione delle due illustrazioni.

Risposte:


13

Un po '.

A volte l'accoppiamento lento che arriva senza troppi sforzi va bene, anche se non si hanno requisiti specifici che richiedono il disaccoppiamento di alcuni moduli. Il frutto basso, per così dire.

D'altra parte, l'ingegnerizzazione eccessiva di ridicole quantità di cambiamento sarà una complessità e uno sforzo inutili. YAGNI, come dici tu, lo colpisce in testa.


22

La pratica di programmazione X è buona o cattiva? Chiaramente, la risposta è sempre "dipende".

Se stai guardando il tuo codice, chiedendoti quali "schemi" puoi iniettare, allora stai sbagliando.

Se stai costruendo il tuo software in modo che gli oggetti non correlati non giochino tra loro, allora lo stai facendo bene.

Se stai "progettando" la tua soluzione in modo che possa essere estesa e cambiata all'infinito, allora la stai rendendo più complicata.

Penso che alla fine della giornata, rimanga l'unica verità: è più o meno complicato disaccoppiare gli oggetti? Se è meno complicato accoppiarli, questa è la soluzione corretta. Se è meno complicato disaccoppiarli, questa è la soluzione giusta.

(Attualmente sto lavorando in una base di codice piuttosto piccola che fa un lavoro semplice in un modo molto complicato, e parte di ciò che lo rende così complicato è la mancanza di comprensione dei termini "accoppiamento" e "coesione" da parte dell'originale sviluppatori.)


4
L'ho fatto solo l'altro giorno: ho pensato che avrei avuto bisogno di un po 'di indiretto tra due classi "per ogni evenienza". Poi mi sono fatto male alla testa mentre cercavo di eseguire il debug di qualcosa, ho strappato la direzione indiretta ed ero molto più felice.
Frank Shearar,

3

Penso che quello che stai arrivando qui sia il concetto di coesione . Questo codice ha un buon scopo? Posso interiorizzare questo scopo e capire il "quadro generale" di ciò che sta succedendo?

Potrebbe portare a codice difficile da seguire, non solo perché ci sono molti più file sorgente (supponendo che si tratti di classi separate), ma perché nessuna singola classe sembra avere uno scopo.

Da una prospettiva agile, potrei suggerire che tale accoppiamento libero sarebbe un anti-schema. Senza la coesione, o anche casi d'uso per essa, non è possibile scrivere test unitari sensibili e non è possibile verificare lo scopo del codice. Ora, il codice agile può portare a un accoppiamento lento, ad esempio quando viene utilizzato lo sviluppo guidato dai test. Ma se sono stati creati i test giusti, nell'ordine giusto, è probabile che ci sia una buona coesione e un accoppiamento lento. E stai parlando solo dei casi in cui chiaramente non c'è coesione.

Anche in questo caso dal punto di vista agile, non si desidera questo livello artificiale di indiretta perché è uno sforzo sprecato su qualcosa che probabilmente non sarà comunque necessario. È molto più facile fare il refactoring quando il bisogno è reale.

Nel complesso, si desidera un accoppiamento elevato all'interno dei moduli e un accoppiamento lento tra di essi. Senza l'accoppiamento elevato, probabilmente non si ha coesione.


1

Per la maggior parte delle domande come questa, la risposta è "dipende". Nel complesso, se riesco a realizzare un design logicamente accoppiato in modo logico in modi che abbiano senso, senza un grande sovraccarico nel farlo, lo farò. Evitare inutili accoppiamenti nel codice è, a mio avviso, un obiettivo di progettazione assolutamente valido.

Una volta che si presenta una situazione in cui sembra che i componenti debbano logicamente essere strettamente accoppiati, cercherò un argomento convincente prima di iniziare a romperli.

Immagino che il principio su cui lavoro con la maggior parte di questi tipi di pratica sia quello dell'inerzia. Ho un'idea di come vorrei che il mio codice funzionasse e se posso farlo in questo modo senza rendere la vita più dura, lo farò. Se farlo renderà più difficile lo sviluppo ma la manutenzione e il lavoro futuro saranno più facili, proverò a indovinare se sarà più lavoro durante la vita del codice e lo userò come mia guida. Altrimenti dovrebbe essere un punto progettuale deliberato per cui valga la pena andare.


1

La semplice risposta è che l'accoppiamento lento è buono se fatto correttamente.

Se il principio di una funzione, uno scopo è seguito, dovrebbe essere abbastanza facile seguire ciò che sta succedendo. Inoltre, il codice di una coppia libera segue naturalmente senza alcuno sforzo.

Regole di progettazione semplici: 1. non sviluppare la conoscenza di più elementi in un unico punto (come indicato ovunque, dipende) a meno che non si stia costruendo un'interfaccia di facciata. 2. una funzione - uno scopo (tale scopo può essere sfaccettato come in una facciata) 3. un modulo - una serie chiara di funzioni correlate - uno scopo chiaro 4. se non puoi semplicemente testare l'unità, allora non ha uno scopo semplice

Tutti questi commenti su più facili da refactificare in seguito sono un carico di merluzzi. Una volta che la conoscenza viene integrata in molti luoghi, in particolare nei sistemi distribuiti, il costo del refactoring, la sincronizzazione dell'implementazione e quasi ogni altro costo lo fa perdere così tanto in considerazione che nella maggior parte dei casi il sistema finisce per essere cestinato a causa sua.

La cosa triste dello sviluppo di software in questi giorni è che il 90% delle persone sviluppa nuovi sistemi e non ha la capacità di comprendere vecchi sistemi, e non è mai in giro quando il sistema ha raggiunto un tale stato di salute a causa del continuo refactoring di bit e pezzi.


0

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:

inserisci qui la descrizione dell'immagine

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:

inserisci qui la descrizione dell'immagine

... 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 IMotione 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 Meshoggetto o IMeshun'interfaccia complessi , o una moltiplicazione vettoriale / matrice che dipendeva float[]o da double[]cui dipendeva FancyMatrixObjectWhichWillRequireDesignChangesNextYearAndDeprecateWhatWeUse.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.