Ist es falsch, einen booleschen Parameter zur Bestimmung von Werten zu verwenden?


39

Laut Ist es falsch , einen Booleschen Parameter zu verwenden Verhalten zu bestimmen? Ich weiß, wie wichtig es ist, keine booleschen Parameter zu verwenden, um ein Verhalten zu bestimmen, z.

Originalfassung

public void setState(boolean flag){
    if(flag){
        a();
    }else{
        b();
    }
    c();
}

neue Version:

public void setStateTrue(){
    a();
    c();
}

public void setStateFalse(){
    b();
    c();
}

Aber wie wäre es mit dem Fall, dass der boolesche Parameter verwendet wird, um Werte anstelle von Verhaltensweisen zu bestimmen? z.B:

public void setHint(boolean isHintOn){
    this.layer1.visible=isHintOn;
    this.layer2.visible=!isHintOn;
    this.layer3.visible=isHintOn;
}

Ich versuche, das isHintOn-Flag zu entfernen und zwei separate Funktionen zu erstellen:

public void setHintOn(){
    this.layer1.visible=true;
    this.layer2.visible=false;
    this.layer3.visible=true;
}

public void setHintOff(){
    this.layer1.visible=false;
    this.layer2.visible=true;
    this.layer3.visible=false;
}

Die geänderte Version scheint jedoch weniger wartbar zu sein, weil:

  1. Es hat mehr Codes als die Originalversion

  2. Es kann nicht eindeutig gezeigt werden, dass die Sichtbarkeit von Layer2 der Hinweisoption entgegengesetzt ist

  3. Wenn eine neue Ebene (zB: Ebene4) hinzugefügt wird, muss ich hinzufügen

    this.layer4.visible=false;
    

    und

    this.layer4.visible=true;  
    

    in setHintOn () und setHintOff () getrennt

Meine Frage ist also, ob es immer noch empfehlenswert ist, diesen booleschen Parameter zu entfernen, wenn der boolesche Parameter nur zur Bestimmung von Werten verwendet wird, aber nicht für Verhaltensweisen (z. B. kein if-else für diesen Parameter).


26
Es ist nie falsch, wenn der resultierende Code lesbarer und wartbarer ist ;-) Ich würde empfehlen, die einzelne Methode anstelle der beiden getrennten Methoden zu verwenden.
Helb

32
Sie führen ein überzeugendes Argument an, dass eine einzelne Implementierung einer Methode, die diese Booleschen Werte festlegt, die Verwaltung der Klasse und das Verständnis ihrer Implementierung erleichtert. Sehr gut; das sind legitime Überlegungen. Die öffentliche Schnittstelle der Klasse muss jedoch nicht deformiert werden, um sie aufzunehmen. Wenn separate Methoden das Verständnis und die Arbeit mit der öffentlichen Schnittstelle erleichtern, definieren Sie Ihre setHint(boolean isHintOn)als private Methode und fügen Sie public setHintOnund setHintOffMethoden hinzu, die bzw. setHint(true)und aufrufen setHint(false).
Mark Amery

9
Ich wäre sehr unglücklich mit den Methodennamen: sie nicht wirklich bieten jeden Nutzen gegenüber setHint(true|false). Kartoffel-Potahto. Zumindest etwas wie setHintund verwenden unsetHint.
Konrad Rudolph


4
@kevincline Wenn die Bedingung ein Name ist, schreiben Sie isam Anfang. isValidusw. Warum sollte man das für zwei Wörter ändern? Dabei liegt "natürlicher" im Auge des Betrachters. Wenn Sie es als englischen Satz aussprechen möchten, dann wäre es für mich natürlicher, "wenn der Hinweis auf" mit einem "eingeblendeten" steht.
Mr Lister

Antworten:


95

Das API-Design sollte sich auf das konzentrieren, was für einen Client der API von der aufrufenden Seite am nützlichsten ist .

Wenn diese neue API beispielsweise erfordert, dass der Aufrufer regelmäßig Code wie diesen schreibt

if(flag)
    foo.setStateTrue();
else
    foo.setStateFalse();

Dann sollte klar sein, dass das Vermeiden des Parameters schlimmer ist als eine API, die es dem Aufrufer ermöglicht, zu schreiben

 foo.setState(flag);

Die frühere Version erzeugt nur ein Problem, das dann auf der aufrufenden Seite (und wahrscheinlich mehr als einmal) gelöst werden muss. Das erhöht weder die Lesbarkeit noch die Wartbarkeit.

Die Implementierungsseite sollte jedoch nicht bestimmen, wie die öffentliche API aussieht. Wenn eine Funktion wie setHintmit einem Parameter bei der Implementierung weniger Code benötigt, eine API für einen Client jedoch einfacher zu verwenden ist setHintOn/ setHintOffaussieht, kann sie folgendermaßen implementiert werden:

private void setHint(boolean isHintOn){
    this.layer1.visible=isHintOn;
    this.layer2.visible=!isHintOn;
    this.layer3.visible=isHintOn;
}

