È sbagliato usare un parametro booleano per determinare il comportamento?


194

Di tanto in tanto ho visto una pratica che "sembra" sbagliata, ma non riesco ad articolare cosa c'è di sbagliato al riguardo. O forse è solo un mio pregiudizio. Ecco qui:

Uno sviluppatore definisce un metodo con un valore booleano come uno dei suoi parametri, e quel metodo chiama un altro, e così via, e infine quel booleano viene utilizzato, solo per determinare se intraprendere o meno una determinata azione. Questo potrebbe essere usato, ad esempio, per consentire l'azione solo se l'utente ha determinati diritti, o forse se siamo (o non siamo) in modalità test o batch o live, o forse solo quando il sistema è in certo stato.

Bene, c'è sempre un altro modo per farlo, sia interrogando quando è il momento di intraprendere l'azione (piuttosto che passando il parametro), sia avendo più versioni del metodo, o più implementazioni della classe, ecc. La mia domanda non è C'è così tanto da migliorare, ma piuttosto se è davvero sbagliato (come sospetto), e se lo è, cosa c'è che non va.


1
Questa è una domanda su dove appartengano le decisioni. Sposta le decisioni in un posto centrale invece di farle sparpagliare ovunque. Ciò manterrà la complessità inferiore rispetto al fattore due dei percorsi del codice ogni volta che si ha un if.

28
Martin Fowler ha pubblicato un articolo su questo: martinfowler.com/bliki/FlagArgument.html
Christoffer Hammarström,


@ ChristofferHammarström Bel link. Lo includerò nella mia risposta poiché spiega in modo molto dettagliato la stessa idea della mia spiegazione.
Alex,

1
Non sono sempre d'accordo con quello che Nick ha da dire, ma in questo caso sono d'accordo al 100%: non usare parametri booleani .
Marjan Venema,

Risposte:


107

Sì, questo è probabilmente un odore di codice, che porterebbe a un codice non sostenibile che è difficile da capire e che ha minori possibilità di essere facilmente riutilizzato.

Come altri poster hanno notato che il contesto è tutto (non esagerare se si tratta di una tantum o se la pratica è stata riconosciuta come debito tecnico deliberatamente sostenuto per essere rielaborata in un secondo momento) ma in generale se c'è un parametro passato in una funzione che seleziona comportamenti specifici da eseguire, è necessario un ulteriore perfezionamento graduale; La suddivisione di questa funzione in funzioni più piccole produrrà altre più altamente coesive.

Quindi cos'è una funzione altamente coesiva?

È una funzione che fa una cosa e una cosa sola.

Il problema con un parametro passato mentre descrivi è che la funzione sta facendo più di due cose; può o meno verificare i diritti di accesso degli utenti a seconda dello stato del parametro booleano, quindi a seconda di tale albero decisionale eseguirà una funzionalità.

Sarebbe meglio separare le preoccupazioni relative al controllo degli accessi da quelle relative a compiti, azioni o comandi.

Come hai già notato, l'intreccio di queste preoccupazioni sembra spento.

Quindi la nozione di coesione ci aiuta a identificare che la funzione in questione non è altamente coesiva e che potremmo riformattare il codice per produrre un insieme di funzioni più coerenti.

Quindi la domanda potrebbe essere riformulata; Dato che siamo tutti d'accordo sul fatto che è meglio evitare di passare parametri di selezione comportamentale, come possiamo migliorare le cose?

Mi sbarazzerei completamente del parametro. La possibilità di disattivare il controllo dell'accesso anche per i test è un potenziale rischio per la sicurezza. A scopo di test, o del controllo di accesso per testare sia gli scenari di accesso consentiti che quelli negati.

Rif: Coesione (informatica)


Rob, vuoi dare qualche spiegazione su cos'è la coesione e come si applica?
Ray,

Ray, più ci penso e più penso che dovresti obiettare al codice basato sul buco di sicurezza che il booleano per girare del controllo di accesso introduce nell'applicazione. Il miglioramento della qualità della base di codice sarà un piacevole effetto collaterale;)
Rob

