Esiste un nome per il modello (anti) di passaggio dei parametri che verrà utilizzato solo per diversi livelli in profondità nella catena di chiamate?


209

Stavo cercando di trovare alternative all'uso della variabile globale in alcuni codici legacy. Ma questa domanda non riguarda le alternative tecniche, sono principalmente preoccupato per la terminologia .

La soluzione ovvia è passare un parametro nella funzione invece di usare un globale. In questa base di codice legacy ciò significherebbe che devo cambiare tutte le funzioni nella lunga catena di chiamate tra il punto in cui verrà eventualmente utilizzato il valore e la funzione che riceve per prima il parametro.

higherlevel(newParam)->level1(newParam)->level2(newParam)->level3(newParam)

dove in newParamprecedenza era una variabile globale nel mio esempio, ma avrebbe potuto essere invece un valore precedentemente codificato. Il punto è che ora il valore di newParam è ottenuto higherlevel()e deve "viaggiare" fino in fondo level3().

Mi chiedevo se ci fosse un nome per questo tipo di situazione / modello in cui è necessario aggiungere un parametro a molte funzioni che "passano" il valore non modificato.

Spero che l'uso della terminologia appropriata mi permetta di trovare più risorse sulle soluzioni per la riprogettazione e descrivere questa situazione ai colleghi.


94
Questo è un miglioramento rispetto all'uso delle variabili globali. Illustra esattamente da quale stato dipende ciascuna funzione (ed è un passo sulla strada per le funzioni pure). Ho sentito che ha chiamato "threading" un parametro, ma non so quanto sia comune questa terminologia.
gardenhead

8
Questo è un tipo di spettro troppo ampio per avere una risposta specifica. A questo livello, lo definirei solo "codifica".
Machado,

38
Penso che il "problema" sia proprio la semplicità. Questa è sostanzialmente un'iniezione di dipendenza. Immagino che potrebbero esserci meccanismi che inietterebbero automagicamente la dipendenza attraverso la catena, se un membro più profondamente annidato ce l'ha, senza gonfiare gli elenchi di parametri delle funzioni. Forse guardare strategie di iniezione di dipendenza con diversi livelli di sofisticazione potrebbe portare alla terminologia che stai cercando, se ce n'è una.
null

7
Anche se apprezzo la discussione se si tratta di un buon modello / antipattern / concept / soluzione, quello che volevo davvero sapere è se c'è un nome per questo.
ecerulm

3
Ho anche sentito che chiamava threading più comunemente, ma anche idraulico , come nel ridurre una linea a piombo in tutto lo stack di chiamate.
wchargin,

Risposte:


202

I dati stessi si chiamano "dati del vagabondo" . È un "odore di codice", che indica che un pezzo di codice sta comunicando con un altro pezzo di codice a distanza, attraverso intermediari.

  • Aumenta la rigidità del codice, specialmente nella catena di chiamate. Sei molto più limitato nel modo in cui rifletti qualsiasi metodo nella catena di chiamate.
  • Distribuisce conoscenze su dati / metodi / architettura in luoghi a cui non importa minimamente. Se è necessario dichiarare i dati che stanno attraversando e la dichiarazione richiede una nuova importazione, è stato inquinato lo spazio dei nomi.

Rifattorizzare per rimuovere le variabili globali è difficile e i dati del vagabondo sono un metodo per farlo, e spesso il modo più economico. Ha i suoi costi.


73
Cercando "dati vagabondi" sono stato in grado di trovare il libro "Codice completo" sul mio abbonamento Safari. C'è una sezione nel libro intitolata "Ragioni per usare i dati globali" e uno dei motivi è "L'uso dei dati globali può eliminare i dati vagabondi". :). Sento che i "dati vagabondi" mi permetteranno di trovare più letteratura su come affrontare i globi. Grazie!
ecerulm

9
@JimmyJames, quelle funzioni fanno ovviamente cose. Non solo con quel nuovo parametro specifico che in precedenza era solo un globale.
ecerulm

