Im Allgemeinen verwende ich private Methoden, um Funktionen zu kapseln, die an mehreren Stellen in der Klasse wiederverwendet werden. Aber manchmal habe ich eine große öffentliche Methode, die sich in kleinere Schritte aufteilen lässt, jede in ihrer eigenen privaten Methode. Dies würde die öffentliche Methode kürzer machen, aber ich bin besorgt, dass die Lesbarkeit beeinträchtigt, wenn jemand, der die Methode liest, gezwungen wird, zu anderen privaten Methoden zu wechseln.
Gibt es einen Konsens darüber? Ist es besser, lange öffentliche Methoden zu haben oder sie in kleinere Teile zu zerlegen, selbst wenn jedes Teil nicht wiederverwendbar ist?
coding-style
readability
Jordak
quelle
quelle
Antworten:
Nein, das ist kein schlechter Stil. In der Tat ist es ein sehr guter Stil.
Private Funktionen müssen nicht nur aus Gründen der Wiederverwendbarkeit vorhanden sein. Das ist sicherlich ein guter Grund, sie zu erschaffen, aber es gibt noch einen anderen: Zersetzung.
Betrachten Sie eine Funktion, die zu viel bewirkt. Es ist hundert Zeilen lang und unmöglich zu überlegen.
Wenn Sie diese Funktion in kleinere Teile "aufteilen", "funktioniert" immer noch so viel wie zuvor, jedoch in kleineren Teilen. Sie ruft andere Funktionen auf, die beschreibende Namen haben sollten. Die Hauptfunktion liest sich fast wie ein Buch: mache A, dann mache B, dann mache C usw. Die Funktionen, die es aufruft, können nur an einer Stelle aufgerufen werden, aber jetzt sind sie kleiner. Jede bestimmte Funktion unterscheidet sich notwendigerweise von den anderen Funktionen: Sie haben unterschiedliche Bereiche.
Wenn Sie ein großes Problem in kleinere Probleme zerlegen, auch wenn diese kleineren Probleme (Funktionen) nur einmal verwendet / gelöst werden, haben Sie mehrere Vorteile:
Lesbarkeit. Niemand kann eine monolithische Funktion lesen und verstehen, was sie vollständig bewirkt. Sie können entweder sich selbst belügen oder es in mundgerechte Stücke aufteilen, die Sinn machen.
Bezugsort. Es ist jetzt unmöglich, eine Variable zu deklarieren und zu verwenden, sie dann zu belassen und 100 Zeilen später erneut zu verwenden. Diese Funktionen haben unterschiedliche Bereiche.
Testen. Während es nur erforderlich ist, die öffentlichen Mitglieder einer Klasse einer Prüfung zu unterziehen, kann es wünschenswert sein, auch bestimmte private Mitglieder zu prüfen. Wenn es einen kritischen Abschnitt einer langen Funktion gibt, der vom Testen profitieren könnte, ist es unmöglich, ihn unabhängig zu testen, ohne ihn in eine separate Funktion zu extrahieren.
Modularität. Nachdem Sie nun über private Funktionen verfügen, können Sie eine oder mehrere Funktionen finden, die in eine separate Klasse extrahiert werden können, unabhängig davon, ob sie nur hier verwendet werden oder wiederverwendbar sind. Bis zum vorherigen Punkt ist diese separate Klasse wahrscheinlich auch einfacher zu testen, da sie eine öffentliche Schnittstelle benötigt.
Die Idee, großen Code in kleinere Teile aufzuteilen, die einfacher zu verstehen und zu testen sind, ist ein zentraler Punkt in Onkel Bobs Buch Clean Code . Zum Zeitpunkt des Schreibens dieser Antwort ist das Buch neun Jahre alt, aber heute genauso aktuell wie damals.
quelle
Es ist wahrscheinlich eine großartige Idee!
Ich habe Probleme mit der Aufteilung langer linearer Aktionssequenzen in separate Funktionen, nur um die durchschnittliche Funktionslänge in Ihrer Codebasis zu verringern:
Jetzt haben Sie tatsächlich Quelltextzeilen hinzugefügt und die Lesbarkeit insgesamt erheblich verringert. Vor allem, wenn Sie jetzt viele Parameter zwischen den einzelnen Funktionen übergeben, um den Status zu verfolgen. Huch!
Allerdings , wenn Sie es geschafft haben, auf eine oder mehrere Zeilen in eine reine Funktion zu extrahieren , die einen einzigen, klaren Zweck (dient nur einmal , auch wenn genannt ), dann haben Sie die Lesbarkeit verbessert:
In der Praxis wird dies wahrscheinlich nicht einfach sein, aber wenn Sie lange genug darüber nachdenken, können Sie häufig Teile der reinen Funktionalität herausfordern.
Sie werden wissen, dass Sie auf dem richtigen Weg sind, wenn Sie Funktionen mit schönen Verbnamen haben und wenn Ihre übergeordnete Funktion sie aufruft und das Ganze praktisch wie ein Absatz in der Prosa liest.
Wenn Sie dann Wochen später zurückkehren, um weitere Funktionen hinzuzufügen, und feststellen, dass Sie eine dieser Funktionen tatsächlich wiederverwenden können, dann oh, entzückende Freude! Was für eine wundersame strahlende Freude!
quelle
foo = compose(reglorbulate, deglorbFramb, getFrambulation);
Die Antwort ist, dass es stark von der Situation abhängt. @Snowman deckt die positiven Aspekte der Aufteilung großer öffentlicher Funktionen ab, es ist jedoch wichtig, sich daran zu erinnern, dass es auch negative Auswirkungen geben kann, da Sie zu Recht besorgt sind.
Viele private Funktionen mit Nebenwirkungen können das Lesen von Code erschweren und zu Problemen führen. Dies gilt insbesondere dann, wenn diese privaten Funktionen von den gegenseitigen Nebenwirkungen abhängen. Vermeiden Sie eng gekoppelte Funktionen.
Abstraktionen sind undicht . Es ist zwar nett vorzutäuschen, wie eine Operation ausgeführt wird oder wie Daten gespeichert werden, aber es gibt Fälle, in denen dies der Fall ist, und es ist wichtig, sie zu identifizieren.
Semantik und Kontext. Wenn Sie nicht klar erfassen, was eine Funktion in ihrem Namen tut, können Sie die Lesbarkeit verringern und die Fragilität der öffentlichen Funktion erhöhen. Dies gilt insbesondere dann, wenn Sie private Funktionen mit einer großen Anzahl von Eingabe- und Ausgabeparametern erstellen. Während Sie in der öffentlichen Funktion sehen, welche privaten Funktionen sie aufruft, sehen Sie in der privaten Funktion nicht, welche öffentlichen Funktionen sie aufrufen. Dies kann zu einem "Bugfix" in einer privaten Funktion führen, der die öffentliche Funktion unterbricht.
Stark zerlegter Code ist für den Autor immer klarer als für andere. Dies bedeutet nicht, dass es für andere nicht immer klar sein kann, aber es ist leicht zu sagen, dass es durchaus Sinn macht, das zum Zeitpunkt des Schreibens
bar()
vorher aufgerufen werden mussfoo()
.Gefährliche Wiederverwendung. Ihre öffentliche Funktion hat wahrscheinlich die Eingabemöglichkeiten für jede private Funktion eingeschränkt. Wenn diese Eingabeannahmen nicht richtig erfasst werden (es ist schwierig, ALLE Annahmen zu dokumentieren), kann jemand eine Ihrer privaten Funktionen falsch wiederverwenden und Fehler in die Codebasis einbringen.
Zerlegen Sie Funktionen anhand ihres inneren Zusammenhalts und ihrer Kopplung, nicht anhand der absoluten Länge.
quelle
MainMethod
(leicht zu bekommen, normalerweise sind Symbole enthalten und Sie sollten eine Symboldereferenzdatei haben), die 2k Zeilen umfasst (Zeilennummern sind niemals enthalten) Fehlerberichte) und es ist ein NRE, das bei 1k dieser Zeilen auftreten kann, nun, ich werde verdammt sein, wenn ich mir das anschaue. Aber wenn sie mir sagen, dass es inSomeSubMethodA
8 Zeilen passiert ist und die Ausnahme ein NRE war, dann werde ich das wahrscheinlich leicht genug finden und beheben.IMHO, der Wert des Herausziehens von Codeblöcken nur als Mittel zum Aufbrechen der Komplexität, hängt häufig mit dem Unterschied in der Komplexität zusammen zwischen:
Eine vollständige und genaue Beschreibung der Funktionsweise des Codes in menschlicher Sprache, einschließlich der Behandlung von Eckfällen und
Der Code selbst.
Wenn der Code viel komplizierter als die Beschreibung ist, kann das Ersetzen des Inline-Codes durch einen Funktionsaufruf das Verständnis des umgebenden Codes erleichtern. Auf der anderen Seite können einige Konzepte besser in einer Computersprache als in der menschlichen Sprache ausgedrückt werden. Ich halte
w=x+y+z;
zum Beispiel für lesbarer alsw=addThreeNumbersAssumingSumOfFirstTwoDoesntOverflow(x,y,z);
.Wenn eine große Funktion aufgeteilt wird, gibt es immer weniger Unterschiede zwischen der Komplexität von Unterfunktionen und ihren Beschreibungen, und der Vorteil weiterer Unterteilungen nimmt ab. Wenn die Dinge so aufgeteilt werden, dass Beschreibungen komplizierter sind als Code, wird der Code durch weitere Aufteilungen verschlimmert.
quelle
int average3(int n1, int n2, int n3)
. Das Aufteilen der Funktion "Durchschnitt" würde bedeuten, dass jemand, der prüfen möchte, ob sie für Anforderungen geeignet ist, an zwei Stellen suchen muss.Es ist eine ausgleichende Herausforderung.
Feiner ist besser
Die private Methode liefert effektiv einen Namen für den enthaltenen Code und manchmal eine aussagekräftige Signatur (es sei denn, die Hälfte ihrer Parameter sind vollständig Ad-hoc- Daten mit unklaren, undokumentierten Abhängigkeiten).
Das Vergeben von Namen für Codekonstrukte ist im Allgemeinen gut, solange die Namen einen sinnvollen Vertrag für den Aufrufer andeuten und der Vertrag der privaten Methode genau dem entspricht, was der Name andeutet.
Indem der ursprüngliche Entwickler gezwungen ist, an sinnvolle Verträge für kleinere Teile des Codes zu denken, kann er einige Fehler erkennen und diese schmerzlos vermeiden, noch bevor er Entwickler- Tests durchführt. Dies funktioniert nur, wenn der Entwickler eine präzise Benennung anstrebt (einfach klingend, aber genau) und bereit ist, die Grenzen der privaten Methode so anzupassen, dass sogar eine präzise Benennung möglich ist.
Eine spätere Wartung wird ebenfalls unterstützt, da zusätzliche Benennungen dazu beitragen, dass sich der Code selbst dokumentiert .
Bis es zu fein wird
Kann man es übertreiben, kleinen Codestücken Namen zu geben, und am Ende zu viele, zu kleine private Methoden haben? Sicher. Eines oder mehrere der folgenden Symptome weisen darauf hin, dass die Methoden zu klein werden, um nützlich zu sein:
XxxInternal
oderDoXxx
, besonders wenn es kein einheitliches Schema für die Einführung dieser gibt.LogDiskSpaceConsumptionUnlessNoUpdateNeeded
quelle
Im Gegensatz zu dem, was die anderen gesagt haben, würde ich argumentieren, dass eine lange öffentliche Methode ein Designgeruch ist, der nicht durch Zerlegung in private Methoden korrigiert wird.
Wenn dies der Fall ist, würde ich argumentieren, dass jeder Schritt sein eigener erstklassiger Bürger mit jeweils einer Verantwortung sein sollte. In einem objektorientierten Paradigma würde ich vorschlagen, eine Schnittstelle und eine Implementierung für jeden Schritt so zu erstellen, dass jeder eine einzelne, leicht identifizierbare Verantwortung hat und auf eine Weise benannt werden kann, dass die Verantwortlichkeit klar ist. Auf diese Weise können Sie die (ehemals) große öffentliche Methode sowie jeden einzelnen Schritt unabhängig voneinander einem Komponententest unterziehen. Sie sollten auch alle dokumentiert werden.
Warum nicht in private Methoden zerlegen? Hier einige Gründe:
Dies ist ein Streit, den ich kürzlich mit einem meiner Kollegen geführt habe. Er argumentiert, dass das gesamte Verhalten eines Moduls in derselben Datei / Methode die Lesbarkeit verbessert. Ich bin damit einverstanden, dass der Code insgesamt leichter zu befolgen ist , aber mit zunehmender Komplexität ist es weniger einfach, über den Code nachzudenken. Wenn ein System wächst, wird es unlösbar, über das gesamte Modul nachzudenken. Wenn Sie komplexe Logik in mehrere Klassen mit jeweils einer Verantwortlichkeit zerlegen, ist es viel einfacher, über jeden Teil nachzudenken.
quelle