È sbagliato usare un parametro booleano per determinare i valori?


39

Secondo È sbagliato usare un parametro booleano per determinare il comportamento? , Conosco l'importanza di evitare l'uso di parametri booleani per determinare un comportamento, ad esempio:

versione originale

public void setState(boolean flag){
    if(flag){
        a();
    }else{
        b();
    }
    c();
}

nuova versione:

public void setStateTrue(){
    a();
    c();
}

public void setStateFalse(){
    b();
    c();
}

Ma che dire del caso in cui il parametro booleano viene utilizzato per determinare i valori anziché i comportamenti? per esempio:

public void setHint(boolean isHintOn){
    this.layer1.visible=isHintOn;
    this.layer2.visible=!isHintOn;
    this.layer3.visible=isHintOn;
}

Sto cercando di eliminare la bandiera isHintOn e creare 2 funzioni separate:

public void setHintOn(){
    this.layer1.visible=true;
    this.layer2.visible=false;
    this.layer3.visible=true;
}

public void setHintOff(){
    this.layer1.visible=false;
    this.layer2.visible=true;
    this.layer3.visible=false;
}

ma la versione modificata sembra meno mantenibile perché:

  1. ha più codici rispetto alla versione originale

  2. non può mostrare chiaramente che la visibilità di layer2 è opposta all'opzione di suggerimento

  3. quando viene aggiunto un nuovo livello (ad esempio: layer4), devo aggiungere

    this.layer4.visible=false;
    

    e

    this.layer4.visible=true;  
    

    in setHintOn () e setHintOff () separatamente

Quindi la mia domanda è, se il parametro booleano viene utilizzato per determinare solo i valori, ma non i comportamenti (ad es. No if-else su quel parametro), si consiglia comunque di eliminare quel parametro booleano?


26
Non è mai sbagliato se il codice risultante è più leggibile e gestibile ;-) Consiglierei di utilizzare il metodo singolo anziché i due metodi separati.
timone del

32
Presentate un argomento convincente secondo cui una singola implementazione di un metodo che imposta questi valori booleani consentirà un più facile mantenimento della classe e la comprensione della sua implementazione. Ottimo; quelle sono considerazioni legittime. Ma l' interfaccia pubblica della classe non deve essere deformata per adattarli. Se i metodi separati renderà l'interfaccia pubblica più facile da capire e lavorare con, definire il setHint(boolean isHintOn)come un privato metodo, e aggiungere pubblico setHintOne setHintOffmetodi che chiamano, rispettivamente, setHint(true)e setHint(false).
Mark Amery,

9
Sarei molto scontento di quei nomi di metodo: non offrono davvero alcun vantaggio setHint(true|false). Potahto di patate. Almeno usa qualcosa come setHinte unsetHint.
Konrad Rudolph,


4
@kevincline Se la condizione è un nome, scrivi isall'inizio. isValidecc. Quindi perché cambiarlo con due parole? Inoltre, "più naturale" è negli occhi di chi guarda. Se vuoi pronunciarlo come una frase inglese, allora per me sarebbe più naturale avere "se il suggerimento è attivo" con un "the" nascosto.
Mr Lister

Risposte:


95

Il design dell'API dovrebbe concentrarsi su ciò che è maggiormente utilizzabile per un client dell'API, dal lato chiamante .

Ad esempio, se questa nuova API richiede al chiamante di scrivere regolarmente codice come questo

if(flag)
    foo.setStateTrue();
else
    foo.setStateFalse();

allora dovrebbe essere ovvio che evitare il parametro è peggio che avere un'API che consente al chiamante di scrivere

 foo.setState(flag);

La prima versione produce solo un problema che deve essere risolto sul lato chiamante (e probabilmente più di una volta). Ciò non aumenta né la leggibilità né la manutenibilità.

Il lato dell'implementazione , tuttavia, non dovrebbe dettare l'aspetto dell'API pubblica. Se una funzione come setHintcon un parametro richiede meno codice nell'implementazione, ma un'API in termini setHintOn/ setHintOffsembra più facile da usare per un client, è possibile implementarla in questo modo:

private void setHint(boolean isHintOn){
    this.layer1.visible=isHintOn;
    this.layer2.visible=!isHintOn;
    this.layer3.visible=isHintOn;
}

public void setHintOn(){
   setHint(true);
}

public void setHintOff(){
   setHint(false);
}

Quindi, sebbene l'API pubblica non abbia un parametro booleano, non esiste una logica duplicata, quindi solo un posto dove cambiare quando arriva un nuovo requisito (come nell'esempio della domanda).

Questo funziona anche al contrario: se il setStatemetodo dall'alto deve passare tra due diversi pezzi di codice, tali pezzi di codice possono essere rifattorizzati in due diversi metodi privati. Quindi IMHO non ha senso cercare un criterio per decidere tra "un parametro / un metodo" e "zero parametri / due metodi" osservando gli interni. Guarda, tuttavia, il modo in cui vorresti vedere l'API nel ruolo di consumatore.

In caso di dubbi, prova a utilizzare "test driven development" (TDD), che ti costringerà a pensare all'API pubblica e a come utilizzarla.


DocBrown, diresti che la linea di demarcazione appropriata è se l'impostazione di ogni stato ha effetti collaterali complessi e possibilmente non reversibili? Ad esempio, se si sta semplicemente alternando un flag che fa ciò che dice sulla latta e non esiste una macchina a stati sottostante, si parametrizzerebbero i diversi stati. Considerando che, ad esempio, non si parametrizzerebbe un metodo come SetLoanFacility(bool enabled), poiché avendo fornito un prestito, potrebbe non essere facile toglierlo di nuovo, e le due opzioni potrebbero comportare una logica completamente diversa - e si vorrebbe passare a Crea separato / Rimuovi metodi.
Steve

15
@Steve: stai ancora cercando di progettare l'API in base ai requisiti che vedi sul lato dell'implementazione. Per essere onesti: questo è totalmente irrilevante. Utilizzare qualsiasi variante dell'API pubblica è più facile da utilizzare dal lato chiamante. Internamente, puoi sempre consentire a due metodi pubblici di chiamare uno privato con un parametro. O viceversa, puoi consentire a un metodo con un parametro di passare tra due metodi privati ​​con logica diversa.
Doc Brown,

@Steve: guarda la mia modifica.
Doc Brown,

Prendo in considerazione tutti i tuoi punti: in realtà ci sto pensando dal lato del chiamante (da qui il mio riferimento a "cosa dice sulla scatola"), e provando a formulare la regola appropriata in cui un chiamante si aspetterebbe di usare ogni approccio. Mi sembra che la regola sia se il chiamante si aspetta che le chiamate ripetute siano idempotenti e che le transizioni di stato siano non vincolate e senza effetti collaterali complessi. L'attivazione e la disattivazione di un interruttore della luce ambiente sarebbe parametrizzata, l'attivazione e la disattivazione di una centrale elettrica regionale sarebbe multi-metodo.
Steve

1
@Steve Quindi, se l'utente deve confermare un'impostazione specifica, Toggle()non è la funzione corretta da fornire. Questo è l'intero punto; se al chiamante interessa solo "cambiarlo" e non "ciò che finisce come", allora Toggle()è l'opzione che evita ulteriori controlli e decisioni. Non lo definirei un caso COMUNE, né consiglierei di renderlo disponibile senza una buona ragione, ma se l'utente ha bisogno di un interruttore, gli darei un interruttore.
Kamil Drakari,

40

Martin Fowler cita Kent Beck nel raccomandare setOn() setOff()metodi separati , ma dice anche che questo non dovrebbe essere considerato inviolabile:

Se si tira i dati [sic] da una fonte booleana, ad esempio un controllo dell'interfaccia utente o fonte di dati, avrei preferito setSwitch(aValue)che

if (aValue)
  setOn();
else
  setOff();

Questo è un esempio che un'API dovrebbe essere scritta per facilitare il chiamante, quindi se sappiamo da dove proviene il chiamante dovremmo progettare l'API tenendo a mente tali informazioni. Questo sostiene anche che a volte possiamo fornire entrambi gli stili se riceviamo i chiamanti in entrambi i modi.

