Domanda

Un mio collega afferma che 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 c'è di 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?

È stato utile?

Soluzione

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

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

Altri suggerimenti

Enums consente anche future modifiche, dove ora vuoi una terza scelta (o più).

Usa quello che modella meglio il tuo problema. Nell'esempio che dai, l'enum è una scelta migliore. Tuttavia, ci sarebbero altre volte in cui un booleano è migliore. Il 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.

Penso che tu abbia quasi risposto a te stesso, penso che l'obiettivo finale sia quello di rendere il codice più leggibile, e in questo caso l'enum lo ha fatto, l'IMO è sempre meglio guardare l'obiettivo finale piuttosto che le regole generali, forse pensare di esso più come una linea guida, vale a dire che gli enum sono spesso più leggibili nel codice rispetto ai bool generici, agli ints, ecc. ma ci saranno sempre delle eccezioni alla regola.

Ricorda la domanda che Adlai Stevenson ha posto all'ambasciatore Zorin alle Nazioni Unite durante il missile cubano crisi ?

  

" Sei nell'aula del tribunale del mondo   opinione adesso, e puoi rispondere    sì o no . Hai negato che [i missili]   esiste e voglio sapere se io   ti ho capito bene .... lo sono   pronto ad aspettare la mia risposta fino al   l'inferno si blocca, se è tuo   . Decisione "

Se la bandiera che hai nel tuo metodo è di natura tale che puoi fissarla a una decisione binaria , e tale decisione non si trasformerà mai in tre -way o decisione n-way, andare per booleano. Indicazioni: la tua bandiera si chiama isXXX .

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

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

Per me, né l'uso booleano né l'enumerazione sono un buon approccio. Robert C. Martin lo cattura molto chiaramente nei suoi Suggerimento codice pulito n. 12: elimina argomenti booleani :

  

Gli argomenti booleani dichiarano fortemente 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 elenco non rende le cose più chiare. Non cambia nulla, è ancora un argomento flag.

Ci sono due ragioni per cui ho riscontrato che questa è una cosa negativa:

  1. Perché alcune persone scriveranno metodi come:

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

    Questo è ovviamente negativo perché è troppo facile confondere i parametri e non hai idea di guardarlo cosa stai specificando. Solo un bool non è poi così male.

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

    public void Write(bool toOptical);
    

    Davvero, dovrebbero essere due metodi

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

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

Immagino dipenda. Non farei un affare troppo grande al riguardo, tranne per il n. 1.

Gli enum sono migliori ma non definirei parametri booleani come "inaccettabili". A volte è solo più facile lanciare un piccolo booleano e andare avanti (pensa a metodi privati ??ecc.)

I booleani potrebbero essere OK nei linguaggi che hanno parametri nominati, 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]

Non sarei d'accordo sul fatto che sia una buona regola . Ovviamente, Enum crea un codice esplicito o dettagliato migliore in alcuni casi, ma di norma sembra molto più che raggiungibile.

Prima di tutto lasciami fare il tuo esempio: La responsabilità (e la capacità) dei programmatori di scrivere un buon codice non è realmente compromessa dall'avere un parametro booleano. Nel tuo esempio il programmatore avrebbe potuto scrivere proprio come un 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 dato è solo "migliore" perché stai passando un CONST. Molto probabilmente nella maggior parte delle applicazioni, almeno alcuni, se non nella maggior parte dei casi, i parametri passati alle funzioni sono VARIABILI. nel qual caso il mio secondo esempio (dare variabili con buoni nomi) è molto meglio ed Enum ti avrebbe dato pochi benefici.

Gli enum hanno un vantaggio decisivo, ma non dovresti semplicemente andare a sostituire tutti i tuoi booleani con 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 ciò che dovrebbero fare, in quanto ti permettono di vedere cosa significa realmente il vero / falso

Le proprietà (specialmente con gli inizializzatori di oggetti C # 3) o gli argomenti di parole chiave (alla ruby ??o python) sono un modo molto migliore per andare dove altrimenti utilizzeresti 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 che mi viene in mente quando un argomento booleano del metodo è 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 :-(

Mentre è vero che in molti casi gli enum sono più leggibili e più estensibili dei booleani, una regola assoluta che "i booleani non sono accettabili" è stupido. È inflessibile e controproducente - non lascia spazio al giudizio umano. Sono un tipo incorporato fondamentale nella maggior parte delle lingue perché sono utili - considera di applicarlo ad altri tipi incorporati: dicendo ad esempio "mai usare un int come parametro" sarebbe solo pazzo.

Questa regola è solo una questione di stile, non di potenziale per bug o prestazioni di runtime. Una regola migliore sarebbe "preferire gli enumerati ai booleani per motivi 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 del valore booleano come parametro sia un grosso problema. La descrizione comando 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 nella descrizione comando.

Dovrei anche aggiungere che c'è un caso in cui dovresti chiaramente fare il refactor dei booleani in un elenco - quando hai due o più booleani nella tua classe, o nei tuoi parametri del metodo, e non tutti gli stati sono validi (es. non è valido per impostarli entrambi su true).

Ad esempio, se la tua classe ha proprietà come

public bool IsFoo
public bool IsBar

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

enum FooBarType { IsFoo, IsBar, IsNeither };

Alcune regole alle quali il tuo collega potrebbe aderire meglio sono:

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

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

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

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 perfettamente senso.

Dipende dal metodo. Se il metodo fa qualcosa che è ovviamente una cosa vera / falsa, allora va bene, ad es. 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 che citi, è meglio usare un'enumerazione. Esistono molti esempi nel .NET Framework stesso in cui questa convenzione non viene seguita, ma è perché hanno introdotto questa linea guida di progettazione abbastanza tardi nel ciclo.

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

Enums può certamente rendere il codice più leggibile. Ci sono ancora alcune cose a cui fare attenzione (almeno in .net)

Poiché l'archiviazione sottostante di un enum è un int, il valore predefinito sarà zero, quindi è necessario assicurarsi che 0 sia un valore predefinito ragionevole. (Ad esempio, le strutture hanno tutti i campi impostati su zero quando vengono creati, quindi non c'è modo di specificare un valore predefinito diverso da 0. Se non si dispone di un valore 0, non è nemmeno possibile testare l'enum senza eseguire il cast su int, che sarebbe cattivo stile.)

Se i tuoi enum sono privati ??del tuo codice (mai esposti pubblicamente), puoi smettere di leggere qui.

Se i tuoi enum sono pubblicati in alcun modo su codice esterno e / o sono salvati al di fuori del programma, considera di numerarli esplicitamente. Il compilatore li numera automaticamente da 0, ma se riorganizzi i tuoi enum senza dar loro valori puoi finire con dei difetti.

Posso legalmente scrivere

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

Per combattere questo, qualsiasi codice che consuma un enum di cui non si può essere certi (ad es. API pubblica) deve verificare se l'enum è valido. Puoi farlo tramite

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

L'unica avvertenza di Enum.IsDefined è che usa la riflessione ed è più lento. Soffre anche di un problema di controllo delle versioni. Se è necessario controllare spesso il valore enum, sarebbe meglio 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 può solo sapere come gestire i 2 enum che hai. Se aggiungi un terzo valore, Enum.IsDefined sarà true, ma il vecchio codice non può necessariamente gestirlo. Ops.

Puoi divertirti ancora di più con le enumerazioni [Flags] e il codice di convalida è leggermente diverso.

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

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

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 usare un enum in cui un bool funzionerebbe sarebbe un esempio dell'uso di 7 parole quando 4 lo faranno.

I booleani hanno senso quando hai un ovvio interruttore che può essere solo una delle due cose (cioè lo stato di una lampadina, acceso o spento). A parte questo, è bene scriverlo in modo tale che sia ovvio ciò che stai passando, ad es. le scritture su disco - senza buffer, con buffer di linea o sincrone - devono essere passate 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 (diciamo, 2 e 3) e il tuo vecchio 0 I valori / 1 verranno trasferiti correttamente, quindi il tuo codice non dovrebbe rompersi.

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

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

Questo approccio si riduce se si hanno più parametri, ognuno dei quali consente un set 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à condivisione (nessuna / lettura / scrittura). Il numero totale di configurazioni è uguale ai prodotti cartesiani delle singole opzioni. Naturalmente in questi casi i sovraccarichi multipli non sono appropriati.

Gli enum possono, in alcuni casi, rendere il codice più leggibile, anche se può essere difficile convalidare l'esatto valore enum in alcune lingue (ad esempio C #).

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

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

Quest'ultimo sovraccarico è diventato disponibile in una versione successiva del framework .NET rispetto al primo.

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


Modifica

Nelle versioni più recenti di C # è possibile usare argomenti con nome che, IMO, possono rendere più chiaro il codice chiamante nello stesso modo in cui gli enum possono farlo. Utilizzando lo stesso esempio di cui sopra:

Enum.Parse(str, ignoreCase: true);

Dove concordo sul fatto che gli Enum sono una buona strada da percorrere, nei metodi in cui hai 2 opzioni (e solo due opzioni puoi avere leggibilità senza enum.)

per es.

public void writeData(Stream data, boolean is_overwrite)

Love the Enums, ma anche il booleano è utile.

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

Un commento in linea aiuta molto a risolvere il problema inatteso bool . L'esempio originale è particolarmente odioso: immagina di provare a nominare la variabile nella funzione declearation! Sarebbe qualcosa di simile

void writeData( DataObject data, bool use_append_mode );

Ma, per esempio, supponiamo che sia la dichiarazione. Quindi, per un argomento booleano altrimenti inspiegabile, ho inserito il nome della variabile in un commento incorporato. Confronta

file.writeData( data, true );

con

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

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

L'uso di enum invece di booleani nel tuo esempio aiuta a rendere più leggibile la chiamata del metodo. Tuttavia, questo è un sostituto del mio articolo preferito in C #, chiamato argomenti nelle chiamate al metodo. Questa sintassi:

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

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

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

Valori booleani solo true / false . Quindi non è chiaro cosa rappresenti. Enum può avere un nome significativo, ad esempio OVERWRITE , APPEND , ecc. Quindi gli enum sono migliori.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top