Ist das Definieren einer Variablen zum Benennen eines Methodenarguments eine bewährte Methode?


73

Aus Gründen der Lesbarkeit definiere ich beim Aufrufen von Funktionen häufig temporäre Variablen, z. B. den folgenden Code

var preventUndo = true;
doSomething(preventUndo);

Die kürzere Version davon dazu wäre,

doSomething(true);

Aber wenn ich zum Code zurückkomme, frage ich mich oft, worauf truesich das bezieht. Gibt es eine Konvention für diese Art von Rätsel?


5
Verwenden Sie eine Art IDE? Die meisten haben eine Art Anzeige der Parameter für die aufgerufene Funktion, wodurch die Notwendigkeit dazu etwas zunichte gemacht wird.
Matthew Scharley

4
Ich verwende keine IDE, aber selbst dann würde der Hinweis eine Aktion erfordern (Mouseover oder Bewegen in die Zeile). Ich möchte wissen, was mein Code tut, indem ich ihn mir ansehe.
Methodofaction

11
Wenn die Sprache dies unterstützt, können Sie eine Aufzählung wiedoSomething( Undo.PREVENT )
James P.

4
Aber du kannst definieren Undo = { PREVENT = true, DONT_PREVENT = false }. Aber in JavaScript ist die Konvention, das so zu machen: function myFunction( mandatoryArg1, mandatoryArg2, otherArgs ) { /*...*/ }und dann myFunction( 1, 2, { option1: true, option2: false } ).
Xavierm02

6
Auf jeden Fall kommt es auf die Sprache an. Wenn dies beispielsweise Python wäre, würde ich nur die Verwendung von Schlüsselwortargumenten vorschlagen, z. B.doSomething(preventUndo=True)
Joel Cornett,

Antworten:


117

Variablen erklären

Ihr Fall ist ein Beispiel für die Einführung in das Variable Refactoring. Kurz gesagt, eine erklärende Variable ist eine Variable, die nicht unbedingt erforderlich ist, die es Ihnen jedoch ermöglicht, einer Sache einen eindeutigen Namen zu geben, um die Lesbarkeit zu verbessern.

Ein guter Qualitätscode teilt dem Leser seine Absicht mit. und als professioneller Entwickler sind Lesbarkeit und Wartbarkeit Ihre obersten Ziele.

Als Faustregel würde ich Folgendes empfehlen: Wenn der Zweck Ihres Parameters nicht sofort offensichtlich ist, können Sie eine Variable verwenden, um ihm einen guten Namen zu geben. Ich denke, dies ist eine gute Praxis im Allgemeinen (sofern nicht missbraucht). Hier ist ein kurzes Beispiel:

editButton.Enabled = (_grid.SelectedRow != null && ((Person)_grid.SelectedRow).Status == PersonStatus.Active);

gegenüber dem etwas längeren, aber wohl klareren:

bool personIsSelected = (_grid.SelectedRow != null);
bool selectedPersonIsEditable = (personIsSelected && ((Person)_grid.SelectedRow).Status == PersonStatus.Active)
editButton.Enabled = (personIsSelected && selectedPersonIsEditable);

Boolesche Parameter

Ihr Beispiel zeigt tatsächlich, warum Boolesche Werte in APIs oft eine schlechte Idee sind - auf der aufrufenden Seite tun sie nichts, um zu erklären, was passiert. Erwägen:

ParseFolder(true, false);

Sie müssten nachsehen, was diese Parameter bedeuten. Wenn sie Aufzählungen wären, wäre es viel klarer:

ParseFolder(ParseBehaviour.Recursive, CompatibilityOption.Strict);

Bearbeiten:

Es wurden Überschriften hinzugefügt und die Reihenfolge der beiden Hauptabsätze vertauscht, da sich zu viele Leute auf den Teil mit den booleschen Parametern konzentrierten (um fair zu sein, es war ursprünglich der erste Absatz). Fügte auch ein Beispiel zum ersten Teil hinzu.