Un'altra raccomandazione è quella di utilizzare un valore enumerato o un tipo di flag per dare nomi migliori truee falsespecifici per il contesto. Nel tuo esempio, showHinte hideHintpotrebbe essere migliore.


16
Nella mia esperienza, setSwitch (valore) risulta quasi sempre in un codice complessivo inferiore a setOn / setOff proprio a causa del codice if / else riportato nella risposta. Tendo a maledire gli sviluppatori che mi danno un'API di setOn / setOff piuttosto che setSwitch (valore).
17 del 26

1
Guardandolo da un obiettivo simile: se devi codificare quale sarà il valore, è facile con entrambi. Tuttavia, se è necessario impostarlo su, ad esempio, l'input dell'utente, se è possibile passare direttamente il valore direttamente, si risparmia un passaggio.
Finanzia la causa di Monica il

@ 17of26 come un controesempio concreto (per dimostrare che "dipende" piuttosto che uno è preferibile all'altro), nell'AppKit di Apple c'è un -[NSView setNeedsDisplay:]metodo in cui si passa YESse una vista deve essere ridisegnata e NOse non dovrebbe. Non hai quasi mai bisogno di dirlo di no, quindi UIKit non ha -[UIView setNeedsDisplay]parametri. Non ha il -setDoesNotNeedDisplaymetodo corrispondente .
Graham Lee,

2
@GrahamLee, penso che l'argomento di Fowler sia abbastanza sottile e si basi davvero sul giudizio. Non userei un bool isPremiumflag nel suo esempio, ma userei un enum ( BookingType bookingType) per parametrizzare lo stesso metodo, a meno che la logica di ogni prenotazione non fosse del tutto diversa. La "logica aggrovigliata" a cui fa riferimento Fowler è spesso desiderabile se si vuole essere in grado di vedere qual è la differenza tra le due modalità. E se fossero radicalmente diversi, esporrei esternamente il metodo parametrizzato e implementerei internamente i metodi separati.
Steve

3

Penso che stai mescolando due cose nel tuo post, l'API e l'implementazione. In entrambi i casi non credo che ci sia una regola forte che puoi usare sempre, ma dovresti considerare queste due cose in modo indipendente (il più possibile).

Cominciamo con l'API, entrambi:

public void setHint(boolean isHintOn)

e:

public void setHintOn()
public void setHintOff()

sono valide alternative a seconda di ciò che il tuo oggetto dovrebbe offrire e di come i tuoi clienti useranno l'API. Come ha sottolineato Doc, se i tuoi utenti hanno già una variabile booleana (da un controllo UI, un'azione dell'utente, un'API esterna, ecc.) La prima opzione ha più senso, altrimenti stai solo forzando un'istruzione if in più sul codice del client . Tuttavia, se ad esempio stai cambiando il suggerimento in vero quando inizi un processo e in falso alla fine la prima opzione ti dà qualcosa del genere:

setHint(true)
// Do your process
…
setHint(false)

mentre la seconda opzione ti dà questo:

setHintOn()
// Do your process
…
setHintOff()

quale IMO è molto più leggibile, quindi andrò con la seconda opzione in questo caso. Ovviamente, nulla ti impedisce di offrire entrambe le opzioni (o più, potresti usare un enum come Graham ha detto se questo ha più senso, ad esempio).

Il punto è che dovresti scegliere la tua API in base a cosa dovrebbe fare l'oggetto e al modo in cui i client lo useranno, non in base a come lo implementerai.

Quindi devi scegliere come implementare la tua API pubblica. Diciamo che abbiamo scelto i metodi setHintOne setHintOffcome nostra API pubblica e condividono questa logica comune come nel tuo esempio. Potresti facilmente astrarre questa logica attraverso un metodo privato (codice copiato da Doc):

private void setHint(boolean isHintOn){
    this.layer1.visible=isHintOn;
    this.layer2.visible=!isHintOn;
    this.layer3.visible=isHintOn;
}

public void setHintOn(){
   setHint(true);
}

public void setHintOff(){
   setHint(false);
}

Al contrario, supponiamo di aver scelto setHint(boolean isHintOn)una nostra API ma invertiamo il tuo esempio, a causa del motivo per cui l'impostazione del suggerimento On è completamente diversa dall'impostazione su Off. In questo caso potremmo implementarlo come segue:

public void setHint(boolean isHintOn){
    if(isHintOn){
        // Set it On
    } else {
        // Set it Off
    }    
}

O anche:

public void setHint(boolean isHintOn){    
    if(isHintOn){
        setHintOn()
    } else {
        setHintOff()
   }    
}

private void setHintOn(){
   // Set it On
}

private void setHintOff(){
   // Set it Off 
}

Il punto è che, in entrambi i casi, abbiamo prima scelto la nostra API pubblica e poi adattato la nostra implementazione per adattarla all'API prescelta (e ai vincoli che abbiamo), non viceversa.

A proposito, penso che lo stesso valga per il post che hai collegato sull'utilizzo di un parametro booleano per determinare il comportamento, vale a dire che dovresti decidere in base al tuo caso d'uso piuttosto che a una regola rigida (sebbene in quel caso particolare di solito la cosa corretta da fare è romperlo in più funzioni).


3
Come nota a margine, se è qualcosa come lo pseudo-blocco (un'istruzione all'inizio, una alla fine), probabilmente dovresti usare begine endo sinonimi, solo per chiarire esplicitamente che cosa fanno e implicare che un l'inizio deve avere una fine e viceversa.
Finanzia la causa di Monica il

Sono d'accordo con Nic e aggiungerei: Se vuoi assicurarti che inizio e fine siano sempre accoppiati, dovresti anche fornire idiomi specifici per il linguaggio per questo: guardie RAII / scope in C ++, usingblocchi in C #, withgestori di contesto di istruzioni in Python, passando il corpo di un oggetto lambda o richiamabile (ad es. sintassi del blocco Ruby), ecc.
Daniel Pryden,

Sono d'accordo con entrambi i punti, stavo solo cercando di dare un semplice esempio per illustrare l'altro caso (anche se è un cattivo esempio come entrambi avete sottolineato :)).
jesm00,

2

Per prima cosa: il codice non è automaticamente meno gestibile, solo perché è un po 'più lungo. La chiarezza è ciò che conta.

Ora, se hai davvero a che fare con i dati, allora quello che hai è un setter per una proprietà booleana. In tal caso potresti voler semplicemente archiviare direttamente quel valore e ricavare le visibilità del layer, ad es

bool isBackgroundVisible() {
    return isHintVisible;
}    

bool isContentVisible() {
    return !isHintVisible;
}

(Mi sono preso la libertà di dare ai livelli nomi reali - se non lo hai nel tuo codice originale, inizierei con quello)

Questo ti lascia ancora con la domanda se avere un setHintVisibility(bool)metodo. Personalmente, consiglierei di sostituirlo con un metodo showHint()e hideHint()- entrambi saranno davvero semplici e non dovrai cambiarli quando aggiungi i livelli. Tuttavia, non è un taglio netto giusto / sbagliato.

Ora se chiamare la funzione dovrebbe effettivamente cambiare la visibilità di quei livelli, si ha effettivamente un comportamento. In tal caso, consiglierei sicuramente metodi separati.


Quindi tl; dr è: consiglieresti di dividere in due funzioni perché non dovrai cambiarle quando aggiungi i layer? Se viene aggiunto un layer4, potrebbe essere necessario dire "this.layer4.visibility = isHintOn", quindi non sono sicuro di essere d'accordo. Semmai, è un demerito contro di esso, poiché ora quando viene aggiunto un livello dobbiamo modificare due funzioni, non solo una.
Erdrik Ironrose,

No (debolmente) lo consiglio per chiarezza ( showHintvs setHintVisibility). Ho citato il nuovo livello solo perché OP era preoccupato. Inoltre, abbiamo solo bisogno di aggiungere un nuovo metodo: isLayer4Visible. showHinte hideHintbasta impostare l' isHintVisibleattributo su true / false e ciò non cambia.
doubleYou

1
@doubleYou, dici che il codice più lungo non è automaticamente meno gestibile. Direi che la lunghezza del codice è una delle principali variabili di manutenibilità e chiarezza, superata solo dalla complessità strutturale. Qualsiasi codice che diventa più lungo e strutturalmente più complesso dovrebbe meritarselo, altrimenti i problemi semplici ricevono un trattamento più complesso del codice di quello che meritano e la base di codice acquisisce boschetti di linee non necessarie (il problema "troppi livelli di indiretta").
Steve

@Steve Sono pienamente d'accordo sul fatto che puoi progettare troppo le cose, ad esempio allungare il codice senza renderlo più chiaro - otoh, puoi sempre accorciare il codice a costo di chiarezza, quindi non esiste una relazione 1: 1 qui.
doubleYou

@Steve Think “codice di golf” - prendendo tale codice e riscriverlo in più linee non solito renderlo più chiaro. "Code golf" è un estremo, ma ci sono ancora molti programmatori là fuori che pensano che stipare tutto in un'unica espressione intelligente sia "elegante" e forse anche più veloce perché i compilatori non stanno ottimizzando abbastanza bene.
BlackJack,

1

I parametri booleani vanno bene nel secondo esempio. Come hai già capito, i parametri booleani non sono di per sé problematici. Sta cambiando il comportamento in base a un flag, il che è problematico.

Il primo esempio è problematico, perché la denominazione indica un metodo setter, ma le implementazioni sembrano essere qualcosa di diverso. Quindi hai l'antipasto di cambio di comportamento e un metodo fuorviante. Ma se il metodo in realtà è un setter regolare (senza cambio di comportamento), allora non ci sono problemi setState(boolean). Avere due metodi setStateTrue()e setStateFalse()complicare inutilmente le cose senza alcun beneficio.


1

Un altro modo per risolvere questo problema è introdurre un oggetto per rappresentare ogni suggerimento e far sì che l'oggetto sia responsabile della determinazione dei valori booleani associati a quel suggerimento. In questo modo è possibile aggiungere nuove permutazioni anziché semplicemente avere due stati booleani.

Ad esempio, in Java, potresti fare:

public enum HintState {
    SHOW_HINT(true, false, true),
    HIDE_HINT(false, true, false);

    private HintState(boolean layer1Visible, boolean layer2Visible, boolean layer3Visible) {
         // constructor body and accessors omitted for clarity
    }
}

E quindi il tuo codice chiamante sarebbe simile al seguente:

setHint(HintState.SHOW_HINT);

E il tuo codice di implementazione sarebbe simile al seguente:

public void setHint(HintState hint) {
    this.layer1Visible = hint.isLayer1Visible();
    this.layer2Visible = hint.isLayer2Visible();
    this.layer3Visible = hint.isLayer3Visible();
}

Ciò mantiene conciso il codice di implementazione e il codice del chiamante, in cambio della definizione di un nuovo tipo di dati che associa chiaramente intenzioni fortemente tipizzate, denominate intenzioni ai corrispondenti set di stati. Penso che sia meglio tutto intorno.


0

Quindi la mia domanda è, se il parametro booleano viene utilizzato per determinare solo i valori, ma non i comportamenti (ad es. No if-else su quel parametro), si consiglia comunque di eliminare quel parametro booleano?

Quando ho dubbi su queste cose. Mi piace immaginare come sarebbe la traccia dello stack.

Per molti anni ho lavorato a un progetto PHP che utilizzava la stessa funzione di setter e getter . Se viene passato null , restituirà il valore, altrimenti impostarlo. È stato orribile con cui lavorare .

Ecco un esempio di una traccia dello stack:

function visible() : line 440
function parent() : line 398
function mode() : line 384
function run() : line 5

Non avevi idea dello stato interno e questo ha reso più difficile il debug. Ci sono un sacco di altri effetti collaterali negativi, ma prova a vedere che c'è un valore nel nome di una funzione dettagliata e chiarezza quando le funzioni eseguono una singola azione.

Ora l'immagine lavora con la traccia dello stack per una funzione che ha un comportamento A o B basato su un valore booleano.

function bar() : line 92
function setVisible() : line 120
function foo() : line 492
function setVisible() : line 120
function run() : line 5

È confuso se me lo chiedi. Le stesse setVisiblelinee producono due diversi percorsi di traccia.

