Wie groß ist eine Klasse?

29

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 PurchaseOrderKlasse, die eine endliche Zustandsmaschine hat, die ihr Verhalten steuert, und eine Sammlung von PurchaseOrderItem, eines der Argumente hier ist, dass wir eine PurchaseOrdereinfache 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 PurchaseOrdersollte Klasse haben issuePO, approvePOund cancelPOMethoden.

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?

Miguel Veloso
quelle
17
Es gibt keinen Grund, den Typnamen in den Methodennamen zu kodieren. Dh , wenn Sie haben PurchaseOrder, können Sie einfach Ihre Methoden nennen issue, approveund cancel. Das POSuffix fügt nur einen negativen Wert hinzu.
Dash-Tom-Bang
2
Solange Sie sich von den Methoden des Schwarzen Lochs fernhalten , sollten Sie in Ordnung sein. :)
Mark C
1
Bei meiner aktuellen Bildschirmauflösung würde ich 145 Zeichen groß sagen.
DavRob60
Vielen Dank an alle für Ihre Kommentare, sie haben mir bei diesem Problem sehr geholfen. Wenn diese Seite erlaubt hätte, hätte ich auch die Antwort von TMN und Lazarus gewählt, aber es erlaubt nur eine, also ging es an Craig. Freundliche Grüße.
Miguel Veloso

Antworten:

27

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.

Craig
quelle
1
Craig, ich kannte das Prinzip der Einzelverantwortung nicht und habe nur darüber gelesen. Das erste, woran ich dachte, war, welchen Umfang eine Verantwortung hat. Ich würde sagen, dass für eine Bestellung der Status verwaltet wird. Dies ist der Grund für die Methoden issuePO, approvePO und cancelPO, umfasst jedoch auch die Interaktion mit der PurchaseOrderItem-Klasse, die den Status des Bestellungselements behandelt. Ich bin mir also nicht sicher, ob dieser Ansatz mit dem SRP vereinbar ist. was würden Sie sagen?
Miguel Veloso
1
+1 für eine sehr schöne und prägnante Antwort. Realistisch kann es kein definitives Maß dafür geben, wie groß eine Klasse sein sollte. Durch das Anwenden des SRP wird sichergestellt, dass die Klasse nur so groß ist, wie sie sein muss .
S.Robins
1
@MiguelVeloso - Das Genehmigen einer Bestellung hat wahrscheinlich eine andere Logik und andere Regeln als das Ausstellen einer Bestellung. In diesem Fall ist es sinnvoll, die Zuständigkeiten in einen POApprover und einen POIssuer zu unterteilen. Die Idee ist, dass, wenn sich die Regeln für eine ändern, warum Sie sich mit der Klasse anlegen möchten, die die andere enthält.
Matthew Flynn
12

Meiner Meinung nach spielt die Klassengröße keine Rolle, solange die Variablen, Funktionen und Methoden für die betreffende Klasse relevant sind.

Mike42
quelle
1
Andererseits weist eine große Größe darauf hin, dass diese Methoden für die betreffende Klasse wahrscheinlich nicht relevant sind.
Billy ONeal
5
@Billy: Ich würde sagen, dass große Klassen ein Codegeruch sind, aber sie sind nicht automatisch schlecht.
David Thornley
@ David: Deshalb gibt es dort das Wort "wahrscheinlich" - es ist wichtig :)
Billy ONeal
Ich würde nicht zustimmen müssen ... Ich arbeite an einem Projekt, das eine ziemlich konsistente Namenskonvention hat und die Dinge sind gut angeordnet ... aber ich habe immer noch eine Klasse, die 50.000 Codezeilen umfasst. Es ist einfach zu groß :)
Jay
2
Diese Antwort zeigt genau, wie der "Weg zur Hölle" verläuft - wenn man die Verantwortlichkeiten einer Klasse nicht einschränkt, gibt es viele Variablen, Funktionen und Methoden, die für die Klasse relevant werden - und dies führt leicht dazu, dass zu viele von ihnen vorhanden sind.
Doc Brown
7

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 carzu groß ist, ist dies für Ihre Autoanalogie wahrscheinlich ein Hinweis darauf, dass sie in Klasse engine, Klasse doors und Klasse windshields usw. aufgeteilt werden muss.

Billy ONeal
quelle
8
Und vergessen wir nicht, dass ein Auto die Fahrzeugschnittstelle implementieren sollte. :-)
Chris
7

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:

  1. Gibt es viele voneinander unabhängige Variablen? (Meine persönliche Toleranz liegt in den meisten Fällen bei 10 bis 20)
  2. Gibt es viele Methoden für Eckfälle? (Meine persönliche Toleranz ist ... na ja, es hängt sehr von meiner Stimmung ab, denke ich.)

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 verfolgen car. 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 carkann, car::open_roof()die jedoch nur für Cabriolets verfügbar sind und car::open_rear_door()nicht für diese zweitürigen Autos gelten. Obwohl Cabrios und Zweitürer per Definition "Autos" sind, carerschwert 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

YYC
quelle
7

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.

Jay Beavers
quelle
Meine persönliche Richtlinie ist auch rund 300 Zeilen. +1
Marcie
Ich denke, das OP sucht nach einer Heuristik, und das ist eine gute.
Neontapir
Danke, es ist wertvoll, genaue Zahlen als Richtlinie zu verwenden.
Will Sheppard
6

Kommen wir zurück:

Gehört das nicht zu den uralten Prinzipien „Zusammenhalt maximieren“ und „Kopplung minimieren“, die ich als Eckpfeiler von OO verstehe?

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;)):

Perfektion wird erreicht, nicht wenn nichts mehr hinzuzufügen ist, sondern wenn nichts mehr wegzunehmen ist.

Je einfacher die Klasse ist, desto sicherer werden Sie sein, dass es richtig ist, desto einfacher wird es sein, sie vollständig zu testen.

Matthieu M.
quelle
3

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 carund personwäre es, als würde man den Code zum Herstellen der elektrischen Verbindung oder sogar zum Drehen des Starters in die personKlasse 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).

Dash-Tom-Bang
quelle
+1 - gute Faustregeln, aber nicht autoritär. Ich würde jedoch sagen, dass eine aufgebrochene Klasse nicht bedeutet, dass die verschiedenen Klassen die privaten Mitglieder der jeweils anderen berühren sollten.
Billy ONeal
Ich denke nicht, dass verschiedene Klassen jemals die privaten Teile des anderen berühren sollten. Während der 'Freund'-Zugang praktisch ist, um etwas zum Laufen zu bringen, ist er (für mich) ein Sprungbrett für ein besseres Design. Im Allgemeinen vermeide ich auch Accessoren, da abhängig von den Interna einer anderen Klasse ein anderer Code-Geruch vorliegt .
Dash-Tom-Bang
1

Wenn eine Klassendefinition mehrere hundert Methoden enthält, ist sie zu groß.

C Johnson
quelle
1

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.

Lazarus
quelle
1

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.

TMN
quelle
Ich denke, der POManager wäre der "Controller" und PurchaseOrder wäre das "Modell" im MVC-Muster, oder?
Miguel Veloso
0

Ich habe mehrere Autoren gefunden, die Rezepte wie "Eine Klasse sollte nur auf eine Seite passen" anbieten.

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.

Sie lauten normalerweise: "Je besser das Modell ist, desto besser repräsentiert es das Objekt in der Anwendung und desto besser kommt alles heraus".

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.

Jonn
quelle
0

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 .

RMalke
quelle