Gibt es Richtlinien, wie viele Parameter eine Funktion akzeptieren soll?

114

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?

Darth Egregious
quelle
4
@Ominus: Die Idee ist, dass Sie Ihre Klassen fokussiert halten möchten. Fokussierte Klassen haben normalerweise nicht so viele Abhängigkeiten / Attribute, daher hätten Sie weniger Parameter für den Konstruktor. Ein Modewort, das die Leute an dieser Stelle verwenden, ist das Prinzip des hohen Zusammenhalts und der Einzelverantwortung . Wenn Sie der Meinung sind, dass diese nicht verletzt werden und dennoch viele Parameter benötigen, ziehen Sie die Verwendung des Builder-Musters in Betracht.
c_maker
2
Folgen Sie auf keinen Fall dem Beispiel von MPI_Sendrecv () , das 12 Parameter akzeptiert !
Chrisaycock
6
Das Projekt, an dem ich gerade arbeite, verwendet ein bestimmtes Framework, in dem Methoden mit mehr als 10 Parametern an der Tagesordnung sind. Ich rufe eine bestimmte Methode mit 27 Parametern an einigen Stellen auf. Jedes Mal, wenn ich es sehe, sterbe ich innerlich ein wenig.
perp
3
Fügen Sie niemals boolesche Schalter hinzu, um das Funktionsverhalten zu ändern. Teilen Sie stattdessen die Funktion. Teilen Sie das gemeinsame Verhalten in neue Funktionen auf.
Kevin Cline
2
@Ominus Was? Nur 10 Parameter? Das ist nichts, es wird viel mehr benötigt . : D
maaartinus

Antworten:

106

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:

  1. Die Funktion tut zu viel. Es sollte in mehrere kleinere Funktionen aufgeteilt werden, die jeweils einen kleineren Parametersatz haben.
  2. Dort versteckt sich ein anderes Objekt. Möglicherweise müssen Sie ein anderes Objekt oder eine andere Datenstruktur erstellen, die diese Parameter enthält. Weitere Informationen finden Sie in diesem Artikel zum Parameterobjektmuster .

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:

  • Dies erleichtert das Lesen Ihres Codes. Ich persönlich finde es viel einfacher, eine "Regelliste" zu lesen, die aus einer ifStruktur besteht, die viele Methoden mit beschreibenden Namen aufruft, als eine Struktur, die alles in einer Methode erledigt.
  • Es ist mehr Einheit testbar. Sie haben Ihr Problem in mehrere kleinere Aufgaben aufgeteilt, die individuell sehr einfach sind. Die Komponententestsammlung würde dann aus einer Verhaltenstestsuite bestehen, die die Pfade durch die Mastermethode prüft, und einer Sammlung kleinerer Tests für jede einzelne Prozedur.
Michael K
quelle
5
Parameterabstraktion wurde in ein Entwurfsmuster umgewandelt? Was passiert, wenn Sie 3 Parameterklassen haben? Fügen Sie 9 weitere Methodenüberladungen hinzu, um die verschiedenen möglichen Parameterkombinationen zu verarbeiten? Das klingt nach einem fiesen Problem mit der O (n ^ 2) -Parameterdeklaration. Oh, Moment mal, Sie können nur eine Klasse in Java / C # erben, so dass mehr Biolerplate (möglicherweise eine weitere Unterklasse) erforderlich ist, damit dies in der Praxis funktioniert. Entschuldigung, ich bin nicht überzeugt. Das Ignorieren der expressiveren Ansätze einer Sprache zugunsten der Komplexität fühlt sich einfach falsch an.
Evan Plaice
Es sei denn, Sie verwenden das Muster Objektmuster, um Variablen in einer Objektinstanz zu verpacken und als Parameter zu übergeben. Dies funktioniert beim Packen, ist jedoch möglicherweise verlockend, Klassen unterschiedlicher Variablen zu erstellen, um die Methodendefinition zu vereinfachen.
Evan Plaice
@EvanPlaice Ich sage nicht, dass Sie dieses Muster immer dann verwenden müssen, wenn Sie mehr als einen Parameter haben - Sie haben absolut Recht, dass dies noch schlimmer wird als die erste Liste. Es kann Fälle geben, in denen Sie wirklich eine große Anzahl von Parametern benötigen und es einfach nicht funktioniert, sie in ein Objekt einzuwickeln. Ich habe noch keinen Fall in der Unternehmensentwicklung kennengelernt, der nicht in einen der beiden in meiner Antwort genannten Bereiche gefallen ist - das heißt aber nicht, dass einer nicht existiert.
Michael K
@MichaelK Wenn Sie sie noch nie verwendet haben, googeln Sie die Objektinitialisierer. Es ist ein ziemlich neuartiger Ansatz, der das Definitions-Boilerplate erheblich reduziert. Theoretisch könnten Sie Konstruktoren, Parameter und Überladungen einer Klasse auf einmal eliminieren. In der Praxis ist es jedoch in der Regel gut, einen gemeinsamen Konstruktor beizubehalten und sich für den Rest der obskuren / Nischen-Eigenschaften auf die Syntax des Objektinitialisierers zu verlassen. Meiner Meinung nach kommt es der Ausdruckskraft von dynamisch getippten Sprachen in einer statisch getippten Sprache am nächsten.
Evan Plaice
@Evain Plaice: Seit wann sind dynamisch typisierte Sprachen ausdrucksstark?
ThomasX
41

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:

Die ideale Anzahl von Argumenten für eine Funktion ist Null (Null). Als nächstes kommt eins (monadisch), dicht gefolgt von zwei (dyadisch). Drei Argumente (Triade) sollten nach Möglichkeit vermieden werden. Mehr als drei (polyadisch) erfordern eine besondere Begründung - und sollten dann sowieso nicht verwendet werden.

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:

LangDetector detector = new LangDetector(someText);
//lots of lines
String language = detector.detectLanguage();

gegen

LangDetector detector = new LangDetector();
//lots of lines
String language = detector.detectLanguage(someText);

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.

Renato Dinhani
quelle
8
"Drei in besonderen Fällen und vier oder mehr, niemals!" BS. Wie wäre es mit Matrix.Create (x1, x2, x3, x4, x5, x6, x7, x8, x9); ?
Lukasz Madon
71
Null ist ideal? Wie zum Teufel bekommen Funktionen Informationen? Global / Instanz / Statisch / Welche Variablen? Igitt
Peter C
9
Das ist ein schlechtes Beispiel. Die Antwort ist offensichtlich: 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.
Matthieu M.
8
@lukas, in Sprachen unterstützen solche Phantasie Konstrukte wie Arrays oder (Keuchen!) Listen, wie etwa , Matrix.Create(input);wo input, sagen wir, ein .NET IEnumerable<SomeAppropriateType>? Auf diese Weise benötigen Sie auch keine separate Überladung, wenn Sie eine Matrix mit 10 statt 9 Elementen erstellen möchten.
CVn
9
Null Argumente als "ideal" ist ein Topf und ein Grund, warum Clean Code meiner Meinung nach viel zu hoch bewertet ist.
user949300
24

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,

3rdGradeClass.finishHomework(int lessonId) {
    result = students.assignHomework(lessonId, dueDate);
    teacher.verifyHomeWork(result);
}

Das ist ganz einfach.

Wenn Sie nicht das richtige Modell haben, sieht die Methode so aus

Manager.finishHomework(grade, students, lessonId, teacher, ...) {
    // This is not good.
}

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.

