Law of Demeter (LoD) – Wissenshäppchen #8

This entry is Teil 8 von 8 in the series Wissenshäppchen

Law of Demeter (LoD)

Um das Law of Demeter (Gesetz von Demeter) in der Softwareentwicklung geht es im achten Wissenshäppchen.

TL;DR

Mit dem Law of Demeter hältst du deinen Code unabhängig vom inneren Aufbau anderer Objekte, indem du Aufgaben an die zuständigen Objekte weitergibst. An einem Versicherungsbeispiel zeige ich dir, wie dadurch fachliche Logik in die passenden Klassen gelangt und Tests einfacher werden. Zusätzliche Methoden können dabei Boilerplate erzeugen, weshalb du bei weitgehend festen Objektstrukturen abwägen solltest, wie konsequent du die Regel anwendest.

Kapitelmarken

  • [00:00:00] Einstieg ins Law of Demeter
  • [00:00:39] Woher der Name Demeter stammt
  • [00:01:18] Definition und Prinzip des geringsten Wissens
  • [00:03:08] Verkettete Zugriffe am Beispiel Person und Adresse
  • [00:05:02] Kapselung und Geheimnisprinzip
  • [00:06:32] Abhängigkeiten bei internen Änderungen
  • [00:08:04] Aufwand für Tests und Stubs
  • [00:09:37] Merkhilfe: Methoden sprechen mit ihren Freunden
  • [00:10:29] Globale Variablen und Singletons
  • [00:11:26] Praxisbeispiel: Gesamtbeitrag einer Versicherungspolice
  • [00:13:09] Fachliche Logik auf die zuständigen Klassen verteilen
  • [00:15:28] Vorteile für verständlichen und testbaren Code
  • [00:16:43] Boilerplate und weitgehend feste Objektstrukturen
  • [00:19:07] Regel im Überblick
  • [00:19:54] Warum Zwischenvariablen die Abhängigkeit erhalten
  • [00:20:31] Buchhinweis und weiterführende Codebeispiele

Probeabo bei Audible (Affiliate)

Inhalt

Das Law of Demeter sagt aus, dass eine Methode nur Zugriff auf die folgenden Objekte/Methoden haben sollte:

  • Andere Instanzmethoden in ihrer eigenen Klasse
  • Ihre Parameter
  • Methoden in Objekten, die sie selbst erzeugt
  • Globale Variablen

Eine einfache andere Definition lautet: Verkette keine Methodenaufrufe („method chaining“). Oder: Verwende nicht mehr als einen ., wenn du auf etwas zugreifst. Ein anderer Name für das LoD ist „Prinzip des geringsten Wissens“ („principle of least knowledge“).

Der Name stammt von Demeter, einer griechischen Göttin. Der „Erfinder“ des Prinzips entwickelte in den 80er-Jahren ein System mit diesem Namen.

Erklärung

  • Vorweg: Globale Variablen waren in den 80ern hip, heute nicht mehr. Bitte verwende sie am besten gar nicht.
  • Je mehr „Punkte“ in einer Aufrufhierarchie auftreten, desto mehr Kenntnis hat eine Komponente vom „Innenleben“ der aufgerufenen Komponenten.
  • Je mehr Kenntnis eine Komponente über die von ihr verwendeten Komponenten hat, desto stärker bindet sie sich an diese. Wenn sich die genutzten Komponenten ändern, muss sie sich mit ändern bzw. ist kaputt.
  • Das verletzt das Geheimnisprinzip und die Kapselung, nach der wir eigentlich in der Objektorientierung arbeiten wollen.
  • Je mehr solcher Abhängigkeiten eine Methode hat, desto schwerer ist sie zu verstehen und zu testen.
  • Code, der sich an das LoD hält, gilt auch als „schüchtern“, weil er „nur mit seinen Freunden spricht“.

Beispielcode

Um an den Gesamtbeitrag einer Versicherungspolice zu kommen, ist die Kenntnis über die darin enthaltenen versicherten Personen und deren Tarife nötig. Da es sich dabei jeweils um Listen handelt, ist sogar noch ein „Flachklopfen“ der Strukturen nötig. Der Code sieht vielleicht cool aus, ist aber stark an die Strukturen gekoppelt. Das heißt, ein Test müsste sehr viel Aufwand betreiben, um die nötigen Strukturen zu schaffen (z.B. mit Mocks).

class Tarif
    def initialize(name)
        @name = name
    end

    def beitrag
        123.45
    end
end

class VersichertePerson
    def initialize(name)
        @name = name
        @tarife = [ Tarif.new("KFZ"), Tarif.new("Hausrat") ]
    end

    def tarife
        @tarife
    end
end

class Versicherungspolice
    def initialize
        @versicherte_personen = [ VersichertePerson.new("Stefan Macke"), VersichertePerson.new("Klaus Meyer") ]
    end

    def versicherte_personen
        @versicherte_personen
    end
end

# cool, aber schwer verständlich, anfällig für Anpassungen und auch schwer zu testen
puts Versicherungspolice.new
  .versicherte_personen
  .flat_map { |vp| vp.tarife }
  .inject(0){|summe,tarif| summe + tarif.beitrag }

Um dies zu vermeiden, können die einzelnen Klassen von ihren inneren Strukturen abstrahieren und stellen simple Zugriffsmethoden für die gewünschten Ergebnisse bereit. Der finale Aufruf sieht nun schon fast langweilig aus. Dafür liegt die fachliche Berechnungslogik da wo sie hingehört: in den jeweiligen Klassen und nicht beim Aufrufer.

class Tarif
    def initialize(name)
        @name = name
    end

    def beitrag
        123.45
    end
end

class VersichertePerson
    def initialize(name)
        @name = name
        @tarife = [ Tarif.new("KFZ"), Tarif.new("Hausrat") ]
    end

    def beitragssumme
        @tarife.inject(0){|summe,tarif| summe + tarif.beitrag }
    end
end