28
Sofern Sie keine benannten Parameter verwenden (benannte Argumente in .NET Framework), doSomething(preventUndo: true);ist dies hinreichend klar. Erstellen Sie auch eine Aufzählung für jeden Booleschen Wert in Ihrer API? Ernsthaft?
Arseni Mourzenko

12
@MainMa: Wenn die Sprache, die Sie verwenden, nicht cool genug ist, um benannte Argumente zu unterstützen, ist die Verwendung einer Aufzählung eine gute Option. Dies bedeutet nicht, dass Sie überall systematisch Aufzählungen einfügen sollten.
Giorgio

18
@MainMa - Um diese Frage zu beantworten, sollten Sie in Ihren APIs (zumindest in Ihren öffentlichen) keine Booleschen Werte verwenden. Sie müssten zu einer Syntax mit benannten Argumenten wechseln, die (zum Zeitpunkt des Schreibens) wahrscheinlich nicht idiomatisch ist. In jedem Fall bedeutet ein boolescher Parameter fast immer, dass Ihre Methode zwei Dinge ausführt und Sie dem Aufrufer dies erlauben wählen. Der Punkt meiner Antwort war jedoch, dass es nichts Falsches ist, erklärende Variablen in Ihrem Code zu verwenden. Es ist eigentlich eine sehr gute Übung.
Daniel B

3
Es ist ein fester Bestandteil der C # -Sprache. Was braucht es mehr "Mainstream-Akzeptanz"? Selbst für jemanden, der das Konstrukt noch nicht kennt (was bedeutet, dass Ihre C # -Entwickler noch nicht einmal die C # -Dokumente gelesen haben), ist es ziemlich offensichtlich, was es bewirkt.
Nate CK

3
Das ist der Grund, warum MS diese netten Zusammenfassungen aller neuen Inhalte veröffentlicht, die sie in jeder Version hinzugefügt haben. Ich denke, es ist nicht sehr arbeitsaufwendig,
Nate CK

43

Schreiben Sie keinen Code, den Sie nicht brauchen.

Wenn Sie doSomething(true)Schwierigkeiten haben zu verstehen, sollten Sie entweder einen Kommentar hinzufügen:

// Do something and prevent the undo.
doSomething(true);

oder, falls die Sprache dies unterstützt, den Parameternamen hinzufügen:

doSomething(preventUndo: true);

Andernfalls verlassen Sie sich auf Ihre IDE, um die Signatur der aufgerufenen Methode zu erhalten:

Bildbeschreibung hier eingeben

Fälle, in denen es nützlich ist

Das Einfügen einer zusätzlichen Variablen kann nützlich sein:

  1. Zum Debuggen:

    var productId = this.Data.GetLastProductId();
    this.Data.AddToCart(productId);
    

    Der gleiche Code kann in eine einzelne Zeile geschrieben werden. Wenn Sie jedoch einen Haltepunkt setzen möchten, bevor Sie ein Produkt in den Warenkorb legen, um zu überprüfen, ob die Produkt-ID korrekt ist, empfiehlt es sich, zwei statt einer Zeile zu schreiben.

  2. Wenn Sie eine Methode mit vielen Parametern haben und jeder Parameter aus einer Bewertung stammt. In einer Zeile kann dies völlig unlesbar werden.

    // Even with indentation, this is unreadable.
    var doSomething(
        isSomethingElse ? 0 : this.getAValue(),
        this.getAnotherOne() ?? this.default,
        (a + b + c + d + e) * f,
        this.hello ? this.world : (this.hello2 ? this.world2 : -1));
    
  3. Wenn die Auswertung eines Parameters zu kompliziert ist. Ein Beispiel für einen Code, den ich gesehen habe:

    // Wouldn't it be easier to have several if/else's (maybe even in a separate method)?
    do(something ? (hello ? world : -1) : (programmers ? stackexchange : (com ? -1 : 0)));
    

Warum nicht in anderen Fällen?