174
In 20 anni di programmazione, non avevo mai sentito letteralmente questo termine prima, né sarebbe stato immediatamente ovvio cosa significasse. Non mi lamento della risposta, sto solo suggerendo che il termine non è così ampiamente usato / conosciuto. Forse sono solo io.
Derek Elkins,

6
Alcuni dati globali vanno bene. Invece di chiamarlo "dati globali", puoi chiamarlo "ambiente", perché è quello che è. L'ambiente potrebbe includere, ad esempio, un percorso di stringa per appdata (su Windows) o nel mio progetto attuale l'intero set di pennelli, penne, caratteri e così via GDI +, utilizzato da tutti i componenti.
Robinson,

7
@Robinson Non proprio. Ad esempio, vuoi davvero che il tuo codice di scrittura delle immagini tocchi% AppData%, o preferiresti che prendesse un argomento su dove scrivere? Questa è la differenza tra lo stato globale e un argomento. "Ambiente" può essere altrettanto facilmente una dipendenza iniettata, presente solo per coloro che sono responsabili dell'interazione con l'ambiente. I pennelli GDI + ecc. Sono più ragionevoli, ma in realtà è più un caso di gestione delle risorse in un ambiente che non può farlo per te - praticamente una carenza delle API sottostanti e / o della tua lingua / librerie / runtime.
Luaan,

102

Non penso che questo, di per sé, sia un anti-schema. Penso che il problema sia che stai pensando alle funzioni come a una catena quando in realtà dovresti pensare a ognuna come una scatola nera indipendente ( NOTA : i metodi ricorsivi sono una notevole eccezione a questo consiglio.)

Ad esempio, supponiamo che sia necessario calcolare il numero di giorni tra due date del calendario in modo da creare una funzione:

int daysBetween(Day a, Day b)

Per fare ciò, creo una nuova funzione:

int daysSinceEpoch(Day day)

Quindi la mia prima funzione diventa semplicemente:

int daysBetween(Day a, Day b)
{
    return daysSinceEpoch(b) - daysSinceEpoch(a);
}

Non c'è niente di anti-pattern in questo. I parametri del metodo daysB Between vengono passati a un altro metodo e non vengono mai citati altrimenti nel metodo, ma sono ancora necessari affinché quel metodo faccia quello che deve fare.

Quello che consiglierei è guardare ogni funzione e iniziare con un paio di domande:

  • Questa funzione ha un obiettivo chiaro e mirato o è un metodo "fai alcune cose"? Di solito il nome della funzione aiuta qui e se c'è qualcosa che non è descritto dal nome, è una bandiera rossa.
  • Ci sono troppi parametri? A volte un metodo può legittimamente richiedere un sacco di input ma avere così tanti parametri rende oneroso da usare o capire.

Se stai osservando un miscuglio di codice senza un singolo scopo raggruppato in un metodo, dovresti iniziare svelandolo. Questo può essere noioso. Inizia con le cose più semplici da estrarre e passare a un metodo separato e ripetere fino a quando non hai qualcosa di coerente.

Se hai solo troppi parametri, considera il refactoring da metodo a oggetto .


2
Bene, non intendevo essere controverso con il (anti-). Ma mi chiedo ancora se esiste un nome per la "situazione" di dover aggiornare molte firme di funzioni. Immagino sia più un "odore di codice" che un antipasto. Mi dice che c'è qualcosa da correggere in questo codice legacy, se devo aggiornare la firma di 6 funzioni per soddisfare l'eliminazione di un globale. Ma penso che il passaggio dei parametri sia di solito ok, e apprezzo i consigli su come affrontare il problema di fondo.
ecerulm

4
@ecerulm Non ne sono consapevole, ma dirò che la mia esperienza mi dice che convertire i globali in parametri è assolutamente il modo giusto per iniziare a eliminarli. Questo elimina lo stato condiviso in modo da poter ulteriormente refactoring. Immagino che ci siano più problemi con questo codice ma non c'è abbastanza nella descrizione per sapere quali sono.
JimmyJames,

2
Di solito seguo anche questo approccio, e probabilmente lo farò anche in questo caso. Volevo solo migliorare il mio vocabolario / terminologia su questo in modo da poter ricercare di più su questo e fare domande migliori e più mirate in futuro.
ecerulm