class Versicherungspolice
    def initialize
        @versicherte_personen = [ VersichertePerson.new("Stefan Macke"), VersichertePerson.new("Klaus Meyer") ]
    end

    def gesamtbeitrag
        @versicherte_personen.inject(0){|summe,vp| summe + vp.beitragssumme }
    end
end

# langweilig, aber einfach zu verstehen und zu testen
puts Versicherungspolice.new.gesamtbeitrag

Vorteile

  • Der Code wird verständlicher, weil nicht mehrere Strukturen bekannt sein müssen.
  • Fachliche Logik landet evtl. eher da, wo sie hingehört. Anstatt externen Komponenten Zugriff auf die inneren Daten zu geben, werden Abstraktionen geschaffen.
  • Die Komponenten sind unabhängiger voneinander und lassen sich leichter testen.
  • Große Test-Setups mit vielen Mocks, die weitere Mocks zurückgeben, lassen sich vermeiden.
  • Interne Änderungen an verwendeten Komponenten wirken sich nicht auf deinen Code aus. Du hast also weniger Anpassungsaufwand.

Nachteile

  • Wenn du die Regel exakt befolgen möchtest, musst du womöglich recht viel Boilerplate-Code schreiben, z.B. simple Getter, die an Instanzvariablen delegieren.
  • Wenn sich die verwendeten Strukturen selten ändern, ist der zusätzliche Aufwand vermutlich unnötig.

Literaturempfehlungen

Zu diesem Wissenshäppchen gibt es ein separates Kapitel in einem meiner absoluten Lieblingsbücher zur Softwareentwicklung: The Pragmatic Programmer*. Es ist vor Kurzem in der 20-Jahre-Edition neu aufgelegt worden, aber das Law of Demeter ist immer noch Thema.

The Pragmatic Programmer: journey to mastery, 20th Anniversary Edition, 2/e: your journey to mastery, 20th Anniversary Edition (Affiliate)*

Links

Zusammenfassung

Was das Law of Demeter bedeutet

Wenn du als IT-Azubi Methoden schreibst, hilft dir das Law of Demeter, die Abhängigkeiten zu anderen Objekten überschaubar zu halten. Es heißt auch Prinzip des geringsten Wissens beziehungsweise Principle of Least Knowledge. Der Name stammt aus einem Softwareprojekt namens Demeter, das nach der griechischen Göttin benannt wurde. Die Regel kommt aus den 1980er-Jahren.

Eine Methode sollte nach der vorgestellten Definition auf Folgendes zugreifen dürfen:

  • Andere Instanzmethoden ihrer eigenen Klasse.
  • Objekte, die sie als Parameter erhält.
  • Methoden von Objekten, die sie selbst erzeugt.
  • Globale Variablen.

Den letzten Punkt streiche ich für heutigen Code aus meiner Empfehlung. Globale Variablen können von überall verändert werden, wodurch sich bei der Fehlersuche schwer nachvollziehen lässt, wer ihren Wert geändert hat. Dieselbe Empfehlung gilt hier für Singletons.

Als Merkhilfe kannst du dir vorstellen, dass eine Methode nur mit ihren "Freunden" spricht: mit Objekten, die ihr unmittelbar bekannt sind.

Was bei verketteten Aufrufen passiert

Bei einem Aufruf wie person.getAdresse().getStrasse().getErsterBuchstabe() bewegst du dich durch mehrere Ebenen einer Objektstruktur. Damit du diesen Code schreiben kannst, musst du wissen, wie die Person ihre Adresse enthält und wie du darüber zur Straße gelangst.

Die Faustregel "höchstens ein Punkt beim Objektzugriff" macht solche Zugriffe sichtbar. Je nach Programmiersprache steht dafür ein anderer Operator, etwa ein Pfeil.

Bei Stream-Operationen wie map, filter oder reduce bleibst du innerhalb einer Stream-Verarbeitung. Für das Law of Demeter sind insbesondere Ketten relevant, bei denen du schrittweise innere Bestandteile anderer Objekte hervorholst.

Auch wenn du die Adresse und anschließend die Straße jeweils in einer eigenen Variablen speicherst, bleibt dein Code an dieselbe innere Struktur gebunden. Die Abhängigkeit besteht über mehrere Zeilen hinweg weiter.

Warum der innere Aufbau verborgen bleiben sollte

Durch Getter, die innere Objekte herausgeben, kann aufrufender Code tief in deren Struktur eingreifen. Damit wird die Kapselung aufgebrochen, die Daten und zugehörige Methoden als Einheit zusammenhalten soll.

Brauchst du den ersten Buchstaben der Straße einer Person, kann die Person dafür eine passende Methode anbieten. Sie kümmert sich intern darum, wie sie diesen Wert ermittelt. Für deinen Aufruf reicht die öffentliche Methode.

Führt ein:e Entwickler:in später eine zusätzliche Ebene ein oder benennt einen Bestandteil um, müssen alle Aufrufe angepasst werden, die diesen Aufbau kennen. Das Geheimnisprinzip erlaubt es, den inneren Aufbau einer Klasse zu ändern, während ihre öffentliche Schnittstelle bestehen bleibt. Dadurch bleiben aufrufende Komponenten von solchen internen Änderungen unabhängig.

Wie sich das auf Tests auswirkt

Um eine tief verkettete Methode zu testen, musst du die beteiligten Objekte passend aufbauen oder durch Stubs ersetzen. Im Personenbeispiel muss die Adresse eine Straße liefern, über die sich der gewünschte Buchstabe abrufen lässt. Fehlt ein benötigtes Objekt, kann ein Null-Pointer-Fehler entstehen.

Sobald sich die Struktur ändert, betrifft das auch dieses Test-Setup. Mit einer direkten Methode für den benötigten Wert werden die Tests kürzer und hängen weniger stark vom inneren Aufbau ab.

Mein Beispiel: Beiträge einer Versicherungspolice