java_mouse
quelle
16

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, r3und 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 ( r0die r3mö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.

  1. Inlining vergrößert die kompilierte Binärdatei, da derselbe Code in binärer Form dupliziert wird, wenn er von mehreren Stellen aus aufgerufen wird. Dies wirkt sich nachteilig auf die Nutzung des I-Cache aus.
  2. Compiler erlauben Inlining normalerweise nur bis zu einem bestimmten Level (3 Schritte IIRC?). Stellen Sie sich vor, Sie rufen eine Inline-Funktion von einer Inline-Funktion aus auf. Binäres Wachstum würde explodieren, wenn inlinees in allen Fällen als obligatorisch behandelt würde .
  3. Es gibt eine Vielzahl von Compilern, die inlineFehler entweder vollständig ignorieren oder tatsächlich anzeigen, wenn sie auf sie stoßen.
Löwe
quelle
Ob die Übergabe einer großen Anzahl von Parametern aus Sicht der Leistung gut oder schlecht ist, hängt von den Alternativen ab. Wenn eine Methode ein Dutzend Informationen benötigt und eine davon hunderte Male mit denselben Werten aufruft, kann es schneller sein, wenn die Methode ein Array aufnimmt, als wenn sie ein Dutzend Parameter aufnimmt. Wenn jedoch jeder Aufruf einen eindeutigen Satz von zwölf Werten benötigt, ist das Erstellen und Auffüllen des Arrays für jeden Aufruf möglicherweise langsamer als das direkte Übergeben der Werte.
Supercat
Löst Inlining dieses Problem nicht?
KjMag
@KjMag: Ja, bis zu einem gewissen Grad. Aber je nach Compiler gibt es eine Menge Fallstricke. Funktionen werden in der Regel nur bis zu einer bestimmten Ebene eingebunden (wenn Sie eine eingebundene Funktion aufrufen, die eine eingebundene Funktion aufruft, die eine eingebundene Funktion aufruft ...). Wenn die Funktion groß ist und von vielen Stellen aus aufgerufen wird, wird die Binärdatei durch Inlining überall größer, was zu mehr Fehlern im I-Cache führen kann. Inlining kann also helfen, aber es ist keine Wunderwaffe. (Ganz zu schweigen davon, dass es einige alte eingebettete Compiler gibt, die dies nicht unterstützen inline.)
Leo,
7

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.

James Anderson
quelle
Ich stimme zu, ein Kontextobjekt kann sich als sehr praktisch herausstellen, wenn Funktionsparameterstrukturen immer komplexer werden. Ich hatte kürzlich über die Verwendung von Kontextobjekten mit einem besucherähnlichen Muster
Lukas Eder,
5

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:

// Example definition
def example_function(normalParam, args*, kwargs**):
  for i in args:
    print 'args' + i + ': ' + args[i] 
  for key in kwargs:
    print 'keyword: %s: %s' % (key, kwargs[key])
  somevar = kwargs.get('somevar','found')
  missingvar = kwargs.get('somevar','missing')
  print somevar
  print missingvar

// Example usage

    example_function('normal parameter', 'args1', args2, 
                      somevar='value', missingvar='novalue')

Ausgänge:

args1
args2
somevar:value
someothervar:novalue
value
missing

Sie können auch die Syntax der Objektliteraldefinition verwenden

Beispiel: Hier ist ein JavaScript jQuery-Aufruf zum Starten einer AJAX GET-Anforderung:

$.ajax({
  type: 'GET',
  url: 'http://someurl.com/feed',
  data: data,
  success: success(),
  error: error(),
  complete: complete(),
  dataType: 'jsonp'
});

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 .

Evan Scholle
quelle
3
Ich bin mir nicht sicher, ob mir diese Methode gefällt, hauptsächlich, weil Sie den selbstdokumentierenden Wert einzelner Parameter verlieren. Für Listen mit ähnlichen Elementen ist dies durchaus sinnvoll (zum Beispiel eine Methode, die eine Liste von Zeichenfolgen aufnimmt und diese verkettet). Für einen beliebigen Parametersatz ist dies jedoch schlechter als der lange Methodenaufruf.
Michael K
@MichaelK Schauen Sie sich die Objektinitialisierer noch einmal an. Mit ihnen können Sie Eigenschaften explizit definieren, im Gegensatz zu denen, die in herkömmlichen Methoden- / Funktionsparametern implizit definiert sind. Lesen Sie dies, msdn.microsoft.com/en-us/library/bb397680.aspx , um zu sehen, was ich meine.
Evan Plaice
3
Das Erstellen eines neuen Typs für die Parameterliste klingt genauso wie die Definition unnötiger Komplexität. Mit dynamischen Sprachen können Sie dies zwar vermeiden, erhalten dann aber nur einen einzigen Ball-of-Goo-Parameter. Unabhängig davon beantwortet dies nicht die gestellte Frage.
Telastyn
@ Telastyn Worüber sprichst du? Es wurde kein neuer Typ erstellt. Sie deklarieren Eigenschaften direkt mithilfe der Objektliteral-Syntax. Es ist wie beim Definieren eines anonymen Objekts, aber die Methode interpretiert es als eine Parametergruppierung key = value. Was Sie sehen, ist eine Methodeninstanziierung (kein Parameter, der ein Objekt einkapselt). Wenn Ihr Rindfleisch eine Parameterverpackung aufweist, werfen Sie einen Blick auf das in einer der anderen Fragen erwähnte Parameterobjektmuster, da es genau das ist, was es ist.
Evan Plaice
@EvanPlaice - Mit der Ausnahme, dass statische Programmiersprachen im Großen und Ganzen einen (häufig neuen) deklarierten Typ erfordern, um das Parameter Object-Muster zuzulassen.
Telastyn,
3

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.

Telastyn
quelle
2

Ä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:

<?php

createBlogPost('the title', 'the summary', 'the author', 'the date of publication, 'the keywords', 'the category', 'etc');

?>

Gehen Sie für:

<?php

// create a hash of post data
$post_data = array(
  'title'    => 'the title',
  'summary'  => 'the summary',
  'author'   => 'the author',
  'pubdate'  => 'the publication date',
  'keywords' => 'the keywords',
  'category' => 'the category',
  'etc'      => 'etc',
);

// and pass it to the appropriate function
createBlogPost($post_data);

?>

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.

Chris Allen Lane
quelle
Das Betrachten dieses Beitrags hebt einen weiteren Vorteil des Pass-a-Hash-Ansatzes hervor: Beachten Sie, dass mein erstes Codebeispiel so lang ist, dass es eine Bildlaufleiste generiert, während das zweite sauber auf die Seite passt. Das gleiche wird wahrscheinlich in Ihrem Code-Editor zutreffen.
Chris Allen Lane
0

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.

Billal Begueradj
quelle
0

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:

Argumente sind schwer. Sie verbrauchen viel konzeptionelle Kraft. Deshalb habe ich fast alle aus dem Beispiel entfernt. Betrachten Sie zum Beispiel das StringBufferim Beispiel. Wir hätten es als Argument weitergeben können, anstatt es zu einer Instanzvariablen zu machen, aber dann hätten unsere Leser es jedes Mal interpretieren müssen, wenn sie es gesehen hätten. Wenn Sie die vom Modul erzählte Geschichte lesen, includeSetupPage() ist das leichter zu verstehen als includeSetupPageInto(newPageContent). Das Argument befindet sich auf einer anderen Abstraktionsebene als der Funktionsname und zwingt Sie, ein Detail zu kennen (mit anderen Worten StringBuffer), das zu diesem Zeitpunkt nicht besonders wichtig ist.

Für sein includeSetupPage()Beispiel oben ist hier ein kleiner Ausschnitt seines überarbeiteten "sauberen Codes" am Ende des Kapitels:

// *** NOTE: Commments are mine, not the author's ***
//
// Java example
public class SetupTeardownIncluder {
    private StringBuffer newPageContent;

    // [...] (skipped over 4 other instance variables and many very small functions)

    // this is the zero-argument function in the example,
    // which calls a method that eventually uses the StringBuffer instance variable
    private void includeSetupPage() throws Exception {
        include("SetUp", "-setup");
    }

    private void include(String pageName, String arg) throws Exception {
        WikiPage inheritedPage = findInheritedPage(pageName);
        if (inheritedPage != null) {
            String pagePathName = getPathNameForPage(inheritedPage);
            buildIncludeDirective(pagePathName, arg);
        }
    }

    private void buildIncludeDirective(String pagePathName, String arg) {
        newPageContent
            .append("\n!include ")
            .append(arg)
            .append(" .")
            .append(pagePathName)
            .append("\n");
    }
}

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.)

Sonny
quelle
-2

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.

Michael Durrant
quelle
12
Wenn eine Funktion keine Parameter hat, gibt sie entweder einen konstanten Wert zurück (unter bestimmten Umständen nützlich, aber eher einschränkend) oder sie verwendet einen verborgenen Zustand, der besser explizit angegeben werden sollte. (In OO-Methodenaufrufen ist das Kontextobjekt so explizit, dass es keine Probleme verursacht.)
Donal Fellows,
4
-1 für das nicht Zitieren der Quelle
Joshua Drake
Wollen Sie ernsthaft sagen, dass im Idealfall alle Funktionen keine Parameter annehmen würden? Oder ist das Übertreibung?
GreenAsJade
1
Siehe Onkel Bobs Argument unter: informit.com/articles/article.aspx?p=1375308 und beachten Sie, dass er unten sagt: "Funktionen sollten eine kleine Anzahl von Argumenten haben. Kein Argument ist am besten, gefolgt von eins, zwei und drei Mehr als drei sind sehr fragwürdig und sollten mit Vorurteilen vermieden werden. "
Michael Durrant
Ich habe die Quelle angegeben. Komisch keine Kommentare seitdem. Ich habe auch versucht, den "Richtlinien" -Teil zu beantworten, da viele Onkel Bob und Clean Code jetzt als Richtlinien betrachten. Interessant, dass die enorm positiv bewertete Top-Antwort (derzeit) sagt, keine Richtlinie zu kennen. Uncle Bob nicht die Absicht , authorative zu sein , aber es etwas ist und diese Antwort zumindest versucht , eine Antwort auf die Besonderheiten der Frage zu sein.
Michael Durrant