public void setHintOn(){
   setHint(true);
}

public void setHintOff(){
   setHint(false);
}

Obwohl die öffentliche API keinen booleschen Parameter hat, gibt es hier keine doppelte Logik, sodass nur eine Stelle geändert werden muss, wenn eine neue Anforderung (wie im Beispiel der Frage) eintrifft.

Dies funktioniert auch umgekehrt: Wenn die setStateobige Methode zwischen zwei verschiedenen Codeteilen wechseln muss, können diese Codeteile in zwei verschiedene private Methoden umgestaltet werden. Daher ist es meiner Meinung nach nicht sinnvoll, anhand der Interna nach einem Kriterium für die Entscheidung zwischen "einem Parameter / einer Methode" und "null Parametern / zwei Methoden" zu suchen. Sehen Sie sich jedoch an, wie Sie die API in der Rolle eines Konsumenten davon sehen möchten.

Versuchen Sie im Zweifelsfall die Verwendung von "Test Driven Development" (TDD), um über die öffentliche API und deren Verwendung nachzudenken.


DocBrown, würden Sie sagen, die richtige Trennlinie ist, ob die Einstellung jedes Zustands komplexe und möglicherweise nicht umkehrbare Nebenwirkungen hat? Wenn Sie beispielsweise einfach eine Flagge umschalten, die genau das tut, was sie sagt, und keine zugrunde liegende Zustandsmaschine vorhanden ist, müssen Sie die verschiedenen Zustände parametrisieren. Während Sie beispielsweise eine Methode wie die nicht parametrisieren würden SetLoanFacility(bool enabled), weil ein Darlehen bereitgestellt wurde, ist es möglicherweise nicht einfach, es wieder zu entfernen, und die beiden Optionen beinhalten möglicherweise eine völlig andere Logik - und Sie möchten das Erstellen trennen / Methoden entfernen.
Steve

15
@Steve: Sie versuchen immer noch, die API anhand der Anforderungen zu entwerfen, die Sie auf der Implementierungsseite sehen. Um ehrlich zu sein: das ist völlig irrelevant. Verwenden Sie die von der aufrufenden Seite am einfachsten zu verwendende Variante der öffentlichen API. Intern können Sie immer zwei öffentliche Methoden einen privaten mit einem Parameter aufrufen lassen. Oder umgekehrt: Sie können eine Methode mit einem Parameter zwischen zwei privaten Methoden mit unterschiedlicher Logik wechseln lassen.
Doc Brown

@Steve: siehe mein edit.
Doc Brown

Ich nehme alle Ihre Punkte auf - ich denke tatsächlich von der Seite des Anrufers darüber nach (daher mein Verweis auf "was auf der Dose steht") und versuche, die geeignete Regel zu formulieren, nach der ein Anrufer normalerweise jeden Ansatz verwenden würde. Es scheint mir die Regel zu sein, ob der Anrufer erwartet, dass wiederholte Anrufe idempotent sind und dass Zustandsübergänge ungehindert und ohne komplexe Nebenwirkungen sind. Das Ein- und Ausschalten eines Raumlichtschalters würde parametrisiert, das Ein- und Ausschalten eines regionalen Kraftwerks würde mehrere Methoden umfassen.
Steve

1
@Steve Wenn der Benutzer also eine bestimmte Einstellung bestätigen muss, Toggle()ist nicht die richtige Funktion bereitzustellen. Das ist der ganze Punkt; Wenn es dem Anrufer nur darum geht, "es zu ändern" und nicht "wie es endet", Toggle()ist die Option, die zusätzliche Überprüfungen und Entscheidungen vermeidet. Ich würde das nicht als GEMEINSAM bezeichnen, noch würde ich empfehlen, es ohne guten Grund zur Verfügung zu stellen, aber wenn der Benutzer einen Umschalter benötigt, würde ich ihm einen Umschalter geben.
Kamil Drakari

40

Martin Fowler zitiert Kent Beck , indem er separate setOn() setOff()Methoden empfiehlt , sagt aber auch, dass dies nicht als unverletzlich angesehen werden sollte:

Wenn Sie Daten aus einer booleschen Quelle ziehen, z. B. einem UI-Steuerelement oder einer Datenquelle, wäre mir das lieber setSwitch(aValue)als

if (aValue)
  setOn();
else
  setOff();

Dies ist ein Beispiel dafür, dass eine API geschrieben werden sollte, um es dem Aufrufer zu erleichtern. Wenn wir also wissen, woher der Aufrufer kommt, sollten wir die API unter Berücksichtigung dieser Informationen entwerfen. Dies argumentiert auch, dass wir manchmal beide Stile bereitstellen können, wenn wir Anrufer auf beide Arten erhalten.

Eine weitere Empfehlung ist ein Aufzählungswert oder Flags zu verwenden geben zu geben trueund falsebesser, kontextspezifische Namen. In deinem Beispiel showHintund hideHintkönnte besser sein.


