it-swarm.com.de

Sind private Methoden mit einer einzigen Referenz ein schlechter Stil?

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 in kleinere Schritte unterteilt werden kann, jede in ihrer eigenen privaten Methode. Dies würde die öffentliche Methode verkürzen, aber ich mache mir Sorgen, dass jeder, der die Methode liest, gezwungen ist, zu verschiedenen privaten Methoden zu springen, die Lesbarkeit beeinträchtigt.

Gibt es einen Konsens darüber? Ist es besser, lange öffentliche Methoden zu haben oder sie in kleinere Teile aufzuteilen, auch wenn jedes Teil nicht wiederverwendbar ist?

141
Jordak

Nein, das ist kein schlechter Stil. In der Tat ist es ein sehr guter Stil.

Private Funktionen müssen nicht nur wegen der Wiederverwendbarkeit existieren. Das ist sicherlich ein guter Grund, sie zu erschaffen, aber es gibt noch einen anderen: Zersetzung.

Stellen Sie sich eine Funktion vor, die zu viel bewirkt. Es ist hundert Zeilen lang und unmöglich zu überlegen.

Wenn Sie diese Funktion in kleinere Teile aufteilen, "erledigt" sie immer noch so viel Arbeit wie zuvor, jedoch in kleinere Teile. Es ruft andere Funktionen auf, die beschreibende Namen haben sollten. Die Hauptfunktion liest sich fast wie ein Buch: mache A, dann mache B, mache dann C usw. Die aufgerufenen Funktionen können nur an einer Stelle aufgerufen werden, aber jetzt sind sie kleiner. Eine bestimmte Funktion ist notwendigerweise von den anderen Funktionen getrennt: Sie haben unterschiedliche Bereiche.

Wenn Sie ein großes Problem in kleinere Probleme zerlegen, erhalten Sie mehrere Vorteile, selbst wenn diese kleineren Probleme (Funktionen) nur einmal verwendet/gelöst werden:

  • Lesbarkeit. Niemand kann eine monolithische Funktion lesen und verstehen, was sie vollständig bewirkt. Sie können sich entweder weiter belügen oder es in mundgerechte Stücke aufteilen, die Sinn machen.

  • Referenzort. Es wird jetzt unmöglich, eine Variable zu deklarieren und zu verwenden, sie dann zu behalten und 100 Zeilen später erneut zu verwenden. Diese Funktionen haben unterschiedliche Bereiche.

  • Testen. Während es nur notwendig ist, die öffentlichen Mitglieder einer Klasse zu testen, kann es wünschenswert sein, auch bestimmte private Mitglieder zu testen. Wenn es einen kritischen Abschnitt einer langen Funktion gibt, der vom Testen profitieren könnte, ist es unmöglich, sie unabhängig zu testen, ohne sie in eine separate Funktion zu extrahieren.

  • Modularität. Nachdem Sie nun über private Funktionen verfügen, finden Sie möglicherweise eine oder mehrere davon, die in eine separate Klasse extrahiert werden können, unabhängig davon, ob sie nur hier verwendet wird oder wiederverwendbar ist. 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 leichter zu verstehen und zu testen sind, ist ein zentraler Punkt von Onkel Bobs Buch Clean Code . Zum Zeitpunkt des Schreibens dieser Antwort ist das Buch neun Jahre alt, aber heute genauso aktuell wie damals.

204
user22815

Es ist wahrscheinlich eine großartige Idee!

Ich habe Probleme mit der Aufteilung langer linearer Aktionssequenzen in separate Funktionen, um lediglich die durchschnittliche Funktionslänge in Ihrer Codebasis zu reduzieren:

function step1(){
  // ...
  step2(zarb, foo, biz);
}

function step2(zarb, foo, biz){
  // ...
  step3(zarb, foo, biz, gleep);
}

function step3(zarb, foo, biz, gleep){
  // ...
}

Jetzt haben Sie tatsächlich Quellzeilen hinzugefügt und die Gesamtlesbarkeit erheblich reduziert. Vor allem, wenn Sie jetzt viele Parameter zwischen den einzelnen Funktionen übergeben, um den Status zu verfolgen. Huch!

Allerdings, wenn Sie es geschafft haben, eine oder mehrere Zeilen in eine reine Funktion zu extrahieren, die einem einzigen, klaren Zweck dient ( auch wenn nur einmal aufgerufen), dann Sie habe die Lesbarkeit verbessert:

function foo(){
  f = getFrambulation();
  g = deglorbFramb(f);
  r = reglorbulate(g);
}

In realen Situationen wird dies wahrscheinlich nicht einfach sein, aber Teile der reinen Funktionalität können oft herausgeputzt werden, wenn Sie lange genug darüber nachdenken.

