Menschen von oben fotografiert, die an einem Tisch sitzen.

adesso Blog

Softwareentwicklung kann man mit dem Autofahren vergleichen: Theoretisch könnt ihr alles über das Autofahren wissen, jedoch bedeutet das nicht, dass ihr perfekt Auto fahren könnt. Genauso ist es mit der Softwareentwicklung. Es ist also nicht zu erwarten, dass man ohne langjährige praktische Erfahrung vom perfekten Code-schreiben sprechen kann. Das gab mir den Anstoß in meinem Blog-Eintrag die Clean-Code-Prinzipien nochmals darzulegen. Diese werde ich gleichzeitig mit den Softwaremetriken verlinken, um zu zeigen, wie man die Code-Qualität messen und verbessern kann.

Zugängliche Metriken

Nicht alle Prinzipien des Clean Codes sind durch Softwaremetriken zugänglich. Die Metriken, bei denen es möglich ist, möchte ich euch kurz vorstellen.

  • Source lines of code (SLOC): Die Anzahl der Programmzeilen.
  • Afferent couplings (Ca): Die Anzahl der Klassen außerhalb des Pakets, die von den Klassen innerhalb des Pakets abhängen. Ca ist ein Indikator für die Verantwortung des Pakets.
  • Efferent couplings (Ce): Die Anzahl der Klassen außerhalb des Pakets, von denen die Klassen in einem Paket abhängen. Ce ist ein Indikator für die Abhängigkeit des Pakets von externen Klassen.
  • Abstractness (A): Das Verhältnis der Anzahl der abstrakten Klassen (und Schnittstellen) zur Gesamtzahl der Klassen im Paket. Der Bereich für diese Metrik liegt zwischen 0 und 1, wobei A = 0 ein vollständig konkretes Paket und A = 1 ein vollständig abstraktes Paket angibt.
  • Instability (I): Das Verhältnis der efferenten Kopplung (Ce) zur Gesamtkopplung (Ce + Ca), so dass I = Ce / (Ce + Ca) ist. Der Bereich für diese Metrik liegt zwischen 0 und 1, wobei I = 0 ein vollständig stabiles Paket und I = 1 ein vollständig instabiles Paket angibt.
  • Distance from the main sequence (D): Der senkrechte Abstand eines Pakets von der idealisierten Linie A + I = 1. D wird berechnet als D = | A + I - 1 |. Diese Metrik ist ein Indikator für das Gleichgewicht zwischen Abstraktheit und Stabilität des Pakets.
  • Number of Children (NOC): Die Anzahl der unmittelbaren Nachkommen der Klasse.
  • Depth of Inheritance Tree (DIT): Die maximale Länge eines Pfads von einer Klasse zu einer Stammklasse in der Vererbungsstruktur eines Systems.
  • Response for Class (RFC): Die Anzahl der von einer Klasse aufgerufenen unterschiedlichen Methoden und Konstruktoren.
  • Cyclic Dependencies: Zyklische Abhängigkeiten zwischen Klassen oder Komponenten.
  • Cyclomatic Complexity: Diese Metrik identifiziert die Anzahl linear unabhängiger Pfade durch den Quellcode, um die Komplexität eines Programms zu messen.

Die folgenden Prinzipien sind die Grundlagen für eine strukturelle gute Softwarearchitektur. Durch Messung der Metriken und der Peer-Review-Verfahren kann sichergestellt werden, dass die Prinzipien auch eingehalten werden. Die Clean-Code-Prinzipien, die hier näher betrachtet werden, sind:

  • Keep it Simple, Stupid (KISS)
  • Don’t Repeat Yourself (DRY)
  • Favor Composition over Inheritance (FCoI)
  • Separation of Concerns (SoC)
  • Information Hiding Principle (IHP)
  • Single Responsibility Principle (SRP)
  • Open Closed Principle (OPP).
  • Liskov Substitution Principle (LSP)
  • Interface Segregation Principle (ISP)
  • Dependency Inversion Principle (DIP)
Keep it Simple, Stupid (KISS)

KISS ist ein Prinzip, das besagt, dass Designs so einfach wie möglich sein sollten. Komplexität sollte in einem System nach Möglichkeit vermieden werden.

Metrik:

  • Source lines of code (SLOC) : Ein kleiner Wert weist auf eine gute Umsetzung hin.
  • Cyclomatic Complexity : Ein niedriger Wert weist auf eine gute Umsetzung hin.
Don’t Repeat Yourself (DRY)
“Every piece of knowledge or logic must have a single, unambiguous representation within a system.” Andy Hunt & Dave Thomas, “The Pragmatic Programmer”

DRY ist ein Prinzip der Softwareentwicklung, das darauf abzielt, die Wiederholung von Informationen aller Art zu reduzieren.

Metrik:

  • Number of Children (NOC) : Ein größerer Wert weist auf eine gute Umsetzung hin.
Favor Composition over Inheritance (FCoI)
“Favor object composition over class inheritance.” Gang of Four, “Design Patterns: Elements of Reusable Object-Oriented Software”.

