I booleani come argomenti del metodo sono inaccettabili? [chiuso]


123

Un mio collega afferma che i booleani come argomenti del metodo non sono accettabili . Sono sostituiti da elenchi. All'inizio non ho visto alcun vantaggio, ma mi ha dato un esempio.

Cosa è più facile da capire?

file.writeData( data, true );

O

enum WriteMode {
  Append,
  Overwrite
};

file.writeData( data, Append );

Ora ho capito! ;-)
Questo è sicuramente un esempio in cui un'enumerazione come secondo parametro rende il codice molto più leggibile.

Allora, qual è la tua opinione su questo argomento?


7
Questa è stata una lettura molto interessante, implementerò questo metodo più spesso.
Sara Chipps

hrm, l'ho già fatto prima, ma non mi sono mai reso conto di quanto sia buono questo modello di design. quindi l'enumerazione va in file?
Shawn

Gli enum hanno sicuramente più senso da un punto di vista semantico. In un'altra nota, sarebbe interessante vedere cosa hanno escogitato alcuni programmatori per gestire la logica fuzzy.
James P.

2
chiedi solo al ragazzo del limone in Adventure Time se questo è inaccettabile
ajax333221

Risposte:


131

I valori booleani rappresentano le scelte "sì / no". Se vuoi rappresentare un "sì / no", usa un booleano, dovrebbe essere autoesplicativo.

Ma se è una scelta tra due opzioni, nessuna delle quali è chiaramente sì o no, allora un enum a volte può essere più leggibile.


3
Inoltre, il nome del metodo deve essere chiaro su cosa fa l'argomento yes o no, ovvero void turnLightOn (bool) impostando chiaramente true o yes accenderà la luce.
simon

10
Anche se in questo caso preferirei forse turnLightOn () e turnLightOff (), a seconda della situazione.
skaffman

14
"turnLightOn (false)" significa "non accendere la luce"? Confusiong.
Jay Bazuzi

17
Che ne dici setLightOn(bool).
Finbarr

10
Commento in ritardo, ma @Jay Bazuzi: se il tuo metodo si chiama turnLightOn e passi false, potresti anche non chiamare affatto il metodo, passando false dice di non accendere la luce. Se la luce è già accesa, non significa spegnerla, significa non accenderla ... Se hai un metodo 'turnLight' un enum con 'On' e 'Off' ha senso, turnLight ( On), accendere Light (Off). Sono d'accordo con skaffman tho, preferirei due diversi metodi espliciti, turnLightOn () e turnLightOff (). (BTW: questa roba è spiegata nel libro di zio Bobs "Clean Code")
Phill


32

Usa quello che meglio modella il tuo problema. Nell'esempio che fornisci, l'enum è una scelta migliore. Tuttavia, ci sarebbero altre volte in cui un booleano è migliore. Che ha più senso per te:

lock.setIsLocked(True);

o

enum LockState { Locked, Unlocked };
lock.setLockState(Locked);

In questo caso, potrei scegliere l'opzione booleana poiché penso che sia abbastanza chiara e inequivocabile, e sono abbastanza sicuro che il mio lucchetto non avrà più di due stati. Tuttavia, la seconda scelta è valida, ma inutilmente complicata, IMHO.


2
nel tuo esempio preferirei avere due metodi. lock.lock () lock.release () e lock.IsSet, ma tutto dipende da ciò che ha più senso per il codice in uso.
Robert Paulson,

3
Questo è un commento corretto, ma penso che illustri anche il punto più ampio che ci sono molti modi per modellare un dato problema. Dovresti usare il modello migliore per le tue circostanze e dovresti anche usare i migliori strumenti che l'ambiente di programmazione fornisce per adattarsi al tuo modello.
Jeremy Bourque,

Sono totalmente d'accordo :), stavo solo commentando il particolare pseudocodice che offre ancora un'altra ripresa. Sono d'accordo con la tua risposta.
Robert Paulson,

14

Per me, né l'uso di booleano né l'enumerazione è un buon approccio. Robert C.Martin lo cattura molto chiaramente nel suo suggerimento sul codice pulito n.12: Elimina gli argomenti booleani :

Gli argomenti booleani dichiarano ad alta voce che la funzione fa più di una cosa. Sono fonte di confusione e dovrebbero essere eliminati.

Se un metodo fa più di una cosa, dovresti piuttosto scrivere due metodi diversi, ad esempio nel tuo caso: file.append(data)e file.overwrite(data).

L'uso di un'enumerazione non rende le cose più chiare. Non cambia nulla, è pur sempre un argomento flag.


7
Questo non significa che una funzione che accetta una stringa ASCII di lunghezza N fa 128 ^ N cose?
detly

@delty È un commento serio? Se sì, codifichi spesso un if su tutti i possibili valori di una stringa? C'è qualche possibile confronto con il caso di un argomento booleano?
Pascal Thivent

Credo che siano accettabili quando imposti un valore booleano all'interno di un oggetto. Un perfetto esempio sarebbe setVisible(boolean visible) { mVisible = visible; }. Quale alternativa suggeriresti?
Brad

2
@Brad show () {mVisible = true} hide () {mVisible = false}
Oswaldo Acauan,

@Oswaldo, sebbene abbia ancora ragione, non credo abbia assolutamente senso avere due metodi diversi per assegnare un valore booleano a valori diversi. Non hai un setIntToOne (), setIntToTwo () setIntToThree () giusto? È un po 'più ambiguo quando puoi avere solo due valori possibili, ma per motivi di pulizia usa un booleano in quel caso.
Brad

13

Penso che tu abbia quasi risposto tu stesso, penso che l'obiettivo finale sia quello di rendere il codice più leggibile, e in questo caso l'enum lo ha fatto, IMO è sempre meglio guardare allo scopo finale piuttosto che alle regole generali, forse pensaci di più come linea guida, ad esempio le enumerazioni sono spesso più leggibili nel codice rispetto a bool generici, int, ecc. ma ci saranno sempre eccezioni alla regola.


13

Ricordate la domanda che Adlai Stevenson ha posto all'ambasciatore Zorin all'ONU durante la crisi dei missili cubani ?

"Sei nell'aula di tribunale dell'opinione pubblica in questo momento e puoi rispondere sì o no . Hai negato che [i missili] esistano, e voglio sapere se ti ho capito correttamente ... Sono pronto ad aspettare per la mia risposta finché l'inferno non si sarà congelato, se questa è la tua decisione. "

Se la bandiera che hai nel tuo metodo è di una natura tale che puoi vincolarla a una decisione binaria , e quella decisione non si trasformerà mai in una decisione a tre o quattro direzioni, scegli booleano. Indicazioni: la tua bandiera si chiama isXXX .

Non renderlo booleano nel caso di qualcosa che è un cambio di modalità . C'è sempre una modalità in più di quanto pensavi quando hai scritto il metodo in primo luogo.

Il dilemma di una modalità in più ha ad esempio infestato Unix, dove le possibili modalità di autorizzazione che un file o una directory possono avere oggi si traducono in strani doppi significati di modalità a seconda del tipo di file, proprietà ecc.


13

Ci sono due ragioni per cui mi sono imbattuto in questa cosa negativa:

  1. Perché alcune persone scriveranno metodi come:

    ProcessBatch(true, false, false, true, false, false, true);
    

    Questo è ovviamente un male perché è troppo facile confondere i parametri e non hai idea di ciò che stai specificando. Un solo bool non è poi così male.

  2. Perché controllare il flusso del programma tramite un semplice ramo sì / no potrebbe significare che hai due funzioni completamente diverse che sono racchiuse in una in modo strano. Per esempio:

    public void Write(bool toOptical);
    

    In realtà, dovrebbero essere due metodi

    public void WriteOptical();
    public void WriteMagnetic();
    

    perché il codice in questi potrebbe essere completamente diverso; potrebbero dover eseguire tutti i tipi di gestione e convalida degli errori diversi, o forse anche dover formattare i dati in uscita in modo diverso. Non puoi dirlo semplicemente usando Write()o anche Write(Enum.Optical)( anche se ovviamente potresti avere uno di questi metodi semplicemente chiamare i metodi interni WriteOptical / Mag se lo desideri).

Immagino che dipenda solo. Non ne farei un affare troppo grande tranne che per # 1.