16
Nach meiner Erfahrung führt setSwitch (value) fast immer zu weniger Gesamtcode als setOn / setOff, und zwar genau aufgrund des in Ihrer Antwort angegebenen if / else-Codes. Ich neige dazu, Entwickler zu verfluchen, die mir eine API von setOn / setOff anstelle von setSwitch (value) geben.
17 von 26

1
Betrachten Sie es mit einem ähnlichen Objektiv: Wenn Sie den Wert fest codieren müssen, ist dies mit beiden einfach. Wenn Sie es jedoch beispielsweise auf Benutzereingabe einstellen müssen, können Sie den Wert direkt übergeben, wodurch ein Schritt gespeichert wird.
Nic Hartley

@ 17of26 als konkretes Gegenbeispiel (um zu zeigen, dass "es abhängt", anstatt dass eines dem anderen vorzuziehen ist), gibt es in Apples AppKit eine -[NSView setNeedsDisplay:]Methode, mit der Sie übergeben, YESob eine Ansicht neu gezeichnet werden soll und NOwenn nicht. Sie müssen es so gut wie nie ablehnen, daher hat UIKit einfach -[UIView setNeedsDisplay]keinen Parameter. Es hat nicht die entsprechende -setDoesNotNeedDisplayMethode.
Graham Lee

2
@ AbrahamLee, ich denke, Fowlers Argument ist ziemlich subtil und beruht wirklich auf dem Urteilsvermögen. bool isPremiumIn seinem Beispiel würde ich kein Flag verwenden, aber ich würde ein enum ( BookingType bookingType) verwenden, um dieselbe Methode zu parametrisieren, es sei denn, die Logik für jede Buchung war ganz anders. Die "verworrene Logik", auf die sich Fowler bezieht, ist oft wünschenswert, wenn man in der Lage sein will, den Unterschied zwischen den beiden Modi zu erkennen. Und wenn sie sich radikal unterscheiden, würde ich die parametrisierte Methode extern verfügbar machen und die separaten Methoden intern implementieren.
Steve

3

Ich denke, Sie mischen zwei Dinge in Ihrem Beitrag, die API und die Implementierung. In beiden Fällen gibt es meines Erachtens keine strenge Regel, die Sie immer anwenden können, aber Sie sollten diese beiden Punkte unabhängig voneinander berücksichtigen (so weit wie möglich).

Beginnen wir mit der API, beides:

public void setHint(boolean isHintOn)

und:

public void setHintOn()
public void setHintOff()

sind gültige Alternativen, abhängig davon, was Ihr Objekt bieten soll und wie Ihre Kunden die API verwenden werden. Wie Doc betonte, ist die erste Option sinnvoller, wenn Ihre Benutzer bereits über eine boolesche Variable verfügen (über ein UI-Steuerelement, eine Benutzeraktion, eine externe API usw.), andernfalls erzwingen Sie lediglich eine zusätzliche if-Anweisung im Code des Clients . Wenn Sie zum Beispiel den Hinweis zu Beginn eines Prozesses auf true und am Ende auf false setzen, erhalten Sie mit der ersten Option Folgendes:

setHint(true)
// Do your process
…
setHint(false)

Die zweite Option bietet Ihnen Folgendes:

setHintOn()
// Do your process
…
setHintOff()

Welche IMO ist viel lesbarer, so werde ich mit der zweiten Option in diesem Fall gehen. Offensichtlich hindert Sie nichts daran, beide Optionen anzubieten (oder mehr, Sie könnten eine Aufzählung verwenden, wie Graham sagte, wenn das zum Beispiel sinnvoller ist).

Der Punkt ist, dass Sie Ihre API basierend darauf auswählen sollten, was das Objekt tun soll und wie die Clients es verwenden werden, nicht basierend darauf, wie Sie es implementieren werden.

Dann müssen Sie auswählen, wie Sie Ihre öffentliche API implementieren. Nehmen wir an, wir haben die Methoden setHintOnund setHintOffals öffentliche API ausgewählt und sie teilen diese gemeinsame Logik wie in Ihrem Beispiel. Sie können diese Logik leicht mit einer privaten Methode (aus Doc kopierter Code) abstrahieren:

private void setHint(boolean isHintOn){
    this.layer1.visible=isHintOn;
    this.layer2.visible=!isHintOn;
    this.layer3.visible=isHintOn;
}

public void setHintOn(){
   setHint(true);
}

public void setHintOff(){
   setHint(false);
}

Nehmen wir umgekehrt an, wir haben setHint(boolean isHintOn)eine unserer APIs ausgewählt, aber wir kehren Ihr Beispiel um, und zwar aus irgendeinem Grund, weil das Setzen des Hinweises auf "Ein" völlig anders ist als das Setzen auf "Aus". In diesem Fall könnten wir es wie folgt implementieren:

public void setHint(boolean isHintOn){
    if(isHintOn){
        // Set it On
    } else {
        // Set it Off
    }    
}

Oder auch:

public void setHint(boolean isHintOn){    
    if(isHintOn){
        setHintOn()
    } else {
        setHintOff()
   }    
}

private void setHintOn(){
   // Set it On
}

private void setHintOff(){
   // Set it Off 
}

Der Punkt ist, dass wir in beiden Fällen zuerst unsere öffentliche API ausgewählt und dann unsere Implementierung angepasst haben, um sie an die gewählte API (und die Einschränkungen, die wir haben) anzupassen, und nicht umgekehrt.

Übrigens, ich denke, dasselbe gilt für den Beitrag, den Sie über die Verwendung eines booleschen Parameters zur Bestimmung des Verhaltens verlinkt haben. Sie sollten sich also eher für Ihren speziellen Anwendungsfall als für eine harte Regel entscheiden (obwohl in diesem speziellen Fall normalerweise das Richtige zu tun ist ist es in mehrere Funktionen zu brechen).


3
Als Randnotiz, wenn es so etwas wie der Pseudoblock ist (eine Aussage am Anfang, eine am Ende), sollten Sie wahrscheinlich beginund / endoder Synonyme verwenden, nur um deutlich zu machen, dass dies das ist, was sie tun, und um zu implizieren, dass a Anfang muss ein Ende haben und umgekehrt.
Nic Hartley

Ich stimme Nic zu und möchte hinzufügen: Wenn Sie sicherstellen möchten, dass Anfang und Ende immer gekoppelt sind, sollten Sie auch sprachspezifische Redewendungen dafür bereitstellen: RAII- / Scope-Guards in C ++, usingBlöcke in C #, withAnweisungskontext-Manager in Python, Übergabe der Körper als Lambda oder aufrufbares Objekt (zB Ruby-Block-Syntax) usw.
Daniel Pryden

Ich stimme beiden Punkten zu, ich habe nur versucht, ein einfaches Beispiel zu geben, um den anderen Fall zu veranschaulichen (obwohl es ein schlechtes Beispiel ist, wie Sie beide hervorgehoben haben :)).
jesm00

2

Das Wichtigste zuerst: Code ist nicht automatisch weniger wartbar, nur weil er etwas länger ist. Klarheit ist wichtig.

Wenn Sie sich wirklich nur mit Daten beschäftigen, ist das, was Sie haben, ein Setter für eine boolesche Eigenschaft. In diesem Fall möchten Sie möglicherweise nur diesen Wert direkt speichern und die Sichtbarkeit des Layers ableiten, d. H

bool isBackgroundVisible() {
    return isHintVisible;
}    

bool isContentVisible() {
    return !isHintVisible;
}

(Ich habe mir die Freiheit genommen, den Ebenen tatsächliche Namen zu geben. Wenn Sie dies nicht in Ihrem ursprünglichen Code haben, würde ich damit beginnen.)

Sie haben immer noch die Frage, ob Sie eine setHintVisibility(bool)Methode haben sollen. Persönlich würde ich empfehlen, es durch eine showHint()und hideHint()-Methode zu ersetzen - beide sind sehr einfach und müssen beim Hinzufügen von Ebenen nicht geändert werden. Es ist jedoch kein eindeutiges Richtig / Falsch.

Wenn nun der Aufruf der Funktion tatsächlich die Sichtbarkeit dieser Ebenen ändern sollte, haben Sie tatsächlich Verhalten. In diesem Fall würde ich auf jeden Fall separate Methoden empfehlen.


Also lautet die Empfehlung: Sie empfehlen die Aufteilung in zwei Funktionen, da Sie diese beim Hinzufügen von Ebenen nicht ändern müssen. Wenn eine Ebene4 hinzugefügt wird, müssen wir möglicherweise auch "this.layer4.visibility = isHintOn" sagen, sodass ich nicht sicher bin, ob ich damit einverstanden bin. Wenn überhaupt, ist das ein Nachteil, da wir jetzt, wenn eine Ebene hinzugefügt wird, zwei Funktionen bearbeiten müssen, nicht nur eine.
Erdrik Ironrose

Nein, ich empfehle es (schwach) aus Gründen der Klarheit ( showHintvs setHintVisibility). Ich erwähnte die neue Schicht nur, weil OP sich darüber Sorgen machte. Auch wir brauchen nur eine neue Methode hinzuzufügen: isLayer4Visible. showHintund hideHintsetzen Sie das isHintVisibleAttribut einfach auf true / false und das ändert sich nicht.
doubleYou

1
@doubleYou, du sagst, längerer Code ist nicht automatisch weniger wartbar. Ich würde sagen , dass Codelänge eines der ist Hauptvariablen in Wartbarkeit , und Klarheit, überragte nur durch strukturelle Komplexität. Jeder Code, der länger und strukturell komplexer wird, sollte es verdienen, ansonsten werden einfache Probleme im Code komplexer behandelt als sie es verdienen, und die Codebasis erwirbt ein Dickicht unnötiger Zeilen (das Problem "zu vieler Indirektionsebenen").
Steve

