Funktionen, die nur andere Funktionen aufrufen. Ist das eine gute Übung?

13

Momentan arbeite ich an einer Reihe von Berichten mit vielen verschiedenen Abschnitten (die alle unterschiedliche Formatierungen erfordern) und ich versuche herauszufinden, wie ich meinen Code am besten strukturieren kann. Ähnliche Berichte, die wir in der Vergangenheit erstellt haben, haben sehr große Funktionen (über 200 Zeilen), mit denen alle Daten für den Bericht bearbeitet und formatiert werden, sodass der Workflow ungefähr so ​​aussieht:

DataTable reportTable = new DataTable();
void RunReport()
{
    reportTable = DataClass.getReportData();
    largeReportProcessingFunction();
    outputReportToUser();
}

Ich möchte in der Lage sein, diese großen Funktionen in kleinere Teile aufzuteilen, aber ich befürchte, dass ich am Ende Dutzende nicht wiederverwendbarer Funktionen und eine ähnliche Funktion "Alles hier machen" habe, deren einzige Aufgabe darin besteht, zu arbeiten Rufen Sie alle diese kleineren Funktionen folgendermaßen auf:

void largeReportProcessingFunction()
{
    processSection1HeaderData();
    calculateSection1HeaderAverages();
    formatSection1HeaderDisplay();
    processSection1SummaryTableData();
    calculateSection1SummaryTableTotalRow();
    formatSection1SummaryTableDisplay();
    processSection1FooterData();
    getSection1FooterSummaryTotals();
    formatSection1FooterDisplay();

    processSection2HeaderData();
    calculateSection1HeaderAverages();
    formatSection1HeaderDisplay();
    calculateSection1HeaderAverages();
    ...
}

Oder wenn wir noch einen Schritt weiter gehen:

void largeReportProcessingFunction()
{
    callAllSection1Functions();

    callAllSection2Functions();

    callAllSection3Functions();
    ...        
}

Ist das wirklich eine bessere Lösung? Aus organisatorischer Sicht denke ich, dass dies der Fall ist (dh, dass alles viel besser organisiert ist als sonst), aber was die Lesbarkeit des Codes anbelangt, bin ich mir nicht sicher (potenziell große Funktionsketten, die nur andere Funktionen aufrufen).

Gedanken?

Eric C.
quelle
Am Ende des Tages ... ist es besser lesbar? Ja, also ist es eine bessere Lösung.
Sylvanaar

Antworten:

18

Dies wird allgemein als funktionale Zersetzung bezeichnet und ist im Allgemeinen eine gute Sache, wenn sie richtig durchgeführt wird.

Außerdem sollte die Implementierung einer Funktion auf einer einzigen Abstraktionsebene erfolgen. Wenn Sie übernehmen largeReportProcessingFunction, müssen Sie festlegen, welche verschiedenen Verarbeitungsschritte in welcher Reihenfolge ausgeführt werden sollen. Die Implementierung jedes dieser Schritte erfolgt auf einer darunter liegenden Abstraktionsebene und largeReportProcessingFunctionsollte nicht direkt davon abhängen.

Bitte beachten Sie, dass dies jedoch eine schlechte Wahl für die Benennung ist:

void largeReportProcessingFunction() {
    callAllSection1Functions();
    callAllSection2Functions();
    callAllSection3Functions();
    ...        
}

Sie sehen, es callAllSection1Functionshandelt sich um einen Namen, der eigentlich keine Abstraktion liefert, weil er nicht sagt, was er tut, sondern wie er es tut. Es sollte stattdessen aufgerufen processSection1werden oder was auch immer im Kontext tatsächlich sinnvoll ist .

back2dos
quelle
6
Ich stimme dieser Antwort im Prinzip zu, außer ich kann nicht sehen, wie "processSection1" ein aussagekräftiger Name ist. Es ist praktisch gleichbedeutend mit "callAllSection1Functions".
Eric King
2
@EricKing: Leckt callAllSection1FunctionsDetails zur Implementierung. Aus der Sicht von außen spielt es keine Rolle, ob es processSection1seine Arbeit auf "alle Funktionen von Abschnitt 1" verschiebt oder ob es sie direkt ausführt oder ob es Pixie-Dust verwendet, um ein Einhorn zu beschwören, das die eigentliche Arbeit erledigt. Alles , was wirklich wichtig ist, dass nach einem Aufruf processSection1, section1 werden kann berücksichtigt verarbeitet . Ich stimme zu, dass die Verarbeitung ein generischer Name ist, aber wenn Sie eine hohe Kohäsion haben (nach der Sie immer streben sollten), ist Ihr Kontext normalerweise eng genug, um ihn zu disambiguieren.
back2dos
2
Ich meinte nur, dass Methoden namens "processXXX" fast immer schreckliche, nutzlose Namen sind. Alle Methoden 'verarbeiten' Dinge, daher ist die Bezeichnung 'processXXX' ungefähr so ​​nützlich wie die Bezeichnung 'doSomethingWithXXX' oder 'manipulateXXX' und ähnliches. Es gibt fast immer einen besseren Namen für Methoden mit dem Namen 'processXXX'. Aus praktischer Sicht ist es genauso nützlich (nutzlos) wie 'callAllSection1Functions'.
Eric King
4

