Warum unterstützen viele Sprachen keine benannten Parameter? [geschlossen]

14

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?


quelle
1
Können Sie eine Sprache angeben, die benannte Parameter haben soll (und kann), aber keine hat?
Bryan Chen
1
Ich denke, es ist in vielen Fällen zu ausführlich. Soweit ich gesehen habe, hängt es davon ab, ob eine Sprache dies verwendet oder nicht, ob sich dies positiv oder negativ auf die Sprache auswirkt. Die meisten Sprachen im C-Stil kommen gut ohne aus. In ihrem Fall ist es oft ziemlich offensichtlich, was sowieso vor sich geht, und seine Abwesenheit trägt wirklich dazu bei, die Unordnung im Allgemeinen zu reduzieren.
Panzercrisis
2
@SteveFallows: Das "Named Parameter Idiom" scheint ein seltsamer Name für die Definition einer fließenden API (wie von Martin Fowler benannt) in einer Builder-Klasse zu sein. Beispiele für dasselbe Muster finden Sie in einem der ersten Artikel von Joshua Blochs Effective Java .
JHOMINAL
3
Dies ist nicht unbedingt eine meinungsbasierte Frage
Catskul
2
Einverstanden. "Warum unterstützen viele Sprachen keine benannten Parameter?" ist eher eine Frage der Sprachgeschichte als der Meinung. Es wäre eine Meinung, wenn jemand fragen würde: "Sind die benannten Parameter nicht besser?".
Andy Fleming

Antworten:

14