1
Splendida spiegazione della coesione e della sua applicazione. Questo dovrebbe davvero ottenere più voti. E sono anche d'accordo con il problema della sicurezza ... anche se sono tutti metodi privati ​​è una potenziale vulnerabilità minore
Ray

1
Grazie Ray. Sembra che sarà abbastanza facile ripensare quando il tempo lo consente. Potrebbe valere la pena rilasciare un commento TODO per evidenziare il problema, trovare un equilibrio tra autorità tecnica e sensibilità alla pressione a cui siamo talvolta sottoposti per fare le cose.
Rob,

"non
mantenibile

149

Ho smesso di usare questo schema molto tempo fa, per un motivo molto semplice; costo di manutenzione. Diverse volte ho scoperto che avevo una funzione dire frobnicate(something, forwards_flag)che era stata chiamata molte volte nel mio codice e che aveva bisogno di individuare tutte le posizioni nel codice in cui il valore è falsestato passato come valore di forwards_flag. Non puoi cercarli facilmente, quindi questo diventa un mal di testa di manutenzione. E se devi correggere un bug in ognuno di questi siti, potresti avere un problema sfortunato se ne perdi uno.

Ma questo problema specifico si risolve facilmente senza cambiare radicalmente l'approccio:

enum FrobnicationDirection {
  FrobnicateForwards,
  FrobnicateBackwards;
};

void frobnicate(Object what, FrobnicationDirection direction);

Con questo codice, si dovrebbe solo cercare istanze di FrobnicateBackwards. Mentre è possibile che ci sia del codice che lo assegna a una variabile, quindi devi seguire alcuni thread di controllo, trovo in pratica che questo è abbastanza raro che questa alternativa funzioni OK.

Tuttavia, c'è un altro problema con il passaggio della bandiera in questo modo, almeno in linea di principio. Questo è che alcuni (solo alcuni) sistemi con questo progetto potrebbero esporre troppe conoscenze sui dettagli di implementazione delle parti profondamente annidate del codice (che usa il flag) agli strati esterni (che devono sapere quale valore passare in questa bandiera). Per usare la terminologia di Larry Constantine, questo design potrebbe avere un accoppiamento troppo forte tra il setter e l'utente della bandiera booleana. Francamente però è difficile dirlo con un certo grado di certezza su questa domanda senza sapere di più sulla base di codice.

Per affrontare gli esempi specifici che fornisci, avrei un certo grado di preoccupazione in ciascuno, ma principalmente per motivi di rischio / correttezza. Cioè, se il tuo sistema ha bisogno di passare dei flag che indicano in che stato si trova il sistema, potresti scoprire che hai un codice che avrebbe dovuto tenerne conto ma non controlla il parametro (perché non è stato passato a questa funzione). Quindi hai un bug perché qualcuno ha omesso di passare il parametro.

Vale anche la pena ammettere che un indicatore dello stato del sistema che deve essere passato a quasi tutte le funzioni è in realtà essenzialmente una variabile globale. Verranno applicati molti degli aspetti negativi di una variabile globale. Penso che in molti casi sia meglio incapsulare la conoscenza dello stato del sistema (o le credenziali dell'utente o l'identità del sistema) all'interno di un oggetto che è responsabile di agire correttamente sulla base di tali dati. Quindi si passa attorno a un riferimento a quell'oggetto anziché ai dati non elaborati. Il concetto chiave qui è l' incapsulamento .


9
Esempi davvero concreti, nonché approfondimenti sulla natura di ciò con cui abbiamo a che fare e su come ci influenza.
Ray,

33
+1. Uso enum per questo il più possibile. Ho visto funzioni in cui boolsono stati aggiunti parametri aggiuntivi in ​​seguito e le chiamate iniziano a sembrare DoSomething( myObject, false, false, true, false ). È impossibile capire cosa significano gli argomenti extra booleani, mentre con valori enum con nomi significativi è facile.
Graeme Perrow,