@Steve Ich stimme voll und ganz zu, dass Sie Dinge überentwickeln können, dh Code länger machen, ohne ihn klarer zu machen. Andererseits können Sie Code auf Kosten der Klarheit immer kürzer machen, sodass es hier keine 1: 1-Beziehung gibt.
doubleYou

@Steve Denken Sie an „Code Golf“ - wenn Sie einen solchen Code nehmen und ihn in mehr Zeilen schreiben, wird er in der Regel klarer. „Code Golf“ ist ein Extrem, aber es gibt immer noch viele Programmierer, die denken, dass es „elegant“ und vielleicht sogar noch schneller ist, alles in einen intelligenten Ausdruck zu packen, weil Compiler nicht gut genug optimieren.
Blackjack

1

Boolesche Parameter sind im zweiten Beispiel in Ordnung. Wie Sie bereits herausgefunden haben, sind boolesche Parameter an sich nicht problematisch. Es ist ein Schaltverhalten basierend auf einem Flag, was problematisch ist.

Das erste Beispiel ist jedoch problematisch, da die Benennung auf eine Setter-Methode hinweist, die Implementierungen jedoch etwas anderes zu sein scheinen. Sie haben also das Antimuster für Verhaltensänderungen und eine irreführend benannte Methode. Aber wenn die Methode tatsächlich ein regulärer Setter ist (ohne Verhaltensänderung), gibt es kein Problem mit setState(boolean). Zwei Methoden zu haben setStateTrue()und setStateFalse()die Dinge unnötig zu komplizieren, bringt keinen Nutzen.


1

Eine andere Möglichkeit, dieses Problem zu lösen, besteht darin, ein Objekt einzuführen, das jeden Hinweis darstellt, und das Objekt für die Bestimmung der diesem Hinweis zugeordneten Booleschen Werte verantwortlich zu machen. Auf diese Weise können Sie neue Permutationen hinzufügen, anstatt einfach zwei Boolesche Zustände zu haben.

In Java können Sie beispielsweise Folgendes tun:

public enum HintState {
    SHOW_HINT(true, false, true),
    HIDE_HINT(false, true, false);

    private HintState(boolean layer1Visible, boolean layer2Visible, boolean layer3Visible) {
         // constructor body and accessors omitted for clarity
    }
}

Und dann würde Ihr Anrufercode so aussehen:

setHint(HintState.SHOW_HINT);

Und Ihr Implementierungscode würde so aussehen:

public void setHint(HintState hint) {
    this.layer1Visible = hint.isLayer1Visible();
    this.layer2Visible = hint.isLayer2Visible();
    this.layer3Visible = hint.isLayer3Visible();
}

Dies hält den Implementierungscode und den Aufrufercode kurz, im Gegenzug zum Definieren eines neuen Datentyps, der stark typisierte, benannte Absichten eindeutig den entsprechenden Mengen von Zuständen zuordnet. Ich denke, das ist überall besser.


0

Meine Frage ist also, ob es immer noch empfehlenswert ist, diesen booleschen Parameter zu entfernen, wenn der boolesche Parameter nur zur Bestimmung von Werten verwendet wird, aber nicht für Verhaltensweisen (z. B. kein if-else für diesen Parameter).

Wenn ich Zweifel an solchen Dingen habe. Ich stelle mir gerne vor, wie der Stack-Trace aussehen würde.

Ich habe viele Jahre an einem PHP-Projekt gearbeitet, das die gleiche Funktion wie ein Setter und Getter verwendet hat . Wenn Sie null übergeben , wird der Wert zurückgegeben, andernfalls wird er festgelegt. Es war schrecklich, damit zu arbeiten .

Hier ist ein Beispiel für einen Stack-Trace:

function visible() : line 440
function parent() : line 398
function mode() : line 384
function run() : line 5

Sie hatten keine Ahnung vom internen Status und das Debuggen wurde schwieriger. Es gibt eine Reihe anderer negativer Nebenwirkungen, aber versuchen Sie herauszufinden, ob ein ausführlicher Funktionsname und die Klarheit von Funktionen von Nutzen sind, wenn Funktionen eine einzelne Aktion ausführen .

Stellen Sie sich nun vor, wie Sie mit dem Stack-Trace für eine Funktion arbeiten, deren A- oder B-Verhalten auf einem booleschen Wert basiert.

function bar() : line 92
function setVisible() : line 120
function foo() : line 492
function setVisible() : line 120
function run() : line 5

Das ist verwirrend, wenn Sie mich fragen. Dieselben setVisibleLinien ergeben zwei unterschiedliche Tracepfade.

Also zurück zu deiner Frage. Stellen Sie sich vor, wie der Stack-Trace aussehen wird, wie er einer Person mitteilt, was gerade passiert, und fragen Sie sich, ob Sie einer zukünftigen Person beim Debuggen des Codes helfen.