Für mein Beispiel enthält eine Versicherungspolice mehrere versicherte Personen. Jede Person besitzt wiederum Tarife, etwa für eine Kfz- oder Hausratversicherung.

Greifst du von außen auf sämtliche Personen und deren Tarife zu, um die Beiträge zu addieren, liegt die gesamte verschachtelte Logik beim aufrufenden Code. Das gilt auch dann, wenn du die Rechnung kompakt mit Stream-Methoden oder Ruby-Methoden wie flatMap und inject ausdrückst.

Ich gebe der Police dafür eine Methode, die ihren Gesamtbeitrag berechnet. Die Aufgaben verteile ich entlang der Objektstruktur:

  • Die Police fragt die Beitragssummen ihrer versicherten Personen ab und addiert sie.
  • Jede versicherte Person summiert die Beiträge ihrer eigenen Tarife.
  • Ein Tarif liefert seinen Beitrag.

Von außen genügt damit ein Aufruf wie police.getBeitrag(). Die fachliche Logik liegt in den jeweils zuständigen Klassen, deren Methoden dadurch kleiner werden. Du kannst die einzelnen Teile leichter verstehen und getrennt testen.

Was du bei zusätzlichen Methoden abwägen solltest

Wenn du die Regel anwendest, musst du beim Lesen des aufrufenden Codes weniger über fremde Objektstrukturen wissen. Interne Änderungen verursachen weniger Folgearbeit. Auch umfangreiche Test-Setups lassen sich reduzieren.

Für reine Weiterleitungen können zusätzliche Methoden entstehen, die jeweils nur einen Aufruf an eine Instanzvariable durchreichen. Solcher Boilerplate-Code muss geschrieben und gegebenenfalls für die gewünschte Testabdeckung getestet werden. Manche Programmiersprachen können diese Weiterleitungen automatisch erzeugen.

Bei einer Struktur, die sich voraussichtlich kaum verändert, kannst du abwägen, ob du diese zusätzlichen Methoden einführst. Weil spätere Änderungen oft schwer vorhersehbar sind, wende ich im Zweifel das Law of Demeter an.

Weiterführende Quellen

In The Pragmatic Programmer findest du ein kurzes Kapitel zum Law of Demeter. Mein Ruby-Codebeispiel und weitere Links mit Beispielen stehen in den Shownotes zum Wissenshäppchen 8.

Transkription der gesamten Episode

Automatisch erzeugte Transkription der Episode

[0:00] Herzlich willkommen bei den Wissenshäppchen des IT-Berufe-Podcasts. In dieser Episode geht es

[0:10] um das Law of Demeter. Viel Spaß! Hallo und herzlich willkommen zum achten Wissenshäppchen

[0:24] des IT-Berufe-Podcasts. Und heute schauen wir uns mal ein weiteres Gesetz der Softwareentwicklung

[0:31] an und zwar das Law of Demeter. Auf deutsch vermutlich Gesetz von Demeter ausgesprochen.

[0:39] Demeter übrigens direkt mal zum Einstieg ist eine griechische Göttin, also jetzt hier nicht

[0:43] ein amerikanischer Softwareentwickler oder sonst irgendwas nach dem, was hier benannt wurde,

[0:47] sondern das Gesetz stammt aus einem Projekt, wo tatsächlich das Projekt selbst oder die Software,

[0:51] die da entwickelt wurde, Demeter genannt wurde, eben nach dieser griechischen Göttin. Und deswegen

[0:57] heißt das Gesetz jetzt auch so. Und es ist uns oder es ist mir eine Podcast-Episode wert,

[1:02] zumindest ein kleines Wissenshäppchen, denn ich finde, dass es in der Praxis ja sehr spannend ist,

[1:08] weil es den Code viel verständlicher macht, finde ich, besser kapselt und auch das Geheimnisprinzip

[1:14] umsetzt. Und was es damit alles auf sich hat, das wollen wir uns heute mal einfach anschauen.

[1:18] Und im Sinne eines kurzen knackigen Wissenshäppchens lege ich jetzt einfach mal direkt los mit einer

[1:24] kurzen Definition. Was ist das Law of Demeter? Das sagt aus, dass eine Methode nur Zugriff auf

[1:31] die folgenden Objekte bzw. Methoden haben sollte. Also die Methode darf nur zugreifen auf andere

[1:38] Instanzmethoden in ihrer eigenen Klasse, ihre eigenen Parameter, Methoden in Objekten,

[1:44] die sie selbst erzeugt und globale Variablen. Nichts anderes soll eine Methode im Zugriff haben,

[1:52] also daran Attribute oder Methoden in diesem Fall aufrufen. Eine etwas andere Definition,

[1:57] die ein bisschen einfacher zu verstehen ist, ist verkette keine Methodenaufrufe. Du kennst

[2:02] vielleicht den englischen Begriff method chaining, das heißt, du machst Methode,

[2:06] Punkt, Methode, Punkt, Methode, also an dem, was die erste Methode zurückgibt, an dem Objekt rufst

[2:11] du die nächste Methode auf, an dem die nächste Methode und so weiter. Dieses method chaining.

[2:15] Das soll man nicht machen. Gut, sehen wir ganz oft gerade heutzutage in Code, wie zum Beispiel

[2:21] Streamverarbeitung in Java oder so was, Punkt mehr, Punkt Filter, Punkt Reduce und so weiter. Ja,

[2:26] das ist aber nicht gemeint. Wir gehen gleich mal im Detail darauf ein, was hier genau damit

[2:30] gemeint ist mit diesem method chaining. Und noch eine allgemeine Regel oder eine etwas andere

[2:35] Formulierung dieser Regel. Verwende niemals mehr als einen Punkt, wenn du auf etwas zugreifst.

[2:41] Vorausgesetzt in deiner Programmiersprache ist der Punkt der Objektzugriff-Operator. In C++,

[2:47] glaube ich, und in PHP ist das zum Beispiel der Pfeil, also Minus und Spitze Klammer zu. In vielen