Quindi torna alla tua domanda. Prova a immaginare come apparirà la traccia dello stack, come comunica a una persona cosa sta succedendo e chiediti se stai aiutando una persona futura a eseguire il debug del codice.

Ecco alcuni consigli:

  • un nome di funzione chiaro che implica l'intento senza la necessità di conoscere i valori dell'argomento.
  • una funzione esegue una singola azione
  • il nome implica mutazione o comportamento immutabile
  • risolvere sfide di debug relative alla capacità di debug della lingua e degli strumenti.

A volte il codice sembra eccessivo al microscopio, ma quando torni all'immagine più grande un approccio minimalista lo farà scomparire. Se ne hai bisogno per distinguerti in modo da poterlo mantenere. L'aggiunta di molte piccole funzioni può sembrare eccessivamente dettagliata, ma migliora la manutenibilità se utilizzata in senso lato in un contesto più ampio.


1
Ciò che mi confonde della setVisibletraccia dello stack è che la chiamata setVisible(true)sembra comportare una chiamata a setVisible(false)(o viceversa, a seconda di come si è verificata la traccia).
David K,

0

In quasi tutti i casi in cui si passa un booleanparametro a un metodo come un flag per modificare il comportamento di qualcosa, è necessario prendere in considerazione un modo più esplicito e sicuro per farlo.

Se non fai altro che usare uno Enumche rappresenta lo stato, hai migliorato la comprensione del tuo codice.

Questo esempio usa la Nodeclasse da JavaFX:

public enum Visiblity
{
    SHOW, HIDE

    public boolean toggleVisibility(@Nonnull final Node node) {
        node.setVisible(!node.isVisible());
    }
}

è sempre meglio di, come si trova su molti JavaFXoggetti:

public void setVisiblity(final boolean flag);

ma penso .setVisible()e .setHidden()sono la soluzione migliore per la situazione in cui la bandiera è una booleanin quanto è la più esplicita e meno dettagliata.

nel caso di qualcosa con selezioni multiple, questo è ancora più importante per farlo in questo modo. EnumSetesiste solo per questo motivo.

Ed Mann ha un ottimo post sul blog proprio su questo argomento. Stavo per parafrasare proprio quello che dice, quindi per non duplicare lo sforzo, pubblicherò semplicemente un link al suo post sul blog come addendum a questa risposta.


0

Quando si decide tra un approccio per qualche interfaccia che passa un parametro (booleano) rispetto a metodi sovraccaricati senza detto parametro, consultare i client che consumano.

Se tutti gli utilizzi passassero valori costanti (es. Vero, falso), ciò sostiene i sovraccarichi.

Se tutti gli utilizzi passassero un valore variabile, ciò vale per il metodo con l'approccio con parametri.

Se nessuno di questi due estremi si applica, ciò significa che esiste un mix di usi del cliente, quindi devi scegliere se supportare entrambi i moduli o fare in modo che una categoria di clienti si adatti all'altro (che per loro è uno stile più innaturale).


E se stai progettando un sistema integrato e alla fine sei sia il produttore del codice che il "cliente consumatore" del codice? Come dovrebbe una persona in piedi nei panni di un cliente consumante formulare la propria preferenza per un approccio rispetto a un altro?
Steve

@Steve, come cliente utilizzatore, sai se stai passando o meno una costante o una variabile. Se si passano costanti, preferire i sovraccarichi senza il parametro.
Erik Eidt,

Ma sono interessato a spiegare perché dovrebbe essere così. Perché non impiegare enumerazioni per un numero limitato di costanti, dal momento che questa è una sintassi leggera nella maggior parte delle lingue progettata proprio per questo tipo di scopo?
Steve

@Steve, se sappiamo in fase di progettazione / fase di compilazione che i client utilizzerebbero un valore costante (vero / falso) in tutti i casi, ciò suggerisce che ci sono davvero due diversi metodi specifici piuttosto che un metodo generalizzato (che accetta un parametro). Direi contro l'introduzione della generalità di un metodo parametrizzato quando non viene utilizzato: è un argomento YAGNI.
Erik Eidt,