Hier sind einige Tipps:

  • Ein eindeutiger Funktionsname, der eine Absicht impliziert, ohne dass die Argumentwerte bekannt sein müssen.
  • Eine Funktion führt eine einzelne Aktion aus
  • Der Name impliziert Mutation oder unveränderliches Verhalten
  • Lösen von Debugging-Problemen in Bezug auf die Debugging-Fähigkeit von Sprache und Tools.

Manchmal sieht Code unter dem Mikroskop übermäßig aus, aber wenn Sie sich auf das Gesamtbild beschränken, lässt ihn ein minimalistischer Ansatz verschwinden. Wenn Sie es brauchen, um aufzufallen, damit Sie es warten können. Das Hinzufügen vieler kleiner Funktionen wirkt möglicherweise zu ausführlich, verbessert jedoch die Wartbarkeit, wenn sie in einem größeren Kontext verwendet werden.


1
Was mich an der setVisibleStapelablaufverfolgung verwirrt, ist, dass ein Aufruf setVisible(true)anscheinend zu einem Aufruf führt setVisible(false)(oder umgekehrt, je nachdem, wie Sie diese Ablaufverfolgung erhalten haben).
David K

0

In fast allen Fällen, in denen Sie einen booleanParameter als Flag an eine Methode übergeben , um das Verhalten von etwas zu ändern, sollten Sie eine explizite und typsichere Methode in Betracht ziehen, um dies zu tun.

Wenn Sie nichts weiter tun, als eine zu verwenden Enum, die den Status darstellt, haben Sie das Verständnis Ihres Codes verbessert.

In diesem Beispiel wird die NodeKlasse von verwendet JavaFX:

public enum Visiblity
{
    SHOW, HIDE

    public boolean toggleVisibility(@Nonnull final Node node) {
        node.setVisible(!node.isVisible());
    }
}

ist immer besser als, wie man es bei vielen JavaFXObjekten findet:

public void setVisiblity(final boolean flag);

Aber ich denke .setVisible()und bin .setHidden()die beste Lösung für die Situation, in der die Flagge eine ist, booleanda sie die expliziteste und am wenigsten ausführliche ist.

Bei etwas mit Mehrfachauswahl ist dies sogar noch wichtiger. EnumSetexistiert nur aus diesem Grund.

Ed Mann hat einen wirklich guten Blogbeitrag zu diesem Thema. Ich wollte gerade umschreiben, was er sagt, also werde ich, um meine Bemühungen nicht zu verdoppeln, einfach einen Link zu seinem Blog-Post als Nachtrag zu dieser Antwort veröffentlichen.


0

Wenn Sie sich für einen Ansatz für eine Schnittstelle entscheiden, die einen (booleschen) Parameter übergibt, oder für überladene Methoden ohne diesen Parameter, achten Sie auf die konsumierenden Clients.

Wenn alle Verwendungen konstante Werte (z. B. true, false) übergeben würden, spricht dies für die Überladung.

Wenn alle Verwendungen einen variablen Wert übergeben würden, spricht dies für die Methode mit Parameteransatz.

Wenn keines dieser Extreme zutrifft, bedeutet dies, dass es eine Mischung aus Client-Verwendungen gibt. Sie müssen also entscheiden, ob beide Formulare unterstützt werden sollen oder ob eine Kategorie von Clients an den anderen (was für sie unnatürlicher ist) Stil angepasst werden soll.


Was passiert , wenn Sie ein integriertes System sind die Gestaltung und Sie sind schließlich sowohl der Code Produzent und der „raubend Client“ des Codes? Wie sollte eine Person, die in den Schuhen eines konsumierenden Kunden steht, ihre Präferenz für einen Ansatz gegenüber einem anderen formulieren?
Steve

@Steve, Als konsumierender Client wissen Sie, ob Sie eine Konstante oder Variable übergeben. Wenn Sie Konstanten übergeben, bevorzugen Sie Überladungen ohne den Parameter.
Erik Eidt

Aber ich bin daran interessiert zu artikulieren, warum das der Fall sein sollte. Warum nicht Enums für eine begrenzte Anzahl von Konstanten verwenden, da dies in den meisten Sprachen eine einfache Syntax ist, die genau für diesen Zweck entwickelt wurde?
Steve

@Steve, wenn wir zur Entwurfs- / Kompilierungszeit wissen, dass Clients in allen Fällen einen konstanten Wert (true / false) verwenden würden, deutet dies darauf hin, dass es zwei verschiedene spezifische Methoden gibt, anstatt nur eine verallgemeinerte Methode (die einen Parameter benötigt). Ich würde gegen die Einführung der Allgemeinheit einer parametrisierten Methode sprechen, wenn sie nicht verwendet wird - es ist ein YAGNI-Argument.
Erik Eidt

0