Durch die Angabe von Parameternamen wird der Code nicht immer besser lesbar. Möchten Sie beispielsweise lieber lesen min(first=0, second=1)oder min(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:

  • Im Allgemeinen ist die Einführung einer zweiten Syntax für einen bereits abgedeckten Bedarf (hier die Übergabe von Parametern) etwas, dessen Nutzen gegen die inhärenten Kosten der eingeführten Sprachkomplexität abgewogen werden muss (sowohl bei der Sprachimplementierung als auch im Denkmodell des Programmierers der Sprache). ;
  • Das Ändern von Parameternamen stellt eine API-Unterbrechungsänderung dar, die möglicherweise nicht der Erwartung eines Entwicklers entspricht, dass das Ändern eines Parameternamens eine triviale, nicht unterbrechende Änderung ist.

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 .

jhominal
quelle
6
Ich codiere regelmäßig in mehreren verschiedenen Sprachen. Fast immer muss ich die Parameter für eine einfache Teilzeichenfolge in einer bestimmten Sprache nachschlagen. Handelt es sich um eine Teilzeichenfolge (Anfang, Länge) oder eine Teilzeichenfolge (Anfang, Ende) und ist das Ende in der Zeichenfolge enthalten?
James Anderson
1
@JamesAnderson - Wie würde ein Named-Parameter-Teilstring Ihr Problem lösen? Sie müssten immer noch nach den Parametern suchen, um zu wissen, wie der Name des zweiten Parameters lautet (es sei denn, beide Funktionen sind vorhanden und die Polymorphismus-Funktion der Sprache ermöglicht Parameter des gleichen Typs mit unterschiedlichen Namen).
Mouviciel
6
@mouviciel: Benannte Parameter sind für den Autor nicht sehr nützlich , für den Leser jedoch fantastisch . Wir alle wissen, wie wichtig Variablennamen für die Erstellung von lesbarem Code sind, und benannte Parameter ermöglichen es dem Ersteller einer Schnittstelle, sowohl gute Parameternamen als auch gute Funktionsnamen anzugeben, was es für Benutzer dieser Schnittstelle einfacher macht, lesbaren Code zu schreiben.
Phoshi
@Phoshi - Sie sind richtig. Ich habe gerade vergessen, wie wichtig es ist, Code zu lesen, als ich meinen vorherigen Kommentar geschrieben habe.
Mouviciel
14

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 Tat Color(alpha: 1, red: 102, green: 205, blue: 170)wäre es viel einfacher zu lesen. Aber leider sagt der Compiler "nein" - es will Color(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 DateTimeAPI verwendete, die zwei Geschwisterklassen für Punkte und Dauer mit fast identischen Schnittstellen hatte. Während DateTime->new(...)akzeptiert ein second => 30Argument, das DateTime::Duration->new(...)wollte seconds => 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:

plot(plotdata$n, plotdata$mu, type="p", pch=17,  lty=1, bty="n", ann=FALSE, axes=FALSE)

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.

  • pchist das Plotzeichen, das für jeden Datenpunkt gezeichnet wird. 17ist ein leerer Kreis oder so ähnlich.
  • ltyist der Leitungstyp. Hier 1ist eine durchgezogene Linie.
  • btyist 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:

  • Argumente können geordnet oder ungeordnet sein,
  • benannt oder unbenannt,
  • erforderlich oder optional.
  • Signaturen können auch nach Größe oder Typ überladen werden.
  • und kann eine nicht spezifizierte Größe mit varargs 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:

  • Nehmen Sie die Reihenfolge der benannten Parameter in die Signatur auf und lassen Sie spätere Änderungen nicht zu. Dies ist nützlich für selbstdokumentierenden Code, hilft jedoch nicht bei optionalen Argumenten.
  • Übergeben Sie eine Schlüsselwert-Datenstruktur an den Angerufenen, der dann nützliche Informationen extrahieren muss. Dies ist im Vergleich sehr teuer und wird normalerweise nur in Skriptsprachen verwendet, bei denen die Leistung nicht im Vordergrund steht.

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:

// Java

static abstract class Arguments {
  public String bar = "default";
  public int    qux = 0;
}

void foo(Arguments args) {
  ...
}

/* using an initializer block */
foo(new Arguments(){{ bar = "baz"; qux = 42; }});
amon
quelle
3
" it is easier to forget the exact names of some parameters than it is to forget their order" ... das ist eine interessante Behauptung.
Catskul
1
Das erste Gegenbeispiel ist kein Problem mit benannten Parametern und eher ein Fehler bei der Zuordnung englischer Pluralformen zu Feldnamen in Schnittstellen. Das zweite Gegenbeispiel ist ähnlich problematisch, wenn Entwickler falsche Namensentscheidungen treffen. (Keine Wahl der Parameterübergabe - Schnittstelle ist für sich ein sein würde ausreichend Bedingung für die Lesbarkeit - die Frage ist , ob es eine notwendige Bedingung oder Beihilfen für sie in einigen Fällen).
mtraceur
1
+1 für die Gesamtpunkte zu Implementierungskosten und Kompromissen bei der Ausführung benannter Parameter. Ich denke nur, der Anfang wird Menschen verlieren.
mtraceur
@mtraceur Du hast einen Punkt. Jetzt, 5 Jahre später, würde ich die Antwort wahrscheinlich ganz anders schreiben. Wenn Sie möchten, können Sie meine Antwort bearbeiten, um weniger hilfreiche Inhalte zu entfernen. (Normalerweise werden solche Änderungen abgelehnt, da sie der Absicht des Autors widersprechen, aber hier bin ich damit einverstanden). ZB glaube ich jetzt, dass viele Probleme mit benannten Parametern nur Einschränkungen dynamischer Sprachen sind und sie nur ein Typensystem bekommen sollten. Beispiel: JavaScript hat Flow und TypeScript, Python hat MyPy. Mit der richtigen IDE-Integration werden die meisten Usability-Probleme behoben. Ich warte immer noch auf etwas in Perl.
Amon
7

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.

Robert Harvey
quelle
4
Python verwendet benannte Parameter und ist ein wunderbares Merkmal der Sprache. Ich wünschte, es würden mehr Sprachen verwendet. Dies erleichtert Standardargumente, da Sie nicht mehr wie in C ++ gezwungen sind, Argumente explizit "vor" den Standardwerten anzugeben. (Wie Sie bereits in Ihrem letzten Absatz gesehen haben, kommt Python ohne Überladung aus ... Mit Standardargumenten und Namensargumenten können Sie dieselben Aktionen ausführen.) Mit benannten Argumenten erzielen Sie auch lesbareren Code. Wenn Sie so etwas haben sin(radians=2), brauchen Sie nicht "Intellisense".
Gort the Robot
2

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.

ChuckCottrill
quelle
2

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.

Rocklan
quelle
1
Ich bin vehement anderer Meinung als "je weniger, desto besser". Codegolf-Gewinner wären die "besten" Programme.
Riley Major
2

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 von foo()Änderungen beschädigt werden. Es foo(name:1)ist jedoch unwahrscheinlicher, dass sie beschädigt werden, wenn der Programmierer weniger Anstrengungen unternimmt, um Änderungen vorzunehmen foo.

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?

foo(int weight, int height, int age = 0);

Die meisten Programmierer werden das Folgende tun.

foo(int weight, int height, int age = 0, string gender = null);

Jetzt ist kein Refactoring erforderlich und die Funktion kann im Legacy-Modus ausgeführt werden, wenn gendernull ist.

Zu konkret genderwird jetzt ein Wert in den Aufruf für HARDCODEDage . Wie dieses Beispiel:

 foo(10,10,0,"female");

Der Programmierer sah sich die Funktionsdefinition an, sah, dass agesie einen Standardwert von hat, 0und verwendete diesen Wert.

Jetzt ist der Standardwert für agein der Funktionsdefinition völlig unbrauchbar.

Mit benannten Parametern können Sie dieses Problem vermeiden.

foo(weight: 10, height: 10, gender: "female");

Neue Parameter können hinzugefügt werden, foound 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.

Reactgular
quelle