Kaffeemaschinen und OOD

Heuristics and Coffee

Per Zufall bin ich auf die Seite von Robert C. Martins Firma gestoßen. Robert C. Martin das ist der Autor von Clean Code. Das Buch muss ich mir übrigens auch unbedingt mal zulegen. Das hab ich leider noch nicht gelesen.

Jedenfalls hab ich seine Seite gefunden und dort ne Menge interessante Artikel gefunden. Unter anderem auch einen der nennt sich Heuristics and Coffee (neuer Link). Das ist ein Kapitel aus seinem Buch UML for Java Programmers.

Der Artikel ist sehr interessant und ich kann nur jedem, der sich mit auch nur irgendwie mit Objektorientierung beschäftigt, raten, ihn zu lesen. Ich habe dadurch ne Menge gelernt.

Grob gesagt zeigt Martin am Beispiel einer recht einfachen Kaffeemaschine typische Schwachstellen von objektorientierten Designs auf. Anfangs wird eine Anforderungsbeschreibung für die Firmware der Kaffeemaschine gegeben und die Aufgabe gestellt, aufgrund dessen einen objektorientierten Entwurf zu erstellen. Leider war ich zu neugierig und hab das nicht (wirklich) gemacht. Ich hab mich leider nur zwei Minuten Gedanken darüber gemacht ohne groß etwas zu skizzieren und dann einfach weiter gelesen. Im Nachhinein würde mich schon interessieren, wie ich es gelöst hätte, wenn ich mich wirklich dran gesetzt hätte. Macht also nicht den Fehler und übergeht die Aufgabe einfach, wie ich es getan habe…

Was ich (u.a.) gelernt habe

  • Mein Design wäre zwar nicht so schlimm geworden, wie das Worst-Case-Beispiel am Anfang, aber dennoch habe ich gemerkt, dass ich dazu neige, Gott-Klassen zu bauen. Etwas weniger schlimme, aber dennoch erkennbar. Einfach aus der Tatsache heraus, dass ich die „Haupt-Klasse“ (hier CoffeeMaker) wohl zu früh einführe. Merke: Haupt-Klasse erst am Schluss modellieren.
  • Mein bisheriger Ansatz „Modelliere erstmal das, was da ist, das Problem löst sich dabei meist von selbst“, klappt so pauschal nicht immer. Falsch angewendet kann das zu schlechten Entwürfen führen.
  • Gerade letztens hab ich behauptet „Wenn die Klasse […] ‚leer‘ wird, ist das kein Problem.“ Auch diese Aussage ist wohl in manchen Fällen kontraproduktiv, da sie u.U. vapor classes rechtfertigt. Ich werde in Zukunft wohl darauf achten, dass ich, wenn ich wieder sowas sage, hinzufüge „Wichtig ist hierbei aber, dass die Abstraktion auch genutzt wird.“
  • Bestimmte Entscheidungen wie ob Multithreading oder Polling im Beispiel lassen problemlos hinausschieben, was manches vereinfachen kann.

Was ich anders machen würde