[2:52] anderen Sprachen, Java, C Sharp, Ruby, ist allerdings der Punkt, der Operator, den du brauchst,

[2:56] um an einem Objekt eine Methode aufrufen zu können, zum Beispiel. Und ein anderer Name für

[3:02] dieses Law of Demeter ist Prinzip des geringsten Wissens oder Principle of Least Knowledge auf

[3:08] Englisch. Und was hat es jetzt mit diesem Gesetz auf sich? Was soll das bedeuten? Also, stell dir

[3:14] vor, du greifst auf ein Objekt zu. Das hat irgendein Attribut. Du holst dir das zum Beispiel

[3:18] über einen Getter da raus. Und an diesem Objekt, was du zurückkriegst, rufst du direkt die nächste

[3:23] Methode auf, weil da wieder irgendwie ein Objekt zurückkommt, was das erste Objekt enthält. Und

[3:28] an dem Objekt, was da zurückkommt, rufst du wieder einen Getter auf, um das letzte Ding aufzurufen,

[3:32] was du eigentlich haben willst. Nehmen wir mal ein einfaches Beispiel. Du hast eine Person. Die

[3:36] Person hat eine Adresse. Die Adresse hat eine Straße. Und die Straße hat vielleicht noch,

[3:40] was auch immer, einen ersten Buchstaben. Und dann würdest du sowas machen wie Person Punkt

[3:45] Get-Adresse Punkt Get-Straße Punkt Get-erster-Buchstabe. Das heißt, du machst Punkt,

[3:50] Punkt, Punkt, Punkt hintereinander, Methode an Methode gekettet. Und hier ist es jetzt etwas

[3:55] anders. Mein Beispiel von eben mit dem Stream, um das mal abzugrenzen. Du willst nicht mehrere

[4:00] Methoden an dem gleichen Ding, nämlich an dem Stream aufrufen, um dann zum Beispiel den zu

[4:05] filtern, zu reducen und so weiter. Sondern jedes Mal bekommst du tatsächlich etwas Unterschiedliches

[4:09] zurück. Also die Person liefert dir die Adresse. Die Adresse liefert dir die Straße. Die Straße

[4:15] liefert dir den Buchstaben. Das heißt, du kriegst jedes Mal ein anderes Objekt zurück. Und das ist

[4:19] immer Teil des Objekts, an dem du diese Methode aufgerufen hast. Also die Adresse ist Teil der

[4:24] Person zum Beispiel. Und damit das alles funktioniert, brauchst du als Aufrufer natürlich

[4:28] ein Wissen darüber, wie diese Struktur intern aufgebaut ist. Du musst wissen, die Person hat

[4:33] eine Adresse. In der Adresse ist die Straße. In der Straße ist der Buchstabe. Das heißt, von

[4:39] draußen siehst du nicht nur die Person, sondern du siehst das gesamte Konstrukt dahinter. Also die

[4:44] gesamten Innereien dieser Objektstruktur müssen dir bekannt sein, damit du überhaupt an

[4:49] diesen Buchstaben rankommst. Du musst wissen, Person hat Adresse, Adresse hat Straße, Straße hat Buchstabe

[4:54] quasi. Und das musst du von draußen alles wissen und hintereinander schreiben, damit du an diesem

[4:58] Buchstaben rankommst. Und das ist nicht so gut. Und warum? Das gucken wir uns jetzt an.

[5:02] Eigentlich nutzen wir ja die Objektorientierung in vielen modernen Sprachen, die wir vermutlich

[5:07] einsetzen, weil wir eines der wichtigen Prinzipien, nämlich die Kapselung, anwenden wollen. Die

[5:13] Objekte kapseln ja die Daten und die zugehörigen Methoden so, dass man sie quasi als Einheit

[5:18] weitergeben kann. Wenn du jetzt aber so einen Inhalt, ein Attribut zum Beispiel, über einen

[5:23] Getter aus diesem Objekt rausholst, dann hast du ihn ja gerade quasi aus dieser Schutzhülle

[5:29] des Objekts befreit und kannst da drin rumwurschteln, wie du willst. Und wenn du da wieder tiefer

[5:34] rein gehst und dir noch was rausholst und da wieder drin rumwurschtelst, dann ist eigentlich

[5:37] die ganze Kapselung aufgebrochen. Beziehungsweise auch das Geheimnisprinzip, was dahinter sich

[5:42] eigentlich verbirgt. Wir wollen gar nicht wissen, wie Objekte intern aufgebaut sind. Wir wollen

[5:47] nicht wissen müssen, dass eine Person eine Adresse hat und die Adresse die Straße hat. Diesen

[5:53] ganzen Aufbau will ich von draußen gar nicht wissen müssen, weil das viel zu kompliziert ist.

[5:57] Das muss ich ja immer wirklich im Code auch abbilden und muss dafür auch wissen, wie das

[6:02] alles aufgebaut ist. Wenn ich aber nur die Person sehe von draußen, dann muss ich mich quasi durch

[6:07] diese ganze Struktur durchwurschteln, bis ich irgendwann beim Ziel bin. Das ist viel zu

[6:10] umständlich. Stattdessen, wenn es ein Use Case ist, dass ich eine Person habe und den ersten

[6:14] Buchstaben der Straße brauche, dann sollte mir die Person vielleicht diesen Buchstaben direkt

[6:19] geben können und nicht alles, was dann letztlich zu den Buchstaben führt, mir quasi vor die Füße

[6:24] werfen, damit ich dasselbe drin rum bohren kann. Das ist irgendwie nicht das Ziel der Kapselung.

[6:27] So, das ist das eine. Das ist irgendwie nicht so objektorientiert, wie es im Buche steht. Jetzt

[6:32] könnte man sagen, ja, mein Gott, ich mache auch andere Sachen, die nicht im Buch stehen. Ja,

[6:34] das ist mir egal. Aber eine konkrete Auswirkung auf deinen Code ist, wenn sich jetzt an dieser

