Richtiger Kommentar für boolesche Funktionsargumente, die "falsch" sind?

19

Aus einigen Open Source-Projekten habe ich den folgenden Codierungsstil zusammengetragen

void someFunction(bool forget);

void ourFunction() {
  someFunction(false /* forget */);
}    

Ich habe immer Zweifel, was falsehier bedeutet. Bedeutet es "vergessen" oder bezieht sich das "vergessen" auf den entsprechenden Parameter (wie im obigen Fall) und "falsch" soll es negieren?

Welcher Stil wird am häufigsten verwendet und wie lässt sich die Mehrdeutigkeit am besten (oder auf eine der besseren Arten) vermeiden?

Johannes Schaub - litb
quelle
38
benutze enums (auch wenn es nur 2 möglichkeiten gibt) anstelle von bools
Esailija
21
Einige Sprachen unterstützen benannte Argumente. In solchen Sprachen könnten Sie verwendensomeFunction(forget: true);
Brian
3
Ich fühle mich verpflichtet, Martin Fowlers Argumente zu Flaggenargumenten zu liefern (spricht auch über Boolesche Setter). Versuchen Sie im Allgemeinen, sie zu vermeiden.
FGreg
3
Um das Offensichtliche zu beleuchten, können Kommentare lügen. So ist immer besser , Ihren Code selbsterklärend zu machen, weil einige ändern truezu falseund nicht den Kommentar aktualisieren. Wenn Sie die API nicht ändern können, ist dies am besten zu kommentierensomeFunction( false /* true=forget, false=remember */)
Mark Lakata,
1
@ Bakuriu - eigentlich hätte ich wahrscheinlich immer noch die öffentliche API mit zwei getrennten Methoden ( sortAscendingund sortDescendingoder ähnlich). Nun, nach innen , können sie rufen beide die gleiche private Methode, die diese Art von Parametern haben könnte. Wenn die Sprache dies unterstützen würde, wäre das, was ich übergeben würde, wahrscheinlich eine Lambda-Funktion, die die Sortierrichtung enthält ...
Clockwork-Muse

Antworten:

33

In dem von Ihnen geposteten Beispielcode sieht es so aus, als ob forgetes sich um ein Flag-Argument handelt. (Ich kann nicht sicher sein, weil die Funktion rein hypothetisch ist.)

Flag-Argumente sind ein Codegeruch. Sie zeigen an, dass eine Funktion mehr als eine Sache tut und eine gute Funktion nur eine Sache tun sollte.

Teilen Sie die Funktion zur Vermeidung des Flags in zwei Funktionen auf, die den Unterschied im Funktionsnamen erklären.

Flag Argument

serveIceCream(bool lowFat)

Kein Flag-Argument

serveTraditionalIceCream()
serveLowFatIceCream()

edit: Im Idealfall müssen Sie eine Funktion mit dem Flag-Parameter überhaupt nicht beibehalten. Es gibt Fälle in der Art, wie Fowler eine verworrene Implementierung nennt, bei der eine vollständige Trennung der Funktionen zu doppeltem Code führt. Je höher jedoch die zyklomatische Komplexität der parametrisierten Funktion ist, desto stärker ist das Argument, sie loszuwerden.


Dies ist nur eine Vermutung, aber ein Parameter namens forgetklingt wie Feature-Neid. Warum sollte ein Anrufer einem anderen Objekt mitteilen, dass es etwas vergessen soll? Möglicherweise liegt ein größeres Designproblem vor.

Aaron Kurtzhals
quelle
4
+17, Helm auf. Wie sehen die Körper aus serveTraditionalIceCreamund serveLowFatIceCreamwie sehen sie aus? Ich habe eine Aufzählung von 14 Eissorten.
JohnMark13
13
Für öffentliche Methoden ist diese Konvention gut, aber wie JohnMark anspielt, ist die andere Hälfte von SRP "eine gute Methode sollte die einzige Methode sein, die das tut, was sie tut". Ich sehe, dass es N + 1 Varianten dieser Methode gibt; N public ohne Parameter, von denen alle eine private Methode mit dem Parameter aufrufen, den Sie nicht verfügbar machen möchten. Irgendwann geben Sie einfach auf und legen den verdammten Parameter offen. Code-Gerüche bedeuten nicht unbedingt, dass Code überarbeitet werden sollte. Sie sind nur Dinge, die einen zweiten Blick in einer Codeüberprüfung verdienen.
KeithS
@Aaron Mit "Feature Neid" was meintest du?
Geek
27

Mit den Worten des Laien:

  • false ist ein wörtliches.
  • Sie übergeben ein wörtliches Wort false
  • Sie sagen someFunction, nicht zu vergessen
  • Sie sagen, someFunctiondass der Parameter vergessen istfalse
  • du sagst someFunctionzu erinnern

Meiner Meinung nach wäre es besser, wenn die Funktion so wäre:

void someFunction(bool remember);

das kannst du so nennen

void ourFunction() {
  someFunction(true);
} 