17
Oh amico, finalmente un buon esempio di come FrobnicateBackwards. Lo cercavo da sempre.
Alex Pritchard,

38

Questo non è necessariamente sbagliato ma può rappresentare un odore di codice .

Lo scenario di base che dovrebbe essere evitato per quanto riguarda i parametri booleani è:

public void foo(boolean flag) {
    doThis();
    if (flag)
        doThat();
}

Quindi, quando chiami, in genere chiami foo(false)e in foo(true)base al comportamento esatto che desideri.

Questo è davvero un problema perché è un caso di cattiva coesione. Stai creando una dipendenza tra metodi che non è davvero necessaria.

Quello che dovresti fare in questo caso è lasciare doThise doThatcome metodi separati e pubblici quindi fare:

doThis();
doThat();

o

doThis();

In questo modo lasci la decisione corretta al chiamante (esattamente come se stessi passando un parametro booleano) senza creare l'accoppiamento.

Ovviamente non tutti i parametri booleani sono usati in modo così brutto ma è sicuramente un odore di codice e hai ragione a diventare sospettoso se vedi molto nel codice sorgente.

Questo è solo un esempio di come risolvere questo problema in base agli esempi che ho scritto. Vi sono altri casi in cui sarà necessario un approccio diverso.

C'è un buon articolo di Martin Fowler che spiega in dettaglio la stessa idea.

PS: se il metodo fooinvece di chiamare due metodi semplici aveva un'implementazione più complessa, tutto ciò che devi fare è applicare un piccolo metodo di estrazione del refactoring in modo che il codice risultante sia simile all'implementazione di fooquello che ho scritto.


1
Grazie per aver chiamato il termine "odore di codice" - sapevo che aveva un cattivo odore, ma non riuscivo a capire quale fosse l'odore. Il tuo esempio è praticamente perfetto con quello che sto guardando.
Ray,

24
Ci sono molte situazioni in cui l' if (flag) doThat()interno foo()è legittimo. Spingendo la decisione di invocare doThat()ogni chiamante forza la ripetizione che dovrà essere rimossa se in seguito si scopre per alcuni metodi, il flagcomportamento deve anche essere chiamato doTheOther(). Preferirei di gran lunga mettere le dipendenze tra i metodi nella stessa classe piuttosto che andare a scrutare tutti i chiamanti in seguito.
Blrfl,

1
@Blrfl: Sì, penso che i refactoring più semplici sarebbero la creazione di a doOnee doBothmetodi (rispettivamente per il caso falso e vero) o l'utilizzo di un tipo enum separato, come suggerito da James Youngman
hugomg,

@missingno: Avresti ancora lo stesso problema di inviare il codice ridondante ai chiamanti per prendere la decisione doOne()o doBoth(). Le subroutine / funzioni / metodi hanno argomenti per cui il loro comportamento può essere variato. Usare un enum per una condizione veramente booleana suona molto come ripetersi se il nome dell'argomento spiega già cosa fa.
Blrfl,

3
Se chiamare due metodi uno dopo l'altro o un solo metodo può essere considerato ridondante, è anche ridondante il modo in cui si scrive un blocco try-catch o forse un if if else. Vuol dire che scriverai una funzione per astrarli tutti? No! Fai attenzione, creare un metodo che tutto ciò che fa è chiamare altri due metodi non rappresenta necessariamente una buona astrazione.
Alex,

29

Prima di tutto: la programmazione non è una scienza tanto quanto è un'arte. Quindi molto raramente esiste un modo "sbagliato" e un "giusto" di programmare. La maggior parte degli standard di codifica sono semplicemente "preferenze" che alcuni programmatori considerano utili; ma alla fine sono piuttosto arbitrari. Quindi non definirei mai una scelta di parametro come "sbagliata" in sé e per sé - e certamente non qualcosa di generico e utile come un parametro booleano. L'uso di un boolean(o un int, del resto) per incapsulare lo stato è perfettamente giustificabile in molti casi.