[6:40] Struktur irgendetwas ändert, dann bist du dran bzw. dein Code. Denn den musst du anpassen. Wenn

[6:46] jetzt irgendwer sagt, Mensch, die Adresse ist ja toll, aber ich will da noch eine Zwischenschicht

[6:50] einführen oder ich nenne die Straße jetzt auf einmal Weg, warum auch immer man das machen

[6:54] sollte, ja, dann musst du deinen Code anpassen. Weil in deinem Code steht ja Person, get Adresse,

[6:59] get Straße. Und wenn sich dann daran irgendetwas ändert, dann musst du deinen Code, der das ja

[7:04] alles aufruft und zusammenstöpselt, entsprechend anpassen. Und das bedeutet, du wirst auf einmal

[7:08] abhängig von Code-Änderungen, mit denen du eigentlich gar nichts zu tun hast und auch

[7:12] gar nicht haben willst. Du willst ja bloß diesen Buchstaben der Straße haben. Was interessiert es

[7:15] sich, ob die Straße jetzt auf einmal in der Adresse ist oder sonst wo? Das muss auf deinen

[7:19] Code gar keine Auswirkung haben. Du willst es gar nicht wissen. Aber in diesem Fall wäre es so,

[7:23] weil du dich halt an diese Struktur gebunden hast. Deswegen wäre es schön, wenn wir das nicht hätten,

[7:27] denn dann müsste ich meinen Code nicht mehr anpassen, wenn sich Änderungen ergeben. Und das

[7:31] ist ja genau die Idee des Geheimnisprinzips. Ich darf in meinen Klassen das ändern, was ich will.

[7:36] Ich will refaktorisieren. Ich will alles über den Haufen schmeißen, alles neu machen, weil ich den

[7:40] perfekten, tollen Algorithmus gefunden habe. Und wenn ich das mache und am Ende müssen fünf andere

[7:44] Entwickler ihren Code anpassen, dann haben die was falsch gemacht. Oder ich, je nachdem, wenn ich die

[7:49] Kapselung nicht umgesetzt habe und alles public gemacht habe. Okay, andere Sache, ja. Aber

[7:53] normalerweise ist es so, dass ich als Entwickler einer Klasse die Hoheit darüber habe, was da drin

[7:58] passiert. Und wenn ich die interne Struktur umstellen will, dann darf das draußen niemanden

[8:04] interessieren. Und jetzt kommen wir noch zu einem weiteren Problem mit dem Code, der nicht dem Law

[8:11] of Dimiter folgt. Und zwar, denk mal an die Tests. Wie sehen die denn jetzt aus? Wenn ich an meiner

[8:16] Person diesen ersten Buchstaben der Straße herausfinden will, dann muss mein Test eine

[8:21] Person, ja, erzeugen, gut zum Testen, das muss er auf jeden Fall, aber es muss eine mock-Adresse

[8:27] geben, die eine mock-Straße zurückliefert, die ein mock-Buchstaben zurückgibt. Beziehungsweise

[8:33] ersetze mock durch stub, weil eigentlich reden wir hier gerade von stubs, ja. Das heißt, ich muss

[8:36] diese ganze Objektstruktur erst mal aufbauen in meinem Test, nur um an der Person diesen blöden

[8:42] Buchstaben aufrufen zu können. Denn wenn ich person get Adresse mache und da kommt nix, dann

[8:48] gibt es einen Null-Pointer, ja. Dann, wenn ich Glück habe und ich habe die Adresse gemockt, dann

[8:52] gibt die mir eine Straße zurück. Wenn da auch nix kommt, gibt's wieder einen Null-Pointer. Das heißt,

[8:55] ich muss das ganze Zeug mocken, Schrägstrich stubben, ja. Übrigens zum Unterschied habe ich

[9:00] schon mal eine Podcast-Episode gemacht. Hört da gerne mal rein, was der Unterschied ist, ja. Aber wenn ich das

[9:04] nicht mache in meinem Test, dann fliegt der mir entweder um die Ohren, ja, Punkt. Also es gibt kein

[9:10] anderes. Fliegt mir um die Ohren, ja. Das heißt, entweder ich teste das nicht oder ich habe einen

[9:14] riesengroßen Testaufwand. Und übrigens, diesen ganzen Mocking-Stubbing-Kram muss ich natürlich

[9:19] auch anpassen, wenn sich die interne Struktur wieder ändert, ne. Wenn jetzt irgendwer entscheidet,

[9:22] Mensch, Adresse, wenn denn die Straße jetzt doch um den Weg, dann muss ich natürlich auch alle Sachen

[9:26] in meinem Test wieder anpassen, weil da heißt es jetzt ja dann auch anders, ja. Also, keine gute Idee,

[9:30] sich so stark an die Innereien fremden Codes zu binden. Das macht mir als Entwickler Probleme,

[9:37] die ich nicht haben will. Außerdem passt das Law of Dameter ganz gut zu uns Entwicklern. Wir sind ja

[9:42] klischee-mäßig sehr schüchtern und zurückhaltend und leben eigentlich nur im Keller. Und der Code,

[9:47] der dem Law of Dameter folgt, ist auch schüchtern oder shy auf Englisch, weil der redet nur mit

[9:54] seinen Freunden. Der redet nicht mit Fremden, der spricht die nicht an. So kann man sich das auch

[9:59] noch mal einmal merken, ja. Wenn wir uns noch mal einmal die Definition vor Augen führen,

[10:02] die Methode darf andere Instanzmethoden ihrer eigenen Klasse aufrufen. Da ist sie ja selber

[10:08] Teil davon, das kennt sie, da fühlt sie sich wohl, das darf sie gern aufrufen. Dann die Parameter,

[10:12] das sind die Dinger, die ihr von draußen reingegeben werden. Die hat sie also schon

[10:16] im Zugriff, die kriegt sie rein, da muss sie nicht selber was aufrufen. Und Methoden und Objekten,

[10:22] die sie selbst erzeugt. Das heißt, wenn die Methode sagt, ich klöppel mir jetzt mal was

