it-swarm.it

Cosa c'è che non va nei riferimenti circolari?

Oggi sono stato coinvolto in una discussione di programmazione in cui ho fatto alcune affermazioni che sostanzialmente presupponevano assiomaticamente che i riferimenti circolari (tra moduli, classi, qualunque cosa) siano generalmente cattivi. Una volta che ho superato il mio passo, il mio collega ha chiesto "cosa c'è che non va nei riferimenti circolari?"

Ho delle forti sensazioni al riguardo, ma è difficile per me verbalizzare in modo conciso e concreto. Qualsiasi spiegazione che mi viene in mente tende a fare affidamento su altri elementi che anch'io considero assiomi ("non posso usare in isolamento, quindi non posso testare", "comportamento sconosciuto/indefinito quando lo stato muta negli oggetti partecipanti", ecc. .), ma mi piacerebbe sentire una ragione concisa per cui i riferimenti circolari sono cattivi che non fanno i tipi di salti di fede che il mio cervello fa, avendo trascorso molte ore nel corso degli anni a districarli per capire, sistemare, ed estendere vari bit di codice.

Modifica: Non sto chiedendo riferimenti circolari omogenei, come quelli in un elenco doppiamente collegato o da puntatore a genitore. Questa domanda si sta davvero ponendo domande su riferimenti circolari di "portata maggiore", come libA che chiama libB che richiama a libA. Sostituisci 'modulo' con 'lib' se vuoi. Grazie per tutte le risposte finora!

167
dash-tom-bang

Ci sono grandi cose molte cose sbagliate con riferimenti circolari:

  • I riferimenti circolari di classe creano un accoppiamento elevato ; entrambi le classi devono essere ricompilate ogni volta che entrambi di esse vengono cambiate.

  • Riferimenti circolari di assemblaggio impediscono il collegamento statico , perché B dipende da A ma da A non può essere assemblato fino al completamento di B.

  • Riferimenti circolari oggetti possono crash algoritmi ricorsivi ingenui (come serializzatori, visitatori e pretty-printers) con overflow dello stack. Gli algoritmi più avanzati avranno il rilevamento del ciclo e falliranno semplicemente con un messaggio di errore/eccezione più descrittivo.

  • I riferimenti circolari agli oggetti rendono anche impossibile l'iniezione di dipendenza , riducendo significativamente il testabilità del tuo sistema.

  • Gli oggetti con un numero molto grande di riferimenti circolari sono spesso God Objects . Anche se non lo sono, hanno la tendenza a portare a Codice Spaghetti .

  • Riferimenti circolari entità (specialmente nei database, ma anche nei modelli di dominio) impediscono l'uso di vincoli di non annullabilità , che può eventualmente portare alla corruzione dei dati o almeno all'incoerenza.

  • I riferimenti circolari in generale sono semplicemente confusi e aumentano drasticamente il carico cognitivo quando tentando di capire come funziona un programma.

Per favore, pensa ai bambini; evita riferimenti circolari ogni volta che puoi.

228
Aaronaught

Un riferimento circolare è il doppio dell'accoppiamento di un riferimento non circolare.

Se Foo conosce Bar e Bar sa Foo, hai due cose che devono essere cambiate (quando viene richiesto che Foos e Bar non devono più conoscersi). Se Foo conosce Bar, ma un Bar non conosce Foo, puoi cambiare Foo senza toccare Bar.

I riferimenti ciclici possono anche causare problemi di bootstrap, almeno in ambienti che durano a lungo (servizi distribuiti, ambienti di sviluppo basati su immagini), in cui Foo dipende dal funzionamento di Bar per caricare, ma Bar dipende anche dal funzionamento di Foo per caricare.

22
Frank Shearar

Quando si collegano due bit di codice, si ottiene effettivamente un grosso pezzo di codice. La difficoltà di mantenere un po 'di codice è almeno il quadrato delle sue dimensioni, e possibilmente maggiore.

Le persone spesso guardano alla complessità della singola classe (/ funzione/file/ecc.) E dimenticano che dovresti davvero considerare la complessità della più piccola unità separabile (incapsulabile). Avere una dipendenza circolare aumenta le dimensioni di quell'unità, possibilmente invisibilmente (fino a quando non inizi a provare a cambiare il file 1 e ti rendi conto che richiede anche modifiche nei file 2-127).

