Le variabili flag sono un male assoluto? [chiuso]


47

Le variabili flag sono malvagie? Le seguenti variabili sono profondamente immorali ed è malvagio usarle?

"variabili booleane o intere a cui si assegna un valore in determinati punti, quindi in basso si controlla quindi in orther per fare qualcosa o no, come ad esempio usando newItem = truepoi alcune righe in basso if (newItem ) then"


Ricordo di aver fatto un paio di progetti in cui ho completamente ignorato l'uso delle bandiere e sono finito con una migliore architettura / codice; tuttavia, è una pratica comune in altri progetti in cui lavoro, e quando il codice cresce e vengono aggiunte le bandiere, cresce anche il codice-spaghetti IMHO.

Diresti che ci sono casi in cui l'uso di flag è una buona pratica o addirittura necessario? O accetti che l'utilizzo di flag nel codice sia ... flag rosso e dovrebbe essere evitato / refactored; io, mi limito a fare funzioni / metodi che controllano invece gli stati in tempo reale.


9
Sembra che MainMa e io abbiamo una diversa definizione di "bandiera". Stavo pensando al preprocessore #ifdefs. Di quale stavi chiedendo?
Karl Bielefeldt,

Questa è davvero una buona domanda. Me lo sono chiesto molto, e in realtà mi sono ritrovato a dire un po 'troppo "oh beh, basta usare una bandiera".
Paul Richter,

I booleani sono bandiere. (Così sono i numeri interi, così come ...)
Thomas Eding,

7
@KarlBielefeldt Credo che OP si riferisca a variabili booleane o intere a cui si assegna un valore in determinati punti, quindi in basso si controlla quindi in orther per fare qualcosa o meno, come, ad esempio, usando newItem = truepoi alcune righe in bassoif (newItem ) then
Tulains Córdova,

1
Considera anche l' introduzione che spiega il refactoring variabile in questo contesto. Fintanto che il metodo rimane breve e ha un basso numero di percorsi, lo considero valido.
Daniel B,

Risposte:


41

Il problema che ho riscontrato quando si mantiene il codice che utilizza flag è che il numero di stati cresce rapidamente e ci sono quasi sempre stati non gestiti. Un esempio per esperienza personale: stavo lavorando su un codice che conteneva queste tre bandiere

bool capturing, processing, sending;