[10:25] zusammen, dann hat sie es ja selbst erzeugt, dann kennt sie das auch und darf auch damit reden.

[10:29] Und jetzt vielleicht noch mal ein Hinweis zum letzten Punkt aus der Definition. Globale

[10:33] Variablen, das ist so ein Ding, das war in den 80ern vielleicht hip, wobei eigentlich auch damals

[10:38] schon nicht, aber heute ist es definitiv No-Go. Globale Variablen oder auf Java, C-Sharp, Ruby

[10:44] übersetzt Singletons. Solche Sachen wollen wir nicht benutzen in unserem Code, auch nicht,

[10:49] wenn das nach Law of Demeter erlaubt wäre. Das wollen wir aus verschiedenen Gründen nicht,

[10:55] da gehe ich jetzt mal nicht im Detail drauf ein, hauptsächlich deswegen, weil globale Variablen

[10:59] ja von jedem geändert werden können. Global heißt ja überall verfügbar und man im Nachhinein nicht

[11:04] mehr herausfinden kann, wer jetzt eigentlich wann wie was geändert hat. Und das führt zu

[11:07] ganz komischem Verhalten im Code, insbesondere bei der Fehlersuche. Also bitte keine globalen

[11:11] Variablen verwenden, deswegen streiche ich den Punkt mal komplett. Heutzutage ist der etwas

[11:16] überholt, möchte ich mal sagen. Aber das Law of Demeter kommt aus den 80ern und da gab es sie

[11:20] noch überall und deswegen war das wohl ganz gut, die aufzuführen. Also, das ist jetzt die Erklärung

[11:26] des Law of Demeters. Ich habe in den Shownotes ein kleines Code-Beispiel dafür mal mitgebracht

[11:30] und das erkläre ich nochmal ganz kurz. Ich habe hier mal ein Szenario aus der Versicherung

[11:34] nachgebaut. Da gibt es das Konzept einer Versicherungspolizei, hast du vielleicht schon

[11:38] mal gehört. Und in so einer Polizei sind in diesem Beispiel mehrere Personen versichert,

[11:43] z.B. Stefan und seine Frau. Und jede dieser Personen hat wiederum Tarife, z.B. Kfz-Versicherung,

[11:49] Hausrat-Versicherung, was auch immer. Das heißt, wir haben quasi eine Versicherungspolizei hat

[11:53] n Personen, jede Person hat wieder n Tarife. Das heißt, wir haben schon eine etwas umfangreichere

[12:00] Struktur mit Listen und so weiter und so fort. So, wenn ich jetzt von der Versicherungspolizei

[12:05] den gesamten Beitrag berechnen will, dann müsste ich über deren versicherte Personen iterieren

[12:12] und jeweils über deren versicherte Tarife und mir aus den Tarifen die Beiträge holen und das

[12:19] dann alles schön aufsummieren. Das heißt, ich friemel bis in die letzten Tiefen dieser

[12:24] Objektsstruktur drin herum. Ich mache sowas wie for each Versicherungspolizei.versichertePersonen

[12:30] und dann noch mal for each Tarife in versichertePersonen und dann sum auf den Beitrag

[12:36] oder sowas. Das kann ich natürlich modern ziemlich cool programmieren, mit so einem Stream,

[12:41] mit einem Map, mit einem Flatmap und einem Reduce und ich weiß nicht was. Das Beispiel ist in Ruby,

[12:46] da heißt es dann Flatmap und Inject, aber es macht das gleiche, was ich gerade beschrieben

[12:50] habe. Und das sieht dann auch ziemlich cool aus und modern und hip und so. Sieht es wirklich. Also

[12:56] ich finde das auch richtig cool, wie das aussieht. Das Problem ist nur, wenn sich irgendwas in dieser

[12:59] Struktur ändert, wie gesagt, da muss ich den ganzen Code anpassen. Von daher, das sieht

[13:03] oberflächlich interessant und spannend und cool aus, ist aber nicht allzu lange wartbar,

[13:09] wenn sich an der Struktur irgendwann mal was ändern sollte. Deswegen gucken wir uns die

[13:12] Alternative an, wie man es denn stattdessen machen könnte. Ich würde der Klasse Versicherungspolizei

[13:17] einfach eine Methode geben, Gesamtbeitrag oder berechnerGesamtbeitrag, wie auch immer. Und diese

[13:22] Methode geht dann über die versichertenPersonen und addiert deren Beitragssumme. Und diese

[13:29] versichertePersonen errechnet die Beitragssumme, indem sie selbst über ihre Tarife iteriert und

[13:35] alle Beiträge aufsummiert. Das heißt, von draußen steht da jetzt nur noch sowas wie,

[13:38] hey, Versicherungspolizei, getBeitrag. Und das war's. Das ist langweilig, ist aber einfach zu

[13:45] verstehen und vor allem auch einfach zu testen. Ich muss nicht 37 Personen und Tarife instanziieren

[13:50] und alle in der richtigen Reihenfolge zusammenstöpseln, um diese Methode zu testen,

[13:53] denn es ist ja einfach nur ein einziger Aufruf. Polizei, getBeitrag, Feierabend. Und wenn wir uns

[13:58] jetzt auch mal als kleinen Nebeneffekt die einzelnen Klassen angucken, dann sehen wir,

[14:02] dass wir sogar die Logik, die wir vorher in dieser aneinandergeketteten Struktur,

[14:06] also Versicherungspolizei, getPersonen, flatMap, tralala, hatten, dass wir die auf einmal in den

[14:11] einzelnen Klassen haben jetzt. Das heißt, die Polizei weiß, wie sie ihren Beitrag berechnet,

[14:16] nämlich über die Personen. Die Person weiß, wie sie ihren Beitrag berechnet, nämlich über die

[14:21] Tarife. Und der Tarif weiß das sowieso, weil der hat einen Beitrag. Das heißt, wir haben vorher

