ebody: Frage bzgl. OOP und einem Klassen Diagramm

Hallo,

ich arbeite jetzt nach ein paar Monaten Pause wieder an einem privaten Projekt, wo ich mit Klassen arbeite und Objekt orientiert arbeiten möchte. Um einen Überblick zu erhalten (sind nur 750 Codezeilen), habe ich versucht mir mal ein Klassen Diagramm zu erstellen.

Klassen Diagramm.

Dabei haben sich einige Fragen ergeben:

  1. Ist es sinnvoller zu erst ein solches Diagramm zu erstellen und dann zu programmieren? Damit man schon anhand des Diagramms erkennt, ob etwas Sinn macht, wie die Abhängigkeiten sein sollten?

  2. Gibt es eine Regel, welche, wie viele Methoden in eine Klasse sollten und wann man besser eine weitere Klasse erstellt? Siehe z.B. die Klasse ReadSheet. Hier gibt es auch Methoden, die die Google Sheet URL und deren Rückgabe prüfen. Gehört das wirklich ins Objekt ReadSheet oder in eine neue Klasse wie CheckSheetUrl?

  3. Für das Diagramm habe ich die VS Code Erweiterung "Draw.io Integration" verwendet. Gibt es hier oder in einer anderen UML Software die Möglichkeit, Kommentare für die Methoden zu hinterlegen, die man beim Mouseover z.B. im Diagramm sehen kann.

Mein Wunsch und Ziel ist es auch nach Monaten Abstand von einem Projekt schnell zu erkennen, was es kann, welche "Legosteine" (Klassen, Funktionen) ich zur Verfügung habe, welche ich zusammengebaut habe und ob ich einen Legostein anpassen/erweitern muss oder neuen brauche.

Gruß ebody

  1. Tach!

    1. Ist es sinnvoller zu erst ein solches Diagramm zu erstellen und dann zu programmieren? Damit man schon anhand des Diagramms erkennt, ob etwas Sinn macht, wie die Abhängigkeiten sein sollten?

    Wenn es dir hilft, dann mach das. Es wäre dann auch sinnvoll, das immer mit dem Code synchron zu halten. Wenn du es (später) immer aus den Klassen automatisch erstellen lässt, dann musst du es nicht dauerhaft aufheben, weil du das ja jederzeit herstellen kannst, wenn du meinst, eins zu brauchen.

    1. Gibt es eine Regel, welche, wie viele Methoden in eine Klasse sollten und wann man besser eine weitere Klasse erstellt?

    Ja, diverse Leute haben diverse Faustregeln formuliert. Aber wichtiger als solche Regeln ist deine persönliche Einschätzung, wann etwas sinnvoll ist und wann nicht. Jedes Projekt hat seine eigenen Anforderungen, und nur du musst damit arbeiten. (Oder dein Team, aber dann musst du es mit dem Team absprechen.)

    Wenn du Regeln brauchst, Prinzipien objektorientierten Designs listet viele auf. Die beschreiben aber auch eher qualitative Dinge und keine quantitativen.

    Siehe z.B. die Klasse ReadSheet. Hier gibt es auch Methoden, die die Google Sheet URL und deren Rückgabe prüfen. Gehört das wirklich ins Objekt ReadSheet oder in eine neue Klasse wie CheckSheetUrl?

    Vielleicht. Vielleicht auch nicht. Für Test Driven Design ist es sinnvoll, nicht zu viel in eine Einheit zu packen, weil der Testaufwand steigt. Für die allgemeine Übersichtlichkeit ist es auch sinnvoll, kleine verständliche Einheiten zu packen. Die Komplexität wird damit aber nicht geringer, sondern veteilt sich nur auf mehr Einheiten. Und damit steigt dann auch der Aufwand, alles unter einen Hut zu bekommen. Das ist dann Abwägungssache, was einem mehr Vorteile bingt.

    Mein Wunsch und Ziel ist es auch nach Monaten Abstand von einem Projekt schnell zu erkennen, was es kann, welche "Legosteine" (Klassen, Funktionen) ich zur Verfügung habe, welche ich zusammengebaut habe und ob ich einen Legostein anpassen/erweitern muss oder neuen brauche.

    Da hilft vor allem ein gutes Ordnungssystem und aussagekräftige Bezeichnungen. Es ist zum Beispiel wenig sinnvoll, alle Interfaces in ein Verzeichnis zu packen, nur weil es Interfaces sind. Sinnvoller empfinde zumindest ich es, die Einheiten nach fachlichen Kriterien zusammenzufassen. Dann kann man schon anhand der Verzeichnisstruktur sehen, wie das Projekt gegliedert ist, und was zu wem gehört.

    dedlfix.

  2. Hallo ebody,

    ich hätte einen Haufen Anmerkungen

    Welches Modellierungstool, bzw. welche graphische Modellierungssprache ist das? In meinem UML Vokabular kommt ein ausgefüllter Pfeil im Klassendiagramm nicht vor.

    In welcher Beziehung steht ListSheetData zu ReadSheet? Ist das eine Assoziation? Eine Subklasse?

    Wenn es eine Assoziation ist - wie navigiert man vom einen zum anderen? Da sehe ich kein zugehöriges Property. Wenn ListSheetData ein Rückgabewert einer ReadSheet-Methode ist, kann man das im Modell darstellen? Ein Pfeil gehört da dann nicht hin, soweit ich weiß.

    Klassen sollten in ihrem Namen keine Tätigkeit darstellen. Sie repräsentieren Dinge, keine Aufgaben. ReadSheet könnte sein Leben als SheetReader führen, in dem Fall müsste es zum SheetReader aber auf jeden Fall noch eine Klasse DataSheet geben. Ist das dein ListSheetData?

    Wichtig ist auf jeden Fall, dass der SheetReader sich nur mit dem Lesen beschäftigt und sonst nichts. "Do One Thing And Do It Right".

    Methoden wie sheetCheckUrl und setSheetUrl sind da schon verdächtig. Wenn der SheetReader Anforderungen an die URL hat, die über formale Ansprüche an eine URL hinausgehen (z.B. wenn Du nur URLs akzeptieren willst, die das Präfix https://docs.google.com/spreadsheets haben) bestimmte Hostnamen akzeptieren willst), dann sollte er sie beim Entgegennehmen der URL prüfen und eine Exception werfen, wenn es nicht passt. Eine explizite Check-Methode scheint unpassend. Eine set-Methode aber eigentlich auch, die URL kannst Du auch an den Konstruktor übergeben. Andere Fehler, wie einen Existenzcheck der URL, würde ich an der Stelle lassen, weil sie einen Netzzugriff brauchen und daher asynchron sind. Das führt zu einem HTTP Fehlercode beim Lesen, das ist früh genug. Oder Du erstellst eine checkExists Methode, wenn Du wirklich nicht mehr tun willst als die Existenz zu testen.

    Die normalen Anforderungen an die URL sollten aber durch die URL-Klasse geprüft werden, die der Browser (außer Internet Explorer) mitbringt. Wenn Du den IE unterstützen willst, musst Du Dir einen URL-Polyfill suchen - aber ich würde die URL Validierung aus dem Reader heraushalten.

    Methoden wie countRows() und countCols() sollten demnach Propertys des DataSheet sein, und die SheetUrl sollte der Reader per Konstruktor bekommen. Ist sie falsch - wie gesagt: Exception.

    countRows und countCols sind ebenfalls falsch benannt. „count“ sieht hier nach einem Verb aus, und ein Verb gehört zu einer Methode. Aber das sind augenscheinlich keine Methoden, sondern Property-Getter, die den Wert liefern, und demnach sollten sie rowCount und columnCount heißen.

    Ein Getter namens transformSheet scheint mir auch verdächtig. Er sollte transformedSheet heißen, wenn er das transformierte Sheet liefert. Alternativ müsste es eine Methode transformSheet() sein, die die Transformation ausführt.

    Frage: Wie löst Du die Asynchronität des Lesevorgangs? Ein Datenzugriff über eine URL dauert eine Weile, das ist in JS prinzipiell asynchron.

    Wenn ich das baute, würde ich eine read-Methode vorsehen, die ein Promise zurückliefert, so dass man als Anwender dies tun kann:

    new SheetReader("http://example.com/somesheet")
    .read()
    .then(sheet => {
       // sheet verarbeiten
    })
    .catch(err => {
       // Fehlerbehandlung
    });
    

    oder mit neuerer JavaScript Syntax, in einer async Funktion:

    try {
       let sheet = await new SheetReader("...").read();
       // sheet verarbeiten
    }
    catch(err) {
       // handle error
    }
    

    Innerhalb von read() kannst Du das mit dem fetch-API implementieren, das ist ohnehin schon Promise-basierend, oder Du kapselst den XmlHttpRequest selbst in ein Promise. Ach, IE. Da bräuchtest Du dann auch einen Polyfill für Promises. Oder Du verzichtest auf IE User, wenn Du Dir das erlauben kannst.

    Rolf

    --
    sumpsi - posui - obstruxi