Questi tre hanno creato otto stati (in realtà, c'erano anche altre due bandiere). Non tutte le possibili combinazioni di valori erano coperte dal codice e gli utenti vedevano bug:

if(capturing && sending){ // we must be processing as well
...
}

Si è scoperto che c'erano situazioni in cui l'ipotesi nell'affermazione if sopra era falsa.

Le bandiere tendono ad aggravarsi nel tempo e nascondono lo stato reale di una classe. Ecco perché dovrebbero essere evitati.


3
+1, "dovrebbe essere evitato". Vorrei aggiungere qualcosa su "ma le bandiere sono necessarie in alcune situazioni" (alcuni potrebbero dire "un male necessario")
Trevor Boyd Smith,

2
@TrevorBoydSmith Nella mia esperienza non lo sono, hai solo bisogno di un po 'più della media potenza del cervello che
useresti

Nel tuo esame dovrebbe essere stato un singolo enum che rappresenta lo stato, non 3 booleani.
user949300

Potresti incontrare un problema simile a quello che sto affrontando in questo momento. Oltre a coprire tutti i possibili stati, due applicazioni potrebbero condividere lo stesso flag (ad esempio, il caricamento dei dati dei clienti). In questo caso, solo un Uploader utilizzerà la bandiera, la esporrà e buona fortuna a trovare il problema in futuro.
Alan,

38

Ecco un esempio in cui i flag sono utili.

Ho un pezzo di codice che genera password (usando un generatore di numeri pseudocasuali crittograficamente sicuro). Il chiamante del metodo sceglie se la password deve contenere o meno lettere maiuscole, minuscole, cifre, simboli di base, simboli estesi, simboli greci, cirillici e unicode.

Con i flag, chiamare questo metodo è semplice:

var password = this.PasswordGenerator.Generate(
    CharacterSet.Digits | CharacterSet.LowercaseLetters | CharacterSet.UppercaseLetters);

e può anche essere semplificato per:

var password = this.PasswordGenerator.Generate(CharacterSet.LettersAndDigits);

Senza flag, quale sarebbe la firma del metodo?

public byte[] Generate(
    bool uppercaseLetters, bool lowercaseLetters, bool digits, bool basicSymbols,
    bool extendedSymbols, bool greekLetters, bool cyrillicLetters, bool unicode);

chiamato così:

// Very readable, isn't it?
// Tell me just by looking at this code what symbols do I want to be included?
var password = this.PasswordGenerator.Generate(
    true, true, true, false, false, false, false, false);

Come notato nei commenti, un altro approccio sarebbe quello di utilizzare una raccolta:

var password = this.PasswordGenerator.Generate(
    new []
    {
        CharacterSet.Digits,
        CharacterSet.LowercaseLetters,
        CharacterSet.UppercaseLetters,
    });

Questo è molto più leggibile rispetto all'insieme di truee false, ma presenta ancora due svantaggi:

Il principale svantaggio è che per consentire valori combinati, come CharacterSet.LettersAndDigitsse stessi scrivendo qualcosa di simile nel Generate()metodo:

if (set.Contains(CharacterSet.LowercaseLetters) ||
    set.Contains(CharacterSet.Letters) ||
    set.Contains(CharacterSet.LettersAndDigits) ||
    set.Contains(CharacterSet.Default) ||
    set.Contains(CharacterSet.All))
{
    // The password should contain lowercase letters.
}

possibilmente riscritto in questo modo:

var lowercaseGroups = new []
{
    CharacterSet.LowercaseLetters,
    CharacterSet.Letters,
    CharacterSet.LettersAndDigits,
    CharacterSet.Default,
    CharacterSet.All,
};

if (lowercaseGroups.Any(s => set.Contains(s)))
{
    // The password should contain lowercase letters.
}

Confronta questo con quello che hai usando le bandiere:

if (set & CharacterSet.LowercaseLetters == CharacterSet.LowercaseLetters)
{
    // The password should contain lowercase letters.
}

Il secondo, piccolo inconveniente è che non è chiaro come si comporterebbe il metodo se chiamato in questo modo:

var password = this.PasswordGenerator.Generate(
    new []
    {
        CharacterSet.Digits,
        CharacterSet.LettersAndDigits, // So digits are requested two times.
    });

10
Credo che OP si riferisca a variabili booleane o intere a cui assegni un valore in determinati punti, quindi in basso sotto il check in poi poi in orther per fare qualcosa o no, come, ad esempio, usando newItem = truepoi alcune righe in bassoif (newItem ) then
Tulains Córdova

1
@MainMa Apparentemente c'è un terzo: la versione con 8 argomenti booleani è ciò a cui ho pensato quando ho letto "flags" ...
Izkata

4
Siamo spiacenti, ma IMHO è il caso perfetto per il concatenamento dei metodi ( en.wikipedia.org/wiki/Method_chaining ). Inoltre, è possibile utilizzare un array di parametri (deve essere un array associativo o una mappa), dove qualsiasi voce in tale array di parametri si omette utilizza il comportamento del valore predefinito per quel parametro. Alla fine, la chiamata tramite concatenamento di metodi o array di parametri può essere succinta ed espressiva come flag di bit, inoltre, non tutte le lingue hanno operatori di bit (in realtà mi piacciono i flag binari, ma utilizzerei invece i metodi che ho appena citato).
dukeofgaming,

3
Non è molto OOP, vero? Farei un'interfaccia ala: String myNewPassword = makePassword (randomComposeSupplier (new RandomLowerCaseSupplier (), new RandomUpperCaseSupplier (), new RandomNumberSupplier)); con String makePassword (fornitore <Character> charSupplier); e fornitore <Carattere> randomComposeSupplier (fornitore <Caratterio> ... fornitori); Ora puoi riutilizzare i tuoi fornitori per altre attività, compilarli nel modo che preferisci e semplificare il tuo metodo generatePassword in modo che utilizzi uno stato minimo.
Dibbeke,

4
@Dibbeke Parla di un regno di nomi ...
Phil,

15

Un enorme blocco funzionale è l'odore, non le bandiere. Se imposti la bandiera sulla linea 5, controlla solo la bandiera sulla linea 354, quindi non va bene. Se imposti la bandiera sulla linea 8 e controlli la bandiera sulla linea 10, va bene. Inoltre, uno o due flag per blocco di codice vanno bene, 300 flag in una funzione sono cattivi.


10

Di solito i flag possono essere completamente sostituiti da un certo tipo di modello di strategia, con un'implementazione della strategia per ogni possibile valore del flag. Questo rende l'aggiunta di nuovi comportamenti molto più semplice.

In situazioni critiche in termini di prestazioni, il costo dell'indirizzamento indiretto potrebbe emergere e rendere necessaria la decostruzione in bandiere chiare. Detto questo, ho difficoltà a ricordare un singolo caso in cui ho dovuto farlo.


6

No, le bandiere non sono cattive o cattive che devono essere rifattorizzate a tutti i costi.

Considera la chiamata Pattern.compile di Java (String regex, int flags) . Questa è una maschera di bit tradizionale e funziona. Dai un'occhiata alle costanti di Java e ovunque vedi un gruppo di 2 n sai che ci sono delle bandiere.

In un mondo refactored ideale, si userebbe invece un EnumSet in cui le costanti sono invece valori in un enum e come dice la documentazione:

Le prestazioni in termini di spazio e tempo di questa classe dovrebbero essere abbastanza buone da consentirne l'uso come alternativa di alta qualità e tipica ai tradizionali "bit flag" basati su int.

In un mondo perfetto, quella chiamata Pattern.compile diventa Pattern.compile(String regex, EnumSet<PatternFlagEnum> flags).

Detto questo, sono ancora bandiere. È molto più facile lavorare Pattern.compile("foo", Pattern.CASE_INSENSTIVE | Pattern.MULTILINE)di quanto non lo sarebbe avere Pattern.compile("foo", new PatternFlags().caseInsenstive().multiline())o qualche altro stile nel cercare di fare ciò che le bandiere sono realmente e per cui è buono.

Le bandiere sono spesso viste quando si lavora con cose a livello di sistema. Quando si interfaccia con qualcosa a livello di sistema operativo, è probabile che si abbia un flag da qualche parte, sia esso il valore di ritorno di un processo, o le autorizzazioni di un file o i flag per l'apertura di un socket. Cercare di rimodellare questi casi in una caccia alle streghe contro un odore di codice percepito probabilmente finirà con un codice peggiore di quello che se uno usasse accettasse e comprendesse la bandiera.

Il problema si verifica quando le persone usano in modo improprio le bandiere gettandole insieme e creando un insieme di bandiere di tutti i tipi di bandiere non correlate o cercando di usarle dove non sono affatto bandiere.


5

Suppongo che stiamo parlando di bandiere all'interno delle firme dei metodi.

L'uso di un singolo flag è abbastanza male.

Non significherà nulla per i tuoi colleghi nel primo momento in cui lo vedranno. Dovranno esaminare il codice sorgente del metodo per stabilire cosa fa. Probabilmente ti troverai nella stessa posizione pochi mesi dopo, quando dimenticherai il tuo metodo.

Passando una bandiera al metodo, normalmente significa che il tuo metodo è responsabile di più cose. All'interno del metodo probabilmente stai facendo un semplice controllo sulle righe di:

if (flag)
   DoFlagSet();
else
   DoFlagNotSet();

Questa è una scarsa separazione delle preoccupazioni e normalmente puoi trovare un modo per aggirarla.

Normalmente ho due metodi separati:

public void DoFlagSet() 
{
}

public void DoFlagNotSet()
{
}

Ciò avrà più senso con i nomi dei metodi applicabili al problema che si sta risolvendo.

Passare più bandiere è due volte più cattivo. Se hai davvero bisogno di passare più flag, considera l'incapsulamento all'interno di una classe. Anche allora, dovrai ancora affrontare lo stesso problema, poiché il tuo metodo sta probabilmente facendo più cose.


3

Le bandiere e la maggior parte delle variabili temporanee hanno un forte odore. Molto probabilmente potrebbero essere refactored e sostituiti con metodi di query.

Revised:

I flag e le variabili temporanee durante l'espressione dello stato devono essere sottoposti a refactoring ai metodi di query. I valori di stato (booleani, ints e altri primitivi) dovrebbero quasi sempre essere nascosti come parte dei dettagli di implementazione.

I flag utilizzati per il controllo, l'instradamento e il flusso generale del programma possono anche indicare l'opportunità di trasformare le sezioni delle strutture di controllo in strategie o fabbriche separate, o qualunque cosa sia opportunamente situazionale, che continui a utilizzare i metodi di query.


2

Quando parliamo di flag, dovremmo sapere che verranno modificati nel corso dell'esecuzione del programma e che influenzeranno il comportamento del programma in base ai loro stati. Finché avremo un controllo accurato su queste due cose, funzioneranno alla grande.

Le bandiere possono funzionare alla grande se

  • Li hai definiti in un ambito appropriato. Per appropriato intendo che l'ambito non dovrebbe contenere alcun codice che non debba / non debba modificarli. O almeno il codice è sicuro (ad esempio potrebbe non essere chiamato direttamente dall'esterno)
  • Se è necessario gestire flag dall'esterno e se ci sono molti flag, possiamo codificare il gestore di flag come unico modo per modificare i flag in modo sicuro. Questo gestore di flag può incapsulare esso stesso flag e metodi per modificarli. Può quindi essere reso singleton e può quindi essere condiviso tra le classi che hanno bisogno di accedere ai flag.
  • E infine per manutenibilità, se ci sono troppe bandiere:
    • Non c'è bisogno di dire che dovrebbero seguire una denominazione ragionevole
    • Dovrebbe essere documentato con valori validi (può essere con enumerazioni)
    • Dovrebbe essere documentato con QUALE CODICE MODIFICA ciascuno di essi, e anche CON QUALE CONDIZIONE comporterà l'assegnazione di un valore particolare alla bandiera.
    • Quale codice li consumerà e quale comportamento comporterà un valore particolare