Es gibt zwei Überlegungen zum Entwerfen:

  • API: welche Schnittstelle Sie dem Benutzer präsentieren,
  • Implementierung: Übersichtlichkeit, Wartbarkeit, etc ...

Sie sollten nicht zusammengeführt werden.

Es ist vollkommen in Ordnung:

  • in der API mehrere Methoden an eine einzige Implementierung delegieren,
  • eine einzige Methode im API-Versand an mehrere Implementierungen abhängig von der Bedingung.

Daher ist jedes Argument, das versucht, die Kosten / Nutzen eines API-Designs mit den Kosten / Nutzen eines Implementierungsdesigns in Einklang zu bringen, zweifelhaft und sollte sorgfältig geprüft werden.


Auf der API-Seite

Als Programmierer bevorzuge ich generell programmierbare APIs. Code ist viel klarer, wenn ich einen Wert weiterleiten kann, als wenn ich eine if/ switch-Anweisung für den Wert benötige , um zu entscheiden, welche Funktion aufgerufen werden soll.

Letzteres kann erforderlich sein, wenn jede Funktion andere Argumente erwartet.

In Ihrem Fall setState(type value)scheint also eine einzige Methode besser zu sein.

Allerdings gibt es nichts Schlimmeres , als namenlos ist true, false, 2usw ... die magischen Werte haben keine Bedeutung auf eigene Faust. Vermeiden Sie primitive Besessenheit und nehmen Sie starkes Tippen an.

So wird aus einem API - POV, ich will: setState(State state).


Auf der Implementierungsseite

Ich empfehle, alles zu tun, was einfacher ist.

Wenn die Methode einfach ist, ist es am besten zusammengehalten. Wenn der Kontrollfluss verschlungen ist, ist es am besten, ihn in mehrere Methoden zu unterteilen, die sich jeweils mit einem Teilfall oder einem Schritt der Pipeline befassen.


Betrachten Sie schließlich die Gruppierung .

In Ihrem Beispiel (mit zusätzlichen Leerzeichen für die Lesbarkeit):

this.layer1.visible = isHintOn;
this.layer2.visible = ! isHintOn;
this.layer3.visible = isHintOn;

Warum layer2widersetzt sich der Trend? Ist es ein Feature oder ein Bug?

Könnte es möglich sein, 2 Listen zu haben [layer1, layer3]und [layer2]mit einem expliziten Namen den Grund für die Gruppierung anzugeben und dann über diese Listen zu iterieren.

Zum Beispiel:

for (auto layer : this.mainLayers) { // layer2
    layer.visible = ! isHintOn;
}
for (auto layer : this.hintLayers) { // layer1 and layer3
    layer.visible = isHintOn;
}

Der Code spricht für sich, es ist klar, warum es zwei Gruppen gibt und sich ihr Verhalten unterscheidet.


0

Abgesehen von der setOn() + setOff()vs- set(flag)Frage würde ich sorgfältig prüfen, ob ein Boolescher Typ hier am besten ist. Sind Sie sicher, dass es niemals eine dritte Option geben wird?

Es könnte sich lohnen, eine Aufzählung anstelle eines Booleschen zu betrachten. Dies ermöglicht nicht nur Erweiterbarkeit, sondern macht es auch schwieriger, den Booleschen Wert in die falsche Richtung zu lenken, z.

setHint(false)

vs

setHint(Visibility::HIDE)

Mit der Aufzählung wird es viel einfacher, die Liste zu erweitern, wenn jemand entscheidet, dass er eine Option "bei Bedarf" wünscht:

enum class Visibility {
  SHOW,
  HIDE,
  IF_NEEDED // New
}

vs

setHint(false)
setHint(true)
setHintAutomaticMode(true) // New

0

Laut ... weiß ich, wie wichtig es ist, keine booleschen Parameter zu verwenden, um ein Verhalten zu bestimmen

Ich würde vorschlagen, dieses Wissen neu zu bewerten.

Zunächst sehe ich nicht die Schlussfolgerung, die Sie in der von Ihnen verknüpften SE-Frage vorschlagen. Meist geht es darum, einen Parameter über mehrere Schritte von Methodenaufrufen weiterzuleiten, wobei er sehr weit in der Kette ausgewertet wird.

In Ihrem Beispiel bewerten Sie den Parameter direkt in Ihrer Methode. Insofern unterscheidet es sich überhaupt nicht von anderen Parametern.

Im Allgemeinen ist die Verwendung von Booleschen Parametern absolut nichts Falsches. und offensichtlich wird jeder Parameter ein Verhalten bestimmen, oder warum sollten Sie es überhaupt haben?


0

Die Frage definieren

Ihre Titelfrage lautet "Ist es falsch, [...]?" - aber was meinst du mit "falsch"?

Laut einem C # - oder Java-Compiler ist das nicht falsch. Ich bin mir sicher, dass Sie sich dessen bewusst sind, und es ist nicht das, wonach Sie fragen. Abgesehen davon haben wir leider nur unterschiedliche Meinungen der nProgrammierer n+1. Diese Antwort zeigt, was das Buch Clean Code dazu zu sagen hat.