3
@ecerulm Non penso che ci sia un nome per questo. È come un sintomo comune a molte malattie, nonché a condizioni non patologiche, ad esempio "bocca secca". Se approfondisci la descrizione della struttura del codice, potrebbe indicare qualcosa di specifico.
JimmyJames,

@ecerulm Ti dice che c'è qualcosa da correggere - ora è molto più ovvio che qualcosa deve essere riparato rispetto a quando era invece una variabile globale.
immibis,

61

BobDalgleish ha già notato che questo modello (anti-) è chiamato " dati del vagabondo ".

Nella mia esperienza, la causa più comune di eccessivi dati sul vagabondo è avere un mucchio di variabili di stato collegate che dovrebbero davvero essere incapsulate in un oggetto o in una struttura di dati. A volte, potrebbe anche essere necessario nidificare un gruppo di oggetti per organizzare correttamente i dati.

Per un semplice esempio, si consideri un gioco che ha un personaggio personalizzabile, con immobili come playerName, playerEyeColore così via. Naturalmente, il giocatore ha anche una posizione fisica sulla mappa del gioco e varie altre proprietà come, diciamo, il livello di salute attuale e massimo e così via.

In una prima iterazione di un gioco del genere, potrebbe essere una scelta perfettamente ragionevole trasformare tutte queste proprietà in variabili globali - dopotutto, c'è un solo giocatore e quasi tutto nel gioco coinvolge in qualche modo il giocatore. Quindi il tuo stato globale potrebbe contenere variabili come:

playerName = "Bob"
playerEyeColor = GREEN
playerXPosition = -8
playerYPosition = 136
playerHealth = 100
playerMaxHealth = 100

Ma ad un certo punto, potresti scoprire che devi cambiare questo design, forse perché vuoi aggiungere una modalità multiplayer al gioco. Come primo tentativo, potresti provare a rendere tutte quelle variabili locali e passandole a funzioni che ne hanno bisogno. Tuttavia, potresti scoprire che un'azione particolare nel tuo gioco potrebbe comportare una catena di chiamate di funzioni come, ad esempio:

mainGameLoop()
 -> processInputEvent()
     -> doPlayerAction()
         -> movePlayer()
             -> checkCollision()
                 -> interactWithNPC()
                     -> interactWithShopkeeper()

... e la interactWithShopkeeper()funzione ha il negoziante che indirizza il giocatore per nome, quindi ora devi improvvisamente passare playerNamei dati del vagabondo attraverso tutte quelle funzioni. E, naturalmente, se il negoziante ritiene che i giocatori con gli occhi azzurri siano ingenui e addebitino prezzi più alti per loro, allora dovrai passare playerEyeColorattraverso l'intera catena di funzioni e così via.

La soluzione corretta , in questo caso, è ovviamente quella di definire un oggetto giocatore che incapsuli il nome, il colore degli occhi, la posizione, la salute e qualsiasi altra proprietà del personaggio del giocatore. In questo modo, devi solo passare quel singolo oggetto a tutte le funzioni che in qualche modo coinvolgono il giocatore.

Inoltre, molte delle funzioni sopra potrebbero essere naturalmente trasformate in metodi dell'oggetto del giocatore, il che darebbe loro automaticamente l'accesso alle proprietà del giocatore. In un certo senso, questo è solo zucchero sintattico, dal momento che la chiamata di un metodo su un oggetto passa effettivamente l'istanza dell'oggetto come parametro nascosto al metodo, ma rende il codice più chiaro e naturale se usato correttamente.

Certo, un gioco tipico avrebbe uno stato molto più "globale" di un semplice giocatore; per esempio, avresti quasi sicuramente una sorta di mappa su cui si svolge il gioco, e un elenco di personaggi non giocanti che si muovono sulla mappa, e forse oggetti posizionati su di essa, e così via. Potresti anche passare tutti quelli come oggetti vagabondi, ma ciò ingombrerebbe nuovamente gli argomenti del tuo metodo.