Se ci sono un sacco di bandiere, un buon lavoro di progettazione dovrebbe precedere dal momento che le bandiere iniziano a svolgere un ruolo chiave nel comportamento del programma. Puoi scegliere diagrammi di stato per la modellazione. Tali diagrammi funzionano anche come documentazione e guida visiva mentre si occupano di essi.

Finché queste cose sono a posto penso che non porterà al caos.


1

Ho ipotizzato dalla domanda che il QA significasse variabili flag (globali) e non bit di un parametro di funzione.

Ci sono situazioni in cui non hai molte altre possibilità. Ad esempio, senza un sistema operativo è necessario valutare gli interrupt. Se un interruzione arriva molto frequentemente e non hai il tempo di fare una lunga valutazione nell'ISR, non è solo permesso, ma a volte anche le migliori pratiche impostare solo alcune bandiere globali nell'ISR (dovresti passare il minor tempo possibile nell'ISR) e per valutare quelle bandiere nel tuo ciclo principale.


0

Non penso che nulla sia un male assoluto nella programmazione, mai.

C'è un'altra situazione in cui le bandiere potrebbero essere in ordine, che non sono state ancora menzionate qui ...

Considera l'uso di chiusure in questo frammento di Javascript:

exports.isPostDraft = function ( post, draftTag ) {
  var isDraft = false;
  if (post.tags)
    post.tags.forEach(function(tag){ 
      if (tag === draftTag) isDraft = true;
    });
  return isDraft;
}

La funzione interna, passata a "Array.forEach", non può semplicemente "restituire vero".

Quindi, è necessario mantenere lo stato fuori con una bandiera.

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.