Komposition an Stelle von Vererbung ist eine Technik, durch die die Klassen entkoppelt werden, was wiederum zu flexibleren und stabileren Designs führt. So ist es möglich, zur Laufzeit das Verhalten einer Klasse zu verändern.

Metrik:

  • Depth of Inheritance Tree (DIT ) : Ein kleiner Wert weist auf eine gute Umsetzung hin.
Separation of Concerns(SoC)

SOC ist ein Prinzip des Softwaredesigns, bei dem der Code in Schichten und Komponenten unterteilt wird, die jeweils unterschiedliche Funktionen mit möglichst geringer Überlappung aufweisen.

Metrik:

  • Cyclomatic Complexity : Ein niedriger Wert weist auf eine gute Umsetzung hin.
  • Response for Class (RFC) : Ein niedriger Wert weist auf eine gute Umsetzung hin.
  • Instability (I) : Ein niedriger Wert weist auf eine gute Umsetzung hin.
Information Hiding Principle (IHP)
"Information Hiding" ist der Vorgang des Versteckens der Details eines Objekts oder einer Funktion. Das Ausblenden dieser Details führt zu einer Abstraktion, die die externe Komplexität verringert und die Verwendung des Objekts oder der Funktion erleichtert.

Metrik:

  • Depth of Inheritance Tree (DIT ) : Ein kleiner Wert weist auf eine gute Umsetzung hin.
  • Response for Class (RFC) : Ein niedriger Wert weist auf eine gute Umsetzung hin.
  • Instability (I) : Ein niedriger Wert weist auf eine gute Umsetzung hin.

Die SOLID-Prinzipien

SOLID ist eine Abkürzung für fünf Designprinzipien, die Softwaredesigns verständlicher, flexibler und wartbarer machen sollen.

Single Responsibility Principle (SRP)
“There should never be more than one reason for a class to change.” Robert C. Martin, „Principles of Object Oriented Design“

Das „Single Responsibility Principle (SRP)“ besagt, dass es niemals mehr als einen Grund für eine Änderung in einer Klasse geben sollte. Dies bedeutet, dass jede Klasse oder ähnliche Struktur in ihrem Code nur eine Funktion beinhaltet. Alles in der Klasse sollte sich auf diesen einen Zweck beziehen.

"Swiss Knife"-Klassen sollten also vermieden werden, denn sie sind nicht übersichtlich, schwer zu warten und sehr zerbrechlich. Aus diesem Grund sollten Änderungen nur auf wenige Klassen zutreffen. Je mehr Codes geändert werden müssen, desto höher ist das Fehlerrisiko. Es ist besser, viele kleine Klassen je mit einer Funktion zu haben, als wenige große.

Ein weiterer Vorteil kleiner, zusammenhängender Klassen besteht darin, dass die Wahrscheinlichkeit einer Klasse, die Fehler enthält, verringert wird. Dies reduziert den Änderungsbedarf, sodass der Code weniger anfällig ist. Da die Klassen nur eine Aufgabe erfüllen, arbeiten mehrere Klassen zusammen, um größere Aufgaben zu erfüllen.

Eine Versicherungsantragsklasse stellt ein konkretes Beispiel dafür dar. Diese Klasse beinhaltet diverse Funktionen wie das Berechnen der Prämie, das Ausdrucken und das Speichern.

Solch ein Design ist gegen das Prinzip. Eine gute Umsetzung würde so aussehen.

Metrik:

  • Cyclomatic Complexity : Ein niedriger Wert weist auf eine gute Umsetzung hin.
  • Source lines of code (SLOC) : Ein kleiner Wert weist auf eine gute Umsetzung hin.
Open Closed Principle (OCP)
“Modules should be both open (for extension) and closed (for modification).” Bertrand Meyer, „Object Oriented Software Construction“

Das „Open Closed Principle (OCP)“ besagt, dass Klassen zur Erweiterung geöffnet, aber zur Änderung geschlossen sein sollten. "Offen für Erweiterungen" bedeutet, dass ihr eure Klassen so gestalten solltet, dass neue Funktionen hinzugefügt werden können. "Gegen Änderung geschlossen" bedeutet, dass ihr eine Klasse, sobald ihr sie entwickelt habt, niemals ändern solltet, außer um Fehler zu korrigieren.

Wenn eine Erweiterung nur durch Änderungen innerhalb der Klasse erreicht werden kann, besteht die Gefahr, dass durch diese Änderungen neue Fehler in den existierenden Funktionen auftreten können.

Es gibt zwei Wege dieses Prinzip umzusetzen:

  • Einsatz von Interfaces
  • Vererbung

Ein Beispiel dafür ist eine Rechnerklasse. Durch Vererbung ist es möglich zusätzliche Funktionen einzufügen.

Metrik:

  • Instability (I) : Ein niedriger Wert weist auf eine gute Umsetzung hin.
  • Cyclic Dependencies : Die Abhängigkeiten verstoßen gegen die OCP.
Liskov Substitution Principle (LSP)
"Subtypes must be substitutable for their base types." Robert C. Martin, “Agile Software Development: Principles, Patterns, and Practices”

Das LSP besagt, dass es möglich sein muss, eine Subklasse anstelle ihrer Superklasse zu verwenden. Die Superklasse kann jederzeit durch eine Subklasse ersetzt werden (substituieren). Das resultierende Programm darf keine semantischen Fehler aufweisen.

Nehmen wir den Klassiker als Beispiel: Ein Zirkel ist ein Eclipse bei dem Radius r1 und r2 gleich sind. Wenn man auf die Subklasse (Eclipse) zugreift, ist es nicht mehr möglich, die Radien separat einzustellen. Es ist kein Eclipse mehr.

Die Umsetzung von LSP würde dann folgendermaßen aussehen:

Es ist nicht möglich, das LSP über Metriken zu erfassen. Darüber hinaus ist es auch meistens nicht einfach, Verstöße gegen LSP in den Codes zu finden. Allerdings könnte man mit einem guten Peer-Review solche Verstöße rausfiltern.

Interface Segregation Principle (ISP)
"Many client-specific interfaces are better than one general-purpose interface." Robert C. Martin, “Agile Software Development: Principles, Patterns, and Practices”

Das „Interface Segregation Principle (ISP)“ besagt, dass Clients nicht gezwungen werden sollten, zu große Schnittstellen zu implementieren, die sie nicht verwenden. Solche "fetten" Schnittstellen sollten in kleinere Schnittstellen geschnitten und von der originalen Klasse implementiert werden. Der Client kann dann über die kleinere Schnittstelle auf die Klasse zugreifen - ohne wissen zu müssen, ob andere Clients vorhanden sind.

Kleinere Schnittstellen sind einfacher zu implementieren und erhöhen auch gleichzeitig die Flexibilität und die Wiederverwendungsmöglichkeit.

In der folgenden Darstellung wird eine „fette“ Schnittstelle veranschaulicht:

Durch die Umsetzung des Prinzips würde das Design folgendermaßen aussehen:

Metrik:

  • Cyclomatic Complexity : Ein niedriger Wert weist auf eine gute Umsetzung hin.
  • Instability (I) : Ein niedriger Wert weist auf eine gute Umsetzung hin.
Dependency inversion principle (DIP)

One should "depend upon abstractions, [not] concretions." Robert C. Martin, “Agile Software Development: Principles, Patterns, and Practices”

Das DIP besagt, dass Klassen einer höheren Ebene nicht von Klassen einer niedrigen Ebene abhängig sein sollen. Die Abhängigkeit beider Klassen sollte allein auf Abstraktionen beziehungsweise auf Interfaces basieren. Interfaces sollten jedoch nicht von Details abhängen, sondern Details von Interfaces.

Nehmen wir die folgenden Beispiele:

Durch Refactoring ist es möglich, dieses Prinzip gut umzusetzen:

Metrik:

  • Cyclic Dependencies : Die Abhängigkeiten verstoßen gegen die DIP.
  • Response for Class (RFC) : Ein niedriger Wert weist auf eine gute Umsetzung hin.
  • Distance from the main sequence (D) : Ein niedriger Wert weist auf eine gute Umsetzung hin.

Fazit

Unter starkem Zeitdruck zu stehen ist das fast unumgänglichste Problem von Softwareprojekten. Um den Anforderungen gerecht zu werden, wird auch (verständlicherweise) manchmal zu einfachen, meistens auch Quick-and-Dirty-Methoden, gegriffen.

Diese Vorgehensweise schafft einige Risiken, die in erster Linie nicht erkannt werden. Die Erweiterung und die Wartung solch einer Software ist schwieriger und kostet mehr Zeit und Geld.

Zu Anfang meines Artikels ging es um den Vergleich der Softwareentwicklung mit Autofahren. Für einen Anfänger ist es ein Alptraum beim Fahren auf den Verkehr zu achten, Gas oder die Kupplung zu bedienen und parallel dazu den Gang zu wechseln. Nach einer bestimmten Zeit und Fahrerfahrung läuft aber alles automatisch.

Genauso ist es bei der Softwareentwicklung. Je mehr versucht wird, diese Clean-Code-Prinzipien umzusetzen, desto mehr Erfolg bringt es. Mit der Zeit benutzt ihr die Prinzipien automatisch und reflexartig in der Designphase. Man kann nur sagen: „Übung macht den Meister.“

Ihr möchtet mehr über spannende Themen aus der adesso-Welt erfahren? Dann werft doch auch einen Blick in unsere bisher erschienenen Blog-Beiträge.

Bild Murat   Fevzioglu

Autor Murat Fevzioglu

Murat Fevzioglu ist bei adesso als Software Architekt im Bereich Banking tätig. Seine Tätigkeitsschwerpunkte liegen in der ganzheitlichen Analyse, Konzeption und Umsetzung von komplexen Projekten.

Diese Seite speichern. Diese Seite entfernen.