Le decisioni di codifica, nel complesso, dovrebbero basarsi principalmente sulle prestazioni e sulla manutenibilità. Se la performance non è in gioco (e non riesco a immaginare come possa mai essere nei tuoi esempi), allora la tua prossima considerazione dovrebbe essere: quanto sarà facile per me (o un futuro redattore) mantenere? È intuitivo e comprensibile? È isolato? Il tuo esempio di chiamate di funzione concatenate sembra in effetti potenzialmente fragile in questo senso: se decidi di cambiare il tuo bIsUpin bIsDown, quanti altri punti del codice dovranno essere cambiati anche? Inoltre, l'elenco dei tuoi parametri è in mongolfiera? Se la tua funzione ha 17 parametri, la leggibilità è un problema e devi riconsiderare se stai apprezzando i vantaggi dell'architettura orientata agli oggetti.


4
Apprezzo l'avvertenza nel primo paragrafo. Sono stato intenzionalmente provocatorio dicendo "sbagliato", e certamente riconosco che stiamo affrontando il regno delle "migliori pratiche" e dei principi di progettazione, e che questo genere di cose sono spesso situazionali e si devono soppesare molteplici fattori
Ray

13
La tua risposta mi ricorda una citazione che non ricordo la fonte di - "se la tua funzione ha 17 parametri, probabilmente ne manchi uno".
Joris Timmermans,

Sono molto d'accordo con questo, e mi rivolgo alla domanda per dire che sì, spesso è una cattiva idea passare una bandiera booleana, ma non è mai così semplice come cattivo / buono ...
JohnB

15

Penso che l' articolo del codice di Robert C Martins Clean affermi che dovresti eliminare gli argomenti booleani ove possibile in quanto mostrano che un metodo fa più di una cosa. Un metodo dovrebbe fare una cosa e solo una cosa penso sia uno dei suoi motti.


@dreza ti riferisci alla legge di Curlys .
MattDavey,

Naturalmente con esperienza dovresti sapere quando ignorare tali argomenti.
gnasher729,

8

Penso che la cosa più importante qui sia essere pratica.

Quando il booleano determina l'intero comportamento, basta creare un secondo metodo.

Quando il booleano determina solo un po 'di comportamento nel mezzo, potresti voler tenerlo in uno per ridurre la duplicazione del codice. Ove possibile, potresti anche essere in grado di dividere il metodo in tre: due metodi di chiamata per entrambe le opzioni booleane e uno che svolge la maggior parte del lavoro.

Per esempio:

private void FooInternal(bool flag)
{
  //do work
}

public void Foo1()
{
  FooInternal(true);
}

public void Foo2()
{
  FooInternal(false);
}

Naturalmente, in pratica avrai sempre un punto tra questi due estremi. Di solito vado solo con ciò che sembra giusto, ma preferisco sbagliare a parte una minore duplicazione del codice.


Uso solo argomenti booleani per controllare il comportamento in metodi privati ​​(come mostrato qui). Ma il problema: se alcuni dufus decidono di aumentare la visibilità FooInternalin futuro, allora?
ADTC

In realtà, farei un'altra strada. Il codice all'interno di FooInternal dovrebbe essere diviso in 4 parti: 2 funzioni per gestire i casi vero / falso booleani, uno per il lavoro che accade prima e un altro per il lavoro che succede dopo. Quindi, il vostro Foo1diventa: { doWork(); HandleTrueCase(); doMoreWork() }. Idealmente, le funzioni doWorke doMoreWorksono suddivise in (una o più) parti significative di azioni discrete (cioè come funzioni separate), non solo due funzioni per il gusto di dividere.
jpaugh,

7

Mi piace l'approccio della personalizzazione del comportamento attraverso metodi di generazione che restituiscono istanze immutabili. Ecco come lo usa GuavaSplitter :

private static final Splitter MY_SPLITTER = Splitter.on(',')
       .trimResults()
       .omitEmptyStrings();

