Wie in den Kommentaren von @ benjamin-gruenbaum angemerkt, wird dies die Boolesche Falle genannt:
Angenommen, ich habe eine Funktion wie diese
UpdateRow(var item, bool externalCall);
und in meinem Controller wird dieser Wert für externalCall
immer WAHR sein. Wie kann man diese Funktion am besten aufrufen? Normalerweise schreibe ich
UpdateRow(item, true);
Aber ich frage mich, ob ich einen Booleschen deklarieren soll, nur um anzugeben, wofür dieser "wahre" Wert steht. Sie können das an der Deklaration der Funktion erkennen, aber es ist offensichtlich schneller und klarer, wenn Sie so etwas gesehen haben
bool externalCall = true;
UpdateRow(item, externalCall);
PD: Ich bin mir nicht sicher, ob diese Frage wirklich hier passt. Wenn nicht, wo kann ich weitere Informationen dazu erhalten?
PD2: Ich habe keine Sprache markiert, weil ich dachte, es sei ein sehr allgemeines Problem. Wie auch immer, ich arbeite mit c # und die akzeptierte Antwort funktioniert für c #
data CallType = ExternalCall | InternalCall
in haskell zum beispiel.Antworten:
Es gibt nicht immer eine perfekte Lösung, aber Sie haben viele Alternativen zur Auswahl:
Verwenden Sie benannte Argumente , sofern diese in Ihrer Sprache verfügbar sind. Dies funktioniert sehr gut und hat keine besonderen Nachteile. In einigen Sprachen kann jedes Argument als benanntes Argument übergeben werden, z. B.
updateRow(item, externalCall: true)
( C # ) oderupdate_row(item, external_call=True)
(Python).Ihr Vorschlag, eine separate Variable zu verwenden, ist eine Möglichkeit, benannte Argumente zu simulieren, hat jedoch nicht die damit verbundenen Sicherheitsvorteile (es gibt keine Garantie dafür, dass Sie den richtigen Variablennamen für dieses Argument verwendet haben).
Verwenden Sie unterschiedliche Funktionen für Ihre öffentliche Schnittstelle mit besseren Namen. Dies ist eine weitere Möglichkeit, benannte Parameter zu simulieren, indem die Parameterwerte in den Namen eingefügt werden.
Dies ist sehr gut lesbar, führt aber zu einer Menge Boilerplate für Sie, die diese Funktionen schreiben. Es kann auch nicht gut mit kombinatorischer Explosion umgehen, wenn es mehrere boolesche Argumente gibt. Ein wesentlicher Nachteil ist, dass Clients diesen Wert nicht dynamisch festlegen können, sondern if / else verwenden müssen, um die richtige Funktion aufzurufen.
Verwenden Sie eine Aufzählung . Das Problem mit Booleschen Werten ist, dass sie als "wahr" und "falsch" bezeichnet werden. Führen Sie stattdessen einen Typ mit besseren Namen ein (z
enum CallType { INTERNAL, EXTERNAL }
. B. ). Als zusätzlicher Vorteil, erhöht dies die Art Sicherheit Ihres Programms (wenn Ihre Sprache Aufzählungen als verschiedene Typen implementiert). Der Nachteil von Enums ist, dass sie Ihrer öffentlich sichtbaren API einen Typ hinzufügen. Für rein interne Funktionen spielt dies keine Rolle und Aufzählungen haben keine wesentlichen Nachteile.In Sprachen ohne Aufzählungen werden stattdessen manchmal kurze Zeichenfolgen verwendet. Dies funktioniert und ist möglicherweise sogar besser als rohe Boolesche Werte, ist jedoch sehr anfällig für Tippfehler. Die Funktion sollte dann sofort bestätigen, dass das Argument mit einer Reihe möglicher Werte übereinstimmt.
Keine dieser Lösungen wirkt sich negativ auf die Leistung aus. Benannte Parameter und Aufzählungen können zur Kompilierungszeit (für eine kompilierte Sprache) vollständig aufgelöst werden. Die Verwendung von Zeichenfolgen kann einen Zeichenfolgenvergleich erfordern, die Kosten hierfür sind jedoch für kleine Zeichenfolgenliterale und die meisten Arten von Anwendungen vernachlässigbar.
quelle
Die richtige Lösung ist, das zu tun, was Sie vorschlagen, aber es in eine Mini-Fassade zu packen:
Lesbarkeit trumpft mit Mikrooptimierung. Sie können sich den zusätzlichen Funktionsaufruf leisten, sicherlich besser als Sie es sich leisten können, die Semantik des Booleschen Flags einmal nachschlagen zu müssen.
quelle
true
tut.) Es wäre auch dann lohnenswert kosten so viel CPU-Zeit wie es Entwicklerzeit spart, da CPU-Zeit viel billiger ist.UpdateRow(item, true /*external call*/);
wäre sauberer in Sprachen, die diese Kommentarsyntax zulassen. Das Aufblähen Ihres Codes mit einer zusätzlichen Funktion, nur um das Schreiben eines Kommentars zu vermeiden, scheint es für einen einfachen Fall nicht wert. Vielleicht würde diese Funktion mehr Anklang finden, wenn es viele andere Argumente für diese Funktion gäbe und / oder etwas überfüllten und kniffligen Umgebungscode. Aber ich denke, wenn Sie debuggen, werden Sie am Ende die Wrapper-Funktion überprüfen, um zu sehen, was sie tut, und sich merken müssen, welche Funktionen Thin Wrapper um eine Bibliotheks-API sind und welche tatsächlich eine Logik haben.Warum hast du so eine Funktion?
Unter welchen Umständen würden Sie das Argument externalCall auf unterschiedliche Werte setzen?
Wenn eine von beispielsweise einer externen Client-Anwendung stammt und die andere sich im selben Programm befindet (dh in verschiedenen Codemodulen), würde ich argumentieren, dass Sie zwei verschiedene Methoden haben sollten, eine für jeden Fall, möglicherweise sogar für verschiedene Schnittstellen.
Wenn Sie den Aufruf jedoch auf der Grundlage eines Datums aus einer Nicht-Programmquelle (z. B. einer Konfigurationsdatei oder einem Datenbanklesevorgang) ausführen würden, wäre die Methode der Booleschen Übergabe sinnvoller.
quelle
Ich bin damit einverstanden, dass es ideal ist , eine Sprachfunktion zu verwenden, um sowohl Lesbarkeit als auch Wertsicherheit zu gewährleisten. Sie können sich jedoch auch für einen praktischen Ansatz entscheiden: Kommentare zur Anrufzeit. Mögen:
oder:
oder (zu Recht, wie von Frax vorgeschlagen):
quelle
UpdateRow(item, /* externalCall */ true )
. Ein vollständiger Satzkommentar ist viel schwerer zu analysieren, es ist eigentlich meistens Rauschen (insbesondere die zweite Variante, die auch sehr locker mit dem Argument verbunden ist).Sie können Ihre Narren "benennen". Nachfolgend finden Sie ein Beispiel für eine OO-Sprache (wo sie in der Klassenbereitstellung ausgedrückt werden kann
UpdateRow()
). Das Konzept selbst kann jedoch in einer beliebigen Sprache angewendet werden:und auf der Anrufseite:
Ich glaube, Punkt 1 ist besser, erzwingt aber nicht, dass der Benutzer neue Variablen verwendet, wenn er die Funktion verwendet. Wenn die Typensicherheit für Sie wichtig ist, können Sie einen
enum
Typ erstellen undUpdateRow()
diesen anstelle einesbool
:UpdateRow(var item, ExternalCallAvailability ec);
Sie können den Namen der Funktion so ändern, dass er die Bedeutung des
bool
Parameters besser widerspiegelt . Nicht sehr sicher, aber vielleicht:UpdateRowWithExternalCall(var item, bool externalCall)
quelle
externalCall=false
der Name der Funktion, mit der Sie sie aufrufen , absolut keinen Sinn ergibt . Der Rest Ihrer Antwort ist gut.UpdateRowWithUsuallyExternalButPossiblyInternalCall
.Eine andere Option, die ich hier noch nicht gelesen habe, ist: Verwenden Sie eine moderne IDE.
Beispielsweise gibt IntelliJ IDEA den Variablennamen der Variablen in der aufgerufenen Methode aus, wenn Sie ein Literal wie
true
odernull
oder übergebenusername + “@company.com
. Dies geschieht in einer kleinen Schrift, damit sie nicht zu viel Platz auf Ihrem Bildschirm einnimmt und sich stark vom tatsächlichen Code unterscheidet.Ich sage immer noch nicht, dass es eine gute Idee ist, überall Booleaner einzusetzen. Das Argument, dass Sie Code weitaus häufiger lesen als schreiben, ist oft sehr stark. In diesem speziellen Fall hängt es jedoch stark von der Technologie ab, mit der Sie (und Ihre Kollegen!) Ihre Arbeit unterstützen. Mit einer IDE ist das weitaus weniger ein Problem als zum Beispiel mit vim.
Geändertes Beispiel eines Teils meines Testaufbaus:
quelle
2 Tage und niemand erwähnte Polymorphismus?
Wenn ich ein Kunde bin, der eine Zeile mit einem Element aktualisieren möchte, möchte ich nicht über den Namen der Datenbank, die URL des Mikrodienstes, das zur Kommunikation verwendete Protokoll oder darüber nachdenken, ob es sich um einen externen Anruf handelt oder nicht müssen verwendet werden, um es zu verwirklichen. Hör auf, mir diese Details aufzuzwingen. Nimm einfach meinen Gegenstand und aktualisiere irgendwo schon eine Zeile.
Wenn Sie das tun, wird es Teil des Konstruktionsproblems. Das kann auf viele Arten gelöst werden. Hier ist eins:
Wenn Sie sich das ansehen und denken "Aber meine Sprache hat Argumente benannt, ich brauche das nicht". Gut, großartig, benutze sie. Halten Sie diesen Unsinn einfach von dem anrufenden Client fern, der nicht einmal wissen sollte, womit er spricht.
Es ist anzumerken, dass dies mehr als ein semantisches Problem ist. Es ist auch ein Designproblem. Übergeben Sie einen Booleschen Wert, und Sie sind gezwungen, mit der Verzweigung umzugehen. Nicht sehr objektorientiert. Müssen zwei oder mehr Booleaner übergeben werden? Möchten Sie, dass Ihre Sprache mehrfach versendet wird? Prüfen Sie, was Sammlungen tun können, wenn Sie sie verschachteln. Die richtige Datenstruktur kann Ihr Leben so viel einfacher machen.
quelle
Wenn Sie die
updateRow
Funktion ändern können, können Sie sie möglicherweise in zwei Funktionen umgestalten. Angesichts der Tatsache, dass die Funktion einen booleschen Parameter hat, vermute ich, dass dies so aussieht:Das kann ein bisschen nach Code riechen. Die Funktion kann je nach
externalCall
Einstellung der Variablen ein dramatisch unterschiedliches Verhalten aufweisen. In diesem Fall hat sie zwei unterschiedliche Verantwortlichkeiten. Das Umgestalten in zwei Funktionen, die nur eine Verantwortung haben, kann die Lesbarkeit verbessern:Überall dort, wo Sie diese Funktionen aufrufen, können Sie jetzt auf einen Blick feststellen, ob der externe Dienst aufgerufen wird.
Dies ist natürlich nicht immer der Fall. Es ist situativ und Geschmackssache. Das Refactoring einer Funktion, die einen booleschen Parameter in zwei Funktionen umwandelt, ist normalerweise erwägenswert, aber nicht immer die beste Wahl.
quelle
Wenn sich UpdateRow in einer kontrollierten Codebasis befindet, würde ich ein Strategiemuster in Betracht ziehen:
Wobei Request eine Art DAO darstellt (ein Rückruf in trivialem Fall).
Wahrer Fall:
Falscher Fall:
Normalerweise wird die Endpunktimplementierung auf einer höheren Abstraktionsebene konfiguriert, und ich würde sie hier nur verwenden. Als nützlichen kostenlosen Nebeneffekt habe ich die Möglichkeit, einen anderen Weg für den Zugriff auf entfernte Daten zu implementieren, ohne den Code zu ändern:
Im Allgemeinen sorgt eine solche Umkehrung der Steuerung für eine geringere Kopplung zwischen Datenspeicher und Modell.
quelle