Ich bin ein langjähriger Entwickler (ich bin 49), aber eher neu in der objektorientierten Entwicklung. Ich lese seit Bertrand Meyers Eiffel über OO, aber ich habe wirklich wenig über OO programmiert.
Der Punkt ist, dass jedes Buch über OO-Design mit einem Beispiel für ein Boot, ein Auto oder ein beliebiges Objekt beginnt, das wir sehr oft verwenden. Sie beginnen, Attribute und Methoden hinzuzufügen und zu erklären, wie sie den Zustand des Objekts modellieren und was damit getan werden kann es.
Sie lauten normalerweise: "Je besser das Modell ist, desto besser repräsentiert es das Objekt in der Anwendung und desto besser kommt alles heraus".
Bisher so gut, aber andererseits habe ich mehrere Autoren gefunden, die Rezepte wie "Eine Klasse sollte auf nur eine Seite passen" (ich würde hinzufügen "Auf welcher Bildschirmgröße?", Jetzt, wo wir es nicht versuchen Code drucken!).
Nehmen wir zum Beispiel eine PurchaseOrder
Klasse, die eine endliche Zustandsmaschine hat, die ihr Verhalten steuert, und eine Sammlung von PurchaseOrderItem
, eines der Argumente hier ist, dass wir eine PurchaseOrder
einfache Klasse mit einigen Methoden (etwas mehr als eine Datenklasse) verwenden sollten und haben eine PurchaseOrderFSM
"Expertenklasse", die die endliche Zustandsmaschine für die behandelt PurchaseOrder
.
Ich würde sagen, das fällt unter die Klassifizierung "Feature Envy" oder "Inappropriate Intimacy" von Jeff Atwoods Code Smells- Post zu Coding Horror. Ich würde es nur gesunden Menschenverstand nennen. Wenn ich ausgeben kann, genehmigen oder zu stornieren meine wirkliche Bestellung, dann die PurchaseOrder
sollte Klasse haben issuePO
, approvePO
und cancelPO
Methoden.
Gehört das nicht zu den uralten Prinzipien „Zusammenhalt maximieren“ und „Kopplung minimieren“, die ich als Eckpfeiler von OO verstehe?
Hilft das nicht auch der Wartbarkeit der Klasse?
quelle
PurchaseOrder
, können Sie einfach Ihre Methoden nennenissue
,approve
undcancel
. DasPO
Suffix fügt nur einen negativen Wert hinzu.Antworten:
Eine Klasse sollte das Prinzip der Einzelverantwortung anwenden. Die meisten sehr großen Klassen, die ich gesehen habe, haben mit vielen Dingen zu tun, weshalb sie zu groß sind. Schauen Sie sich jede Methode und jeden Code an, um zu entscheiden, ob sie zu dieser Klasse gehören oder ob separater, doppelter Code ein Hinweis ist. Möglicherweise haben Sie eine issuePO-Methode, aber enthält sie beispielsweise 10 Zeilen Datenzugriffscode? Dieser Code sollte wahrscheinlich nicht da sein.
quelle
Meiner Meinung nach spielt die Klassengröße keine Rolle, solange die Variablen, Funktionen und Methoden für die betreffende Klasse relevant sind.
quelle
Das größte Problem bei großen Klassen ist das Testen dieser Klassen oder die Interaktion mit anderen Klassen. Große Klassen sind an sich kein Problem, aber wie alle Gerüche deutet dies auf ein potenzielles Problem hin. Wenn die Klasse groß ist, ist dies im Allgemeinen ein Hinweis darauf, dass die Klasse mehr tut als eine einzelne Klasse sollte.
Wenn die Klasse
car
zu groß ist, ist dies für Ihre Autoanalogie wahrscheinlich ein Hinweis darauf, dass sie in Klasseengine
, Klassedoor
s und Klassewindshield
s usw. aufgeteilt werden muss.quelle
Ich glaube nicht, dass es eine spezifische Definition für eine zu große Klasse gibt. Anhand der folgenden Richtlinien würde ich jedoch bestimmen, ob ich meine Klasse umgestalten oder den Umfang der Klasse neu definieren soll:
Stellen Sie sich in Bezug auf 1 vor, Sie definieren Ihre Windschutzscheibengröße, Radgröße, das Rücklicht-Lampenmodell und 100 weitere Details in Ihrer Klasse
car
. Obwohl sie alle für das Auto "relevant" sind, ist es sehr schwierig, das Verhalten einer solchen Klasse zu verfolgencar
. Und wenn Ihr Kollege eines Tages versehentlich (oder in einigen Fällen absichtlich) die Größe der Windschutzscheibe basierend auf dem Rücklichtbirnenmodell ändert, müssen Sie für immer herausfinden, warum die Windschutzscheibe nicht mehr in Ihr Auto passt.Stellen Sie sich in Bezug auf 2 vor, dass Sie versuchen, alle Verhaltensweisen zu definieren, die ein Auto in der Klasse haben
car
kann,car::open_roof()
die jedoch nur für Cabriolets verfügbar sind undcar::open_rear_door()
nicht für diese zweitürigen Autos gelten. Obwohl Cabrios und Zweitürer per Definition "Autos" sind,car
erschwert ihre Implementierung in der Klasse die Klasse. Die Klasse wird schwieriger zu bedienen und zu warten.Abgesehen von diesen beiden Richtlinien würde ich eine klare Definition des Klassenumfangs vorschlagen. Wenn Sie den Bereich definiert haben, implementieren Sie die Klasse streng basierend auf der Definition. Die meisten Leute bekommen eine "zu große" Klasse wegen der schlechten Definition des Gültigkeitsbereichs oder wegen der willkürlichen Eigenschaften, die der Klasse hinzugefügt wurden
quelle
Sagen Sie in 300 Zeilen, was Sie brauchen
Hinweis: Bei dieser Faustregel wird davon ausgegangen, dass Sie dem Ansatz "Eine Klasse pro Datei" folgen, der für sich genommen eine gute Idee für die Wartbarkeit darstellt
Dies ist keine absolute Regel, aber wenn ich in einer Klasse über 200 Codezeilen sehe, bin ich misstrauisch. 300 Zeilen und Warnglocken gehen an. Wenn ich den Code einer anderen Person öffne und 2000 Zeilen finde, weiß ich, dass es Zeit für das Refactoring ist, ohne die erste Seite zu lesen.
Meist kommt es auf die Wartbarkeit an. Wenn Ihr Objekt so komplex ist, dass Sie sein Verhalten nicht in 300 Zeilen ausdrücken können, wird es für andere sehr schwierig sein, es zu verstehen.
Ich stelle fest, dass ich diese Regel in der Welt "Modell -> Ansichtsmodell -> Ansicht" verbiege, in der ich ein "Verhaltensobjekt" für eine Benutzeroberflächenseite erstelle, da häufig mehr als 300 Zeilen erforderlich sind, um die gesamte Reihe von Verhaltensweisen zu beschreiben eine Seite. Ich bin jedoch noch neu in diesem Programmiermodell und finde gute Möglichkeiten, Verhaltensweisen zwischen Seiten / Ansichtsmodellen umzugestalten / wiederzuverwenden, wodurch sich die Größe meiner ViewModels allmählich verringert.
quelle
Kommen wir zurück:
Tatsächlich ist es ein Eckpfeiler der Programmierung, von der OO nur ein Paradigma ist.
Ich lasse Antoine de Saint-Exupéry Ihre Frage beantworten (weil ich faul bin;)):
Je einfacher die Klasse ist, desto sicherer werden Sie sein, dass es richtig ist, desto einfacher wird es sein, sie vollständig zu testen.
quelle
Ich bin mit dem OP über die Feature Envy Klassifikation. Nichts irritiert mich mehr als eine Reihe von Klassen mit einer Reihe von Methoden, die mit den Interna einer anderen Klasse funktionieren. OO schlägt vor, dass Sie Daten und die Vorgänge für diese Daten modellieren. Das bedeutet nicht, dass Sie die Operationen an einem anderen Ort als die Daten platzieren! Es wird versucht, Sie dazu zu bringen, die Daten und diese Methoden zusammenzufügen .
Bei den vorherrschenden Modellen
car
undperson
wäre es, als würde man den Code zum Herstellen der elektrischen Verbindung oder sogar zum Drehen des Starters in dieperson
Klasse einordnen. Ich würde hoffen, dass dies für jeden erfahrenen Programmierer offensichtlich falsch wäre .Ich habe nicht wirklich die ideale Klassen- oder Methodengröße, aber ich ziehe es vor, meine Methoden und Funktionen auf einem Bildschirm zusammenzufassen, damit ich ihre Gesamtheit an einem Ort sehen kann. (Ich habe Vim so konfiguriert, dass ein Fenster mit 60 Zeilen geöffnet wird. Dies entspricht ungefähr der Anzahl der Zeilen auf einer gedruckten Seite.) Es gibt Bereiche, in denen dies nicht sehr sinnvoll ist, aber für mich funktioniert es. Ich befolge gerne die gleichen Richtlinien für Klassendefinitionen und versuche, meine Dateien unter ein paar "Seiten" zu halten. Alles über 300, 400 Zeilen fühlt sich unhandlich an (hauptsächlich, weil die Klasse zu viele direkte Aufgaben übernommen hat).
quelle
Wenn eine Klassendefinition mehrere hundert Methoden enthält, ist sie zu groß.
quelle
Ich stimme voll und ganz der Verwendung des Grundsatzes der Einzelverantwortung für Klassen zu, aber wenn dies befolgt wird und zu großen Klassen führt, ist es auch so. Das Hauptaugenmerk sollte darauf liegen, dass einzelne Methoden und Eigenschaften nicht zu groß sind. Die zyklomatische Komplexität sollte dabei als Leitfaden dienen. Dies kann zu vielen privaten Methoden führen, die die Gesamtgröße der Klasse erhöhen, diese aber auf einer granularen Ebene lesbar und testbar lassen. Bleibt der Code lesbar, spielt die Größe keine Rolle.
quelle
Das Ausgeben einer Bestellung ist häufig ein komplizierter Vorgang, der mehr als nur die Bestellung umfasst. In diesem Fall ist es wahrscheinlich richtig, die Geschäftslogik in einer separaten Klasse zu halten und die Bestellung zu vereinfachen. Im Allgemeinen haben Sie jedoch Recht - Sie möchten keine "Datenstruktur" -Klassen. Beispielsweise sollte Ihre "POManager" -Business-Class-
issue()
Methode wahrscheinlich die PO-issue()
Methode aufrufen . Die Bestellung kann dann ihren Status auf "Ausgestellt" setzen und das Ausstellungsdatum notieren, während der POManager dann eine Benachrichtigung an die Kreditorenbuchhaltung senden kann, damit sie wissen, dass eine Rechnung erwartet wird, und eine weitere Benachrichtigung an den Lagerbestand, damit sie wissen, dass etwas eingeht und das erwartete Datum.Jeder, der "Regeln" für die Methoden- / Klassengröße drängt, hat offensichtlich nicht genug Zeit in der realen Welt verbracht. Wie andere bereits erwähnt haben, handelt es sich häufig um einen Refactoring-Indikator, aber manchmal (insbesondere bei Arbeiten vom Typ "Datenverarbeitung") müssen Sie wirklich eine Klasse mit einer einzigen Methode schreiben, die sich über mehr als 500 Zeilen erstreckt. Lass dich nicht aufhängen.
quelle
In Bezug auf die physische Größe einer Klasse ist das Sehen einer großen Klasse ein Hinweis auf einen Codegeruch, bedeutet jedoch nicht unbedingt, dass es immer Gerüche gibt. (In der Regel sind sie es jedoch) Wie die Piraten der Karibikpiraten sagen würden, ist dies mehr, was Sie als "Richtlinien" bezeichnen würden, als als tatsächliche Regeln. Ich hatte einmal streng versucht, die "nicht mehr als einhundert LOC in einer Klasse" zu befolgen, und das Ergebnis war eine Menge unnötiger Überentwicklungen.
Normalerweise beginne ich meine Entwürfe damit. Was macht diese Klasse in der realen Welt? Wie sollte meine Klasse dieses Objekt widerspiegeln, wie in den Anforderungen angegeben? Wir fügen hier ein bisschen State hinzu, dort ein Feld, eine Methode, um an diesen Feldern zu arbeiten, fügen ein paar mehr hinzu und voila! Wir haben eine Arbeiterklasse. Füllen Sie nun die Signaturen mit den richtigen Implementierungen aus, und wir können loslegen, oder? Jetzt haben wir eine tolle Klasse mit allen Funktionen, die wir brauchen. Das Problem dabei ist jedoch, dass es nicht die Art und Weise berücksichtigt, wie die reale Welt funktioniert. Und damit meine ich, dass es "ÄNDERUNG" nicht berücksichtigt.
Der Grund, warum Klassen vorzugsweise in kleinere Teile aufgeteilt werden, ist, dass es schwierig ist, große Klassen später zu ändern, ohne die vorhandene Logik zu beeinträchtigen oder einen oder zwei neue Fehler unbeabsichtigt einzuführen oder einfach alles schwer wiederzuverwenden. Hier kommen Refactoring, Entwurfsmuster, SOLID usw. ins Spiel, und das Endergebnis ist normalerweise eine kleine Klasse, die an anderen kleineren, granulareren Unterklassen arbeitet.
Außerdem erscheint es in Ihrem Beispiel unlogisch, IssuePO, ApprovePO und Cancel PO zur PurchaseOrder-Klasse hinzuzufügen. Bestellungen werden nicht ausgestellt, genehmigt oder storniert. Wenn PO über Methoden verfügen soll, sollten die Methoden an ihrem Status und nicht an der Klasse als Ganzes arbeiten. Sie sind wirklich nur Daten- / Datensatzklassen, an denen andere Klassen arbeiten.
quelle
Groß genug, um die gesamte für die Ausführung des Auftrags erforderliche Logik zu enthalten.
Klein genug, um gewartet werden zu können und dem Prinzip der Einzelverantwortung zu folgen .
quelle