oder behalten Sie den alten Namen bei, ändern Sie jedoch Ihre Wrapper-Funktion in

void ourFunctionWithRemember() {
  someFunction(false);
} 

BEARBEITEN:

Wie @Vorac sagte, bemühen Sie sich immer, positive Wörter zu verwenden. Doppelte Verneinung ist verwirrend.

Tulains Córdova
quelle
15
+1 für die Idee, immer nach positiven Worten zu streben. Doppelte Verneinung ist verwirrend.
Vorac
1
Einverstanden, tolle Idee. Es ist verwirrend, dem System mitzuteilen, nichts zu tun. Wenn Sie einen booleschen Parameter akzeptieren, drücken Sie ihn positiv aus.
Brandon
In den meisten Fällen würden Sie wahrscheinlich auch präziser sein wollen als remember, es sei denn, der Funktionsname hat die Bedeutung von remember sehr offensichtlich gemacht. rememberToCleanUp* or *persistoder so.
Itsbruce
14

Der Parameter kann einen guten Namen haben. es ist schwer zu sagen, ohne den Namen der Funktion zu kennen. Ich gehe davon aus, dass der Kommentar von den ursprünglichen Autor der Funktion geschrieben wurde, und es war eine Erinnerung daran, was vorbei falsein someFunctionMittel, sondern auch für alle später kommen zusammen, es ist ein wenig unklar , auf den ersten Blick.

Die Verwendung eines positiven Variablennamens (vorgeschlagen in Code Complete ) ist möglicherweise die einfachste Änderung, die das Lesen dieses Snippets erleichtert, z

void someFunction(boolean remember);

dann ourFunctionwird:

void ourFunction() {
    someFunction(true /* remember */);
}

Durch die Verwendung einer Aufzählung wird der Funktionsaufruf jedoch auf Kosten eines unterstützenden Codes noch verständlicher:

public enum RememberFoo {
    REMEMBER,
    FORGET
}

...

void someFunction(RememberFoo remember);

...

void ourFunction() {
    someFunction(RememberFoo.REMEMBER);
}

Wenn Sie die Signatur aus someFunctionirgendeinem Grund nicht ändern können , vereinfacht die Verwendung einer temporären Variablen auch das Lesen des Codes, indem Sie eine Variable nur einführen, um den Code für den Menschen einfacher parsen zu können .

void someFunction(boolean remember);

...

void ourFunction() {
    boolean remember = false;
    someFunction(remember);
}
Mike Partridge
quelle
1
Auf remembertrue setzen bedeutet vergessen (in Ihrem Beispiel von someFunction(true /* forget */);)?
Brian
2
Das enumist bei weitem die beste Lösung. Nur weil ein Typ kann als dargestellt werden bool-ie, sie sind isomorph-es bedeutet nicht , dass es sollte als solche dargestellt werden. Das gleiche Argument gilt für stringund sogar int.
Jon Purdy
10

Benennen Sie die Variable um, damit ein bool-Wert Sinn macht.

Dies ist millionenfach besser als das Hinzufügen eines Kommentars zur Erläuterung von Argumenten zu einer Funktion, da der Name nicht eindeutig ist.

Enderland
quelle
3
Das beantwortet die Frage nicht. Alle Dinge sind offensichtlich, wenn die Methode in derselben Datei definiert und im Abstand von 4 Zeilen aufgerufen wird. Aber was ist, wenn Sie im Moment nur den Anrufer sehen? Was ist, wenn es mehrere Boolesche Werte hat? Manchmal reicht ein einfacher Inline-Kommentar aus.
Brandon
@Brandon Das ist kein Argument gegen den Aufruf des Booleschen doNotPersist (oder besser Persist). Es "vergessen" zu nennen, ohne zu sagen, was zu vergessen ist, ist ehrlich gesagt nicht hilfreich. Oh, und eine Methode, die mehrere Boolesche Werte als Option akzeptiert, stinkt in den Himmel.
Itsbruce
5

Erstellen Sie einen lokalen Booleschen Wert mit einem aussagekräftigeren Namen und weisen Sie ihm den Wert zu. Auf diese Weise wird die Bedeutung klarer.

void ourFunction() {
    bool takeAction = false;  /* false means to forget */
    someFunction( takeAction );
}    

Wenn Sie die Variable nicht umbenennen können, sollte der Kommentar etwas aussagekräftiger sein:

void ourFunction() {
    /* false means that the method should forget what was requested */
    someFunction( false );
}    

quelle
1
Auf jeden Fall ein guter Rat, aber ich glaube nicht, dass er das Problem angeht, dass der /* forget */Kommentar da ist, um es anzusprechen. Ohne die Funktionsdeklaration direkt vor Ihnen kann es schwierig sein, sich zu erinnern, worauf eingestellt wird false. (Aus diesem Grund denke ich, dass @ Esailijas Rat besser ist, eine Aufzählung hinzuzufügen, und warum ich Sprachen liebe, die benannte Parameter zulassen.)
Gort the Robot,
@StevenBurnap - danke! Sie haben Recht damit, dass meine alte Antwort bei der Beantwortung der Frage des OP nicht klar genug war. Ich habe es bearbeitet, um es klarer zu machen.
3