Punti molto buoni! Due parametri booleani in un metodo sembrano davvero terribili (a meno che tu non sia fortunato ad avere parametri denominati, ovviamente).
Yarik

Questa risposta potrebbe trarre vantaggio da una riformattazione, però! ;-)
Yarik

7

Le enumerazioni sono migliori ma non definirei i parametri booleani "inaccettabili". A volte è solo più facile inserire un piccolo booleano e andare avanti (pensa a metodi privati ​​ecc.)


basta rendere il metodo molto descrittivo in modo che sia chiaro cosa significhi vero o sì.
simon

6

I booleani possono essere OK nei linguaggi che hanno parametri denominati, come Python e Objective-C, poiché il nome può spiegare cosa fa il parametro:

file.writeData(data, overwrite=true)

o:

[file writeData:data overwrite:YES]

1
IMHO, writeData () è un cattivo esempio di utilizzo di un parametro booleano, indipendentemente dal fatto che i parametri denominati siano o meno supportati. Non importa come si denomina il parametro, il significato del valore False non è ovvio!
Yarik

4

Non sono d'accordo sul fatto che sia una buona regola . Ovviamente, Enum rende un codice più esplicito o dettagliato in alcuni casi, ma di regola sembra molto più che estendibile.

Per prima cosa, lasciatemi fare il vostro esempio: la responsabilità (e capacità) dei programmatori di scrivere un buon codice non è realmente messa a repentaglio dall'avere un parametro booleano. Nel tuo esempio il programmatore avrebbe potuto scrivere codice dettagliato scrivendo:

dim append as boolean = true
file.writeData( data, append );

o preferisco più generale

dim shouldAppend as boolean = true
file.writeData( data, shouldAppend );

Secondo: l'esempio Enum che hai fornito è solo "migliore" perché stai superando un CONST. Molto probabilmente nella maggior parte delle applicazioni almeno alcuni, se non la maggior parte, dei parametri che vengono passati alle funzioni sono VARIABILI. in tal caso il mio secondo esempio (fornire variabili con buoni nomi) è molto migliore e Enum ti avrebbe dato pochi vantaggi.


1
Sebbene sia d'accordo che i parametri booleani siano accettabili in molti casi, nel caso di questo esempio writeData (), il parametro booleano come shouldAppend è molto inappropriato. Il motivo è semplice: non è immediatamente ovvio quale sia il significato di Falso.
Yarik

4

Le enumerazioni hanno un netto vantaggio, ma non dovresti semplicemente sostituire tutti i tuoi booleani con le enumerazioni. Ci sono molti posti in cui vero / falso è in realtà il modo migliore per rappresentare ciò che sta accadendo.

Tuttavia, usarli come argomenti del metodo è un po 'sospetto, semplicemente perché non puoi vedere senza scavare nelle cose cosa dovrebbero fare, poiché ti permettono di vedere cosa significa effettivamente il vero / falso