Sie sagten, Sie verwenden C #, und ich frage mich, ob Sie eine strukturierte Klassenhierarchie aufbauen können, indem Sie die Tatsache ausnutzen, dass Sie eine objektorientierte Sprache verwenden. Wenn es beispielsweise sinnvoll ist, den Bericht in Abschnitte zu unterteilen, lassen Sie eine SectionKlasse zu und erweitern Sie sie für kundenspezifische Anforderungen.

Es kann sinnvoll sein, darüber nachzudenken, als würden Sie eine nicht interaktive Benutzeroberfläche erstellen. Stellen Sie sich die Objekte vor, die Sie möglicherweise erstellen, als wären sie unterschiedliche GUI-Komponenten. Fragen Sie sich, wie ein Basisbericht aussehen würde? Wie wäre es mit einem komplexen? Wo sollen die Erweiterungspunkte sein?

Jeremy Heiler
quelle
3

Es hängt davon ab, ob. Wenn alle Funktionen, die Sie erstellen, wirklich eine einzige Arbeitseinheit sind, ist es in Ordnung, wenn nur die Zeilen 1-15, 16-30, 31-45 ihrer aufrufenden Funktion schlecht sind. Lange Funktionen sind nicht schlecht, wenn sie eine Sache tun, wenn Sie 20 Filter für Ihren Bericht haben, die eine Validierungsfunktion haben, die alle zwanzig prüft, wird es lange dauern. Das ist in Ordnung, denn die Aufteilung nach Filtern oder Filtergruppen erhöht die Komplexität nur, ohne dass ein wirklicher Nutzen daraus resultiert.

Viele private Funktionen erschweren auch das automatisierte Testen von Einheiten, daher werden einige versuchen, dies aus diesem Grund zu vermeiden. Meiner Meinung nach ist dies jedoch eine eher schlechte Argumentation, da dadurch tendenziell ein größeres, komplexeres Design für das Testen erstellt wird.

Ryathal
quelle
3
Meiner Erfahrung nach langen Funktionen sind böse.
Bernard
Wenn Ihr Beispiel in der OO-Sprache wäre, würde ich eine Basisfilterklasse erstellen und jeder der 20 Filter würde sich in verschiedenen Ableitungsklassen befinden. Die switch-Anweisung wird durch einen Erstellungsaufruf ersetzt, um den richtigen Filter zu erhalten. Jede Funktion macht eine Sache und alle sind klein.
DXM
3

Versuchen Sie, mehr in Objekten zu denken, da Sie C # verwenden. Es sieht so aus, als würden Sie immer noch prozedural codieren, indem Sie "globale" Klassenvariablen haben, von denen nachfolgende Funktionen abhängen.

Das Aufteilen Ihrer Logik in separate Funktionen ist definitiv besser, als die gesamte Logik in einer einzigen Funktion zu haben. Ich wette, Sie könnten noch weiter gehen, indem Sie auch die Daten, an denen die Funktionen arbeiten, in mehrere Objekte aufteilen.

Verwenden Sie eine ReportBuilder-Klasse, in die Sie die Berichtsdaten übergeben können. Wenn Sie ReportBuilder in separate Klassen aufteilen können, tun Sie dies ebenfalls.

Kodierer
quelle
2
Prozeduren erfüllen eine vollkommen gute Funktion.
DeadMG
1

Ja.

Wenn Sie ein Codesegment haben, das an mehreren Stellen verwendet werden muss, besitzen Sie eine eigene Funktion. Andernfalls müssen Sie die Änderung an mehreren Stellen vornehmen, wenn Sie Änderungen an der Funktionalität vornehmen müssen. Dies erhöht nicht nur die Arbeit, sondern auch das Risiko von Fehlern, die auftreten, wenn der Code an einer Stelle geändert wird, an keiner anderen, oder wenn ein, aber an einer anderen Stelle, aber nicht an der anderen eingeführt wird.

