Ich dachte nur, wie viel einfacher es wäre, Code zu lesen, wenn Sie beim Aufrufen einer Funktion schreiben könnten:
doFunction(param1=something, param2=somethingElse);
Ich kann mir keine Nachteile vorstellen und es würde den Code viel besser lesbar machen. Ich weiß, Sie könnten ein Array als einziges Argument übergeben und die Array-Schlüssel als Parameternamen verwenden, aber das wäre immer noch nicht so lesbar.
Gibt es einen Nachteil, den ich vermisse? Wenn nein, warum lassen dies viele Sprachen nicht zu?
Antworten:
Durch die Angabe von Parameternamen wird der Code nicht immer besser lesbar. Möchten Sie beispielsweise lieber lesen
min(first=0, second=1)
odermin(0, 1)
?Wenn Sie das Argument des vorherigen Absatzes akzeptieren, ist es ziemlich offensichtlich, dass die Angabe von Parameternamen nicht obligatorisch sein sollte. Warum machen nicht alle Sprachen die Angabe von Parameternamen zu einer gültigen Option?
Mir fallen mindestens zwei Gründe ein:
Beachten Sie, dass ich keine Sprache kenne, die benannte Parameter implementiert, ohne auch optionale Parameter (für die benannte Parameter erforderlich sind) zu implementieren. Daher sollten Sie die Vorteile der Lesbarkeit, die durch die Definition von Fluent Interfaces konsistenter erzielt werden können, überbewerten .
quelle
Benannte Parameter erleichtern das Lesen und erschweren das Schreiben von Code
Wenn ich einen Code lese, können benannte Parameter einen Kontext einführen, der das Verständnis des Codes erleichtert. Betrachten wir zum Beispiel diesen Konstruktor:
Color(1, 102, 205, 170)
. Was um alles in der Welt bedeutet das? In der TatColor(alpha: 1, red: 102, green: 205, blue: 170)
wäre es viel einfacher zu lesen. Aber leider sagt der Compiler "nein" - es willColor(a: 1, r: 102, g: 205, b: 170)
. Wenn Sie Code mit benannten Parametern schreiben, verbringen Sie unnötig viel Zeit damit, nach den genauen Namen zu suchen. Es ist einfacher, die genauen Namen einiger Parameter zu vergessen, als ihre Reihenfolge zu vergessen.Das hat mich einmal gebissen, als ich eine
DateTime
API verwendete, die zwei Geschwisterklassen für Punkte und Dauer mit fast identischen Schnittstellen hatte. WährendDateTime->new(...)
akzeptiert einsecond => 30
Argument, dasDateTime::Duration->new(...)
wollteseconds => 30
, und ähnliches für andere Einheiten. Ja, es ist absolut sinnvoll, aber das hat mir gezeigt, dass die genannten Parameter einfach zu bedienen sind.Schlechte Namen machen es nicht einmal einfacher zu lesen
Ein anderes Beispiel, wie benannte Parameter schlecht sein können, ist wahrscheinlich die R- Sprache. Dieser Code erstellt einen Datenplot:
Sie sehen zwei Positionsargumente für die x- und y -Datenzeilen und dann eine Liste benannter Parameter. Es gibt viel mehr Optionen mit Standardeinstellungen, und es werden nur die aufgeführt, deren Standardeinstellungen ich ändern oder explizit angeben wollte. Wenn wir einmal ignorieren, dass dieser Code magische Zahlen verwendet und von der Verwendung von Aufzählungen (falls R welche hat!) Profitieren könnte, ist das Problem, dass viele dieser Parameternamen ziemlich unkenntlich sind.
pch
ist das Plotzeichen, das für jeden Datenpunkt gezeichnet wird.17
ist ein leerer Kreis oder so ähnlich.lty
ist der Leitungstyp. Hier1
ist eine durchgezogene Linie.bty
ist der Boxtyp. Durch das Festlegen von wird"n"
vermieden, dass ein Rahmen um das Diagramm gezogen wird.ann
Steuert das Erscheinungsbild von Achsenanmerkungen.Für jemanden, der nicht weiß, was jede Abkürzung bedeutet, sind diese Optionen eher verwirrend. Dies zeigt auch, warum R diese Bezeichnungen verwendet: Nicht als selbstdokumentierender Code, sondern (als dynamisch typisierte Sprache) als Schlüssel, um die Werte ihren korrekten Variablen zuzuordnen.
Eigenschaften von Parametern und Signaturen
Funktionssignaturen können die folgenden Eigenschaften haben:
Verschiedene Sprachen landen auf verschiedenen Koordinaten dieses Systems. In C sind Argumente geordnet, unbenannt, immer erforderlich und können varargs sein. In Java ist die Situation ähnlich, außer dass Signaturen überladen werden können. In Objective C werden Signaturen sortiert, benannt und benötigt und können nicht überladen werden, da es sich nur um syntaktischen Zucker um C handelt.
Dynamisch typisierte Sprachen mit varargs (Befehlszeilenschnittstellen, Perl,…) können optionale benannte Parameter emulieren. Sprachen mit Überladung der Signaturgröße haben so etwas wie optionale Positionsparameter.
So implementieren Sie keine benannten Parameter
Wenn wir an benannte Parameter denken, nehmen wir normalerweise benannte, optionale, ungeordnete Parameter an. Die Implementierung ist schwierig.
Optionale Parameter können Standardwerte haben. Diese müssen von der aufgerufenen Funktion angegeben werden und sollten nicht in den aufrufenden Code übersetzt werden. Andernfalls können die Standardeinstellungen nicht aktualisiert werden, ohne den gesamten abhängigen Code neu zu kompilieren.
Eine wichtige Frage ist nun, wie die Argumente tatsächlich an die Funktion übergeben werden. Mit geordneten Parametern können die Argumente in einem Register oder in ihrer inhärenten Reihenfolge auf dem Stapel übergeben werden. Wenn wir die Register für einen Moment ausschließen, besteht das Problem darin, wie ungeordnete optionale Argumente auf den Stapel gelegt werden.
Dafür benötigen wir eine gewisse Reihenfolge der optionalen Argumente. Was passiert, wenn der Deklarationscode geändert wird? Da die Reihenfolge irrelevant ist, sollte eine Neuordnung in der Funktionsdeklaration die Position der Werte auf dem Stapel nicht verändern. Wir sollten auch prüfen, ob das Hinzufügen eines neuen optionalen Parameters möglich ist. Aus Benutzersicht scheint dies so zu sein, da Code, der diesen Parameter zuvor nicht verwendet hat, weiterhin mit dem neuen Parameter funktionieren sollte. Ausgenommen hiervon sind also Bestellungen, bei denen beispielsweise die Reihenfolge in der Erklärung oder die alphabetische Reihenfolge verwendet wird.
Berücksichtigen Sie dies auch im Hinblick auf die Untertypisierung und das Liskov-Substitutionsprinzip. In der kompilierten Ausgabe sollten dieselben Anweisungen in der Lage sein, die Methode für einen Untertyp mit möglicherweise neuen benannten Parametern und für einen Supertyp aufzurufen.
Mögliche Implementierungen
Wenn wir keine definitive Reihenfolge haben können, benötigen wir eine ungeordnete Datenstruktur.
Die einfachste Implementierung besteht darin, einfach den Namen der Parameter zusammen mit den Werten zu übergeben. Auf diese Weise werden benannte Parameter in Perl oder mit Befehlszeilentools emuliert. Dies löst alle oben genannten Erweiterungsprobleme, kann jedoch eine enorme Platzverschwendung darstellen - keine Option für leistungskritischen Code. Außerdem ist die Verarbeitung dieser benannten Parameter jetzt viel komplizierter als das einfache Entfernen von Werten von einem Stapel.
Tatsächlich kann der Platzbedarf durch die Verwendung von String-Pooling verringert werden, wodurch spätere String-Vergleiche auf Zeigervergleiche reduziert werden können (es sei denn, es kann nicht garantiert werden, dass statische Strings tatsächlich gepoolt werden. In diesem Fall müssen die beiden Strings verglichen werden Detail).
Stattdessen könnten wir auch eine clevere Datenstruktur übergeben, die als Wörterbuch benannter Argumente fungiert. Dies ist auf der Anruferseite günstig, da der Tastensatz am Anrufort statisch bekannt ist. Dies würde es ermöglichen, eine perfekte Hash-Funktion zu erstellen oder einen Versuch vorab zu berechnen. Der Angerufene muss noch prüfen, ob alle möglichen Parameternamen vorhanden sind, was etwas teuer ist. So etwas wird von Python verwendet.
Es ist also in den meisten Fällen einfach zu teuer
Wenn eine Funktion mit benannten Parametern ordnungsgemäß erweiterbar sein soll, kann keine endgültige Reihenfolge angenommen werden. Es gibt also nur zwei Lösungen:
Andere Fallstricke
Die Variablennamen in einer Funktionsdeklaration haben normalerweise eine interne Bedeutung und sind nicht Teil der Schnittstelle - auch wenn sie in vielen Dokumentationswerkzeugen weiterhin angezeigt werden. In vielen Fällen möchten Sie unterschiedliche Namen für eine interne Variable und das entsprechende benannte Argument. Sprachen, bei denen die von außen sichtbaren Namen eines benannten Parameters nicht ausgewählt werden können, erhalten nicht viel davon, wenn der Variablenname nicht im Hinblick auf den aufrufenden Kontext verwendet wird.
Ein Problem bei Emulationen benannter Argumente ist die fehlende statische Überprüfung auf der Anruferseite. Dies ist besonders leicht zu vergessen, wenn Sie ein Wörterbuch mit Argumenten übergeben (Sie betrachten, Python). Dies ist wichtig, da das Übergeben eines Wörterbuchs eine häufige Problemumgehung darstellt, z
foo({bar: "baz", qux: 42})
. B. in JavaScript:. Hier können weder die Typen der Werte noch die Existenz oder Abwesenheit bestimmter Namen statisch überprüft werden.Emulieren benannter Parameter (in statisch typisierten Sprachen)
Die einfache Verwendung von Zeichenfolgen als Schlüssel und eines Objekts als Wert ist bei Vorhandensein einer statischen Typprüfung nicht sehr nützlich. Benannte Argumente können jedoch mit Strukturen oder Objektliteralen emuliert werden:
quelle
it is easier to forget the exact names of some parameters than it is to forget their order
" ... das ist eine interessante Behauptung.Aus dem gleichen Grund, dass die ungarische Notation nicht mehr weit verbreitet ist; Intellisense (oder sein moralisches Äquivalent in IDEs, die nicht von Microsoft stammen). Die meisten modernen IDEs zeigen Ihnen alles, was Sie über einen Parameter wissen müssen, indem Sie einfach mit der Maus über die Parameterreferenz fahren.
Das heißt, eine Reihe von Sprachen unterstützen benannte Parameter, einschließlich C # und Delphi. In C # ist es optional, sodass Sie es nicht verwenden müssen, wenn Sie es nicht möchten, und es gibt andere Möglichkeiten, um Mitglieder spezifisch zu deklarieren, z. B. die Objektinitialisierung.
Benannte Parameter sind vor allem dann nützlich, wenn Sie nur eine Teilmenge optionaler Parameter und nicht alle angeben möchten. In C # ist dies sehr nützlich, da Sie keine überladenen Methoden mehr benötigen, um dem Programmierer diese Flexibilität zu bieten.
quelle
sin(radians=2)
, brauchen Sie nicht "Intellisense".Bash unterstützt diese Art der Programmierung auf jeden Fall, und sowohl Perl als auch Ruby unterstützen die Übergabe von Namen- / Wert-Parameterlisten (wie jede Sprache mit nativer Hash- / Map-Unterstützung). Nichts hindert Sie daran, eine Struktur / einen Datensatz oder einen Hash / eine Map zu definieren, die Namen an Parameter anfügt.
Für Sprachen, die Hash- / Karten- / Schlüsselwertspeicher nativ enthalten, können Sie die gewünschte Funktionalität erhalten, indem Sie einfach die Redewendung übernehmen, um Schlüssel- / Wert-Hashes an Ihre Funktionen / Methoden zu übergeben. Sobald Sie es in einem Projekt ausprobiert haben, haben Sie einen besseren Einblick, ob Sie etwas gewonnen haben, entweder durch eine höhere Produktivität aufgrund der einfachen Bedienung oder durch eine verbesserte Qualität aufgrund reduzierter Fehler.
Beachten Sie, dass erfahrene Programmierer sich mit der Übergabe von Parametern nach Reihenfolge / Slot vertraut gemacht haben. Sie können auch feststellen, dass diese Redewendung nur dann von Nutzen ist, wenn Sie mehr als ein paar Argumente haben (sagen wir> 5/6). Und da eine große Anzahl von Argumenten häufig auf (zu) komplexe Methoden hinweist, können Sie feststellen, dass diese Redewendung nur für die komplexesten Methoden von Vorteil ist.
quelle
Ich denke, es ist der gleiche Grund, warum c # beliebter ist als VB.net. VB.NET ist zwar "lesbarer", z. B. geben Sie "end if" anstelle einer schließenden Klammer ein, es sind jedoch mehr Informationen, die den Code auffüllen und das Verständnis eventuell erschweren.
Es stellt sich heraus, dass das, was den Code verständlicher macht, die Prägnanz ist. Je weniger desto besser. Funktionsparameternamen sind normalerweise ohnehin ziemlich offensichtlich und helfen Ihnen nicht wirklich dabei, den Code zu verstehen.
quelle
Benannte Parameter sind eine Lösung für ein Problem beim Refactoring von Quellcode und sollen den Quellcode nicht besser lesbar machen . Mit benannten Parametern kann der Compiler / Parser Standardwerte für Funktionsaufrufe auflösen. Wenn Sie den Code lesbarer machen möchten, fügen Sie aussagekräftige Kommentare hinzu.
Sprachen, deren Refaktorierung schwierig ist, unterstützen häufig benannte Parameter, da
foo(1)
sie bei der Signatur vonfoo()
Änderungen beschädigt werden. Esfoo(name:1)
ist jedoch unwahrscheinlicher, dass sie beschädigt werden, wenn der Programmierer weniger Anstrengungen unternimmt, um Änderungen vorzunehmenfoo
.Was tun Sie, wenn Sie der folgenden Funktion einen neuen Parameter hinzufügen müssen und diese Funktion in Hunderten oder Tausenden von Codezeilen aufgerufen wird?
Die meisten Programmierer werden das Folgende tun.
Jetzt ist kein Refactoring erforderlich und die Funktion kann im Legacy-Modus ausgeführt werden, wenn
gender
null ist.Zu konkret
gender
wird jetzt ein Wert in den Aufruf für HARDCODEDage
. Wie dieses Beispiel:Der Programmierer sah sich die Funktionsdefinition an, sah, dass
age
sie einen Standardwert von hat,0
und verwendete diesen Wert.Jetzt ist der Standardwert für
age
in der Funktionsdefinition völlig unbrauchbar.Mit benannten Parametern können Sie dieses Problem vermeiden.
Neue Parameter können hinzugefügt werden,
foo
und Sie müssen sich keine Gedanken über die Reihenfolge machen, in der sie hinzugefügt wurden. Sie können die Standardwerte ändern, indem Sie wissen, dass der von Ihnen festgelegte Standardwert wirklich der wahre Standardwert ist.quelle