Der sauberste Weg, logisch prozedurale Software in einer OO-Sprache zu schreiben

12

Ich bin Elektrotechniker und weiß nicht, was zum Teufel ich mache. Bitte speichern Sie die zukünftigen Betreuer meines Codes.

Vor kurzem habe ich an einer Reihe kleinerer Programme (in C #) gearbeitet, deren Funktionalität logisch "prozedural" ist. Eines davon ist beispielsweise ein Programm, das Informationen aus verschiedenen Datenbanken sammelt, diese Informationen verwendet, um eine Art Zusammenfassungsseite zu generieren, sie zu drucken und dann zu beenden.

Die dafür erforderliche Logik liegt bei etwa 2000 Zeilen. Ich möchte das alles auf keinen Fall in einem einzigen packen main()und dann mit #regions "aufräumen", wie es einige frühere Entwickler getan haben (Schauder).

Hier sind einige Dinge, die ich bereits ohne große Befriedigung ausprobiert habe:

Erstellen Sie statische Dienstprogramme für jede grobe Funktionalität, z. B. einen DatabaseInfoGetter, einen SummaryPageGenerator und eine PrintUtility. Die Hauptfunktion sieht folgendermaßen aus:

int main()
{
    var thisThing = DatabaseInfoGetter.GetThis();
    var thatThing = DatabaseInfoGetter.GetThat();
    var summary = SummaryPageGenerator.GeneratePage(thisThing, thatThing);

    PrintUtility.Print(summary);
}

Für ein Programm habe ich sogar Schnittstellen verwendet

int main()
{
    /* pardon the psuedocode */

    List<Function> toDoList = new List<Function>();
    toDoList.Add(new DatabaseInfoGetter(serverUrl));
    toDoList.Add(new SummaryPageGenerator());
    toDoList.Add(new PrintUtility());

    foreach (Function f in toDoList)
        f.Do();
}

Nichts davon fühlt sich richtig an. Wenn der Code länger wird, werden beide Ansätze hässlich.

Was ist ein guter Weg, um solche Dinge zu strukturieren?

Lombard
quelle
2
Ich bin nicht sicher, ob dies streng genommen ein Duplikat ist, aber bitte lesen Sie die Einzelmethode mit vielen Parametern und viele Methoden, die der Reihe nach aufgerufen werden müssen .
@Snowman, als jemand, der neu im Schreiben größerer Codeteile ist, ist es beruhigend zu sehen, dass ich nicht die einzige Person bin, die auf diese Art von Problemen stößt. Ihre Antwort auf diese Frage hat mir gefallen. Es hilft.
Lombard
@Lombard Die Idee, die Komplexität auf ein überschaubares Maß zu reduzieren, ist eine wirklich gute Fähigkeit, sich zu entwickeln. Niemand kann eine große Codebasis verstehen, nicht einmal Superman (vielleicht Batman). Es ist entscheidend, Wege zu finden, um Ideen einfach auszudrücken und den Code selbst zu dokumentieren.
"Vor kurzem habe ich an einer Reihe kleinerer Programme (in C #) gearbeitet, deren Funktionalität logisch" prozedural "ist. Eines davon ist beispielsweise ein Programm, das Informationen aus verschiedenen Datenbanken sammelt und diese Informationen verwendet, um eine Art Zusammenfassung zu erstellen Seite, druckt es aus und beendet es dann. ": Sind Sie sicher, dass Sie" prozedural "nicht mit" imperativ "verwechseln? Sowohl prozedural als auch objektorientiert sind (können) zwingend erforderlich, dh sie können Operationen ausdrücken, die in einer Sequenz ausgeführt werden sollen.
Giorgio

Antworten:

18

Da Sie kein professioneller Programmierer sind, würde ich empfehlen, sich an die Einfachheit zu halten. Es wird für Programmierer VIEL einfacher sein, Ihren modularisierten prozeduralen Code zu verwenden und ihn später zu OO zu machen, als für sie, ein schlecht geschriebenes OO-Programm zu reparieren. Wenn Sie keine Erfahrung haben, können Sie OO-Programme erstellen, die sich in ein unheiliges Chaos verwandeln können, das weder Ihnen noch denjenigen hilft, die nach Ihnen kommen.

Ich denke, Ihr erster Instinkt, der "this thing-that thing" -Code im ersten Beispiel, ist der richtige Weg. Es ist klar und offensichtlich, was Sie tun möchten. Sorgen Sie sich nicht zu sehr um die Codeeffizienz, Klarheit ist viel wichtiger.

Wenn ein Codesegment zu lang ist, teilen Sie es in mundgerechte Abschnitte auf, von denen jeder seine eigene Funktion hat. Wenn es zu kurz ist, sollten Sie weniger Module verwenden und mehr in eine Linie bringen.

---- Nachtrag: OO Design Traps

Erfolgreich mit OO-Programmierung zu arbeiten kann schwierig sein. Es gibt sogar einige Leute , die das ganze Modell für fehlerhaft halten. Es gibt ein sehr gutes Buch, das ich beim ersten Erlernen der OO-Programmierung verwendet habe: Thinking in Java (jetzt in der 4. Ausgabe). Der gleiche Autor hat ein entsprechendes Buch für C ++. Es gibt tatsächlich eine andere Frage zu Programmierern, die sich mit häufigen Fallstricken in der objektorientierten Programmierung befasst .

Einige Fallstricke sind raffiniert, aber es gibt viele Möglichkeiten, Probleme auf sehr grundlegende Weise zu verursachen. Zum Beispiel gab es vor ein paar Jahren einen Praktikanten in meiner Firma, der die erste Version einer von mir geerbten Software schrieb und Schnittstellen für alles erstellte, was möglich warEines Tages haben mehrere Implementierungen. Natürlich gab es in 98% der Fälle nur eine einzige Implementierung, daher wurde der Code mit nicht verwendeten Schnittstellen geladen, was das Debuggen sehr ärgerlich machte, da Sie nicht über einen Schnittstellenaufruf zurücktreten können, sodass Sie am Ende eine ausführen müssen Textsuche für die Implementierung (obwohl ich jetzt IntelliJ verwende, hat es die Funktion "Alle Implementierungen anzeigen", aber damals hatte ich das nicht). Die Regel hier ist die gleiche wie bei der prozeduralen Programmierung: Immer eine Sache fest codieren. Erstellen Sie nur dann eine Abstraktion, wenn Sie zwei oder mehr Dinge haben.

Eine ähnliche Art von Designfehlern findet sich in der Java Swing API. Sie verwenden ein Publish-Subscribe-Modell für das Swing-Menüsystem. Dies macht das Erstellen und Debuggen von Menüs in Swing zu einem Albtraum. Die Ironie ist, dass es völlig sinnlos ist. Es gibt praktisch nie eine Situation, in der mehrere Funktionen einen Menüklick "abonnieren" müssten. Außerdem war Publish-Subscribe dort eine völlige Fehlzündung, da normalerweise immer ein Menüsystem verwendet wird. Es ist nicht so, dass Funktionen zufällig abonniert und dann abbestellt werden. Die Tatsache, dass "professionelle" Entwickler bei Sun einen solchen Fehler gemacht haben, zeigt nur, wie einfach es selbst für Profis ist, monumentale Fehler im OO-Design zu machen.

Ich bin ein sehr erfahrener Entwickler mit jahrzehntelanger Erfahrung in der OO-Programmierung, aber selbst ich würde als erster zugeben, dass es Tonnen gibt, die ich nicht kenne, und selbst jetzt bin ich sehr vorsichtig, wenn ich viel OO verwende. Ich habe lange Vorträge von einem Kollegen gehört, der ein OO-Fanatiker war, wie man bestimmte Designs macht. Er wusste wirklich, was er tat, aber ehrlich gesagt fiel es mir schwer, seine Programme zu verstehen, weil sie so ausgefeilte Designmodelle hatten.

Tyler Durden
quelle
1
+1 für "Klarheit ist viel wichtiger [als Effizienz]"
Stephen
Könnten Sie mich auf einen Link verweisen, der beschreibt, was ein "schlecht geschriebenes OO-Programm" ausmacht, oder in einer Bearbeitung weitere Informationen dazu hinzufügen?
Lombard
@Lombard Ich habe das gemacht.
Tyler Durden
1

Der erste Ansatz ist in Ordnung. In prozeduralen Sprachen wie C besteht der Standardansatz darin, die Funktionalität in Namespaces zu unterteilen - die Verwendung statischer Klassen wie DatabaseInfoGetterist im Grunde dasselbe. Offensichtlich führt dieser Ansatz zu einem einfacheren, modulareren und besser lesbaren / wartbaren Code, als alles in eine Klasse zu verschieben, selbst wenn alles in Methoden unterteilt ist.

Apropos, versuchen Sie, die Methoden darauf zu beschränken, so wenig Aktionen wie möglich auszuführen. Einige Programmierer bevorzugen etwas weniger Granularität, aber große Methoden werden immer als schädlich angesehen.

Wenn Sie immer noch mit Komplexität zu kämpfen haben, müssen Sie Ihr Programm höchstwahrscheinlich noch weiter aufteilen. Erstellen Sie mehr Ebenen in der Hierarchie - möglicherweise DatabaseInfoGettermüssen Sie auf andere Klassen wie ProductTableEntryoder so verweisen . Sie schreiben prozeduralen Code, aber denken Sie daran, dass Sie C # verwenden und OOP Ihnen eine Reihe von Tools zur Reduzierung der Komplexität bietet, z.

int main() 
{
    var thisthat = Database.GetThisThat();
}

public class ThisThatClass
{
    public String This;
    public String That;
}

public static class Database 
{
    public ThisThatClass GetThisThat()
    {
        return new ThisThatClass 
        {
            This = GetThis(),
            That = GetThat()
        };
    }

    public static String GetThis() 
    { 
        //stuff
    }
    public static String GetThat() 
    { 
        //stuff
    }

}

Seien Sie auch vorsichtig mit statischen Klassen. Datenbankklassen sind gute Kandidaten. Solange Sie normalerweise eine Datenbank haben, haben Sie die Idee.

Wenn Sie in mathematischen Funktionen denken und C # nicht zu Ihrem Stil passt, probieren Sie etwas wie Scala, Haskell usw. Sie sind auch cool.

Skepsis
quelle
0

Ich antworte 4 Jahre zu spät, aber wenn Sie versuchen, Dinge in einer OO-Sprache prozedural zu machen, arbeiten Sie an einem Antimuster. Das sollten Sie nicht tun! Ich habe einen Blog gefunden, der sich mit diesem Antipattern befasst und eine Lösung dafür zeigt, aber es erfordert viel Refactoring und eine Änderung der Denkweise. Weitere Informationen finden Sie unter Prozeduraler Code im objektorientierten Code .
Wie Sie "dies" und "das" erwähnt haben, schlagen Sie grundsätzlich vor, dass Sie zwei verschiedene Klassen haben. A Diese Klasse (schlechter Name!) Und eine Diese Klasse. Und Sie könnten dies zB übersetzen:

var summary = SummaryPageGenerator.GeneratePage(thisThing, thatThing);

das mögen:

var summary = SummaryPageGenerator.GeneratePage(new This(DatabaseInfoGetter), new That(DatabaseInfoGetter));

Nun wäre es interessant, diese über 2.000 Zeilen prozeduralen Codes zu sehen und zu bestimmen, wie verschiedene Teile in separaten Klassen zusammengefasst werden könnten, und verschiedene Prozeduren in diese Klassen zu verschieben.
Jede Klasse sollte einfach sein und sich darauf konzentrieren, nur eine Sache zu tun. Und es scheint mir, dass einige Teile des Codes bereits Superklassen behandeln, die tatsächlich viel zu groß sind, um nützlich zu sein. Der DatabaseInfoGetter zum Beispiel klingt viel zu verwirrend. Was bekommt es überhaupt? Es klingt zu allgemein. SummaryPageGenerator ist jedoch schrecklich spezifisch! Und PrintUtility ist wieder generisch.
Zu Beginn sollten Sie daher die generischen Namen für Ihre Klassen vermeiden und stattdessen spezifischere Namen verwenden. Die PrintUtility-Klasse wäre beispielsweise eher eine Print-Klasse mit einer Header-Klasse, einer Footer-Klasse, einer TextBlock-Klasse, einer Image-Klasse und möglicherweise einer Möglichkeit, anzugeben, wie sie im Druck selbst fließen würden PrintUtility- Namespace .
Für die Datenbank ähnliche Geschichte. Selbst wenn Sie das Entity Framework für den Datenbankzugriff verwenden, sollten Sie es auf die von Ihnen verwendeten Elemente beschränken und in logische Gruppen unterteilen.
Aber ich frage mich, ob Sie sich nach 4 Jahren immer noch mit diesem Problem befassen. Wenn ja, dann ist es eine Denkweise, die vom "prozeduralen Konzept" zum "objektorientierten Konzept" geändert werden muss. Was eine Herausforderung ist, wenn Sie nicht die Erfahrung haben ...

Wim zehn Brink
quelle
-3

Meiner Meinung nach sollten Sie Ihren Code so ändern, dass er einfach, konsistent und lesbar ist.

Ich habe einige Blog-Beiträge zu diesem Thema geschrieben und vertraue mir, dass sie einfach zu verstehen sind: Was ist guter Code?

Einfach: Genau wie eine Leiterplatte verschiedene Teile enthält, von denen jedes eine Verantwortung trägt. Code sollte in kleinere, einfachere Teile unterteilt werden.

Konsistent: Verwenden Sie Muster, einen Standard zum Benennen von Elementen und Dingen. Sie mögen Werkzeuge, die immer auf die gleiche Weise funktionieren, und möchten wissen, wo Sie sie finden. Ähnlich verhält es sich mit Code.

Lesbar: Verwenden Sie keine Akronyme, es sei denn, sie sind in der Domäne, auf die die Software abzielt (mattnz), weit verbreitet und bekannt. Bevorzugen Sie aussprechbare Namen, die klar und leicht zu verstehen sind.

Pablo Granados
quelle
1
Akronyme sind akzeptabel, wenn sie in der Domäne, auf die die Software abzielt, weit verbreitet und bekannt sind. Versuchen Sie, eine Flugsicherungssoftware zu schreiben, ohne die zu verwenden - wir haben Akronyme, die aus Akronymen bestehen -, niemand würde wissen, wovon Sie gesprochen haben, wenn Sie den vollständigen Namen verwendet haben. Dinge wie HTTP für die Vernetzung, ABS in der Automobilindustrie usw. sind völlig akzeptabel.
Mattnz