Warum sollten Sie in einfachen Fällen keine zusätzlichen Variablen erstellen?

  1. Nicht wegen der Leistungseinbußen. Dies wäre eine sehr falsche Annahme eines Anfängers, der seine App mikrooptimiert . In den meisten Sprachen treten keine Leistungseinbußen auf, da der Compiler die Variable einfügt. In den Sprachen, in denen der Compiler dies nicht tut, können Sie einige Mikrosekunden gewinnen, indem Sie es von Hand einfügen, was es nicht wert ist. Tu das nicht.

  2. Aber wegen der Gefahr, den Namen zu dissoziieren, geben Sie der Variablen den Namen des Parameters.

    Beispiel:

    Angenommen, der ursprüngliche Code lautet:

    void doSomething(bool preventUndo)
    {
        // Does something very interesting.
        undoHistory.removeLast();
    }
    
    // Later in code:
    var preventUndo = true;
    doSomething(preventUndo);
    

    Später stellte ein Entwickler, der an doSomethingHinweisen arbeitet , dass der Verlauf des Rückgängigmachens kürzlich zwei neue Methoden eingeführt hat:

    undoHistory.clearAll() { ... }
    undoHistory.disable() { ... }
    

    Nun preventUndoscheint nicht sehr klar. Bedeutet das, dass nur die letzte Aktion verhindert wird? Oder bedeutet dies, dass der Benutzer die Funktion zum Rückgängigmachen nicht mehr verwenden kann? Oder dass der Rückgängig-Verlauf gelöscht wird? Die klareren Namen wären:

    doSomething(bool hideLastUndo) { ... }
    doSomething(bool removeAllUndo) { ... }
    doSomething(bool disableUndoFeature) { ... }
    

    Nun haben Sie:

    void doSomething(bool hideLastUndo)
    {
        // Does something very interesting.
        undoHistory.removeLast();
    }
    
    // Later in code, very error prone, while the signature of the method is clear:
    var preventUndo = true;
    doSomething(preventUndo);
    

Was ist mit Aufzählungen?

Einige andere Leute schlugen vor, Aufzählungen zu verwenden. Während es das unmittelbare Problem löst, schafft es ein größeres. Lass uns mal sehen:

enum undoPrevention
{
    keepInHistory,
    prevent,
}

void doSomething(undoPrevention preventUndo)
{
    doTheJob();
    if (preventUndo == undoPrevention.prevent)
    {
        this.undoHistory.discardLastEntry();
    }
}

doSomething(undoPrevention.prevent);

Probleme damit:

  1. Es ist zu viel Code. if (preventUndo == undoPrevention.prevent)? Ernsthaft?! Ich möchte nicht ifjedes Mal solche s schreiben .

  2. Das Hinzufügen eines Elements zur Aufzählung ist später sehr verlockend, wenn ich dieselbe Aufzählung an einer anderen Stelle verwende. Was ist, wenn ich es so ändere:

    enum undoPrevention
    {
        keepInHistory,
        prevent,
        keepButDisable, // Keeps the entry in the history, but makes it disabled.
    }
    

    Was wird jetzt passieren? Funktioniert die doSomethingMethode wie erwartet? Um dies zu verhindern, müsste diese Methode von Anfang an so geschrieben werden:

    void doSomething(undoPrevention preventUndo)
    {
        if (![undoPrevention.keepInHistory, undoPrevention.prevent].contains(preventUndo))
        {
            throw new ArgumentException('preventUndo');
        }
    
        doTheJob();
        if (preventUndo == undoPrevention.prevent)
        {
            this.undoHistory.discardLastEntry();
        }
    }
    

    Die Variante, die Boolesche Werte verwendet, sieht so gut aus!

    void doSomething(bool preventUndo)
    {
        doTheJob();
        if (preventUndo)
        {
            this.undoHistory.discardLastEntry();
        }
    }
    

3
"Es ist zu viel Code. If (preventUndo == undoPrevention.prevent)? Ernsthaft ?! Ich möchte solche ifs nicht jedes Mal schreiben.": Wie wäre es mit einer guten alten switch-Anweisung? Wenn Sie einen Booleschen Wert verwenden, benötigen Sie außerdem eine if-Anweisung. Ehrlich gesagt, scheint mir Ihre Kritik in diesem Punkt etwas künstlich.
Giorgio