[14:26] sehr komplizierte, geschachtete Logik auf einmal auf mehrere Klassen verteilt und in diesen einzelnen

[14:32] Klassen ist die Logik aber viel, viel kleiner. Da ist es teilweise so ein Einzeiler. Das heißt,

[14:36] wir haben zusätzlich den Effekt, dass wir diese vorher zentralisierte Logik etwas verteilt haben

[14:42] und das wird auch wieder viel einfacher testbar jetzt und vor allem auch viel einfacher zu

[14:45] verstehen. Das heißt, diese ganze Methodchain, die ich da vorher hatte, die nachzuvollziehen,

[14:49] ist nicht ganz so einfach. Gucke ich jetzt in eine Klasse rein und sehe hier ein Einzeiler für die

[14:54] Berechnung, dann verstehe ich das sofort. Und viel, viel wichtiger ist jetzt aber, von draußen

[14:59] mache ich einfach, hey Police, gib mir deinen Beitrag. Und es interessiert mich null, wie sie

[15:03] den berechnet, ob sie sich irgendwelche Listen erzeugt und was aufsummiert und ich weiß nicht,

[15:07] was. Das ist mir ganz egal. Ich will ja nur die Zahl haben. Ich will doch nur den Beitrag. Also,

[15:11] warum muss ich das alles selber machen? Brauche ich nicht. Ich frage die Police und die macht

[15:15] das einfach für mich und delegiert alles, was sie nicht weiß, weiter an die anderen Klassen.

[15:19] Das wäre jetzt so ein kleines Vorher-Nachher-Beispiel zum Verständnis. Einfach gerne mal

[15:24] in die Shownotes gucken, da kannst du das im Detail angucken. Und damit wäre ich jetzt auch

[15:28] schon mal meinen kleinen Vor- und Nachteile-Listen. Ich habe mehr Vorteile als Nachteile. Wenig

[15:33] überraschend. Ich finde, das läuft damit eigentlich auch ziemlich cool, muss ich sagen. Vorteil 1,

[15:38] der Code wird verständlicher. Es müssen nicht mehr diese ganzen Strukturen bekannt sein. Habe

[15:41] ich jetzt im Detail darüber geredet. Dann, das letzte Beispiel eben, die fachliche Logik landet

[15:46] eventuell da, wo sie hingehört und nicht beim Aufrufer, der sich alles zusammen basteln muss.

[15:51] Stattdessen haben wir ein paar Abstraktionen mehr, wie gerade Police-Nummer, Get-Gesamtbeitrag.

[15:56] Das ist natürlich sehr viel abstrakter als Wurschtel, Wurschtel, Wurschtel in irgendwelchen

[16:00] Strukturen rum. Dann haben wir schon gelernt, die Komponenten sind unabhängiger voneinander

[16:04] und lassen sich viel, viel, viel leichter testen. Ich brauche keine Mocks oder Stubs oder irgendwas.

[16:08] Ich brauche nur Versicherungspolice-Gesamtbeitrag. Dann die ganzen Test-Setups, die vielleicht nötig

[16:15] wären, wie gerade beschrieben, lassen sich damit auf jeden Fall vermeiden. Meine Tests werden also

[16:19] viel, viel kürzer und lesbarer und brechen auch nicht so schnell, wenn sich irgendwo was an der

[16:24] Struktur ändert. Und ganz wichtig, interne Änderungen an den verwendeten Komponenten sind

[16:29] möglich für die jeweiligen Entwickler, ohne dass ich davon beeinträchtigt werde. Das heißt, ich

[16:35] selber habe weniger Anpassungsaufwand und die Entwickler, die ihre Komponenten ändern, können

[16:38] das machen. Die können also herumprobieren, neue Sachen entwickeln, optimieren, was auch immer sie

[16:43] wollen, ohne dass ich davon irgendwie beeinflusst werde. Zwei kleine Nachteile gibt es aber auch.

[16:50] In meinem Code-Beispiel ist es tatsächlich sinnvoll, diese einzelnen Methoden in die

[16:53] Ebenen einzuführen, weil sie da auch einen Sinn ergeben. Habe ich tatsächlich hauptsächlich so

[16:57] Getter-Logik, die irgendwie jetzt nichts weiter machen, als an irgendwelche internen Instanz-

[17:02] variablen zu delegieren, die dann wieder delegieren und so weiter, dann sieht das natürlich so ein

[17:06] bisschen nach Boilerplate aus. Wenn ich für jeden Kram, den ich da haben will, immer ein Getter

[17:10] brauche, gehen wir zu dem Beispiel zurück von der Adresse. Die Person macht Get-Adresse, die

[17:16] Adresse hat Get-Straße, die Straße hat Get-Buchstabe und im Zweifel haben die alle nur eine einzige

[17:21] Zeile Code und geben das jeweils an ihre Instanz-Variablen weiter. Das ist natürlich

[17:25] ja quasi die Definition von Boilerplate-Code. Das will ich vielleicht nicht unbedingt haben,

[17:30] dadurch wird der Code auch nicht unbedingt schöner und vor allem ist natürlich auch Arbeit für mich.

[17:33] Ich muss das ja alles programmieren, wenn ich das nicht irgendwie automatisiert generiert

[17:37] bekomme oder meine Programmiersprache das automatisch macht. Ich habe mir sagen lassen,

[17:40] es gibt Programmiersprachen, wo sowas geht. Da kann man irgendwie a Delegate oder ich weiß nicht

[17:45] was dran schreiben, also nicht die Delegates aus C-Sharp, das meine ich nicht, sondern sowas wie

[17:48] der Getter delegiert automatisch an die Instanz-Variable und so. Habe ich mal irgendwo gesehen,

[17:51] ich weiß gar nicht mehr in welcher Sprache. Coole Sache, dann brauche ich das hier alle nicht,

[17:54] aber ansonsten muss ich die ganzen Dinger ja ausprogrammieren und das ist wenig spannend.

