Sunday, October 14, 2007

Patterns 2 - Prinzipen

Im zweiten Beitrag zu Patterns möchte ich einige Prinzpien ansprechen welche helfen einen ausgereiften und qualtitativ guten Softwaredesign zu finden. Diesen Prinzipien zu folgen ist nicht immer ganz einfach, aber sie machen schlussendlich den Unterschied zwischen einer längerfristig wartbaren und qualtitativ hochstehenden Software und einem Hack aus.

DRY: Das Prinzip 'Don't repeat yourself' besagt dass in der Programmierung keine Duplikationen und Redundanzen enthalten sein sollen. Dies gilt für den Programmcode, für Datenbankdefinitionen usw... Das befolgen dieses Prinzips erhöht die Wiederverwendung von Programmcode enorm und ist Ausdruck einer sauberen Programmierung schlechthin.

Law of Demeter: Vereinfacht gesagt definiert das Gesetz von Demeter die Regel 'Sprich nur mit deinen engsten Freunden'. Damit wird erreicht dass eine Klasse sich nur an das nächste Umfeld koppelt (die engsten Freunde) und nichts über die Struktur des weiteren Umfelds wissen muss. Das Gesetz definiert formal für die Methode M auf dem Objekt o:
  1. o.M() -> darf auf Methoden von o zugreifen (z.B mit this.X())
  2. o.M(a) -> darf auf Methoden von a zugreifen (z.B mit a.X())
  3. 0.M() -> darf auf Methoden von selbst erzeugten Objekten zugreifen (z.B a = new A() wird in der Methode M() angelegt, dann darf o.M() -> a.X() aufrufen)
  4. o.M() -> darf auf Methoden von direkt aggregierten Objekten zugreifen(z.B a = new A() wird im Konstruktor angelegt, dann darf o.M() -> a.X() aufrufen)
Open/Close Principle: Das Prinzip verlangt dass eine Softwarekomponente (Module, Klasse, Methode usw...) erweitert werden kann ohne dass die Komponente in ihrer inneren Struktur angepasst werden muss. Der Sinn dahinter ist dass eine Komponente ihre Verantwortlichkeit (gemäß Single Responsibilty Principle sollte da nur eine sein) erfüllt ohne sich um die entsprechenden Variationen kümmern zu müssen. Für die Variation sollte die Komponente aber entsprechende Erweiterungpunkte mittels Delegation an Abstrakte Interfaces oder Vererbung durch Abstrakte Methoden gegen aussen zur Verfügung stellen. Auf der anderen Seite sollte die Komponente auch ihre interne Struktur gegen aussen verbergen. Dies ermöglicht dass die Komponente ihre Implementation ändern kann, ohne dass Erweiterung und Variationen davon betroffen wären.

Single Responsibilty Principle: Das Prinzip besagt, dass ein Komponente (Module, Klasse, Methode usw..) genau eine Verantwortlichkeit haben soll. Eine Komponente sei somit nur anzupassen, wenn bezüglich dieser Verantwortlichkeit die Anforderungen ändern. Um dies erreichen zu können müssen folgende wechselwirkenden Aspekte betrachtet werden.

  1. Es sollte eine hohe Kohäsion erreicht werden. Eine Komponente hat ein hohe Kohäsion falls sie eine wohldefinierte Aufgabe abschliessend und vollständig löst (siehe auch Link)

  2. Durch lose Kopplung sollen Komponenten über ein wohldefinierte und möglichst kleine Schnittstelle miteinander verbunden werden

  3. Mit Separation of Concerns sollen die Verantwortlichkeiten in einer Software systematisch aufgeteilt werden. Dabei werden die Verantwortlichkeiten eindeutig an ein Module, Klasse oder Methode übergeben.
Liskov Substitution Principle: "Methoden welche Basisklassen (auch Abstrakte Klassen oder Interfaces) als Parameter haben müssen diese Objekte benutzen können ohne die konkrete Klasse zu kennen". Dies bedeutet für die Überschreibung einer Methode oder die Implementierung einer Methode aus einem Interface:

  1. Vorbedingung dürfen nicht verschärft werden
  2. Nachbedingungen dürfen nicht abgeschwächt werden
  3. Es dürfen nur Klassen von Exceptions geworfen werden welche auch von der Basis-Methode geworfen werden oder als mögliche Exception dort dokumentiert wurde. Es dürfen auch Ableitungen von diesen Exceptions geworfen werden
Eine Verletzung des Liskov Subsitution Principle führt zu einer schlecht funktionierenden Umsetzung des Open/Close Principle da sich die Erweiterung instabil verhalten wird.

Dependency Inversion Principle: Das Prinzip hat zum Ziel einen flexiblen und somit wartbaren Design zu finden. Die Regeln besagen:
  1. Übergeordnete Komponenten (Module, Klasse, Methode usw...) und untergeordnete Komponenten sollten nicht direkt voneinander abhängig sein. Beide sollten nur von Abstraktionen abhängig sein.
  2. Abstraktionen sollten nicht von den Details abhängig sein. Details sollten von den Abstraktionen abhängig sein.

Das Prinzip ist auch für den TDD Entwicklungsansatz enorm wichtig, da dadurch das mocken und stubben von untergeordneten Modulen ermöglicht wird. So gesehen stellt TDD einen flexible Design sicher, da der übergeordnete Code im Unitest mit Mocks und Stubs und in der laufenden Applikation mit den konkreten untergeordneten Modulen arbeitet.

Noch was vergessen?

No comments: