Sind Flaggenvariablen ein absolutes Übel? [geschlossen]

47

Sind Flaggenvariablen böse? Sind die folgenden Variablen zutiefst unmoralisch und ist es böse, sie zu verwenden?

"Boolesche oder ganzzahlige Variablen, denen Sie an bestimmten Stellen einen Wert zuweisen, dann unten überprüfen Sie, ob Sie etwas tun oder nicht, beispielsweise indem Sie newItem = trueeinige Zeilen darunter verwenden. if (newItem ) then"


Ich erinnere mich an ein paar Projekte, bei denen ich die Verwendung von Flags völlig vernachlässigt und bessere Architektur / Code erhalten habe. In anderen Projekten, in denen ich arbeite, ist dies jedoch eine gängige Praxis, und wenn Code wächst und Flags hinzugefügt werden, wächst IMHO-Code-Spaghetti ebenfalls.

Würden Sie sagen, dass es Fälle gibt, in denen die Verwendung von Flags eine gute oder sogar notwendige Praxis ist? Oder würden Sie zustimmen, dass die Verwendung von Flags im Code ... rote Fahnen sind und vermieden / überarbeitet werden sollten; Ich komme nur damit zurecht, Funktionen / Methoden auszuführen, die stattdessen in Echtzeit nach Zuständen suchen.

dukeofgaming
quelle
9
Es scheint, dass MainMa und ich eine andere Definition von "Flagge" haben. Ich dachte Präprozessor #ifdefs. Nach welchem ​​haben Sie gefragt?
Karl Bielefeldt
Das ist eine wirklich gute Frage. Ich habe mich selbst sehr gefragt und tatsächlich festgestellt, dass ich etwas zu viel gesagt habe: "Oh, lass uns einfach eine Flagge benutzen."
Paul Richter
Boolesche Werte sind Flags. (So ​​sind ganze Zahlen, so sind ...)
Thomas Eding
7
@KarlBielefeldt Ich glaube , OP boolean oder Integer - Variablen ist beziehe , dass Sie einen Wert an bestimmten Orten zuordnen dann unten Sie dann in orther überprüfen etwas zu tun oder nicht, wie zum Beispiel mit newItem = truedann ein paar Zeilen weiter untenif (newItem ) then
Tulains Córdova
1
Beachten Sie in diesem Zusammenhang auch die Einführung zur Erläuterung des Variablen- Refactorings. Solange die Methode kurz bleibt und nur eine geringe Anzahl von Pfaden durchläuft, halte ich dies für eine gültige Verwendung.
Daniel B

Antworten:

41

Das Problem, das ich beim Verwalten von Code gesehen habe, der Flags verwendet, ist, dass die Anzahl der Status schnell zunimmt und es fast immer unbehandelte Status gibt. Ein Beispiel aus eigener Erfahrung: Ich habe an einem Code mit diesen drei Flags gearbeitet

bool capturing, processing, sending;

Diese drei schufen acht Staaten (tatsächlich gab es auch zwei andere Flaggen). Der Code deckte nicht alle möglichen Wertekombinationen ab, und die Benutzer sahen Fehler:

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

Es stellte sich heraus, dass es Situationen gab, in denen die Annahme in der obigen if-Anweisung falsch war.

Flags setzen sich im Laufe der Zeit zusammen und verbergen den aktuellen Status einer Klasse. Deshalb sollten sie vermieden werden.

Ben
quelle
3
+1, "sollte vermieden werden". Ich würde etwas hinzufügen über "aber Flaggen sind in einigen Situationen notwendig" (einige könnten sagen "ein notwendiges Übel")
Trevor Boyd Smith
2
@TrevorBoydSmith In meiner Erfahrung , die sie nicht sind, benötigen Sie nur nur ein wenig mehr als die durchschnittliche Gehirnenergie würden Sie für einen Flag verwenden
dukeofgaming
In Ihrem Beispiel sollte es sich um eine einzelne Aufzählung handeln, die den Status darstellt, nicht um 3 Boolesche Werte.
user949300
Sie könnten zu einem ähnlichen Problem kommen, mit dem ich gerade konfrontiert bin. Zusätzlich zu allen möglichen Zuständen können zwei Anwendungen dasselbe Flag aufweisen (z. B. das Hochladen von Kundendaten). In diesem Fall wird nur ein Uploader das Flag verwenden, es auslösen und viel Glück beim Finden des Problems in der Zukunft.
Alan
38

Hier ist ein Beispiel, wenn Flags nützlich sind.

Ich habe einen Code, der Passwörter generiert (unter Verwendung eines kryptografisch sicheren Pseudozufallszahlengenerators). Der Aufrufer der Methode entscheidet, ob das Passwort Großbuchstaben, Kleinbuchstaben, Ziffern, Grundsymbole, erweiterte Symbole, griechische Symbole, kyrillische Symbole und Unicode enthalten soll oder nicht.

Mit Flags ist das Aufrufen dieser Methode einfach:

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

und es kann sogar vereinfacht werden:

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

Was wäre die Methodensignatur ohne Flags?

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

so genannt:

// 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);

Wie in den Kommentaren erwähnt, besteht ein anderer Ansatz darin, eine Sammlung zu verwenden:

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

Dies ist im Vergleich zum Satz von trueund viel besser lesbar false, weist jedoch immer noch zwei Nachteile auf:

Der Hauptnachteil ist, dass Sie, um kombinierte Werte zuzulassen, so CharacterSet.LettersAndDigitsetwas wie das in Generate()Methode schreiben würden :

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.
}

möglicherweise so umgeschrieben:

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.
}

Vergleichen Sie dies mit dem, was Sie haben, indem Sie Flags verwenden:

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

Der zweite, sehr kleine Nachteil ist, dass nicht klar ist, wie sich die Methode verhalten würde, wenn sie wie folgt aufgerufen würde:

var password = this.PasswordGenerator.Generate(
    new []
    {
        CharacterSet.Digits,
        CharacterSet.LettersAndDigits, // So digits are requested two times.
    });
Arseni Mourzenko
quelle
10
Ich glaube , OP boolean oder beziehe Integer - Variablen, einen Wert an bestimmten Orten zuordnen dann unten Sie überprüfen dann in orther etwas zu tun oder nicht, wie zum Beispiel mit newItem = truedann ein paar Zeilen weiter untenif (newItem ) then
Tulains Córdova
1
@MainMa Anscheinend gibt es eine dritte: Die Version mit 8 Booleschen Argumenten ist das, woran ich gedacht habe, als ich "Flags" gelesen habe ...
Izkata,
4
Sorry, aber IMHO ist dies der perfekte Fall für die Verkettung von Methoden ( en.wikipedia.org/wiki/Method_chaining ). Außerdem können Sie ein Parameterarray (muss ein assoziatives Array oder eine Zuordnung sein) verwenden, in dem jeder Eintrag in diesem Parameterarray vorhanden ist Wenn Sie dies weglassen, wird das Standardwertverhalten für diesen Parameter verwendet. Letztendlich kann der Aufruf über Methodenverkettung oder Parameter-Arrays so prägnant und aussagekräftig sein wie Bit-Flags. Außerdem hat nicht jede Sprache Bit-Operatoren (ich mag eigentlich Binär-Flags, würde aber stattdessen die eben erwähnten Methoden verwenden).
dukeofgaming
3
Es ist nicht sehr OOP, oder? Ich würde ein Interface erstellen: String myNewPassword = makePassword (randomComposeSupplier (neues RandomLowerCaseSupplier (), neues RandomUpperCaseSupplier (), neues RandomNumberSupplier)); with String makePassword (Supplier <Character> charSupplier); und Supplier <Character> randomComposeSupplier (Supplier <Character> ... suppliers); Jetzt können Sie Ihre Lieferanten für andere Aufgaben wiederverwenden, sie nach Belieben zusammenstellen und Ihre generatePassword-Methode vereinfachen, sodass der minimale Status verwendet wird.
Dibbeke
4
@Dibbeke Sprechen Sie über ein Königreich der Nomen ...
Phil
15

Ein großer Funktionsblock ist der Geruch, nicht die Fahnen. Wenn Sie das Flag in Zeile 5 setzen, dann überprüfen Sie nur das Flag in Zeile 354, dann ist das schlecht. Wenn Sie das Flag in Zeile 8 setzen und in Zeile 10 nach dem Flag suchen, ist das in Ordnung. Außerdem sind ein oder zwei Flags pro Codeblock in Ordnung, 300 Flags in einer Funktion sind schlecht.

nbv4
quelle
10

Normalerweise können Flags vollständig durch eine Variante des Strategiemusters ersetzt werden, wobei für jeden möglichen Wert des Flags eine Strategieimplementierung vorhanden ist. Dies erleichtert das Hinzufügen eines neuen Verhaltens erheblich.

In leistungskritischen Situationen können die Kosten der Indirektion auftauchen und eine Dekonstruktion in eindeutige Flags erforderlich machen. Trotzdem fällt es mir schwer, mich an einen einzigen Fall zu erinnern, in dem ich das tatsächlich tun musste.

back2dos
quelle
6

Nein, Flaggen sind nicht schlecht oder ein Übel, das um jeden Preis umgestaltet werden muss.

Betrachten Sie Javas Aufruf Pattern.compile (String regex, int flags) . Dies ist eine traditionelle Bitmaske und es funktioniert. Werfen Sie einen Blick auf die Konstanten in Java und wo immer Sie 2 n sehen , wissen Sie, dass dort Flaggen sind.

In einer ideal umgestalteten Welt würde man stattdessen ein EnumSet verwenden, bei dem die Konstanten stattdessen Werte in einer Aufzählung sind und wie in der Dokumentation heißt:

Die räumliche und zeitliche Leistung dieser Klasse sollte ausreichen, um sie als hochwertige, typsichere Alternative zu herkömmlichen int-basierten "Bit-Flags" verwenden zu können.

In einer perfekten Welt wird dieser Aufruf von Pattern.compile Pattern.compile(String regex, EnumSet<PatternFlagEnum> flags).

Alles, was gesagt wurde, es ist immer noch Flaggen. Es ist viel einfacher, damit zu arbeiten, Pattern.compile("foo", Pattern.CASE_INSENSTIVE | Pattern.MULTILINE)als wenn man Pattern.compile("foo", new PatternFlags().caseInsenstive().multiline())versucht, das zu tun, was Flaggen wirklich sind und wozu sie gut sind.

Flags werden häufig beim Arbeiten mit Dingen auf Systemebene gesehen. Wenn man mit etwas auf Betriebssystemebene arbeitet, hat man wahrscheinlich irgendwo ein Flag - sei es der Rückgabewert eines Prozesses oder die Berechtigungen einer Datei oder die Flags zum Öffnen eines Sockets. Der Versuch, diese Instanzen bei einer Hexenjagd gegen einen wahrgenommenen Codegeruch umzugestalten, führt wahrscheinlich zu einem schlechteren Code, als wenn man die Flagge akzeptiert und verstanden hätte.

Das Problem tritt auf, wenn Leute Flaggen missbrauchen, indem sie sie zusammenwerfen und ein frankenflag-Set aller Arten von nicht verwandten Flaggen erstellen oder versuchen, sie dort zu verwenden, wo sie überhaupt keine Flaggen sind.


quelle
5

Ich gehe davon aus, dass es sich um Flags in Methodensignaturen handelt.

Die Verwendung einer einzelnen Flagge ist schon schlimm genug.

Es wird Ihren Kollegen nichts bedeuten, wenn sie es sehen. Sie müssen sich den Quellcode der Methode ansehen, um festzustellen, was sie tut. Sie werden wahrscheinlich einige Monate später in der gleichen Position sein, wenn Sie vergessen, worum es bei Ihrer Methode ging.

Das Übergeben eines Flags an die Methode bedeutet normalerweise, dass Ihre Methode für mehrere Dinge verantwortlich ist. Innerhalb der Methode führen Sie wahrscheinlich eine einfache Überprüfung der folgenden Zeilen durch:

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

Das ist eine schlechte Trennung der Bedenken und man kann normalerweise einen Weg finden, dies zu umgehen.

Normalerweise habe ich zwei verschiedene Methoden:

public void DoFlagSet() 
{
}

public void DoFlagNotSet()
{
}

Dies ist bei Methodennamen sinnvoller, die für das zu lösende Problem gelten.

Das Übergeben mehrerer Flags ist doppelt so schlimm. Wenn Sie wirklich mehrere Flags übergeben müssen, sollten Sie in Betracht ziehen, diese in einer Klasse zu kapseln. Selbst dann werden Sie immer noch vor dem gleichen Problem stehen, da Ihre Methode wahrscheinlich mehrere Dinge tut.

CodeART
quelle
3

Flags und die meisten temporären Variablen sind ein starker Geruch. Höchstwahrscheinlich könnten sie überarbeitet und durch Abfragemethoden ersetzt werden.

Überarbeitet:

Flags und temporäre Variablen beim Ausdrücken des Status sollten in Abfragemethoden umgestaltet werden. Die Zustandswerte (Boolesche Werte, Ints und andere Primitive) sollten im Rahmen der Implementierungsdetails fast immer ausgeblendet werden.

Flags, die für die Steuerung, das Routing und den allgemeinen Programmfluss verwendet werden, weisen möglicherweise auch auf die Möglichkeit hin, Abschnitte der Steuerungsstrukturen in separate Strategien oder Fabriken umzugliedern, oder was auch immer situativ angemessen sein mag, die weiterhin die Abfragemethoden verwenden.

JustinC
quelle
2

Wenn wir über Flags sprechen, sollten wir wissen, dass sie im Laufe der Programmausführung geändert werden und dass sie das Verhalten von Programmen auf der Grundlage ihrer Zustände beeinflussen. Solange wir die Kontrolle über diese beiden Dinge haben, werden sie großartig funktionieren.

Fahnen können gut funktionieren, wenn

  • Sie haben sie in einem geeigneten Bereich definiert. Mit angemessen meine ich, dass der Geltungsbereich keinen Code enthalten sollte, der sie braucht / nicht modifiziert. Oder zumindest ist der Code sicher (zum Beispiel kann er nicht direkt von außen aufgerufen werden)
  • Wenn es notwendig ist, Flags von außen zu behandeln, und wenn es viele Flags gibt, können wir den Flag-Handler als einzige Möglichkeit zum sicheren Ändern von Flags codieren. Dieser Flag-Handler kann selbst Flags und Methoden zu deren Änderung einschließen. Es kann dann zu einem Singleton gemacht und dann von Klassen gemeinsam genutzt werden, die Zugriff auf Flags benötigen.
  • Und schließlich für die Wartbarkeit, wenn es zu viele Flags gibt:
    • Sie müssen nicht sagen, dass sie einer vernünftigen Namensgebung folgen sollten
    • Sollte mit den gültigen Werten dokumentiert werden (kann mit Aufzählungen sein)
    • Sollte dokumentiert werden, mit WELCHEM CODE jedes von ihnen ÄNDERN WIRD, und mit WELCHEM ZUSTAND wird der Flagge ein bestimmter Wert zugewiesen.
    • WELCHER CODE VERBRAUCHT sie und WELCHES VERHALTEN ergibt sich für einen bestimmten Wert

Wenn es sehr viele Flags gibt, sollte gute Designarbeit vorausgehen, da Flags eine Schlüsselrolle im Programmverhalten spielen. Sie können sich für Zustandsdiagramme zur Modellierung entscheiden. Solche Diagramme dienen auch als Dokumentation und visuelle Anleitung, wenn Sie mit ihnen arbeiten.

Solange diese Dinge an Ort und Stelle sind, denke ich, wird es nicht zu dem Durcheinander führen.

Mahesha999
quelle
1

Ich ging von der Frage aus, dass die QA Flag-Variablen (globale Variablen) und keine Bits eines Funktionsparameters bedeutet.

Es gibt Situationen, in denen Sie nicht viele andere Möglichkeiten haben. Ohne Betriebssystem müssen Sie beispielsweise Interrupts auswerten. Wenn ein Interrupt sehr häufig auftritt und Sie keine Zeit für eine ausführliche Auswertung im ISR haben, ist es nicht nur zulässig, sondern manchmal sogar die beste Vorgehensweise, nur einige globale Flags im ISR zu setzen (Sie sollten so wenig Zeit wie möglich verwenden in der ISR), und diese Flags in Ihrer Hauptschleife auszuwerten.

vsz
quelle
0

Ich denke nicht, dass irgendetwas jemals ein absolutes Übel in der Programmierung ist.

Es gibt eine andere Situation, in der Fahnen in Ordnung sein könnten, die hier noch nicht erwähnt wurden ...

Betrachten Sie die Verwendung von Verschlüssen in diesem Javascript-Snippet:

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

Die innere Funktion, die an "Array.forEach" übergeben wird, kann nicht einfach "true" zurückgeben.

Daher müssen Sie den Staat mit einer Flagge draußen lassen.

firstdoit
quelle