Antworten

Clean Code spricht stark gegen Funktionsargumente im Allgemeinen:

Argumente sind schwer. Sie verbrauchen viel konzeptionelle Kraft. [...] unsere Leser hätten es jedes Mal interpretieren müssen, wenn sie es gesehen hätten.

Ein "Leser" kann hier der API-Verbraucher sein. Es kann auch der nächste Codierer sein, der noch nicht weiß, was dieser Code tut - und der Sie in einem Monat sein könnte. Sie durchlaufen entweder 2 Funktionen getrennt oder 1 Funktion zweimal , trueeinmal falseim Gedächtnis und einmal .
Kurz gesagt, verwenden Sie so wenig Argumente wie möglich .

Der spezielle Fall eines Flag-Arguments wird später direkt angesprochen:

Flag Argumente sind hässlich. Es ist eine schreckliche Praxis, einen Booleschen Wert in eine Funktion umzuwandeln. Es erschwert sofort die Signatur der Methode und verkündet lautstark, dass diese Funktion mehr als eine Sache tut. Es macht eine Sache, wenn die Flagge wahr ist und eine andere, wenn die Flagge falsch ist!

Um Ihre Fragen direkt zu beantworten:
Laut Clean Code wird empfohlen, diesen Parameter zu entfernen.


Zusätzliche Information:

Ihr Beispiel ist recht einfach, aber auch dort können Sie die Einfachheit erkennen, die sich in Ihrem Code ausbreitet: Die Funktionen ohne Parameter führen nur einfache Zuweisungen aus, während die andere Funktion eine Boolesche Arithmetik ausführen muss, um dasselbe Ziel zu erreichen. In diesem vereinfachten Beispiel ist es eine triviale Boolesche Arithmetik, die in einer realen Situation jedoch recht komplex sein kann.


Ich habe hier viele Argumente gesehen, die Sie vom API-Benutzer abhängig machen sollten, weil es dumm wäre, dies an vielen Stellen tun zu müssen:

if (isAfterSunset) light.TurnOn();
else light.TurnOff();

Ich kann bestätigen , dass etwas nicht optimal hier geschieht. Vielleicht ist es zu offensichtlich, um es zu sehen, aber Ihr allererster Satz erwähnt "die Wichtigkeit, keine booleschen Parameter zu verwenden, um ein Verhalten zu bestimmen", und das ist die Basis für die ganze Frage. Ich sehe keinen Grund, dem API-Benutzer das zu vereinfachen, was schlecht ist.


Ich weiß nicht, ob Sie testen - in diesem Fall sollten Sie auch Folgendes berücksichtigen:

Aus der Sicht der Tests sind die Argumente noch schwieriger. Stellen Sie sich vor, wie schwierig es ist, alle Testfälle zu schreiben, um sicherzustellen, dass alle verschiedenen Argumentkombinationen ordnungsgemäß funktionieren. Wenn es keine Argumente gibt, ist dies trivial.


Sie haben das Lede hier wirklich begraben: "... Ihr allererster Satz erwähnt," wie wichtig es ist, keine booleschen Parameter zu verwenden, um ein Verhalten zu bestimmen ", und das ist die Basis für die ganze Frage. Ich verstehe nicht ein Grund, dem API-Benutzer das zu erleichtern, was schlecht ist. " Dies ist ein interessanter Punkt, aber Sie untergraben lieber Ihr eigenes Argument in Ihrem letzten Absatz.
Wildcard

Die Lede begraben? Der Kern dieser Antwort ist "Verwenden Sie so wenig Argumente wie möglich", was in der ersten Hälfte dieser Antwort erläutert wird. Alles, was danach folgt, ist nur eine zusätzliche Information: Widerlegung eines widersprüchlichen Arguments (von einem anderen Benutzer, nicht von OP) und etwas, das nicht für alle gilt.
R. Schmitz

Der letzte Absatz versucht nur zu verdeutlichen, dass die Titelfrage nicht klar genug definiert ist, um beantwortet zu werden. OP fragt, ob es "falsch" ist, sagt aber nicht, nach wem oder was. Laut dem Compiler? Sieht nach gültigem Code aus, also nicht falsch. Nach dem Buch Clean Code? Es wird ein Flag-Argument verwendet, also ist es "falsch". Allerdings schreibe ich stattdessen "empfohlen", weil pragmatisch> dogmatisch. Meinen Sie, ich muss das klarer machen?
R. Schmitz

Warten Sie, Ihre Verteidigung Ihrer Antwort ist, dass die Titelfrage zu unklar ist, um beantwortet zu werden? : D Okay ... ich dachte eigentlich, der Punkt, den ich zitierte, war eine interessante neue Einstellung.
Wildcard

1
Klar jetzt; Gut gemacht!
Wildcard
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.