MY_SPLITTER.split("one,,,,  two,three");

I vantaggi di questo sono:

  • Leggibilità superiore
  • Chiara separazione tra configurazione e metodi di azione
  • Promuove la coesione costringendoti a pensare a cosa sia l'oggetto, cosa dovrebbe e non dovrebbe fare. In questo caso è a Splitter. Non avresti mai inserito someVaguelyStringRelatedOperation(List<Entity> myEntities)una classe chiamata Splitter, ma avresti pensato di inserirla come metodo statico in una StringUtilsclasse.
  • Le istanze sono preconfigurate, quindi facilmente iniettabili in dipendenza. Il cliente non deve preoccuparsi se passare trueo falsea un metodo per ottenere il comportamento corretto.

1
Sono parziale della tua soluzione come grande amante ed evangelista della Guava ... ma non posso darti un +1, perché salti la parte che sto davvero cercando, che è ciò che è sbagliato (o puzzolente) sull'altro modo. Penso che sia effettivamente implicito in alcune delle tue spiegazioni, quindi forse se potessi renderlo esplicito, risponderebbe meglio alla domanda.
Ray,

Mi piace l'idea di separare i metodi di configurazione e acton!
Sher10ck,

I collegamenti alla biblioteca di Guava sono interrotti
Josh Noe,

4

Sicuramente un odore di codice . Se non viola il singolo principio di responsabilità, allora probabilmente viola Tell, Don't Ask . Tener conto di:

Se si scopre che non viola uno di questi due principi, dovresti comunque usare un enum. Le bandiere booleane sono l'equivalente booleano dei numeri magici . foo(false)ha tanto senso quanto bar(42). Gli enum possono essere utili per il modello di strategia e avere la flessibilità di permetterti di aggiungere un'altra strategia. (Ricorda solo di nominarli in modo appropriato .)

Il tuo esempio particolare mi dà particolarmente fastidio. Perché questo flag viene passato attraverso così tanti metodi? Sembra che tu debba dividere il parametro in sottoclassi .


4

TL; DR: non usare argomenti booleani.

Vedi sotto perché sono cattivi e come sostituirli (in grassetto).


Gli argomenti booleani sono molto difficili da leggere e quindi difficili da mantenere. Il problema principale è che lo scopo è generalmente chiaro quando leggi la firma del metodo in cui l'argomento è chiamato. Tuttavia, la denominazione di un parametro non è generalmente richiesta nella maggior parte delle lingue. Quindi avrai anti-pattern come RSACryptoServiceProvider#encrypt(Byte[], Boolean)dove il parametro booleano determina quale tipo di crittografia deve essere usato nella funzione.

Quindi riceverai una chiamata come:

rsaProvider.encrypt(data, true);

dove il lettore deve cercare la firma del metodo semplicemente per determinare cosa diavolo truepotrebbe effettivamente significare. Passare un numero intero è ovviamente altrettanto negativo:

rsaProvider.encrypt(data, 1);

te lo direi altrettanto - o meglio: altrettanto poco. Anche se si definiscono le costanti da utilizzare per l'intero, gli utenti della funzione possono semplicemente ignorarle e continuare a utilizzare i valori letterali.

Il modo migliore per risolverlo è utilizzare un'enumerazione . Se devi passare un enum RSAPaddingcon due valori: OAEPo PKCS1_V1_5allora sarai immediatamente in grado di leggere il codice:

rsaProvider.encrypt(data, RSAPadding.OAEP);

I booleani possono avere solo due valori. Ciò significa che se si dispone di una terza opzione, è necessario riformattare la firma. Generalmente questo non può essere facilmente eseguito se la compatibilità con le versioni precedenti è un problema, quindi dovresti estendere qualsiasi classe pubblica con un altro metodo pubblico. Questo è ciò che Microsoft ha fatto alla fine quando ha introdotto il RSACryptoServiceProvider#encrypt(Byte[], RSAEncryptionPadding)punto in cui utilizzava un'enumerazione (o almeno una classe che imita un'enumerazione) anziché un valore booleano.

