Viele haben über die Größe der Funktion diskutiert. Sie sagen, dass Funktionen im Allgemeinen ziemlich kurz sein sollten. Die Meinungen reichen von etwa 15 Zeilen bis zu "ungefähr einem Bildschirm", der heute wahrscheinlich zwischen 40 und 80 Zeilen beträgt.
Außerdem sollten Funktionen immer nur eine Aufgabe erfüllen.
Es gibt jedoch eine Art von Funktion, die häufig in beiden Kriterien in meinem Code fehlschlägt: Initialisierungsfunktionen.
Zum Beispiel muss in einer Audio-Anwendung die Audio-Hardware / API eingerichtet werden, Audiodaten müssen in ein geeignetes Format konvertiert werden und der Objektzustand muss richtig initialisiert werden. Dies sind eindeutig drei verschiedene Aufgaben und abhängig von der API kann dies leicht mehr als 50 Zeilen umfassen.
Die Sache mit init-Funktionen ist, dass sie in der Regel nur einmal aufgerufen werden, so dass keine der Komponenten wiederverwendet werden muss. Würden Sie sie immer noch in mehrere kleinere Funktionen zerlegen, würden Sie große Initialisierungsfunktionen für in Ordnung halten?
Ich würde die Funktion immer noch nach Aufgabe aufbrechen und dann jede der Funktionen auf niedrigerer Ebene aus meiner öffentlichen Initialisierungsfunktion heraus aufrufen:
%Vor% Beim Schreiben von prägnanten Funktionen geht es genauso darum, Fehler und Änderungen zu isolieren, als Dinge lesbar zu halten. Wenn Sie wissen, dass der Fehler in _convert_format()
ist, können Sie die ~ 40 Zeilen, die für einen Fehler verantwortlich sind, ein bisschen schneller aufspüren. Das gleiche gilt, wenn Sie Änderungen bestätigen, die nur eine Funktion berühren.
Ein letzter Punkt, ich verwende assert()
ziemlich häufig, so dass ich "oft scheitern und früh versagen kann", und der Beginn einer Funktion ist der beste Ort für ein paar Plausibilitätsprüfungen. Wenn Sie die Funktion kurz halten, können Sie die Funktion aufgrund ihrer engeren Pflichten genauer testen. Es ist sehr schwierig, eine 400-Zeilen-Funktion zu testen, die 10 verschiedene Dinge macht.
Wenn das Aufteilen in kleinere Teile den Code besser strukturiert und / oder lesbarer macht - tun Sie es, egal was die Funktion tut. Es geht nicht um die Anzahl der Zeilen, sondern um die Codequalität.
Ich würde immer noch versuchen, die Funktionen in logische Einheiten aufzuteilen. Sie sollten so lang oder so kurz wie sinnvoll sein. Zum Beispiel:
%Vor%Wenn Sie ihnen eindeutige Namen zuweisen, wird alles intuitiver und lesbarer. Wenn Sie sie auseinander brechen, können zukünftige Änderungen und / oder andere Programme leichter wieder verwendet werden.
In solch einer Situation kommt es auf eine persönliche Vorliebe an. Ich bevorzuge es, dass Funktionen nur eine Sache haben, also würde ich die Initialisierung in separate Funktionen aufteilen, auch wenn sie nur einmal aufgerufen werden. Wenn jedoch jemand alles in einer einzigen Funktion machen wollte, würde ich mir nicht zu viele Gedanken darüber machen (solange der Code klar war). Es gibt wichtigere Dinge, über die man streiten kann (zum Beispiel, ob geschweifte Klammern zu ihrer eigenen separaten Zeile gehören).
Wenn Sie viele Komponenten haben, die ineinander gesteckt werden müssen, kann es natürlich vernünftig sein, eine große Methode zu haben - selbst wenn die Erstellung jeder Komponente in eine separate Methode umgestaltet wird, wo dies möglich ist.
>Eine Alternative dazu ist die Verwendung eines Dependency-Injection-Frameworks (z. B. Spring, Castle Windsor, Guice usw.). Das hat definitiv Vor- und Nachteile ... während der Weg durch eine große Methode ziemlich schmerzhaft sein kann, haben Sie zumindest eine gute Vorstellung davon, wo alles initialisiert ist, und Sie müssen sich keine Sorgen darüber machen, welche "Magie" vor sich geht . Außerdem kann die Initialisierung nach der Bereitstellung nicht geändert werden (wie dies beispielsweise bei einer XML-Datei für Spring der Fall ist).
Ich denke, es macht Sinn, den Hauptteil Ihres Codes so zu gestalten, dass er > injiziert werden kann - aber ob diese Injektion über ein Framework oder nur eine hart codierte (und möglicherweise lange) Liste ist von Initialisierungsaufrufen ist eine Wahl, die sich für verschiedene Projekte durchaus ändern kann. In beiden Fällen sind die Ergebnisse schwer zu testen, wenn Sie die Anwendung nicht einfach ausführen.
Zunächst sollte anstelle einer Initialisierungsfunktion eine Factory verwendet werden. Das heißt, anstatt initialize_audio()
haben Sie new AudioObjectFactory
(Sie können sich hier einen besseren Namen vorstellen). Dies hält die Trennung von Bedenken aufrecht.
Achten Sie jedoch darauf, nicht zu früh zu abstrahieren. Natürlich haben Sie zwei Bedenken bereits: 1) Audio-Initialisierung und 2) mit diesem Audio. Bis Sie zum Beispiel das zu initialisierende Audiogerät abstrahieren oder die Art und Weise, wie ein bestimmtes Gerät während der Initialisierung konfiguriert werden kann, sollte Ihre Factory-Methode ( audioObjectFactory.Create()
oder was auch immer) wirklich auf nur eine große Methode beschränkt bleiben. Frühe Abstraktion dient nur dazu, Design zu verschleiern.
Beachten Sie, dass audioObjectFactory.Create()
nicht als Einheit getestet werden kann. Das Testen ist ein Integrationstest, und bis es Teile davon gibt, die abstrahiert werden können, bleibt es ein Integrationstest. Später können Sie feststellen, dass Sie mehrere verschiedene Fabriken für unterschiedliche Konfigurationen haben. An diesem Punkt kann es nützlich sein, die Hardwareaufrufe in eine Schnittstelle zu abstrahieren, sodass Sie Komponententests erstellen können, um sicherzustellen, dass die verschiedenen Fabriken die Hardware ordnungsgemäß konfigurieren.
Ich denke, es ist der falsche Ansatz, die Anzahl der Zeilen zu berechnen und zu zählen und darauf basierende Funktionen zu bestimmen. Für etwas wie Initialisierungscode habe ich oft eine separate Funktion dafür, aber meistens so, dass die Load- oder Init- oder New-Funktionen nicht überladen und verwirrend sind. Wenn Sie es in ein paar Aufgaben aufteilen können, wie es andere vorgeschlagen haben, dann können Sie es als nützlich bezeichnen und helfen, es zu organisieren. Selbst wenn Sie es nur einmal aufrufen, ist es keine schlechte Angewohnheit, und oft finden Sie, dass es andere Zeiten gibt, wenn Sie Dinge neu initiieren möchten und diese Funktion wieder verwenden können.
Ich dachte nur, ich würde das hier rauswerfen, da es noch nicht erwähnt wurde - das Fassadenmuster ist manchmal als Schnittstelle zu einem komplexen Subsystem zitiert. Ich habe selbst nicht viel damit gemacht, aber die Metaphern sind normalerweise etwas wie das Einschalten eines Computers (erfordert mehrere Schritte) oder das Einschalten eines Heimkinosystems (Fernseher einschalten, Empfänger einschalten, Lichter ausschalten usw.). .)
Je nach Code-Struktur kann es sich lohnen, die großen Initialisierungsfunktionen wegzuspulen. Ich stimme immer noch mit Meagar Punkt, obwohl das Aufteilen von Funktionen in _init_X(), _init_Y()
, etc. ist ein guter Weg zu gehen. Selbst wenn Sie Kommentare in diesem Code nicht wiederverwenden, wird es bei Ihrem nächsten Projekt, wenn Sie sich selbst sagen: "Wie habe ich diese X-Komponente initialisiert?", Viel einfacher sein, sie zurückzuholen der kleineren _init_X()
-Funktion, als es wäre, es aus einer größeren Funktion herauszunehmen, besonders wenn die X-Initialisierung darin verstreut ist.
Die Funktionslänge ist, wie Sie getaggt haben, eine sehr subjektive Angelegenheit. Eine bewährte Methode besteht jedoch darin, Code zu isolieren, der häufig wiederholt wird und / oder als eigene Entität fungieren kann. Wenn Ihre Initialisierungsfunktion beispielsweise Bibliotheksdateien oder Objekte lädt, die von einer bestimmten Bibliothek verwendet werden, sollte dieser Codeblock modularisiert werden.
Damit ist es nicht schlecht, eine Initialisierungsmethode zu haben, die lang ist, solange sie nicht lang ist, weil viel Code wiederholt wird oder andere Snippets, die abstrahiert werden können.
Hoffe das hilft,
Carlos Nunez
Tags und Links initialization function code-size