1
Ich vermisse hier eine alternative Lösung. Wollen Sie damit sagen, dass er sich an das Wort halten sollte - nichts, was einfach wahr und falsch ist, und sich auf die IDE verlassen sollte (die er nicht verwendet)? Ein Kommentar könnte wie der Variablenname verrotten.
Sichern Sie sich den

2
@superM: Ich finde es nicht viel schwieriger, den Namen einer temporären Variablen zu ändern, die nur für den Aufruf verwendet wird. Wenn ein Enum-Name geändert wird, ist dies sogar noch besser, da der Anrufcode nicht mehr ausgeführt wird und einen Fehler direkt in Ihr Gesicht wirft. Wenn die Funktionalität der Funktion durch eine Enum-Erweiterung radikal geändert wird, müssen Sie jedenfalls alle Aufrufe erneut prüfen, um die Richtigkeit zu überprüfen. Andernfalls programmieren Sie auf Hoffnung und nicht auf Logik. Nicht einmal zu denken , um die Werte zu ändern, zB negiert preventUndozu allowUndoin der Signatur ...
Sichere

4
@superM Das Synchronisieren von Kommentaren mit dem Code ist in der Tat sehr problematisch, da Sie bei der Suche nach Inkonsistenzen keine Hilfe von Ihrem Compiler haben.
Buhb

7
"... verlassen Sie sich auf Ihre IDE, um die Signatur der aufgerufenen Methode zu erhalten" - dies ist nützlich, wenn Sie den Code schreiben, aber nicht so sehr, wenn Sie ihn lesen. Wenn ich Code für eine Klasse lese, möchte ich wissen, was es tut, indem ich es mir ansehe. Wenn Sie sich aus Gründen der Lesbarkeit auf die IDE verlassen müssen, ist Ihr Code nicht selbstdokumentierend.
Phil

20

Sie sollten auch keine Methoden mit Booleschen Flags schreiben.

Um Robert C. Martin zu zitieren, heißt es in seinem Buch Clean Code (ISBN-13 978-0-13-235088-4): "Das Übergeben eines Booleschen in eine Funktion ist eine wirklich schreckliche Praxis."

Um ihn zu paraphrasieren: Die Tatsache, dass Sie einen True / False-Schalter haben, bedeutet, dass Ihre Methode höchstwahrscheinlich zwei verschiedene Dinge ausführt (dh "Tun Sie etwas mit Rückgängig" und "Tun Sie etwas ohne Rückgängig") und sollte es daher sein Aufteilung in zwei verschiedene Methoden (die möglicherweise intern dasselbe aufrufen).