[17:58] Ich muss das Zeug auch noch testen, wenn ich eine gute Code-Coverage brauche und das ist natürlich

[18:02] ja nicht so meine Lieblingsbeschäftigung als Entwickler, sei mal so. Ja und dann außerdem,

[18:08] wenn ich weiß, dass die Struktur, die ich da gerade abfrage, sich so gut wie nie ändern wird,

[18:12] dann gehe ich noch mal zurück zu Person, Adresse, Straße. Wer würde auf die Idee kommen,

[18:15] die Straße jetzt umzubinden in den Weg oder die Straße woanders hinzupacken und nicht mehr in die

[18:19] Adresse, ist wahrscheinlich nicht so oft angesagt, würde ich sagen. Deswegen kann man jetzt natürlich

[18:25] überlegen, wenn die Struktur hier wirklich fest ist und die wird sich so gut wie nie ändern oder

[18:30] ich bin mir sehr sicher, dass es wenig Änderungen geben wird, dann kann ich natürlich darauf

[18:34] verzichten, diese ganzen Methoden einzuführen und sage einfach, komm, ist doch egal. Lass ihn über

[18:38] die Adresse an die Straße, mach einfach. Ja, das ist auch wieder so ein bisschen trade-off. Wenn

[18:43] ich es ganz sicher weiß, kann ich es vielleicht machen, wenn nicht und meistens ist es so,

[18:47] dass man eben voraus eben nicht daran denkt oder nicht glaubt, dass sich was ändern kann,

[18:51] ja dann hat man den Salat am Ende. Ja, also im Zweifel würde ich lieber das Law of Damnita

[18:57] anwenden, als es wegzulassen, weil die Chance, dass sich doch irgendwas ändert, ist eigentlich

[19:02] fast immer irgendwie gegeben, auch wenn man es zum Anfang nicht geglaubt hat. Gut, ich fasse

[19:07] nochmal zusammen. Das Law of Damnita sagt, dass eine Methode nur Zugriff haben sollte auf andere

[19:13] Instanzmethoden in ihrer eigenen Klasse, ihre Parameter, Methoden in Objekten, die sie selbst

[19:19] erzeugt und globale Variablen vergisst du ganz schnell, die wollen wir gar nicht mehr benutzen.

[19:23] Also eigentlich nur auf drei Dinge, ja und kurz zusammengefasst, alles was die Methode rein bekommt,

[19:28] selbst erzeugt oder in der eigenen Klasse liegt, darf sie aufrufen und sonst nichts und das bedeutet,

[19:33] du machst kein Method-Chaining a la Person, Adresse, Straße etc., sondern jedes Objekt

[19:39] delegiert Methodenaufrufe an seine Instanzvariablen, darf es alles machen, aber du als Aufrufer setzt

[19:45] nur einen Punkt zwischen das Objekt und die Methode, die du aufrufen willst und danach muss

[19:49] Feierabend sein. So erkennst du, dass du das Law of Damnita anwendest. Übrigens noch ganz schnell

[19:54] hinterhergeschoben, wenn du jetzt auf die Idee kommst, das kann ich. Ich mache jetzt einfach

[19:59] var Person gleich Person und dann mache ich in der nächsten Zeile var Adresse gleich Person

[20:04] get Adresse und dann mache ich var Straße gleich Adresse get Straße. Nee, nee, nee, nee, nee, das

[20:10] ist nicht damit gemeint. Das ist natürlich das Gleiche in grün, bloß dass der Punkt in drei

[20:14] unterschiedlichen Zeilen steht, ja. Also nicht clever werden, sondern denk dran, es geht darum,

[20:21] dass du diese Struktur nicht kennen musst und die musst du kennen, wenn du diese Variablen

[20:26] nacheinander da aufrufen willst. Also das ist das Gleiche in grün, sieht bloß anders aus.

[20:31] Gut, wenn du noch ein bisschen was dazu lesen willst, dann kannst du gerne in The Pragmatic Programmer,

[20:36] eines meiner absoluten Lieblingsbücher, schauen. Ist gerade frisch neu aufgelegt worden in der

[20:40] 20-Jahres-Jubiläums-Edition. Oder das ist, glaube ich, schon ein, zwei Jahre her sogar tatsächlich.

[20:46] Kann ich nur wärmstens empfehlen. Stehen noch ganz, ganz viele andere coole Sachen drin und da werde

[20:50] ich ganz sicher auch noch mal einen Buchclub zumachen. Aber es gibt auch ein kurzes Kapitel

[20:53] tatsächlich zum Law of Demeter, wo das alles nochmal so ein bisschen erklärt wird, was ich

[20:57] auch erzählt habe. Hier ist nicht allzu lang tatsächlich. Ich habe aber auch noch ein paar

[21:00] Links in die Shownotes gepackt, wo das noch ein bisschen mit Codebeispielen und so auch noch

[21:04] weiter erklärt wird. Also schau gerne rein. Die Shownotes findest du übrigens unter itberufe-podcast.de

[21:10] slash WH8, Wissenshäppchen 8. Großes W, großes H und dann die 8 dahinter. So, das war das achte

[21:21] Wissenshäppchen. Ich bin knapp über 20 Minuten. Das ist für ein Häppchen gerade noch so okay,

[21:26] wie ich finde. Ich hoffe, es hat dir ein bisschen was gebracht und du versuchst,

[21:29] das jetzt in deinem Code auch anzuwenden. Wird ihn sicherlich verständlicher,

[21:32] kürzer, besser, toller machen. Auf jeden Fall. So, viel Spaß beim Coden und bis zum nächsten Mal. Tschüss!

Wissenshäppchen

Dependency Inversion Principle (DIP) – Wissenshäppchen #7
Polyglot Clean Code Developer
About the Author
Ausbildungsleiter für Fachinformatiker:innen Anwendungsentwicklung, IHK-Prüfer und ehemaliger Hochschuldozent für Programmierung und Software-Engineering.

Schreibe einen Kommentar

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