Es gibt einen guten Artikel, der genau diese Situation erwähnt, da er sich auf Qt-Style-APIs bezieht. Dort heißt es The Boolean Parameter Trap und es ist eine Lektüre wert.

Der Kern davon ist:

  1. Es ist besser, die Funktion zu überladen, damit der Bool nicht benötigt wird
  2. Die Verwendung einer Aufzählung (wie Esailija vorschlägt) ist am besten
Steven Evers
quelle
2

Dies ist ein bizarrer Kommentar.

Aus der Sicht des Compilers someFunction(false /* forget */);ist dies tatsächlich someFunction(false);(der Kommentar wird entfernt). In dieser Zeile wird also nur someFunctiondas erste (und einzige) Argument aufgerufen, das auf gesetzt ist false.

/* forget */ist nur der Name des Parameters. Es ist wahrscheinlich nichts weiter als eine schnelle (und schmutzige) Erinnerung, die nicht unbedingt da sein muss. Verwenden Sie einfach einen weniger mehrdeutigen Parameternamen, und Sie brauchen den Kommentar überhaupt nicht.

user2590712
quelle
1

Einer der Ratschläge von Clean Code ist, die Anzahl unnötiger Kommentare 1 zu minimieren (da sie zum Verrotten neigen) und Funktionen und Methoden richtig zu benennen.

Danach würde ich nur den Kommentar entfernen. Schließlich wird in modernen IDEs (wie Eclipse) ein Kästchen mit Code angezeigt, wenn Sie mit der Maus über die Funktion fahren. Das Sehen des Codes sollte die Mehrdeutigkeit beseitigen.


1 Das Kommentieren von komplexem Code ist in Ordnung.

BЈовић
quelle
btw Wer hat so etwas gesagt: "Das schlimmste Problem eines Programmierers ist, wie man eine Variable benennt und um eins versetzt"?
BЈовић
4
Sie suchen wahrscheinlich nach martinfowler.com/bliki/TwoHardThings.html als Quelle dafür. Ich habe zu "Es gibt nur zwei schwierige Dinge in der Informatik: Cache-Ungültigmachung, Benennung von Dingen und Fehler."
1

Um das Offensichtliche zu beleuchten, können Kommentare lügen. So ist immer besser , um Ihren Code selbsterklärend, ohne auf die Kommentare zurückgreifen zu erklären, weil einige Menschen (vielleicht Sie) ändern truezu falseund nicht den Kommentar aktualisieren.

Wenn Sie die API nicht ändern können, greife ich auf zwei Optionen zurück

  • Ändern Sie den Kommentar so, dass er unabhängig vom Code immer wahr ist. Wenn Sie dies nur einmal aufrufen, ist dies eine gute Lösung, da die Dokumentation lokal bleibt.
     someFunction (false / * true = vergessen, false = erinnern * /); `
  • Verwenden Sie #defines, insbesondere wenn Sie es mehrmals aufrufen.
     #define FORGET true
     #define REMEMBER false
     someFunction (REMEMBER);
Mark Lakata
quelle
1

Mir gefällt die Antwort, dass der Kommentar immer wahr ist , aber obwohl er gut ist, vermisse ich das grundlegende Problem mit diesem Code - er wird mit einem Literal aufgerufen.

Sie sollten die Verwendung von Literalen beim Aufrufen von Methoden vermeiden. Lokale Variablen, optionale Parameter, benannte Parameter, Aufzählungen - wie Sie sie am besten vermeiden, hängt von der Sprache und den verfügbaren Informationen ab. Versuchen Sie jedoch, sie zu vermeiden. Literale haben Werte, aber sie haben keine Bedeutung.

jmoreno
quelle
-1

In C # habe ich benannte Parameter verwendet, um dies zu verdeutlichen

someFunction(forget: false);

Oder enum:

enum Memory { Remember, Forget };

someFunction(Memory.Forget);

Oder Überladungen:

someFunctionForget();

Oder Polymorphismus`:

var foo = new Elephantine();

foo.someFunction();
Jay Bazuzi
quelle
-2

Die Benennung sollte immer die Mehrdeutigkeit für Boolesche Werte auflösen. Ich nenne Boolean immer so etwas wie 'isThis' oder 'shouldDoThat', zum Beispiel:

void printTree(Tree tree, bool shouldPrintPretty){ ... }

und so weiter. Wenn Sie jedoch auf den Code einer anderen Person verweisen, ist es am besten, beim Übergeben von Werten nur einen Kommentar zu hinterlassen.

dchhetri
quelle
Wie beantwortet dies die gestellte Frage?
gnat
@gnat Ich beantwortete seine Frage zum Auflösen von Mehrdeutigkeiten mit booleschen Parametern. Vielleicht habe ich seine Frage falsch gelesen.
Dchhetri