17
Alex Feinman

Potrebbero essere cattivi non da soli ma come indicatore di un possibile cattivo design. Se Foo dipende da Bar e Bar dipende da Foo, è giustificato chiedersi perché siano due invece di un FooBar unico.

14
mouviciel

Hmm ... dipende da cosa intendi per dipendenza circolare, perché in realtà ci sono alcune dipendenze circolari che penso siano molto utili.

Prendi in considerazione un DOM XML: ha senso che ogni nodo abbia un riferimento al proprio genitore e che ogni genitore abbia un elenco dei suoi figli. La struttura è logicamente un albero, ma dal punto di vista di un algoritmo di garbage collection o simile la struttura è circolare.

10
Billy ONeal

È come il problema pollo o uovo .

Esistono molti casi in cui i riferimenti circolari sono inevitabili e utili ma, ad esempio, nel seguente caso non funzionano:

Il progetto A dipende dal progetto B e B dipende da A. A deve essere compilato per essere utilizzato in B che richiede che B sia compilato prima di A che richiede che B sia compilato prima di A che ...

9
Victor Hurdugaci

Mentre sono d'accordo con la maggior parte dei commenti qui, vorrei presentare un caso speciale per il riferimento circolare "genitore"/"figlio".

Una classe ha spesso bisogno di sapere qualcosa sul suo genitore o sulla sua classe proprietaria, forse sul comportamento predefinito, sul nome del file da cui provengono i dati, sull'istruzione sql che ha selezionato la colonna o sulla posizione di un file di registro ecc.

Puoi farlo senza un riferimento circolare avendo una classe contenitiva in modo che quello che prima era il "genitore" sia ora un fratello, ma non è sempre possibile ricodificare il codice esistente per farlo.

L'altra alternativa è passare tutti i dati di cui un bambino potrebbe aver bisogno nel suo costruttore, il che finisce per essere semplicemente orribile.

6
James Anderson

In termini di database, i riferimenti circolari con relazioni PK/FK appropriate rendono impossibile l'inserimento o l'eliminazione di dati. Se non è possibile eliminare dalla tabella a se il record non è passato dalla tabella b e non è possibile eliminare dalla tabella b a meno che il record non sia passato dalla tabella A, non è possibile eliminare. Lo stesso con gli inserti. questo è il motivo per cui molti database non consentono di impostare aggiornamenti a cascata o eliminazioni se è presente un riferimento circolare perché a un certo punto non diventa possibile. Sì, è possibile impostare questo tipo di relazioni senza che il PK/Fk venga dichiarato formalmente, ma in seguito (il 100% delle volte nella mia esperienza) avrà problemi di integrità dei dati. Questo è solo un cattivo design.

5
HLGEM

Prenderò questa domanda dal punto di vista della modellazione.

Fintanto che non aggiungi relazioni che non sono effettivamente lì, sei al sicuro. Se li aggiungi, otterrai meno integrità nei dati (causa ridondanza) e codice più strettamente associato.

La cosa con i riferimenti circolari in particolare è che non ho visto un caso in cui sarebbero stati effettivamente necessari tranne un riferimento personale. Se si modellano alberi o grafici, è necessario ed è perfettamente a posto perché l'autoreferenzialità è innocua dal punto di vista della qualità del codice (nessuna dipendenza aggiunta).

Credo che nel momento in cui inizi a aver bisogno di un non-auto-riferimento, dovresti immediatamente chiedere se non puoi modellarlo come un grafico (comprimere le entità multiple in uno - nodo). Forse c'è un caso tra cui si fa un riferimento circolare ma modellarlo come grafico non è appropriato ma ne dubito fortemente.