Le proprietà (specialmente con gli inizializzatori di oggetti C # 3) o gli argomenti delle parole chiave (a la ruby ​​o python) sono un modo molto migliore per andare dove altrimenti useresti un argomento booleano.

Esempio C #:

var worker = new BackgroundWorker { WorkerReportsProgress = true };

Esempio di rubino

validates_presence_of :name, :allow_nil => true

Esempio di Python

connect_to_database( persistent=true )

L'unica cosa a cui posso pensare dove un argomento del metodo booleano è la cosa giusta da fare è in java, dove non hai né proprietà né argomenti di parole chiave. Questo è uno dei motivi per cui odio java :-(


4

Anche se è vero che in molti casi le enumerazioni sono più leggibili ed estensibili dei booleani, una regola assoluta per cui "i booleani non sono accettabili" è stupida. È inflessibile e controproducente: non lascia spazio al giudizio umano. Sono un tipo incorporato fondamentale nella maggior parte dei linguaggi perché sono utili - considera di applicarlo ad altri tipi incorporati: dire ad esempio "non usare mai un int come parametro" sarebbe semplicemente folle.

Questa regola è solo una questione di stile, non di potenziali bug o prestazioni di runtime. Una regola migliore sarebbe "preferire le enumerazioni ai booleani per ragioni di leggibilità".

Guarda il framework .Net. I booleani sono usati come parametri su parecchi metodi. L'API .Net non è perfetta, ma non credo che l'uso di boolean come parametri sia un grosso problema. Il suggerimento ti dà sempre il nome del parametro e puoi anche creare questo tipo di guida: inserisci i tuoi commenti XML sui parametri del metodo, verranno visualizzati nel suggerimento.

Dovrei anche aggiungere che c'è un caso in cui dovresti rifattorizzare chiaramente i booleani a un'enumerazione - quando hai due o più booleani sulla tua classe, o nei parametri del tuo metodo, e non tutti gli stati sono validi (ad esempio non è valido averli entrambi impostati su vero).

Ad esempio, se la tua classe ha proprietà come

public bool IsFoo
public bool IsBar

Ed è un errore averli entrambi veri allo stesso tempo, quello che hai effettivamente sono tre stati validi, meglio espressi come qualcosa del tipo:

enum FooBarType { IsFoo, IsBar, IsNeither };

4

Alcune regole a cui il tuo collega potrebbe aderire meglio sono:

  • Non essere dogmatico con il tuo design.
  • Scegli quello che si adatta meglio agli utenti del tuo codice.
  • Non provare a colpire i pioli a forma di stella in ogni buco solo perché ti piace la forma questo mese!

3

Un valore booleano sarebbe accettabile solo se non si intende estendere la funzionalità del framework. L'Enum è preferito perché è possibile estendere l'enumerazione e non interrompere le implementazioni precedenti della chiamata di funzione.

L'altro vantaggio dell'Enum è che è più facile da leggere.


2

Se il metodo pone una domanda come:

KeepWritingData (DataAvailable());

dove

bool DataAvailable()
{
    return true; //data is ALWAYS available!
}

void KeepWritingData (bool keepGoing)
{
   if (keepGoing)
   {
       ...
   }
}

Gli argomenti del metodo booleano sembrano avere un senso assolutamente perfetto.


Un giorno dovrai aggiungere "continua a scrivere se hai spazio libero", e poi passerai comunque da bool a enum.
Ilya Ryzhenkov

E poi avrai un cambiamento radicale, o avrai un sovraccarico obsoleto, o potresti essere qualcosa come KeppWritingDataEx :)
Ilya Ryzhenkov

1
@ Ilya o forse no! Creare una possibile situazione in cui attualmente non esiste non nega la soluzione.
Jesse C. Slicer

1
Jesse ha ragione. Pianificare un cambiamento del genere è sciocco. Fai ciò che ha senso. In questo caso, il valore booleano è sia intuitivo che chiaro. c2.com/xp/DoTheSimplestThingThatCouldPossiblyWork.html
Derek Park,

@Derek, in questo caso, booleano non è nemmeno necessario, perché DataAvailable restituisce sempre true :)
Ilya Ryzhenkov

2

Dipende dal metodo. Se il metodo fa qualcosa che è chiaramente una cosa vera / falsa, allora va bene, ad esempio sotto [anche se non sto dicendo che questo è il miglior design per questo metodo, è solo un esempio di dove l'uso è ovvio].

CommentService.SetApprovalStatus(commentId, false);

Tuttavia, nella maggior parte dei casi, come nell'esempio citato, è meglio utilizzare un'enumerazione. Ci sono molti esempi nello stesso .NET Framework in cui questa convenzione non viene seguita, ma questo perché hanno introdotto questa linea guida di progettazione abbastanza tardi nel ciclo.


2

Rende le cose un po 'più esplicite, ma inizia ad estendere in modo massiccio la complessità delle tue interfacce - in una pura scelta booleana come aggiungere / sovrascrivere sembra eccessivo. Se devi aggiungere un'ulteriore opzione (a cui non riesco a pensare in questo caso), puoi sempre eseguire un refactoring (a seconda della lingua)


1
Che ne dici di anteporre come terza opzione possibile? ;-))
Yarik

2

Le enumerazioni possono certamente rendere il codice più leggibile. Ci sono ancora alcune cose a cui prestare attenzione (almeno in .net)

