Ich habe festgestellt, dass einige Funktionen, mit denen ich arbeite, 6 oder mehr Parameter haben, während ich in den meisten Bibliotheken selten eine Funktion finde, die mehr als 3 benötigt.
Oft sind viele dieser zusätzlichen Parameter binäre Optionen, um das Funktionsverhalten zu ändern. Ich denke, dass einige dieser Funktionen mit mehreren Parametern wahrscheinlich überarbeitet werden sollten. Gibt es eine Richtlinie für zu viele Nummern?
functions
parameters
Darth Egregious
quelle
quelle
Antworten:
Ich habe noch nie eine Richtlinie gesehen, aber meiner Erfahrung nach weist eine Funktion mit mehr als drei oder vier Parametern auf eines von zwei Problemen hin:
Ohne weitere Informationen ist es schwierig zu sagen, was Sie sehen. Das Refactoring, das Sie durchführen müssen, besteht wahrscheinlich darin, die Funktion in kleinere Funktionen aufzuteilen, die vom übergeordneten Element aufgerufen werden, abhängig von den Flags, die aktuell an die Funktion übergeben werden.
Es gibt einige gute Vorteile, wenn Sie dies tun:
if
Struktur besteht, die viele Methoden mit beschreibenden Namen aufruft, als eine Struktur, die alles in einer Methode erledigt.quelle
Laut "Clean Code: Ein Handbuch für agiles Softwarehandwerk" ist Null das Ideal, eins oder zwei sind akzeptabel, drei in Sonderfällen und vier oder mehr, niemals!
Die Worte des Autors:
In diesem Buch wird in einem Kapitel nur auf Funktionen eingegangen, bei denen Parameter ausführlich behandelt werden. Ich denke, dieses Buch kann eine gute Richtlinie dafür sein, wie viele Parameter Sie benötigen.
Meiner persönlichen Meinung nach ist ein Parameter besser als keiner, weil ich der Meinung bin, dass klarer ist, was los ist.
Zum Beispiel ist meiner Meinung nach die zweite Wahl besser, weil klarer ist, was die Methode verarbeitet:
gegen
Bei vielen Parametern kann dies ein Zeichen dafür sein, dass einige Variablen zu einem einzelnen Objekt gruppiert werden können, oder in diesem Fall können viele Boolesche Werte angeben, dass die Funktion / Methode mehr als eine Sache tut. Es ist besser, jedes dieser Verhaltensweisen in einer anderen Funktion umzugestalten.
quelle
String language = detectLanguage(someText);
. In beiden Fällen, in denen Sie genau die gleiche Anzahl von Argumenten übergeben haben, haben Sie die Funktionsausführung aufgrund einer schlechten Sprache in zwei Teile geteilt.Matrix.Create(input);
woinput
, sagen wir, ein .NETIEnumerable<SomeAppropriateType>
? Auf diese Weise benötigen Sie auch keine separate Überladung, wenn Sie eine Matrix mit 10 statt 9 Elementen erstellen möchten.Wenn die Domänenklassen in der Anwendung korrekt entworfen wurden, wird die Anzahl der Parameter, die wir an eine Funktion übergeben, automatisch verringert, da die Klassen wissen, wie sie ihre Arbeit erledigen sollen, und über genügend Daten verfügen, um ihre Arbeit zu erledigen.
Angenommen, Sie haben eine Manager-Klasse, die eine Klasse der 3. Klasse auffordert, die Aufgaben zu erledigen.
Wenn Sie richtig modellieren,
Das ist ganz einfach.
Wenn Sie nicht das richtige Modell haben, sieht die Methode so aus
Das richtige Modell reduziert immer die Funktionsparameter zwischen den Methodenaufrufen, da die richtigen Funktionen an ihre eigenen Klassen delegiert werden (Einzelverantwortung) und über genügend Daten verfügen, um ihre Arbeit zu erledigen.
Immer wenn ich sehe, dass die Anzahl der Parameter zunimmt, überprüfe ich mein Modell, um festzustellen, ob ich mein Anwendungsmodell richtig entworfen habe.
Es gibt jedoch einige Ausnahmen: Wenn ich ein Übertragungsobjekt oder Konfigurationsobjekte erstellen muss, benutze ich ein Builder-Muster, um zuerst kleine erstellte Objekte zu erstellen, bevor ich ein großes Konfigurationsobjekt konstruiere.
quelle
Ein Aspekt, den die anderen Antworten nicht berücksichtigen, ist die Leistung.
Wenn Sie in einer ausreichend niedrigen Programmiersprache (C, C ++, Assembly) programmieren, kann eine große Anzahl von Parametern die Leistung einiger Architekturen erheblich beeinträchtigen, insbesondere wenn die Funktion häufig aufgerufen wird.
Wenn beispielsweise in ARM ein Funktionsaufruf ausgeführt wird, werden die ersten vier Argumente in Registern abgelegt
r0
,r3
und die verbleibenden Argumente müssen auf den Stapel geschrieben werden. Wenn Sie die Anzahl der Argumente unter fünf halten, kann dies für kritische Funktionen einen erheblichen Unterschied bedeuten.Bei Funktionen, die extrem oft aufgerufen werden, kann diesbezüglich sogar die Tatsache, dass das Programm die Argumente vor jedem Aufruf einrichten muss, die Leistung beeinträchtigen (
r0
dier3
möglicherweise von der aufgerufenen Funktion überschrieben wird und vor dem nächsten Aufruf ersetzt werden muss) Null Argumente sind am besten.Aktualisieren:
KjMag bringt das interessante Thema Inlining auf den Punkt. Durch Inlining wird dies in gewisser Weise verringert, da der Compiler dieselben Optimierungen vornehmen kann, die Sie beim Schreiben in reiner Assembly durchführen können. Mit anderen Worten, der Compiler kann sehen, welche Parameter und Variablen von der aufgerufenen Funktion verwendet werden, und kann die Registernutzung optimieren, so dass das Lesen / Schreiben im Stapel minimiert wird.
Es gibt jedoch einige Probleme mit Inlining.
inline
es in allen Fällen als obligatorisch behandelt würde .inline
Fehler entweder vollständig ignorieren oder tatsächlich anzeigen, wenn sie auf sie stoßen.quelle
inline
.)Wenn die Parameterliste auf mehr als fünf erweitert wird, sollten Sie eine "Kontext" -Struktur oder ein "Kontext" -Objekt definieren.
Dies ist im Grunde genommen nur eine Struktur, die alle optionalen Parameter enthält, wobei einige sinnvolle Standardeinstellungen festgelegt sind.
In der C-Verfahrenswelt würde eine einfache Struktur ausreichen. In Java, C ++ reicht ein einfaches Objekt aus. Verwirren Sie nicht mit Gettern oder Setzern, da der einzige Zweck des Objekts darin besteht, "öffentlich" einstellbare Werte zu halten.
quelle
Nein, es gibt keine Standardrichtlinie
Es gibt jedoch einige Techniken, die eine Funktion mit vielen Parametern erträglicher machen können.
Sie können einen list-if-args-Parameter (args *) oder einen Dictionary-of-args-Parameter (kwargs
**
) verwenden.Zum Beispiel in Python:
Ausgänge:
Sie können auch die Syntax der Objektliteraldefinition verwenden
Beispiel: Hier ist ein JavaScript jQuery-Aufruf zum Starten einer AJAX GET-Anforderung:
Wenn Sie sich die Ajax-Klasse von jQuery ansehen, gibt es eine Menge (ungefähr 30) mehr Eigenschaften, die festgelegt werden können. Meistens, weil Ajax-Kommunikationen sehr komplex sind. Glücklicherweise macht die Objekt-Literal-Syntax das Leben leichter.
C # intellisense bietet eine aktive Dokumentation der Parameter, sodass es nicht ungewöhnlich ist, sehr komplexe Anordnungen überladener Methoden zu sehen.
Dynamisch getippte Sprachen wie Python / Javascript verfügen nicht über solche Funktionen. Daher werden häufig Schlüsselwortargumente und Objektliteraldefinitionen angezeigt.
Ich bevorzuge Objektliteraldefinitionen ( auch in C # ) für die Verwaltung komplexer Methoden, da Sie explizit sehen können, welche Eigenschaften festgelegt werden, wenn ein Objekt instanziiert wird. Sie müssen ein wenig mehr arbeiten, um mit Standardargumenten umzugehen, aber auf lange Sicht wird Ihr Code viel besser lesbar sein. Mit Objektliteraldefinitionen können Sie Ihre Abhängigkeit von der Dokumentation aufheben, um auf den ersten Blick zu verstehen, was Ihr Code tut.
IMHO sind überladene Methoden hoch überbewertet.
Hinweis: Wenn ich mich recht erinnere, sollte die schreibgeschützte Zugriffssteuerung für Objektliteralkonstruktoren in C # funktionieren. Sie funktionieren im Wesentlichen genauso wie das Festlegen von Eigenschaften im Konstruktor.
Wenn Sie noch nie nicht-trivialen Code in einer dynamisch (Python) und / oder funktionalen / prototypischen JavaScript-basierten Sprache geschrieben haben, empfehle ich dringend, ihn auszuprobieren. Es kann eine aufschlussreiche Erfahrung sein.
Es kann beängstigend sein, zuerst das Vertrauen in die Parameter für den End-All-All-Ansatz zur Funktions- / Methodeninitialisierung zu brechen, aber Sie werden lernen, mit Ihrem Code noch viel mehr zu tun, ohne unnötige Komplexität hinzufügen zu müssen.
Aktualisieren:
Ich hätte wahrscheinlich Beispiele liefern sollen, um die Verwendung in einer statisch typisierten Sprache zu demonstrieren, aber ich denke derzeit nicht in einem statisch typisierten Kontext. Grundsätzlich habe ich in einem dynamisch getippten Kontext zu viel Arbeit geleistet, um plötzlich zurückzuschalten.
Was ich weiß , ist, dass die Syntax der Objektliteraldefinition in statisch typisierten Sprachen (zumindest in C # und Java) vollständig möglich ist, da ich sie zuvor verwendet habe. In statisch typisierten Sprachen werden sie "Objektinitialisierer" genannt. Hier sind einige Links, um ihre Verwendung in Java und C # zu zeigen .
quelle
Bei mehr als 2 wird mein Code-Geruchsalarm ausgelöst. Wenn Sie Funktionen als Operationen betrachten (d. H. Eine Übersetzung von Eingabe zu Ausgabe), kommt es selten vor, dass in einer Operation mehr als 2 Parameter verwendet werden. Verfahren (das ist eine Reihe von Schritten, um ein Ziel zu erreichen) erfordern mehr Eingaben und sind manchmal der beste Ansatz, aber in den meisten Sprachen sollten diese Tage nicht die Norm sein.
Aber auch dies ist eher eine Richtlinie als eine Regel. Ich habe oft Funktionen, die aufgrund ungewöhnlicher Umstände oder einfacher Bedienung mehr als zwei Parameter annehmen.
quelle
Ähnlich wie Evan Plaice sagt, bin ich ein großer Fan davon, assoziative Arrays (oder die vergleichbare Datenstruktur Ihrer Sprache) nach Möglichkeit einfach in Funktionen umzuwandeln.
Anstelle von (zum Beispiel) diesem:
Gehen Sie für:
Wordpress macht eine Menge Sachen auf diese Weise, und ich denke, es funktioniert gut. (Obwohl mein Beispielcode oben imaginär ist und selbst kein Beispiel von Wordpress ist.)
Mit dieser Technik können Sie auf einfache Weise eine Vielzahl von Daten an Ihre Funktionen übergeben, müssen sich jedoch nicht mehr an die Reihenfolge erinnern, in der die einzelnen Daten übergeben werden müssen.
Sie werden diese Technik auch zu schätzen wissen, wenn es Zeit für eine Umgestaltung ist - anstatt möglicherweise die Reihenfolge der Argumente einer Funktion zu ändern (z. B. wenn Sie feststellen, dass Sie ein weiteres Argument übergeben müssen), müssen Sie die Parameter Ihrer Funktionen nicht ändern Liste überhaupt.
Dies erspart Ihnen nicht nur das erneute Schreiben Ihrer Funktionsdefinition, sondern erspart Ihnen auch die Notwendigkeit, die Reihenfolge der Argumente bei jedem Aufruf der Funktion zu ändern. Das ist ein großer Gewinn.
quelle
In einer früheren Antwort wurde ein zuverlässiger Autor genannt, der erklärte, je weniger Parameter Ihre Funktionen haben, desto besser geht es Ihnen. Die Antwort hat nicht erklärt, warum, aber die Bücher erklären es, und hier sind zwei der überzeugendsten Gründe, warum Sie diese Philosophie übernehmen müssen und mit denen ich persönlich einverstanden bin:
Parameter gehören zu einer Abstraktionsebene, die sich von der der Funktion unterscheidet. Dies bedeutet, dass der Leser Ihres Codes über die Art und den Zweck der Parameter Ihrer Funktionen nachdenken muss: Dieses Denken ist "untergeordnet" als das des Namens und des Zwecks der entsprechenden Funktionen.
Der zweite Grund dafür, dass eine Funktion so wenig Parameter wie möglich enthält, ist das Testen: Wenn Sie beispielsweise eine Funktion mit 10 Parametern haben, überlegen Sie, wie viele Parameterkombinationen Sie für alle Testfälle zum Beispiel für eine Einheit abdecken müssen Prüfung. Weniger Parameter = weniger Tests.
quelle
Um den Rat für die ideale Anzahl von Funktionsargumenten, die Null sind, in Robert Martins "Clean Code: Ein Handbuch für agiles Softwarehandwerk" näher zu erläutern, sagt der Autor Folgendes als einen seiner Punkte:
Für sein
includeSetupPage()
Beispiel oben ist hier ein kleiner Ausschnitt seines überarbeiteten "sauberen Codes" am Ende des Kapitels:Die "Schule des Denkens" des Autors argumentiert für kleine Klassen, geringe (idealerweise 0) Anzahl von Funktionsargumenten und sehr kleine Funktionen. Auch wenn ich ihm nicht ganz zustimme, fand ich es nachdenklich und bin der Meinung, dass die Idee der Nullfunktionsargumente als Ideal erwägenswert sein kann. Beachten Sie außerdem, dass selbst sein kleines Code-Snippet oben auch Argumentfunktionen ungleich Null enthält. Ich denke also, dass dies vom Kontext abhängt.
(Und wie andere darauf hingewiesen haben, argumentiert er auch, dass mehr Argumente es unter Testgesichtspunkten schwieriger machen. Aber hier wollte ich hauptsächlich das obige Beispiel und seine Begründung für Nullfunktionsargumente hervorheben.)
quelle
Idealerweise Null. Eins oder zwei sind in Ordnung, drei in bestimmten Fällen.
Vier oder mehr ist normalerweise eine schlechte Praxis.
Neben den Prinzipien der Einzelverantwortung, die von anderen festgestellt wurden, können Sie diese auch aus Sicht des Testens und Debuggens betrachten.
Wenn es einen Parameter gibt, ist es relativ einfach, seine Werte zu kennen, sie zu testen und Fehler damit zu finden, da es nur einen Faktor gibt. Wenn Sie die Faktoren erhöhen, steigt die Gesamtkomplexität schnell an. Für ein abstraktes Beispiel:
Überlegen Sie, was Sie bei diesem Wetter tragen sollten. Überlegen Sie, was mit einem Eingang möglich ist - der Temperatur. Wie Sie sich vorstellen können, sind die Ergebnisse des Tragens aufgrund dieses einen Faktors ziemlich einfach. Überlegen Sie nun, was das Programm tun könnte / könnte / sollte, wenn es tatsächlich Temperatur, Luftfeuchtigkeit, Taupunkt, Niederschlag usw. überschreitet.
quelle