Potrebbe anche essere più semplice utilizzare un oggetto completo o un'interfaccia come parametro, nel caso in cui il parametro stesso debba essere parametrizzato. Nell'esempio sopra, il padding OAEP stesso potrebbe essere parametrizzato con il valore hash da usare internamente. Si noti che ora ci sono 6 algoritmi hash SHA-2 e 4 algoritmi hash SHA-3, quindi il numero di valori di enumerazione potrebbe esplodere se si utilizza solo una singola enumerazione anziché parametri (questa è probabilmente la prossima cosa che Microsoft scoprirà ).


I parametri booleani possono anche indicare che il metodo o la classe non è progettato correttamente. Come nell'esempio sopra: qualsiasi libreria crittografica diversa da quella .NET non usa affatto un flag di padding nella firma del metodo.


Quasi tutti i guru del software che mi piacciono mettono in guardia contro argomenti booleani. Ad esempio, Joshua Bloch mette in guardia contro di loro nel libro molto apprezzato "Effective Java". In generale, semplicemente non dovrebbero essere usati. Si potrebbe sostenere che potrebbero essere usati se esiste un parametro che è facile da capire. Ma anche allora: Bit.set(boolean)è probabilmente meglio implementato usando due metodi : Bit.set()e Bit.unset().


Se non è possibile refactificare direttamente il codice, è possibile definire costanti per renderle almeno più leggibili:

const boolean ENCRYPT = true;
const boolean DECRYPT = false;

...

cipher.init(key, ENCRYPT);

è molto più leggibile di:

cipher.init(key, true);

anche se preferiresti avere:

cipher.initForEncryption(key);
cipher.initForDecryption(key);

anziché.


3

Sono sorpreso che nessuno abbia menzionato parametri nominati .

Il problema che vedo con le bandiere booleane è che danneggiano la leggibilità. Ad esempio, cosa fa truedentro

myObject.UpdateTimestamps(true);

fare? Non ne ho idea. Ma per quanto riguarda:

myObject.UpdateTimestamps(saveChanges: true);

Ora è chiaro che cosa intende fare il parametro che stiamo passando: stiamo dicendo alla funzione di salvare le sue modifiche. In questo caso, se la classe non è pubblica, penso che il parametro booleano vada bene.


Ovviamente, non puoi forzare gli utenti della tua classe a usare parametri nominati. Per questo motivo, di enumsolito sono preferibili uno o due metodi separati, a seconda di quanto sia comune il tuo caso predefinito. Questo è esattamente ciò che fa .Net:

//Enum used
double a = Math.Round(b, MidpointRounding.AwayFromZero);

//Two separate methods used
IEnumerable<Stuff> ascendingList = c.OrderBy(o => o.Key);
IEnumerable<Stuff> descendingList = c.OrderByDescending(o => o.Key); 

1
La domanda non riguarda ciò che è preferibile a un comportamento che determina la bandiera, è se tale bandiera ha un odore e, in tal caso, perché
Ray,

2
@Ray: non vedo differenze tra queste due domande. In un linguaggio in cui è possibile imporre l'uso di parametri nominati o quando si è certi che verranno sempre utilizzati parametri denominati (ad es. Metodi privati) , i parametri booleani vanno bene. Se i parametri con nome non possono essere applicati dal linguaggio (C #) e la classe fa parte di un'API pubblica, o se il linguaggio non supporta parametri con nome (C ++), quindi quel codice come myFunction(true)potrebbe essere scritto, è un codice- odore.
BlueRaja - Danny Pflughoeft,

L'approccio con parametri nominati è ancora più sbagliato. Senza un nome, si sarà costretti a leggere il documento API. Con un nome, pensi di non aver bisogno: ma il parametro potrebbe essere errato. Ad esempio, avrebbe potuto essere usato per salvare (tutte) le modifiche, ma in seguito l'implementazione è cambiata un po 'in modo da salvare solo grandi cambiamenti (per un valore di grande).
Ingo,