0

Vi sono due considerazioni da progettare:

  • API: quale interfaccia si presenta all'utente,
  • Implementazione: chiarezza, manutenibilità, ecc ...

Non dovrebbero essere confusi.

È perfettamente bene:

  • avere più metodi nel delegato API per una singola implementazione,
  • disporre di un singolo metodo nell'invio dell'API a più implementazioni a seconda della condizione.

Pertanto, qualsiasi argomento che tenti di bilanciare i costi / benefici di una progettazione API con i costi / benefici di una progettazione di implementazione è discutibile e dovrebbe essere esaminato attentamente.


Dal lato API

Come programmatore, in genere preferisco le API programmabili. Il codice è molto più chiaro quando posso inoltrare un valore rispetto a quando ho bisogno di un'istruzione if/ switchsul valore per decidere quale funzione chiamare.

Quest'ultimo può essere necessario se ogni funzione prevede argomenti diversi.

Nel tuo caso, quindi, un singolo metodo setState(type value)sembra migliore.

Tuttavia , non c'è niente di peggio che senza nome true, false, 2, ecc ... quei valori magici non hanno significato in proprio. Evita l'ossessione primitiva e abbraccia la digitazione forte.

Così, da un POV API, che voglio: setState(State state).


Dal punto di vista dell'implementazione

Consiglio di fare tutto ciò che è più facile.

Se il metodo è semplice, è meglio tenerlo insieme. Se il flusso di controllo è contorto, è meglio separarlo in più metodi, ciascuno con un sotto-caso o un passaggio della pipeline.


Infine, considera il raggruppamento .

Nel tuo esempio (con spazi bianchi aggiunti per la leggibilità):

this.layer1.visible = isHintOn;
this.layer2.visible = ! isHintOn;
this.layer3.visible = isHintOn;

Perché in layer2controtendenza? È una funzionalità o è un bug?

Potrebbe essere possibile avere 2 elenchi [layer1, layer3]e [layer2], con un nome esplicito che indica il motivo per cui sono raggruppati insieme, e quindi scorrere su tali elenchi.

Per esempio:

for (auto layer : this.mainLayers) { // layer2
    layer.visible = ! isHintOn;
}
for (auto layer : this.hintLayers) { // layer1 and layer3
    layer.visible = isHintOn;
}

Il codice parla da solo, è chiaro perché ci sono due gruppi e il loro comportamento differisce.


0

Separatamente dalla domanda setOn() + setOff()vs set(flag), prenderei attentamente in considerazione se un tipo booleano sia il migliore qui. Sei sicuro che non ci sarà mai una terza opzione?

Potrebbe valere la pena considerare un enum anziché un booleano. Oltre a consentire l'estensibilità, ciò rende anche più difficile ottenere il booleano in modo errato, ad esempio:

setHint(false)

vs

setHint(Visibility::HIDE)

Con l'enum, sarà molto più facile estenderlo quando qualcuno decide di voler un'opzione 'se necessario':

enum class Visibility {
  SHOW,
  HIDE,
  IF_NEEDED // New
}

vs

setHint(false)
setHint(true)
setHintAutomaticMode(true) // New

0

Secondo ..., conosco l'importanza di evitare l'uso di parametri booleani per determinare un comportamento

Suggerirei di rivalutare questa conoscenza.

Prima di tutto, non vedo la conclusione che stai proponendo nella domanda SE che hai collegato. Si tratta principalmente di inoltrare un parametro su più fasi di chiamate di metodo, dove viene valutato molto lungo la catena.

Nel tuo esempio, stai valutando il parametro nel tuo metodo. A tale proposito, non differisce affatto da nessun altro tipo di parametro.

In generale, non c'è assolutamente nulla di sbagliato nell'uso dei parametri booleani; e ovviamente qualsiasi parametro determinerà un comportamento, o perché dovresti averlo in primo luogo?


0

Definire la domanda

La tua domanda sul titolo è "È sbagliato [...]?" - ma cosa intendi con "sbagliato"?