Invece, la soluzione consiste nel fare in modo che gli oggetti memorizzino i riferimenti a qualsiasi altro oggetto con cui hanno relazioni permanenti o temporanee. Quindi, per esempio, l'oggetto giocatore (e probabilmente anche qualsiasi oggetto NPC) probabilmente dovrebbe memorizzare un riferimento all'oggetto "mondo di gioco", che avrebbe un riferimento al livello / mappa corrente, in modo che un metodo come player.moveTo(x, y)non abbia bisogno di ricevere esplicitamente la mappa come parametro.

Allo stesso modo, se il nostro personaggio del giocatore avesse, diciamo, un cane da compagnia che li seguiva in giro, raggrupperemmo naturalmente tutte le variabili di stato che descrivono il cane in un singolo oggetto e darebbe al giocatore un riferimento al cane (in modo che il giocatore possa , diciamo, chiama il cane per nome) e viceversa (in modo che il cane sappia dove si trova il giocatore). E, naturalmente, vorremmo probabilmente rendere il giocatore e il cane oggetti entrambe le sottoclassi di un oggetto "attore" più generico, in modo che possiamo riutilizzare lo stesso codice per, diciamo, spostandoci entrambi sulla mappa.

Ps. Anche se ho usato un gioco come esempio, ci sono altri tipi di programmi in cui si presentano anche questi problemi. Nella mia esperienza, tuttavia, il problema di fondo tende ad essere sempre lo stesso: hai un sacco di variabili separate (sia locali che globali) che vogliono davvero essere raggruppate in uno o più oggetti interconnessi. Indipendentemente dal fatto che i "dati vaganti" che si intromettono nelle tue funzioni consistano in impostazioni "globali" delle opzioni o query nella cache memorizzate o vettori di stato in una simulazione numerica, la soluzione è invariabilmente identificare il contesto naturale a cui appartengono i dati e trasformarlo in un oggetto (o qualunque sia l'equivalente più vicino nella lingua scelta).


1
Questa risposta fornisce alcune soluzioni a una classe di problemi che potrebbero esistere. Potrebbero esserci situazioni in cui sono stati utilizzati globuli che indicherebbero una soluzione diversa. Ho un problema con l'idea che rendere i metodi parte della classe del giocatore equivale a passare oggetti ai metodi. Questo ignora il polimorfismo che non si replica facilmente in questo modo. Ad esempio, se voglio creare diversi tipi di giocatori con regole diverse sui movimenti e diversi tipi di proprietà, il semplice passaggio di questi oggetti a un metodo di implementazione richiederà molta logica condizionale.
JimmyJames,

6
@JimmyJames: il tuo punto sul polimorfismo è buono, e ho pensato di farlo da solo, ma l'ho lasciato fuori per evitare che la risposta si allungasse ancora. Il punto che stavo cercando (forse male) di chiarire era che, puramente in termini di flusso di dati, c'è poca differenza tra foo.method(bar, baz)e method(foo, bar, baz), ci sono altri motivi (tra cui polimorfismo, incapsulamento, località, ecc.) Per preferire il primo.
Ilmari Karonen,

@IlmariKaronen: anche l'ovvio vantaggio di rendere a prova di futuro i prototipi di funzioni da eventuali cambiamenti / aggiunte / eliminazioni / rifatturazioni negli oggetti (ad esempio, playerAge). Questo da solo è inestimabile.
smci

34

Non sono a conoscenza di un nome specifico per questo, ma credo che valga la pena menzionare che il problema che descrivi è solo il problema di trovare il miglior compromesso per l'ambito di tale parametro:

  • come variabile globale, l'ambito è troppo grande quando il programma raggiunge una certa dimensione

  • come parametro puramente locale, l'ambito potrebbe essere troppo piccolo, quando porta a molti elenchi di parametri ripetitivi nelle catene di chiamate

  • così come un compromesso, puoi spesso rendere tale parametro una variabile membro in una o più classi, ed è quello che definirei un corretto design di classe .


10
+1 per un corretto design di classe. Sembra un classico problema in attesa di una soluzione OO.
l0b0

21

Credo che lo schema che stai descrivendo sia esattamente l' iniezione di dipendenza . Numerosi commentatori hanno affermato che si tratta di un modello , non di un modello , e io tenderei ad essere d'accordo.

Concordo anche con la risposta di @ JimmyJames, in cui afferma che è buona prassi di programmazione trattare ogni funzione come una scatola nera che accetta tutti i suoi input come parametri espliciti. Cioè, se stai scrivendo una funzione che produce un panino con burro di arachidi e gelatina, potresti scriverlo come

Sandwich make_sandwich() {
    PeanutButter pb = get_peanut_butter();
    Jelly j = get_jelly();
    return pb + j;
}
extern PhysicalRefrigerator g_refrigerator;
PeanutButter get_peanut_butter() {
    return g_refrigerator.get("peanut butter");
}
Jelly get_jelly() {
    return g_refrigerator.get("jelly");
}

ma sarebbe meglio applicare l'iniezione di dipendenza e scriverla così:

Sandwich make_sandwich(Refrigerator& r) {
    PeanutButter pb = get_peanut_butter(r);
    Jelly j = get_jelly(r);
    return pb + j;
}
PeanutButter get_peanut_butter(Refrigerator& r) {
    return r.get("peanut butter");
}
Jelly get_jelly(Refrigerator& r) {
    return r.get("jelly");
}

Ora hai una funzione che documenta chiaramente tutte le sue dipendenze nella sua firma della funzione, che è ottima per la leggibilità. Dopo tutto, è vero che per poter make_sandwichaccedere a un Refrigerator; quindi la vecchia firma della funzione era sostanzialmente disingenua non prendendo il frigorifero come parte dei suoi input.

Come bonus, se esegui la tua gerarchia di classi nel modo giusto, evita il taglio e così via, puoi persino testare l'unità della make_sandwichfunzione passando un MockRefrigerator! (Potrebbe essere necessario testare l'unità in questo modo perché l'ambiente di test unità potrebbe non avere accesso a nessun messaggio di posta PhysicalRefrigeratorelettronica.)

Capisco che non tutti gli usi dell'iniezione di dipendenza richiedono di inserire un parametro con lo stesso nome su molti livelli nello stack di chiamate, quindi non sto rispondendo esattamente alla domanda che hai posto ... ma se stai cercando ulteriori letture su questo argomento, "Iniezione delle dipendenze" è sicuramente una parola chiave pertinente per te.


10
Questo è molto chiaramente un modello anti . Non è assolutamente necessario che venga superato un frigorifero. Ora, passare un ingrediente generico potrebbe funzionare, ma cosa succede se ottieni il pane dalla pattumiera, il tonno dalla dispensa, il formaggio dal frigorifero ... iniettando una dipendenza dalla fonte di ingredienti nell'operazione non correlata di formare quelli ingredienti in un panino, hai violato la separazione delle preoccupazioni e fatto un odore di codice.
Dewi Morgan,

8
@DewiMorgan: Ovviamente potresti rifattorizzare ulteriormente per generalizzare Refrigeratorin un IngredientSource, o anche generalizzare la nozione di "sandwich" in template<typename... Fillings> StackedElementConstruction<Fillings...> make_sandwich(ElementSource&); si chiama "programmazione generica" ​​ed è ragionevolmente potente, ma sicuramente è molto più arcano di quanto l'OP voglia davvero entrare in questo momento. Sentiti libero di aprire una nuova domanda sul corretto livello di astrazione per i programmi sandwich. ;)
Quuxplusone

11
Non commettere errori, l'utente non privilegiato non dovrebbe avere accesso a make_sandwich().
dotancohen,


19
L'errore più grave nel tuo codice è che stai conservando il burro di arachidi nel frigorifero.
Malvolio,

15

Questa è praticamente la definizione da manuale di accoppiamento , un modulo che ha una dipendenza che influenza profondamente un altro e che crea un effetto a catena quando viene cambiato. Gli altri commenti e risposte hanno ragione sul fatto che si tratta di un miglioramento rispetto al globale, poiché l'accoppiamento è ora più esplicito e più facile da vedere per il programmatore, anziché sovversivo. Ciò non significa che non dovrebbe essere riparato. Dovresti essere in grado di rifattorizzare per rimuovere o ridurre l'accoppiamento, anche se se è stato lì per un po 'può essere doloroso.


3
Se level3()necessario newParam, questo è sicuramente accoppiamento, ma in qualche modo parti diverse di codice devono comunicare tra loro. Non definirei necessariamente un parametro di funzione accoppiamento errato se tale funzione si avvale del parametro. Penso che l'aspetto problematico della catena sia l' accoppiamento aggiuntivo introdotto level1()e level2()che non serve a nulla newParamse non per trasmetterlo. Buona risposta, +1 per l'accoppiamento.
null

6
@null Se davvero non lo usassero , potrebbero recuperare un valore invece di ricevere dal loro chiamante.
Casuale 832

3

Sebbene questa risposta non risponda direttamente alla tua domanda, penso che sarei negligente lasciarla passare senza menzionare come migliorarla (poiché, come dici, potrebbe essere un anti-schema). Spero che tu e altri lettori possiate trarre valore da questo commento aggiuntivo su come evitare i "dati vagabondi" (come Bob Dalgleish lo ha così utilmente chiamato per noi).

Concordo con le risposte che suggeriscono di fare qualcosa di più OO per evitare questo problema. Tuttavia, un altro modo per aiutare a ridurre profondamente questo passaggio di argomenti senza passare semplicemente a " passare una classe in cui passavi molti argomenti! " È il refactoring in modo che alcuni passaggi del processo avvengano a livello superiore anziché inferiore uno. Ad esempio, eccone alcuni prima del codice:

public void PerformReporting(StuffRepository repo, string desiredName) {
   var stuffs = repo.GetStuff(DateTime.Now());
   FilterAndReportStuff(stuffs, desiredName);
}

public void FilterAndReportStuff(IEnumerable<Stuff> stuffs, string desiredName) {
   var filter = CreateStuffFilter(FilterTypes.Name, desiredName);
   ReportStuff(stuffs.Filter(filter));
}

public void ReportStuff(IEnumerable<Stuff> stuffs) {
   stuffs.Report();
}

Si noti che questo peggiora ancora di più le cose da fare ReportStuff. Potrebbe essere necessario passare l'istanza del reporter che si desidera utilizzare. E tutti i tipi di dipendenze che devono essere consegnate, funzionano in funzione annidata.

Il mio suggerimento è di portare tutto ciò a un livello superiore, in cui la conoscenza dei passaggi richiede la vita in un singolo metodo invece di essere diffusa in una catena di chiamate di metodo. Naturalmente sarebbe più complicato nel codice reale, ma questo ti dà un'idea:

public void PerformReporting(StuffRepository repo, string desiredName) {
   var stuffs = repo.GetStuff(DateTime.Now());
   var filter = CreateStuffFilter(FilterTypes.Name, desiredName);
   var filteredStuffs = stuffs.Filter(filter)
   filteredStuffs.Report();
}

Nota che la grande differenza qui è che non devi passare le dipendenze attraverso una lunga catena. Anche se ti appiattisci non solo a un livello, ma a pochi livelli di profondità, se anche questi livelli raggiungono un certo "appiattimento" in modo che il processo venga visto come una serie di passaggi a quel livello, avrai apportato un miglioramento.

Mentre questo è ancora procedurale e nulla è stato ancora trasformato in un oggetto, è un buon passo per decidere quale tipo di incapsulamento è possibile ottenere trasformando qualcosa in una classe. Il metodo profondamente incatenato nello scenario precedente nasconde i dettagli di ciò che sta realmente accadendo e può rendere il codice molto difficile da capire. Mentre puoi esagerare e finire per far conoscere il codice di livello superiore a cose che non dovrebbero, o fare un metodo che fa troppe cose violando così il principio della responsabilità singola, in generale ho scoperto che l'appiattimento delle cose aiuta un po ' nella chiarezza e nel fare cambiamenti incrementali verso un codice migliore.

Nota che mentre stai facendo tutto questo, dovresti considerare la testabilità. Le chiamate del metodo concatenato in realtà rendono più difficile il test unitario perché non si dispone di un buon punto di ingresso e punto di uscita nell'assieme per la sezione che si desidera verificare. Nota che con questo appiattimento, poiché i tuoi metodi non richiedono più così tante dipendenze, sono più facili da testare, non richiedono altrettante beffe!

Di recente ho provato ad aggiungere test unitari a una classe (che non avevo scritto) che richiedeva qualcosa come 17 dipendenze, tutte da deridere! Non ho ancora capito tutto, ma ho diviso la classe in tre classi, ciascuna con uno dei nomi separati di cui si occupava, e ho ridotto l'elenco delle dipendenze a 12 per il peggiore e circa 8 per il il migliore.

La testabilità ti costringerà a scrivere codice migliore. Dovresti scrivere unit test perché scoprirai che ti fa pensare al tuo codice in modo diverso e scriverai un codice migliore sin dall'inizio, indipendentemente da quanti bug potresti aver avuto prima di scrivere i test unitari.


2

Non stai letteralmente violando la Legge di Demetra, ma il tuo problema è simile a quello in qualche modo. Poiché il punto della tua domanda è trovare risorse, ti suggerisco di leggere su Legge di Demetra e vedere quanto di questi consigli si applica alla tua situazione.


1
Un po 'debole nei dettagli, il che probabilmente spiega i downvotes. Eppure, nello spirito, questa risposta è esatta: OP dovrebbe leggere la Legge di Demetra - questo è il termine pertinente.
Konrad Rudolph,

4
FWIW, non credo che la Legge di Demetra (alias "il privilegio minimo") sia rilevante. Il caso dell'OP è quello in cui la sua funzione non sarebbe in grado di svolgere il suo lavoro se non avesse i dati del vagabondo (perché il ragazzo successivo nella pila delle chiamate ne ha bisogno, perché il ragazzo successivo ne ha bisogno, e così via). Il privilegio minimo / Legge di Demetra è rilevante solo se il parametro è veramente inutilizzato , e in tal caso la correzione è ovvia: rimuovere il parametro inutilizzato!
Quuxplusone

2
La situazione di questa domanda non ha esattamente nulla a che fare con la Legge di Demetra ... C'è una somiglianza superficiale per quanto riguarda la catena di chiamate al metodo, ma per il resto è molto diversa.
Eric King,

@Quuxplusone Possibile, anche se in questo caso la descrizione è abbastanza confusa perché le chiamate concatenate non hanno davvero senso in quello scenario: dovrebbero invece essere nidificate .
Konrad Rudolph,

1
Il problema è molto simile alle violazioni del LoD, dal momento che il solito refactoring suggerito per affrontare le violazioni del LoD è introdurre dati vagabondi. IMHO, questo è un buon punto di partenza per ridurre l'accoppiamento ma non è sufficiente.
Jørgen Fogh,

1

Ci sono casi in cui il migliore (in termini di efficienza, manutenibilità e facilità di implementazione) di avere determinate variabili come globali piuttosto che il sovraccarico di passare sempre tutto intorno (diciamo che hai circa 15 variabili che devono persistere). Quindi ha senso trovare un linguaggio di programmazione che supporti meglio l'ambito (come le variabili statiche private di C ++) per alleviare il potenziale disordine (dello spazio dei nomi e manomettere le cose). Naturalmente questa è solo conoscenza comune.

Tuttavia, l'approccio indicato dall'OP è molto utile se si sta eseguendo la programmazione funzionale.


0

Non esiste affatto un anti-pattern, perché il chiamante non è a conoscenza di tutti questi livelli sottostanti e non gli importa.

Qualcuno sta chiamando higherLevel (params) e si aspetta che higherLevel faccia il suo lavoro. Quello che un livello superiore fa con params non è affare dei chiamanti. higherLevel gestisce il problema nel miglior modo possibile, in questo caso passando i parametri a livello 1 (parametri). Va assolutamente bene.

Vedi una catena di chiamate, ma non esiste una catena di chiamate. C'è una funzione in alto che fa il suo lavoro nel miglior modo possibile. E ci sono altre funzioni. Ogni funzione può essere sostituita in qualsiasi momento.

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.