Esiste il pericolo che le persone pensino di aver bisogno di un riferimento circolare, ma in realtà non lo fanno. Il caso più comune è "Il caso uno dei tanti". Ad esempio, hai un cliente con più indirizzi da cui uno dovrebbe essere contrassegnato come indirizzo principale. È molto allettante modellare questa situazione come due relazioni separate has_address e is_primary_address_of ma non è corretto. Il motivo è che essendo l'indirizzo principale non è una relazione separata tra utenti e indirizzi ma invece è n attributo del la relazione ha indirizzo . Perché? Perché il suo dominio è limitato agli indirizzi dell'utente e non a tutti gli indirizzi presenti. Scegli uno dei link e lo contrassegni come il più forte (primario).

(Adesso parliamo di database) Molte persone optano per la soluzione a due relazioni perché comprendono "primario" come un puntatore univoco e una chiave esterna è una specie di puntatore. Quindi la chiave esterna dovrebbe essere la cosa da usare, giusto? Sbagliato. Le chiavi esterne rappresentano relazioni ma "primaria" non è una relazione. È un caso degenerato di un ordinamento in cui un elemento è soprattutto e il resto non è ordinato. Se avessi bisogno di modellare un ordinamento totale, ovviamente lo considereresti come l'attributo di una relazione perché sostanzialmente non c'è altra scelta. Ma nel momento in cui la degeneri, c'è una scelta piuttosto orribile: modellare qualcosa che non è una relazione come relazione. Quindi ecco che arriva la ridondanza delle relazioni che non è certo qualcosa da sottovalutare. Il requisito di unicità dovrebbe essere imposto in un altro modo, ad esempio da indici parziali univoci.

Quindi, non permetterei che si verifichi un riferimento circolare a meno che non sia assolutamente chiaro che proviene dalla cosa che sto modellando.

(nota: questo è leggermente distorto per la progettazione del database, ma scommetto che è abbastanza applicabile anche ad altre aree)

4
clime

Risponderei a questa domanda con un'altra domanda:

Quale situazione mi puoi dare se mantenere un modello di riferimento circolare è il modello migliore per quello che stai cercando di costruire?

Dalla mia esperienza, il modello migliore non coinvolgerà praticamente mai riferimenti circolari nel modo in cui penso che tu lo intenda. Detto questo, ci sono molti modelli in cui usi sempre riferimenti circolari, è semplicemente estremamente semplice. Genitore -> Relazioni figlio, qualsiasi modello di grafico, ecc., Ma questi sono modelli ben noti e penso che ti riferisca a qualcos'altro.

2
Joseph

Alcuni garbage collector hanno difficoltà a ripulirli, perché a ogni oggetto fa riferimento un altro.

EDIT: Come notato dai commenti qui sotto, questo è vero solo per un tentativo estremamente ingenuo di un garbage collector, non uno che mai incontreresti nella pratica.

1
shmuelp

Un costrutto di riferimento circolare è problematico, non solo dal punto di vista del design, ma anche dal punto di vista della cattura dell'errore.

Considera la possibilità di un errore del codice. Non hai inserito errori corretti in entrambe le classi, o perché non hai ancora sviluppato i tuoi metodi finora o sei pigro. Ad ogni modo, non hai un messaggio di errore per dirti cosa è successo e devi eseguire il debug. Come buon programmatore di programmi, sai quali metodi sono correlati a quali processi, quindi puoi restringerlo a quei metodi rilevanti per il processo che ha causato l'errore.

Con riferimenti circolari, i tuoi problemi sono ora raddoppiati. Poiché i tuoi processi sono strettamente legati, non hai modo di sapere quale metodo in quale classe potrebbe aver causato l'errore o da dove provenga l'errore, perché una classe dipende dall'altra dipende dall'altra. Ora devi dedicare del tempo a testare entrambe le classi insieme per scoprire quale è realmente responsabile dell'errore.

Naturalmente, la corretta rilevazione degli errori risolve questo problema, ma solo se si sa quando è probabile che si verifichi un errore. E se stai usando messaggi di errore generici, non stai ancora molto meglio.

1
Zibbobz

I riferimenti circolari nelle strutture di dati sono talvolta il modo naturale di esprimere un modello di dati. Per quanto riguarda la codifica, non è sicuramente l'ideale e può essere (in una certa misura) risolto dall'iniezione di dipendenza, spingendo il problema dal codice ai dati.

1
Vatine