Poiché l'archiviazione sottostante di un enum è un int, il valore predefinito sarà zero, quindi dovresti assicurarti che 0 sia un valore predefinito ragionevole. (Ad esempio, gli struct hanno tutti i campi impostati su zero quando vengono creati, quindi non c'è modo di specificare un valore predefinito diverso da 0. Se non hai un valore 0, non puoi nemmeno testare l'enumerazione senza eseguire il cast a int, che sarebbe cattivo stile.)

Se le tue enumerazioni sono private del tuo codice (mai esposte pubblicamente), puoi smettere di leggere qui.

Se le tue enumerazioni sono pubblicate in qualche modo in codice esterno e / o vengono salvate fuori dal programma, considera di numerarle esplicitamente. Il compilatore li numera automaticamente da 0, ma se riorganizzi le tue enumerazioni senza dare loro valori puoi finire con dei difetti.

Posso scrivere legalmente

WriteMode illegalButWorks = (WriteMode)1000000;
file.Write( data, illegalButWorks );

Per contrastare questo problema, qualsiasi codice che utilizza un'enum di cui non si può essere certi (ad esempio l'API pubblica) deve controllare se l'enum è valido. Lo fai tramite

if (!Enum.IsDefined(typeof(WriteMode), userValue))
    throw new ArgumentException("userValue");

L'unico avvertimento Enum.IsDefinedè che utilizza la riflessione ed è più lento. Inoltre soffre di un problema di controllo delle versioni. Se devi controllare spesso il valore enum, faresti meglio a fare quanto segue:

public static bool CheckWriteModeEnumValue(WriteMode writeMode)
{
  switch( writeMode )
  {
    case WriteMode.Append:
    case WriteMode.OverWrite:
      break;
    default:
      Debug.Assert(false, "The WriteMode '" + writeMode + "' is not valid.");
      return false;
  }
  return true;
}

Il problema del controllo delle versioni è che il vecchio codice potrebbe solo sapere come gestire i 2 enumeratori che hai. Se aggiungi un terzo valore, Enum.IsDefined sarà true, ma il vecchio codice non può necessariamente gestirlo. Ops.

C'è ancora più divertimento che puoi fare con le [Flags]enumerazioni e il codice di convalida per questo è leggermente diverso.

Noterò anche che per la portabilità, dovresti usare call ToString()sull'enum e usarli Enum.Parse()quando li rileggi. Entrambi ToString()e Enum.Parse()possono gestire anche gli [Flags]enum, quindi non c'è motivo per non usarli. Intendiamoci, è ancora un altro trabocchetto, perché ora non puoi nemmeno cambiare il nome dell'enumerazione senza possibilmente infrangere il codice.

Quindi, a volte devi soppesare tutto quanto sopra quando ti chiedi Posso farla franca solo con un bool?


1

IMHO sembra che un enum sarebbe la scelta ovvia per qualsiasi situazione in cui sono possibili più di due opzioni. Ma ci sono sicuramente situazioni in cui un booleano è tutto ciò di cui hai bisogno. In quel caso direi che l'uso di un enum in cui un bool funzionerebbe sarebbe un esempio di utilizzo di 7 parole quando 4 andrà bene.


0

I booleani hanno senso quando si dispone di un evidente interruttore che può essere solo una delle due cose (cioè lo stato di una lampadina, accesa o spenta). Oltre a questo, è bene scriverlo in modo tale che sia ovvio ciò che stai passando, ad esempio le scritture su disco, senza buffer, con buffer di riga o sincrono, dovrebbero essere passati come tali. Anche se non vuoi consentire le scritture sincrone ora (e quindi sei limitato a due opzioni), vale la pena considerare di renderle più dettagliate allo scopo di sapere cosa fanno a prima vista.

Detto questo, puoi anche usare False e True (booleano 0 e 1) e quindi se hai bisogno di più valori in seguito, espandi la funzione per supportare i valori definiti dall'utente (ad esempio, 2 e 3) ei tuoi vecchi valori 0/1 verrà eseguito correttamente, quindi il tuo codice non dovrebbe rompersi.


0

A volte è semplicemente più semplice modellare comportamenti diversi con i sovraccarichi. Per continuare dal tuo esempio sarebbe:

file.appendData( data );  
file.overwriteData( data );

Questo approccio degrada se si dispone di più parametri, ciascuno dei quali consente un insieme fisso di opzioni. Ad esempio, un metodo che apre un file potrebbe avere diverse permutazioni di modalità file (apri / crea), accesso ai file (lettura / scrittura), modalità di condivisione (nessuno / lettura / scrittura). Il numero totale di configurazioni è uguale ai prodotti cartesiani delle singole opzioni. Naturalmente in questi casi i sovraccarichi multipli non sono appropriati.

Le enumerazioni possono, in alcuni casi, rendere il codice più leggibile, sebbene la convalida del valore esatto dell'enumerazione in alcuni linguaggi (C # ad esempio) possa essere difficile.

Spesso un parametro booleano viene aggiunto all'elenco dei parametri come nuovo overload. Un esempio in .NET è:

Enum.Parse(str);  
Enum.Parse(str, true); // ignore case

Quest'ultimo overload è diventato disponibile in una versione successiva del framework .NET rispetto alla prima.

Se sai che ci saranno solo due scelte, un booleano potrebbe andare bene. Gli enum sono estensibili in un modo che non interrompe il vecchio codice, sebbene le vecchie librerie potrebbero non supportare i nuovi valori enum, quindi il controllo delle versioni non può essere completamente ignorato.


MODIFICARE

Nelle versioni più recenti di C # è possibile utilizzare argomenti denominati che, IMO, possono rendere più chiaro il codice di chiamata nello stesso modo in cui possono fare le enumerazioni. Utilizzando lo stesso esempio di cui sopra:

Enum.Parse(str, ignoreCase: true);

0

Dove concordo sul fatto che gli enumeratori siano un buon modo per andare, nei metodi in cui hai 2 opzioni (e solo due opzioni puoi avere leggibilità senza enumerazione).

per esempio

public void writeData(Stream data, boolean is_overwrite)

Adoro gli Enum, ma anche booleano è utile.


0

Questa è una voce in ritardo su un vecchio post, ed è così in fondo alla pagina che nessuno lo leggerà mai, ma poiché nessuno l'ha già detto ...

Un commento in linea fa molto per risolvere il boolproblema imprevisto . L'esempio originale è particolarmente atroce: immagina di provare a nominare la variabile nella funzione di declearazione! Sarebbe qualcosa di simile

void writeData( DataObject data, bool use_append_mode );

Ma, per amore di esempio, diciamo che questa è la dichiarazione. Quindi, per un argomento booleano altrimenti inspiegabile, inserisco il nome della variabile in un commento in linea. Confrontare

file.writeData( data, true );

con

file.writeData( data, true /* use_append_mode */);

-1

Dipende davvero dall'esatta natura dell'argomento. Se non è un sì / no o vero / falso, un enum lo rende più leggibile. Ma con un'enumerazione è necessario controllare l'argomento o avere un comportamento predefinito accettabile poiché possono essere passati valori non definiti del tipo sottostante.


-1

L'uso di enumerazioni invece di booleane nel tuo esempio aiuta a rendere più leggibile la chiamata al metodo. Tuttavia, questo è un sostituto del mio elemento wish preferito in C #, argomenti denominati nelle chiamate ai metodi. Questa sintassi:

var v = CallMethod(pData = data, pFileMode = WriteMode, pIsDirty = true);

sarebbe perfettamente leggibile e si potrebbe quindi fare ciò che dovrebbe fare un programmatore, ovvero scegliere il tipo più appropriato per ogni parametro nel metodo indipendentemente da come appare nell'IDE.

C # 3.0 consente argomenti con nome nei costruttori. Non so perché non possano farlo anche con i metodi.


Un'idea interessante. Ma saresti in grado di riordinare i parametri? Omettere parametri? Come farebbe il compilatore a sapere a quale sovraccarico stavi collegando se questo fosse facoltativo? Inoltre, dovresti nominare tutti i parametri nell'elenco?
Drew Noakes,

-1

Valori booleani true/ falsesolo. Quindi non è chiaro cosa rappresenti. Enumpuò avere nome significativo, ad esempio OVERWRITE, APPEND, ecc Quindi enumerazioni sono migliori.

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.