@Ingo Non sono d'accordo. Questo è un problema di programmazione generico; puoi facilmente definire un altro SaveBignome. Qualsiasi codice può essere rovinato, questo tipo di rovina non è particolare per i parametri nominati.
Maarten Bodewes,

1
@Ingo: se sei circondato da idioti, vai a cercare lavoro altrove. Questo genere di cose è ciò per cui hai recensioni di codice.
gnasher729,

1

Non riesco ad articolare ciò che è sbagliato.

Se sembra un odore di codice, sembra un odore di codice e - beh - odora di odore di codice, è probabilmente un odore di codice.

Quello che vuoi fare è:

1) Evitare metodi con effetti collaterali.

2) Gestire gli stati necessari con una macchina a stati centrale e formale (come questa ).


1

Sono d'accordo con tutte le preoccupazioni dell'utilizzo di parametri booleani per non determinare le prestazioni al fine di; migliorare, leggibilità, affidabilità, riduzione della complessità, riduzione dei rischi derivanti da incapsulamento e coesione scadenti e riduzione del costo totale di proprietà con manutenibilità.

Ho iniziato a progettare hardware a metà degli anni '70, che ora chiamiamo SCADA (controllo di supervisione e acquisizione dati) e questi erano hardware ottimizzati con codice macchina in EPROM che eseguivano macro telecomandi e raccoglievano dati ad alta velocità.

La logica si chiama macchine Mealey & Moore che chiamiamo ora macchine a stati finiti . Anche questi devono essere fatti con le stesse regole di cui sopra, a meno che non sia una macchina a tempo reale con un tempo di esecuzione finito e quindi le scorciatoie devono essere fatte per servire allo scopo.

I dati sono sincroni ma i comandi sono asincroni e la logica dei comandi segue la logica booleana senza memoria ma con comandi sequenziali basati sulla memoria dello stato successivo precedente, presente e desiderato. Affinché ciò funzionasse nel linguaggio macchina più efficiente (solo 64 KB), è stata posta grande cura nel definire ogni processo in modo euristico IBM HIPO. Ciò a volte significava passare variabili booleane e fare rami indicizzati.

Ma ora con molta memoria e facilità di OOK, l'incapsulamento è oggi un ingrediente essenziale ma una penalità quando il codice è stato contato in byte per il tempo reale e il codice macchina SCADA.


0

Non è necessariamente sbagliato, ma nel tuo esempio concreto dell'azione che dipende da qualche attributo dell '"utente" passerei attraverso un riferimento all'utente piuttosto che una bandiera.

Ciò chiarisce e aiuta in vari modi.

Chiunque legga l'istruzione invocante si renderà conto che il risultato cambierà a seconda dell'utente.

Nella funzione che alla fine viene chiamata puoi facilmente implementare regole aziendali più complesse perché puoi accedere a qualsiasi attributo degli utenti.

Se una funzione / metodo nella "catena" fa qualcosa di diverso a seconda di un attributo utente, è molto probabile che una dipendenza simile sugli attributi dell'utente verrà introdotta in alcuni degli altri metodi della "catena".


0

Il più delle volte, considererei questa cattiva codifica. Posso pensare a due casi, tuttavia, in cui questa potrebbe essere una buona pratica. Dato che ci sono già molte risposte che dicono perché è male, offro due volte quando potrebbe essere buono:

Il primo è se ogni chiamata nella sequenza ha senso a sé stante. Avrebbe senso se il codice chiamante potesse essere modificato da vero a falso o falso in vero, o se il metodo chiamato potesse essere modificato per utilizzare direttamente il parametro booleano anziché trasmetterlo. La probabilità di dieci di queste chiamate di seguito è piccola, ma potrebbe accadere, e se lo facesse sarebbe una buona pratica di programmazione.