Außerdem wird die Methode lesbarer, wenn beschreibende Methodennamen verwendet werden.

SoylentGray
quelle
1

Die Tatsache, dass Sie zur Unterscheidung von Funktionen die Nummerierung verwenden, deutet darauf hin, dass es Probleme mit der Duplizierung gibt oder dass eine Klasse zu viel tut. Das Aufteilen einer großen Funktion in viele kleinere Funktionen kann ein nützlicher Schritt sein, um die Duplizierung zu verbessern, sollte jedoch nicht der letzte sein. Beispielsweise erstellen Sie die zwei Funktionen:

processSection1HeaderData();
processSection2HeaderData();

Gibt es keine Ähnlichkeiten im Code, der zum Verarbeiten von Abschnittsüberschriften verwendet wird? Können Sie für beide eine gemeinsame Funktion extrahieren, indem Sie die Unterschiede parametrisieren?

Garrett Hall
quelle
Dies ist eigentlich kein Seriencode, daher sind die Funktionsnamen nur Beispiele (in der Produktion sind die Funktionsnamen aussagekräftiger). Im Allgemeinen gibt es keine Ähnlichkeiten zwischen verschiedenen Abschnitten (obwohl ich bei Ähnlichkeiten versuche, Code so oft wie möglich wiederzuverwenden).
Eric C.
1

Das Aufteilen einer Funktion in logische Unterfunktionen ist hilfreich, auch wenn die Unterfunktionen nicht wiederverwendet werden - es zerlegt sie in kurze, leicht verständliche Blöcke. Dies muss jedoch durch logische Einheiten erfolgen, nicht nur durch n Zeilen. Wie Sie es aufteilen sollten, hängt von Ihrem Code ab. Häufige Themen sind Schleifen, Bedingungen und Operationen für dasselbe Objekt. im Grunde alles, was Sie als diskrete Aufgabe beschreiben können.

Also, ja, es ist ein guter Stil - das mag seltsam klingen, aber angesichts Ihrer eingeschränkten Wiederverwendung würde ich empfehlen, dass Sie über die Wiederverwendung von ATTEMPTING sorgfältig nachdenken. Stellen Sie sicher, dass die Verwendung wirklich identisch ist und nicht nur zufällig. Der Versuch, alle Fälle im selben Block zu behandeln, führt häufig dazu, dass Sie die große unbeholfene Funktion an der ersten Stelle haben.

jmoreno
quelle
0

Ich denke, das ist hauptsächlich eine persönliche Präferenz. Wenn Ihre Funktionen wiederverwendet würden (und sind Sie sicher, dass Sie sie nicht allgemeiner und wiederverwendbarer machen könnten?), Würde ich sie definitiv aufteilen. Ich habe auch gehört, dass es ein gutes Prinzip ist, dass keine Funktion oder Methode mehr als 2-3 Code-Bildschirme enthalten sollte. Wenn also Ihre große Funktion immer weiter besteht, wird Ihr Code möglicherweise besser lesbar, um ihn aufzuteilen.

Sie erwähnen nicht, welche Technologie Sie zum Entwickeln dieser Berichte verwenden. Möglicherweise verwenden Sie C #. Ist das korrekt? In diesem Fall können Sie auch die Direktive #region als Alternative in Betracht ziehen, wenn Sie Ihre Funktion nicht in kleinere Funktionen aufteilen möchten.

Joshua Carmody
quelle
Ja, ich arbeite in C #. Es ist möglich, dass ich einige Funktionen wiederverwenden kann, aber für viele dieser Berichte haben die verschiedenen Abschnitte sehr unterschiedliche Daten und Layouts (Kundenanforderungen).
Eric C.
1
+1 mit Ausnahme der vorgeschlagenen Regionen. Ich würde keine Regionen verwenden.
Bernard
@Bernard - Aus irgendeinem Grund? Ich gehe davon aus, dass der Fragesteller die Verwendung von #regions nur in Betracht ziehen würde, wenn er die Aufteilung der Funktion bereits ausgeschlossen hätte.
Joshua Carmody
2
Regionen neigen dazu, Code zu verbergen und können missbraucht werden (dh verschachtelte Regionen). Ich mag es, den gesamten Code auf einmal in einer Codedatei zu sehen. Wenn ich Code in einer Datei visuell trennen muss, verwende ich eine Reihe von Schrägstrichen.
Bernard
2
Regionen sind normalerweise ein Zeichen dafür, dass der Code überarbeitet werden muss
Coder