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?
quelle
Antworten:
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 undlargeReportProcessingFunction
sollte nicht direkt davon abhängen.Bitte beachten Sie, dass dies jedoch eine schlechte Wahl für die Benennung ist:
Sie sehen, es
callAllSection1Functions
handelt sich um einen Namen, der eigentlich keine Abstraktion liefert, weil er nicht sagt, was er tut, sondern wie er es tut. Es sollte stattdessen aufgerufenprocessSection1
werden oder was auch immer im Kontext tatsächlich sinnvoll ist .quelle
callAllSection1Functions
Details zur Implementierung. Aus der Sicht von außen spielt es keine Rolle, ob esprocessSection1
seine 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 AufrufprocessSection1
, 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.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
Section
Klasse 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?
quelle
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.
quelle
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.
quelle
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.
quelle
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:
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?
quelle
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.
quelle
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.
quelle