Sie werden wissen, dass Sie auf dem richtigen Weg sind, wenn Sie Funktionen mit netten Verbenamen haben und wenn Ihre übergeordnete Funktion sie aufruft und das Ganze praktisch wie ein Absatz von Prosa liest.

Wenn Sie dann Wochen später zurückkehren, um weitere Funktionen hinzuzufügen, und feststellen, dass Sie tatsächlich wiederverwenden können eine dieser Funktionen, dann oh, entzückende Freude! Was für ein Wunder strahlende Freude !

36
DaveGauer

Die Antwort ist, dass es stark von der Situation abhängt. @Snowman behandelt die positiven Aspekte der Auflösung großer öffentlicher Funktionen, aber es ist 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 dazu führen, dass Code schwer lesbar und sehr zerbrechlich ist. Dies gilt insbesondere dann, wenn diese privaten Funktionen voneinander abhängig sind. Vermeiden Sie eng gekoppelte Funktionen.

  • Abstraktionen sind undicht . Es ist zwar schön 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 sind wichtig. Wenn Sie nicht klar erfassen, was eine Funktion in ihrem Namen tut, können Sie die Lesbarkeit erneut 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. Und während Sie in der öffentlichen Funktion sehen, welche privaten Funktionen aufgerufen werden, sehen Sie in der privaten Funktion nicht, welche öffentlichen Funktionen sie aufrufen. Dies kann zu einer "Fehlerbehebung" in einer privaten Funktion führen, die 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 immer noch nicht klar sein kann, aber es ist leicht zu sagen, dass es durchaus Sinn macht, dass bar() zum Zeitpunkt des Schreibens vor foo() aufgerufen werden muss .

  • Gefährliche Wiederverwendung. Ihre öffentliche Funktion hat wahrscheinlich die Eingabe 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 nicht ordnungsgemäß wiederverwenden und Fehler in die Codebasis einfügen.

Zerlegen Sie Funktionen basierend auf ihrer inneren Kohäsion und Kopplung, nicht auf der absoluten Länge.

19
dlasalle

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 ist vollständig ad-hoc ) Trampdaten mit unklaren, undokumentierten Abhängigkeiten).

Codekonstrukten Namen zu geben ist im Allgemeinen gut, solange die Namen einen aussagekräftigen Vertrag für den Aufrufer suggerieren und der Vertrag der privaten Methode genau entspricht was der Name andeutet.

Indem der Erstentwickler gezwungen wird, an sinnvolle Verträge für kleinere Teile des Codes zu denken, kann er einige Fehler erkennen und sie bereits vor Entwicklertests schmerzlos vermeiden. 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 eine präzise Benennung überhaupt möglich ist.

Eine spätere Wartung wird ebenfalls unterstützt, da durch zusätzliche Benennung der Code selbstdokumentierend wird.

  • Verträge über kleine Codeteile
  • Übergeordnete Methoden werden manchmal zu einer kurzen Folge von Aufrufen, von denen jeder etwas Bedeutendes und eindeutig Benanntes tut - begleitet von der dünnen Schicht der allgemeinen, äußersten Fehlerbehandlung. Eine solche Methode kann zu einer geschätzten Schulungsressource für jeden werden, der schnell ein Experte für die Gesamtstruktur des Moduls werden muss.

Bis es zu fein wird

Ist es möglich, kleine Codestücke zu übertreiben und zu viele, zu kleine private Methoden zu erhalten? Sicher. Eines oder mehrere der folgenden Symptome weisen darauf hin, dass die Methoden zu klein werden, um nützlich zu sein:

  • Zu viele Überladungen, die nicht wirklich alternative Signaturen für dieselbe wesentliche Logik darstellen, sondern nur einen einzigen festen Aufrufstapel.
  • Synonyme, die nur verwendet werden, um wiederholt auf dasselbe Konzept zu verweisen ("unterhaltsame Benennung")
  • Viele oberflächliche Namensdekorationen wie XxxInternal oder DoXxx, insbesondere wenn es kein einheitliches Schema für die Einführung dieser gibt.
  • Unbeholfene Namen, die fast länger sind als die Implementierung selbst, wie LogDiskSpaceConsumptionUnlessNoUpdateNeeded
2
Jirka Hanika

IMHO, der Wert des Herausziehens von Codeblöcken nur als Mittel zum Aufbrechen der Komplexität, hängt oft mit dem Unterschied in der Komplexität zusammen zwischen:

  1. Eine vollständige und genaue Beschreibung der Funktionsweise des Codes in der menschlichen Sprache einschließlich der Behandlung von Eckfällen und

  2. 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. Andererseits können einige Konzepte in einer Computersprache besser lesbar ausgedrückt werden als in einer menschlichen Sprache. Ich würde zum Beispiel w=x+y+z; Als lesbarer betrachten als w=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 als Code sind, wird der Code durch weitere Aufteilungen schlechter.

2
supercat

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.

Aber manchmal habe ich eine große öffentliche Methode, die in kleinere Schritte unterteilt werden kann

Wenn dies der Fall ist, würde ich argumentieren, dass jeder Schritt sein eigener erstklassiger Bürger mit jeweils einer einzigen Verantwortung sein sollte. In einem objektorientierten Paradigma würde ich vorschlagen, für jeden Schritt eine Schnittstelle und eine Implementierung so zu erstellen, dass jeder eine einzelne, leicht identifizierbare Verantwortung hat und so benannt werden kann, dass die Verantwortung klar ist. Auf diese Weise können Sie die (ehemals) große öffentliche Methode sowie jeden einzelnen Schritt unabhängig voneinander testen. Sie sollten auch alle dokumentiert werden.

Warum nicht in private Methoden zerlegen? Hier einige Gründe:

  • Enge Kupplung und Testbarkeit. Durch die Reduzierung der Größe Ihrer öffentlichen Methode haben Sie die Lesbarkeit verbessert, aber der gesamte Code ist immer noch eng miteinander verbunden. Sie können die einzelnen privaten Methoden (unter Verwendung der erweiterten Funktionen eines Testframeworks) einem Komponententest unterziehen, die öffentliche Methode jedoch nicht einfach unabhängig von den privaten Methoden testen. Dies widerspricht den Prinzipien des Unit-Tests.
  • Klassengröße und Komplexität. Sie haben die Komplexität einer Methode reduziert, aber die Komplexität der Klasse erhöht. Die öffentliche Methode ist einfacher zu lesen, aber die Klasse ist jetzt schwieriger zu lesen, da sie über mehr Funktionen verfügt, die ihr Verhalten definieren. Ich bevorzuge kleine Klassen mit Einzelverantwortung, daher ist eine lange Methode ein Zeichen dafür, dass die Klasse zu viel tut.
  • Kann nicht einfach wiederverwendet werden. Es ist häufig der Fall, dass die Wiederverwendbarkeit eines Codes mit zunehmender Reife nützlich ist. Wenn sich Ihre Schritte in privaten Methoden befinden, können sie nirgendwo anders wiederverwendet werden, ohne sie vorher irgendwie zu extrahieren. Darüber hinaus kann das Kopieren und Einfügen gefördert werden, wenn an anderer Stelle ein Schritt erforderlich ist.
  • Eine Aufteilung auf diese Weise ist wahrscheinlich willkürlich. Ich würde argumentieren, dass das Aufteilen einer langen öffentlichen Methode nicht so viele Gedanken oder Designüberlegungen erfordert, als ob Sie die Verantwortlichkeiten in Klassen aufteilen würden. Jede Klasse muss mit einem Eigennamen, einer Dokumentation und Tests begründet werden, während eine private Methode nicht so stark berücksichtigt wird.
  • Versteckt das Problem. Sie haben sich also entschlossen, Ihre öffentliche Methode in kleine private Methoden aufzuteilen. Jetzt gibt es kein Problem! Sie können immer mehr Schritte hinzufügen, indem Sie immer mehr private Methoden hinzufügen! Im Gegenteil, ich denke, das ist ein großes Problem. Es wird ein Muster zum Hinzufügen von Komplexität zu einer Klasse erstellt, auf das nachfolgende Fehlerkorrekturen und Feature-Implementierungen folgen. Bald werden Ihre privaten Methoden wachsen und sie müssen aufgeteilt werden.

ich mache mir jedoch Sorgen, dass jeder, der die Methode liest, gezwungen ist, zu verschiedenen privaten Methoden zu springen, die Lesbarkeit beeinträchtigt

Dies ist ein Streit, den ich kürzlich mit einem meiner Kollegen hatte. Er argumentiert, dass das gesamte Verhalten eines Moduls in derselben Datei/Methode die Lesbarkeit verbessert. Ich bin damit einverstanden, dass der Code einfacher zu folgen ist, wenn alles zusammen ist, aber der Code ist weniger einfach zu Grund dafür mit zunehmender Komplexität. Wenn ein System wächst, wird es schwierig, über das gesamte Modul als Ganzes nachzudenken. Wenn Sie komplexe Logik in mehrere Klassen mit jeweils einer Verantwortung zerlegen, wird es viel einfacher, über jeden Teil nachzudenken.

1
Samuel