DoSomething()
{...

DoSomethingUndoable()
{....

7
Nach dieser Logik müsste mein einzelner HotDog-Konstruktor ungefähr 16 verschiedene Methoden haben: HotDogWithMustardRelishKrautOnion (), HotDogWithMustardNorelishKrautOnion () und so weiter. Es könnte sich auch um eine einfache HotDogWithOptions-Methode (int-Option) handeln, bei der ich die Optionen als Bitfeld interpretiere, aber dann sind wir wieder bei einer Methode angelangt, die mehrere angeblich unterschiedliche Aktionen ausführt. Muss ich also, wenn ich mich für die erste Option entscheide, eine große Bedingung schreiben, um herauszufinden, welcher Konstruktor basierend auf den Dingen, die sonst Boolesche Parameter gewesen wären, aufgerufen werden soll? Und wenn das Q ein int verwendet, beantwortet dieses A das Q nicht.
Caleb

4
Nachdem ich alle Antworten gelesen und meinen Code angesehen habe, komme ich zu dem Schluss, dass dies der richtige Weg ist, dies zu tun. doSomethingUndoable()könnte die Änderungen erfassen und dann "doSomething()
methodofaction"

1
Wenn ich also eine Funktion habe, die viel verarbeitet und die Option hat, eine E-Mail zu senden, wenn sie fertig ist, sollte ich kein boolesches Argument haben, das das Senden der E-Mail verhindern kann. Ich sollte eine separate Funktion erstellen.
Yakatz

2
@Caleb In diesem Fall würde ich entweder einen Hotdog erstellen und die Zutaten einzeln hinzufügen, oder ich würde in einer stark typisierten Sprache ein Objekt HotDogSettings übergeben, in dem ich alle Booleschen Flags gesetzt hätte. Ich hätte nicht 15 verschiedene True / False-Flags, die ein Albtraum wären. Denken Sie daran, dass es bei Code um Wartung geht, die nicht geschrieben wird, da dies 80% der Kosten der Anwendung ausmacht. var hd = MakeHotDog (); hd.Add (Zwiebel); ...
Nick B.

2
@Caleb Ich denke, der Unterschied besteht darin, dass in Ihrem Beispiel die Booleschen Werte als tatsächliches Datenfeld verwendet werden, während die meisten Verwendungszwecke darin bestehen, den Ausführungsfluss zu steuern. daher Onkel Bobs Geländer dagegen. Auch sein Rat ist etwas extrem - die nächsten paar Zeilen im Buch raten davon ab, mehr als 2 Parameter zu haben, was möglicherweise eine noch extremere Haltung ist. Es gibt Methode für den Wahnsinn. Sie haben absolut Recht, dass es die Frage nicht beantwortet, ein Fehler, den ich in meiner eigenen Antwort gemacht habe.
Daniel B

5

Wenn Sie in zwei Klassen zu sehen , wie gekoppelter sie sind, eine der Kategorien ist Datenkopplung , die in einer Klasse Aufrufe von Methoden einer anderen Klasse Code bezieht und nur in Daten übergibt, wie in welchem Monat Sie für einen Bericht mögen, und eine andere Kategorie ist die Steuerungskopplung , das Aufrufen von Methoden und die Übergabe von Elementen, die das Verhalten der Methode steuern. Das Beispiel, das ich in der Klasse verwende, ist eine verboseFlagge oder eine reportTypeFlagge, aber es preventUndoist auch ein großartiges Beispiel. Wie Sie gerade gezeigt haben, macht es die Steuerungskopplung schwierig, aufrufenden Code zu lesen und zu verstehen, was passiert. Außerdem wird der aufrufende Code für Änderungen anfällig, doSomething()die dasselbe Flag verwenden, um das Rückgängigmachen und Archivieren zu steuern oder einen weiteren Parameter hinzuzufügendoSomething() um die Archivierung zu kontrollieren und so Ihren Code zu beschädigen und so weiter.

Das Problem ist, dass der Code zu eng gekoppelt ist. Das Übergeben eines Bools zur Verhaltenskontrolle ist meiner Meinung nach ein Zeichen für eine schlechte API. Wenn Sie die API besitzen, ändern Sie sie. Zwei Methoden doSomething()und doSomethingWithUndo()wären besser. Wenn Sie es nicht besitzen, schreiben Sie zwei Wrapper-Methoden selbst in den Code, den Sie besitzen, und lassen Sie einen von ihnen doSomething(true)und den anderen aufrufen doSomething(false).


4

Es ist nicht die Rolle des Aufrufers, die Rolle von Argumenten zu definieren. Ich greife immer zur Inline-Version und überprüfe im Zweifelsfall die Signatur der aufgerufenen Funktion.

Variable sollte nach ihrer Rolle im aktuellen Bereich benannt werden. Nicht Der Bereich, in den sie gesendet werden.


1
Die Rolle der Variablen im aktuellen Gültigkeitsbereich besteht darin, anzugeben, wofür sie verwendet wird. Ich verstehe nicht, inwiefern dies der Verwendung temporärer Variablen durch OP widerspricht.
SuperM

4

Was ich in JavaScript tun würde, ist, dass die Funktion ein Objekt als einzigen Parameter annimmt:

function doSomething(settings) {
    var preventUndo = settings.hasOwnProperty('preventUndo') ? settings.preventUndo : false;
    // Deal with preventUndo in the normal way.
}

Dann nenne es mit:

doSomething({preventUndo: true});

HA!!! Wir haben beide eine doSomething(x)Methode gemacht! lol ... Ich habe Ihren Beitrag bis jetzt nicht einmal bemerkt.
Fleisch

Was würde passieren, wenn ich eine Zeichenfolge "noUndo" übergeben würde, und wie wird der Person, die Ihre Signatur verwendet, klar, welche Einstellungen enthalten sollen, ohne in die Implementierung zu schauen?
Nick B.

1
Dokumentieren Sie Ihren Code. Das ist, was alle anderen tun. Es erleichtert das Lesen Ihres Codes und das Schreiben muss nur einmal erfolgen.
Ridecar2

1
@ NickB. Konvention ist mächtig. Wenn der Großteil Ihres Codes Objekte mit Argumenten akzeptiert, ist dies klar.
Orip

4

Ich nutze gern Sprachfunktionen, um mir mehr Klarheit zu verschaffen. In C # können Sie beispielsweise Parameter nach Namen angeben:

 CallSomething(name: "So and so", age: 12, description: "A random person.");

In JavaScript bevorzuge ich normalerweise ein options-Objekt als Argument aus diesem Grund:

function doSomething(args) { /*...*/ }

doSomething({ name: 'So and so', age: 12, description: 'A random person.' });

Das ist aber nur meine Präferenz. Ich nehme an, es hängt stark von der aufgerufenen Methode ab und davon, wie die IDE dem Entwickler hilft, die Signatur zu verstehen.


1
Das ist der "Nachteil" von lose getippten Sprachen. Ohne eine gewisse Validierung können Sie eine Signatur nicht wirklich erzwingen. Ich wusste nicht, dass Sie in JS solche Namen hinzufügen können.
James P.

1
@JamesPoulson: Du wusstest wahrscheinlich, dass du das in JS machen kannst und hast es einfach nicht gemerkt. Es ist alles vorbei in JQuery: $.ajax({url:'', data:''})etc etc.
blesh

Wahr. Auf den ersten Blick scheint es ungewöhnlich, aber es ähnelt den Inline-Pseudoobjekten, die in einigen Sprachen existieren können.
James P.

3

Ich finde, das ist am einfachsten und am einfachsten zu lesen:

enum Undo { ALLOW, PREVENT }

doSomething(Undo u) {
    if (Undo.ALLOW == u) {
        // save stuff to undo later.
    }
    // do your thing
}

doSomething(Undo.ALLOW);

MainMa gefällt das nicht. Möglicherweise müssen wir zustimmen, dass wir nicht zustimmen, oder wir können eine Lösung verwenden, die Joshua Bloch vorschlägt:

enum Undo {
    ALLOW {
        @Override
        public void createUndoBuffer() {
            // put undo code here
        }
    },
    PREVENT {
        @Override
        public void createUndoBuffer() {
            // do nothing
        }
    };

    public abstract void createUndoBuffer();
}

doSomething(Undo u) {
    u.createUndoBuffer();
    // do your thing
}

Wenn Sie jetzt jemals ein Undo.LIMITED_UNDO hinzufügen, wird Ihr Code erst kompiliert, wenn Sie die createUndoBuffer () -Methode implementieren. Und in doSomething () gibt es kein if (Undo.ALLOW == u). Ich habe es in beide Richtungen gemacht, und die zweite Methode ist ziemlich schwerfällig und ein wenig schwer zu verstehen, wenn die Undo-Aufzählung auf Seiten und Seiten Code erweitert wird, aber das lässt Sie nachdenken. Normalerweise halte ich mich an die einfachere Methode, um einen einfachen Booleschen Wert durch eine 2-Wert-Aufzählung zu ersetzen, bis ich Grund zur Änderung habe. Wenn ich einen dritten Wert hinzufüge, verwende ich meine IDE, um "Verwendungen zu finden" und dann alles zu reparieren.


2

Ich denke, Ihre Lösung macht Ihren Code ein bisschen lesbarer, aber ich würde es vermeiden, eine zusätzliche Variable zu definieren, nur um Ihren Funktionsaufruf klarer zu machen. Wenn Sie eine zusätzliche Variable verwenden möchten, würde ich diese als konstant markieren, wenn Ihre Programmiersprache dies unterstützt.

Ich kann mir zwei Alternativen vorstellen, die keine zusätzliche Variable beinhalten:

1. Verwenden Sie einen zusätzlichen Kommentar

doSomething(/* preventUndo */ true);

2. Verwenden Sie eine zweiwertige Aufzählung anstelle eines Booleschen Werts

enum Options
{
    PreventUndo,
    ...
}

...

doSomething(PreventUndo);

Sie können Alternative 2 verwenden, wenn Ihre Sprache Aufzählungen unterstützt.

BEARBEITEN

Die Verwendung benannter Argumente ist natürlich auch eine Option, wenn Ihre Sprache diese unterstützt.

In Bezug auf die zusätzliche Codierung, die von Enums benötigt wird, scheint es mir wirklich vernachlässigbar. Anstatt von

if (preventUndo)
{
    ...
}

du hast

if (undoOption == PreventUndo)
{
    ...
}

oder

switch (undoOption)
{
  case PreventUndo:
  ...
}

Und selbst wenn Sie etwas mehr tippen, denken Sie daran, dass Code einmal geschrieben und mehrmals gelesen wird. Es kann sich also lohnen, jetzt etwas mehr zu tippen, um sechs Monate später einen besser lesbaren Code zu finden.


2

Ich stimme dem zu, was @GlenPeterson gesagt hat:

doSomething(Undo.ALLOW); // call using a self evident enum

aber auch interessant wäre folgendes, da es für mich nur zwei möglichkeiten gibt (wahr oder falsch)

//Two methods    

doSomething()  // this method does something but doesn't prevent undo

doSomethingPreventUndo() // this method does something and prevents undo

2
Gute Idee - daran hatte ich nicht gedacht. Wenn Sie komplizierter werden wie doSomething (String, String, String, Boolean, Boolean, Boolean), sind benannte Konstanten die einzig sinnvolle Lösung. Nun, Josh Bloch schlägt ein Factory- und Konstruktorobjekt vor, wie in: MyThingMaker thingMaker = MyThingMaker.new (); thingMaker.setFirstString ("Hi"); thingMaker.setSecondString ("There"); etc ... Thing t = thingMaker.makeIt (); t.doSomething (); Das ist einfach viel mehr Arbeit, also sollte es sich lohnen.
GlenPeterson

Ein Problem bei letzterem Ansatz besteht darin, dass es umständlich ist, eine Methode zu schreiben, die verwendet doSomething, dem Aufrufer jedoch die Wahl lässt, ob das Rückgängigmachen zugelassen werden soll. Abhilfe kann eine Überladung schaffen, die die Option Boolean verwendet, aber auch Wrapper-Versionen, die die frühere Version mit dem Parameter always true oder always false aufrufen.
Superkatze

@GlenPeterson Eine Factory ist eine Möglichkeit und in Javascript möglich (von OP erwähnt).
James P.

2

Da Sie hauptsächlich nach Javascript fragen, können Sie mit coffeescript ein benanntes Argument wie die Syntax haben, super einfach:

# declare method, ={} declares a default value to prevent null reference errors
doSomething = ({preventUndo} = {}) ->
  if preventUndo
    undo = no
  else
    undo = yes

#call method
doSomething preventUndo: yes
#or 
doSomething(preventUndo: yes)

kompiliert nach

var doSomething;

doSomething = function(_arg) {
  var preventUndo, undo;
  preventUndo = (_arg != null ? _arg : {}).preventUndo;
  if (preventUndo) {
    return undo = false;
  } else {
    return undo = true;
  }
};

doSomething({
  preventUndo: true
});

doSomething({
  preventUndo: true
});

Viel Spaß bei http://js2coffee.org/ , um die Möglichkeiten auszuprobieren


Ich benutze coffeeScript nur, wenn ich den OOP-Wahnsinn von Javascript vermeiden muss. Dies ist eine nette Lösung, die ich möglicherweise nur für die Codierung verwende. Es wäre schwierig, Kollegen zu rechtfertigen, dass ich der Lesbarkeit halber ein Objekt anstelle eines Booleschen übergebe!
Methodofaction

1

Nur für eine weniger konventionelle Lösung und da das OP Javascript erwähnte, besteht die Möglichkeit, ein assoziatives Array zum Abbilden von Werten zu verwenden. Der Vorteil gegenüber einem einzelnen Booleschen Wert ist, dass Sie so viele Argumente haben können, wie Sie möchten, und sich nicht um deren Reihenfolge sorgen müssen.

var doSomethingOptions = new Array();

doSomethingOptions["PREVENTUNDO"] = true;
doSomethingOptions["TESTMODE"] = false;

doSomething( doSomethingOptions );

// ...

function doSomething( doSomethingOptions ){
    // Check for null here
    if( doSomethingOptions["PREVENTUNDO"] ) // ...
}

Mir ist klar, dass dies ein Reflex von jemandem mit stark typisiertem Hintergrund ist und möglicherweise nicht so praktisch ist, aber ich halte dies für originell.

Eine andere Möglichkeit ist, ein Objekt zu haben und ist auch in Javascript möglich. Ich bin nicht zu 100% sicher, dass dies syntaktisch korrekt ist. Schauen Sie sich Muster wie Factory an, um weitere Inspirationen zu erhalten.

function SomethingDoer( preventUndo ) {
    this.preventUndo = preventUndo;
    this.doSomething = function() {
            if( this.preventUndo ){
                    // ...
            }
    };
}

mySomethingDoer = new SomethingDoer(true).doSomething();

1
Ich denke, dies kann besser in der Punktnotation ( doSomethingOptions.PREVENTUNDO) anstelle der Array-Zugriffsnotation geschrieben werden.
Paŭlo Ebermann

1

Der Fokus Ihrer Frage und der anderen Antworten liegt auf der Verbesserung der Lesbarkeit, wenn die Funktion aufgerufen wird. Diesem Fokus stimme ich zu. Alle spezifischen Richtlinien, Ereignis "keine booleschen Argumente", sollten immer als Mittel zu diesem Zweck fungieren und nicht zu einem Selbstzweck werden.

Ich denke, es ist nützlich zu bemerken, dass dieses Problem meistens vermieden wird, wenn die Programmiersprache benannte Argumente / Schlüsselwortargumente unterstützt, wie in C # 4 und Python, oder wenn die Methodenargumente im Methodennamen verschachtelt sind, wie in Smalltalk oder Objective-C.

Beispiele:

// C# 4
foo.doSomething(preventUndo: true);
# Python
foo.doSomething(preventUndo=True)
// Objective-C
[foo doSomethingWith:bar shouldPreventUndo:YES];

0

Man sollte vermeiden, boolesche Variablen als Argumente an Funktionen zu übergeben. Weil Funktionen eine Sache nach der anderen tun sollten. Durch die Übergabe einer booleschen Variablen hat die Funktion zwei Verhaltensweisen. Dies führt auch zu Lesbarkeitsproblemen für einen späteren Zeitpunkt oder für andere Programmierer, die dazu neigen, Ihren Code zu sehen. Dies führt auch zu Problemen beim Testen der Funktion. Wahrscheinlich müssen Sie in diesem Fall zwei Testfälle erstellen. Stellen Sie sich vor, Sie haben eine Funktion mit switch-Anweisung und ändern ihr Verhalten basierend auf dem Schaltertyp. Dann müssen Sie so viele verschiedene Testfälle definieren.

Immer wenn man auf ein boolesches Argument für die Funktion stößt, muss der Code so angepasst werden, dass überhaupt kein boolesches Argument geschrieben wird.


Sie würden also eine Funktion mit einem booleschen Argument in zwei Funktionen konvertieren, die sich nur in einem winzigen Teil ihres Codes unterscheiden (wo das Original einen hätte if(param) {...} else {...})?
Paŭlo Ebermann

-1
int preventUndo = true;
doSomething(preventUndo);

Ein guter Compiler für Low-Lavel-Sprachen wie C ++ optimiert den Maschinencode von mehr als 2 Zeilen wie folgt:

doSomething(true); 

Es spielt also keine Rolle für die Leistung, erhöht aber die Lesbarkeit.

Es ist auch hilfreich, eine Variable zu verwenden, falls sie später variabel wird und möglicherweise auch andere Werte akzeptiert.

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.