Il secondo caso è un po 'allungato dato che abbiamo a che fare con un booleano. Ma se il programma ha più thread o eventi, passare parametri è il modo più semplice per tenere traccia dei dati specifici di thread / eventi. Ad esempio, un programma può ricevere input da due o più socket. Potrebbe essere necessario che il codice in esecuzione per un socket produca messaggi di avviso e uno in esecuzione per un altro potrebbe non esserlo. Quindi (in un certo senso) ha senso che un set booleano ad un livello molto elevato passi attraverso molte chiamate di metodo ai luoghi in cui potrebbero essere generati messaggi di avviso. I dati non possono essere salvati (tranne che con grande difficoltà) in qualsiasi tipo di globale perché più thread o eventi interlacciati avrebbero bisogno del loro valore.

A dire il vero, in quest'ultimo caso probabilmente sarei creare una classe / struttura il cui unico contenuto è stato un valore booleano e passo che intorno al posto. Sarei quasi certo di aver bisogno di altri campi in poco tempo, ad esempio dove inviare i messaggi di avviso.


Un enum WARN, SILENT avrebbe più senso, anche quando si utilizza una classe / struttura mentre si scrive (cioè un contesto ). O semplicemente configurare la registrazione esternamente - non è necessario passare nulla.
Maarten Bodewes,

-1

Il contesto è importante. Tali metodi sono abbastanza comuni in iOS. Come solo un esempio di uso frequente, UINavigationController fornisce il metodo-pushViewController:animated: e il animatedparametro è BOOL. Il metodo svolge essenzialmente la stessa funzione in entrambi i modi, ma anima la transizione da un controller di visualizzazione a un altro se si passa in SÌ e non se si passa a NO. Questo sembra del tutto ragionevole; sarebbe sciocco dover fornire due metodi al posto di questo solo per poter determinare se usare o meno l'animazione.

Può essere più semplice giustificare questo genere di cose in Objective-C, dove la sintassi di denominazione dei metodi fornisce più contesto per ciascun parametro di quanto si ottenga in linguaggi come C e Java. Tuttavia, penso che un metodo che accetta un singolo parametro potrebbe facilmente assumere un valore booleano e avere comunque senso:

file.saveWithEncryption(false);    // is there any doubt about the meaning of 'false' here?

21
In realtà non ho idea di cosa falsesignifichi file.saveWithEncryptionnell'esempio. Significa che salverà senza crittografia? Se è così, perché nel mondo il metodo dovrebbe avere "con crittografia" proprio nel nome? Potrei capire avere un metodo simile save(boolean withEncryption), ma quando vedo file.save(false), non è affatto ovvio a colpo d'occhio che il parametro indica che sarebbe con o senza crittografia. Penso, in effetti, questo sia il primo punto di James Youngman, sull'uso di un enum.
Ray,

6
Un'ipotesi alternativa è che falsesignifica, non sovrascrivere alcun file esistente con lo stesso nome. Esempio contrastato, lo so, ma per essere sicuri dovrai controllare la documentazione (o il codice) per la funzione.
James Youngman,

5
Un metodo chiamato saveWithEncryptionche a volte non viene salvato con la crittografia è un bug. Dovrebbe essere possibile file.encrypt().save(), o come Javas new EncryptingOutputStream(new FileOutputStream(...)).write(file).
Christoffer Hammarström,

2
In realtà, il codice che fa qualcos'altro rispetto a quello che dice non funziona, ed è quindi un bug. Non vola per creare un metodo chiamato saveWithEncryption(boolean)che a volte salva senza crittografia, proprio come non vola per fare un metodo saveWithoutEncryption(boolean)che a volte salva con crittografia.
Christoffer Hammarström,

2
Il metodo è errato, perché c'è ovviamente "dubbio sul significato di 'falso' qui". Comunque, non scriverei mai un metodo come quello in primo luogo. Il salvataggio e la crittografia sono azioni separate e un metodo dovrebbe fare una cosa e farlo bene. Vedi i miei commenti precedenti per esempi migliori su come farlo.
Christoffer Hammarström,
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.