Secondo un compilatore C # o Java, non è sbagliato. Sono certo che ne sei consapevole e non è quello che stai chiedendo. Temo a parte questo, abbiamo solo nprogrammatori n+1opinioni diverse. Questa risposta presenta ciò che il libro Clean Code ha da dire al riguardo.

Risposta

Clean Code è un valido argomento contro gli argomenti delle funzioni in generale:

Gli argomenti sono difficili. Prendono molto potere concettuale. [...] i nostri lettori avrebbero dovuto interpretarlo ogni volta che lo vedevano.

Un "lettore" qui può essere il consumatore API. Può anche essere il prossimo programmatore, che non sa ancora cosa fa questo codice - che potresti essere tu tra un mese. O passeranno attraverso 2 funzioni separatamente o attraverso 1 funzione due volte , una volta mantenute truee una volta falsein mente.
In breve, usa il minor numero di argomenti possibile .

Il caso specifico di un argomento flag viene successivamente affrontato direttamente:

Gli argomenti della bandiera sono brutti. Passare un booleano in una funzione è una pratica davvero terribile. Ciò complica immediatamente la firma del metodo, proclamando a gran voce che questa funzione fa più di una cosa. Fa una cosa se la bandiera è vera e un'altra se la bandiera è falsa!

Per rispondere direttamente alle tue domande:
secondo Clean Code , si consiglia di eliminare quel parametro.


Informazioni addizionali:

Il tuo esempio è piuttosto semplice, ma anche lì puoi vedere la semplicità propagarsi al tuo codice: le funzioni senza parametri eseguono solo compiti semplici, mentre l'altra funzione deve fare l'aritmetica booleana per raggiungere lo stesso obiettivo. È banale aritmetica booleana in questo esempio semplificato, ma potrebbe essere piuttosto complessa in una situazione reale.


Ho visto molti argomenti qui che dovresti renderlo dipendente dall'utente API, perché doverlo fare in molti posti sarebbe stupido:

if (isAfterSunset) light.TurnOn();
else light.TurnOff();

Io non sono d'accordo che qualcosa non ottimale che sta accadendo qui. Forse è troppo ovvio da vedere, ma la tua prima frase menziona "l'importanza di evitare di usare parametri booleani per determinare un comportamento" e questa è la base dell'intera domanda. Non vedo un motivo per rendere quella cosa che è brutta da fare più facile per l'utente API.


Non so se esegui dei test - in tal caso, considera anche questo:

Gli argomenti sono ancora più difficili da un punto di vista sperimentale. Immagina la difficoltà di scrivere tutti i casi di test per garantire che tutte le varie combinazioni di argomenti funzionino correttamente. Se non ci sono argomenti, questo è banale.


Hai davvero seppellito il lede qui: "... la tua prima frase menziona" l'importanza di evitare [sic] di usare parametri booleani per determinare un comportamento "e questa è la base dell'intera domanda. Non vedo un motivo per rendere più facile per l'utente API quello che è brutto. " Questo è un punto interessante, ma piuttosto minare il proprio argomento nell'ultimo paragrafo.
Wildcard il

Sepolto il lede? Il nucleo di questa risposta è "usa il minor numero di argomenti possibile", che è spiegato nella prima metà di questa risposta. Tutto ciò che segue sono solo informazioni aggiuntive: confutare un argomento contraddittorio (di un altro utente, non OP) e qualcosa che non si applica a tutti.
R. Schmitz,

L'ultimo paragrafo sta solo cercando di chiarire che la domanda del titolo non è abbastanza ben definita per rispondere. OP chiede se è "sbagliato", ma non dice in base a chi o cosa. Secondo il compilatore? Sembra un codice valido, quindi non è sbagliato. Secondo il libro Clean Code? Utilizza un argomento flag, quindi sì, è "sbagliato". Tuttavia, scrivo invece "consigliato", perché pragmatico> dogmatico. Pensi che debba renderlo più chiaro?
R. Schmitz,

quindi aspetta, la tua difesa della tua risposta è che la domanda del titolo è troppo poco chiara per poter rispondere? : D Va bene ... In realtà ho pensato che il punto che ho citato fosse un'interessante nuova interpretazione.
Carattere jolly

1
Vividamente chiaro ora; buon lavoro!
Wildcard il
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.