Wer mich kennt, kann sich sicher denken, dass ich mal wieder… äh… Verbesserungsmöglichkeiten gefunden habe. 😉 So ist es auch hier.

  • Größter Kritikpunkt vorweg: Die Bezeichner sind eindeutig verbesserungswürdig. Das hab ich eigentlich nicht erwartet, wo Martin ja Clean Code geschrieben hat, das ja eigentlich genau von so Zeug, von Lesbarkeit handelt. Gut, Clean Code ist fünf Jahre später geschrieben, vielleicht ist das ja der Grund. Ich merke, ich muss das Buch mal lesen. Hier mal ein paar Beispiele von verbesserungswürdigen Bezeichnern:
    • ContainmentVessel.containerAvailable() Was suggeriert diese Methode. Wohl dass sie zurückgibt, ob der Behälter verfügbar ist. Äh ne. Das tut sie nicht. Sie gibt gar nix zurück, sondern macht, dass wieder Kaffee nachfließt. 😯 Gedanke ist, dass der Behälter verfügbar ist, also Kaffee nachfließen kann (die Kaffeemaschine verhindert die Sauerei, die entsteht, wenn man vergessen hat den Pot unter die Kaffeemaschine zu stellen). Intention schön und gut, aber lesbar ist das nicht. Wenn dann handleContainerAvailable() oder sowas…
    • ähnlich ist es mit done() „Ei jetzt done mal!“ Normalerweise sollten Methodenbezeichner Imperative sein. Hier ein Partizip. Das passt nicht. Diese Methode sagt absolut nicht aus, was sie macht.
    • ContainmentVessel.isComplete Dieses Feld gibt natürlich nicht an, ob das Gefäß komplett ist (was auch immer das heißen mag). Das sagt aber der Bezeichner. Stattdessen handelt es sich hierbei um die Aussage „Der User hat den Kaffee noch nicht vollständig verbraucht.“
    • ContainmentVessel.start() Das Gefäß soll starten. Aha. Is das nun ne Rakete oder eine Kaffeekanne? Bitte was tut das Gefäß da?
    • poll() OK. Der Bezeichner ist aufgrund des Interfaces gewählt. Deshalb kann man hier keinen besseren Bezeichner vergeben. Hier würde ich mich genötigt sehen, einen Kommentar zu schreiben, der das Verhalten erklärt. Allerdings kann guter Code viele Kommentare unnötig machen. So könnte man den Inhalt der Methode in eine besser bezeichnete auslagern und diese dann in poll() aufrufen. Damit wurde sogar schon angefangen. checkButton() heißt eine Methode. Diese wird aber zu poll() umbenannt, statt sie protected zu machen und in poll() aufzurufen.
  • if (isComplete == false) *urgs* In Java funktioniert das vielleicht sogar immer, schön ist es aber trotzdem nicht.
  • } else { // potStatus == api.POT_EMPTY Der Kommentar ist gut gemeint, aber auch das kann man besser machen. ==> else if und im else-Teil ein assert false;. Aber ich glaube das Thema eignet sich für einen separaten Blog-Post oder mehr.
  • Was mir an dem Entwurf nicht so gefällt ist die starke Kopplung zwischen den abstrakten Klassen. Gut, das sind abstrakte Klassen, aber was muss das ContainmentVessel vom UserInterface wissen? Das kann dem doch vollkommen egal sein! Nun kann sowas aber gerade problematisch sein, wenn das Programm erweitert werden soll. Beispielsweise, weil die Kaffeemaschine jetzt auch Milch aufschäumen will, was ja gar kein ungewöhnliches Szenario wäre. In dem Fall müsste ContainmentVessel erweitert werden um, sagen wir mal, eine MilkSource entgegenzunehmen. Eigentlich kann der ContainmentVessel aber vollkommen egal sein, ob da jetzt noch n Milchbehälter rumsteht. Nur muss der irgendwie mitkriegen, wann der Behälter nicht mehr da ist und ggf. wie auch die HotWaterSource aufhören die Gegend vollzukleckern. Also halten wir fest hier ist die Kopplung zu stark. Wie kann man das ändern? Durch geeignete Indirektionsmechanismen. Beispielsweise ein Observer-Pattern. Dadurch muss jetzt bei der genannten Erweiterung ContainmentVessel nicht mehr angefasst werden. So sparen wir uns auch nebenbei die init-Methoden und können den Konstruktor benutzen.

Wie kommt es aber nun zu diesen ganzen Schwachstellen? Meiner Meinung nach durch die Art und Weise, wie das Design entwickelt wurde. In meinen Augen zu technisch. So, wie Gott-Klassen und vapor classes typische Schwächen der Herangehensweise „erstmal modellieren was da ist“ sind, so sind das hier typische Schwächen der hier benutzten eher technischen Herangehensweise „was muss denn passieren?“. Überspitzt formuliert orientiert sich Martins problemorientierter Ansatz an der Frage „Was muss wer wissen (um das Problem zu lösen)?“ und mein modellorientierter an der Frage „Was darf wer wissen?“ Konzentriert man sich zu sehr auf das Problem, neigt man dazu, seine Objekte zu gesprächig zu machen, ihnen zu viele Aufgaben zu geben.

Hier noch ein Beispiel dazu: Wenn das Pot entfernt wird, ruft ContainmentVessel HotWaterSource.Pause() auf um zu verhindern, dass weiter Kaffee fließt. Ist das aber Aufgabe der ContainmentVessel? Sollte das nicht eher die der HotWaterSource sein? Mit meinem modellorientierten Ansatz stoße ich direkt auf das Problem, indem ich mich zuerst von der Frage leiten lasse „Wer macht was?“ und dann sehe ob jeder genügend Infos dafür hat. Her sehe ich dann, dass noch Infos gebraucht werden und realisiere das dann über einen Event-Mechanismus, in Java wohl über das ObserverPattern. Nebenbei bekomme ich durch meinen Ansatz tendenziell bessere Bezeichner, eben weil meine Attribute und Methoden vom Modell motiviert werden und nicht von irgendwelchen „technischen“ Notwendigkeiten à la „Aber ich brauch die Info da und dort“.

Ist es deshalb schlecht, den problemorientierten Ansatz zu nehmen? Nein, natürlich nicht. Genauso wenig, wie es schlecht ist, modellorientiert zu entwerfen, weil das zu god classes verleitet. Es sind einfach unterschiedliche Ansätze mit Vor- und Nachteilen. Ich werde weiterhin bei meinem bleiben und weiß auch warum, jedoch hat mir der Artikel gezeigt, welche Probleme auftreten können und auf was ich bei meinen Entwürfen vielleicht besser achten sollte. Deshalb nochmal mein Aufruf: Lest den Artikel, es lohnt sich.

//Update (14.01.13): Objectmentor ist momentan down. Der Artikel findet sich aber auch auf Martins eigener Seite. Ich hab den direkten Link oben ergänzt.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.