Karl Gorman
18
April
2016
Tags: Clean CodeEntwicklung
Chudovo blog

Clean Code: Prinzipien vom Class Design

Wir setzen mit den Artikeln über Clean Code-Konzepte fort, die auf einem berühmten Buch von R. Martin Clean Code basieren: Ein Handbuch für agile Software-Handwerkskunst, Klassifizierung von Urs Enzler von BBV und unsere Erfahrung.

Wir erlätern die Prinzipien des Class Designs: Single Responsibility Prinzip, Open Closed Prinzip, Liskov Substitution Prinzip, Dependency Inversion Prinzip, Interface Segregation Prinzip, Class Grösse.

Single Responsibility Prinzip

Das Single Responsibility Prinzip (SRP) lautet, dass eine Klasse oder ein Modul einen und nur einen Grund zur Änderung haben sollte. Dieses Prinzip gibt uns sowohl eine Definition der Verantwortung als auch Richtlinien für die Klassengröße. Der Unterricht sollte eine Verantwortung haben – einen Grund zur Änderung.

Die scheinbar kleine SuperDashboard-Klasse in Listing hat zwei Gründe, sich zu ändern. Erstens werden Versionsinformationen verfolgt, die jedes Mal aktualisiert werden sollen, wenn die Software ausgeliefert wird. Zweitens werden einige GUI-Komponenten verwaltet. Der Versuch, Gründe für Änderungen festzustellen, hilft uns oft dabei, bessere Abstraktionen in unserem Code zu erkennen und zu erstellen. Wir können alle drei SuperDashboard-Methoden, die sich mit Versionsinformationen zu tun haben, einwandfrei in eine separate Klasse „Version“ extrahieren.

SRP ist eines der wichtigsten Konzepte im OO-Design. Es ist auch eines der einfacheren Konzepte zum Vestehen. Komischerweise ist SRP eines der am häufigsten missbrauchten Prinzipien des Class Designs. Wir begegnen regelmäßig Klassen, die viel zu viele Aufgaben erfüllen. Warum? Software entwickeln und Software sauber zu machen, sind zwei sehr unterschiedliche Aktivitäten. Die meisten von uns haben denken eingeschränkt, daher konzentrieren wir uns darauf, dass unser Code eher funktioniert als auf seine Organisation und Sauberkeit. Dies ist völlig normal.

Das Problem ist, dass zu viele von uns denken, dass wir fertig sind, sobald das Programm funktioniert. Wir schauen nicht auf die Organisation und Sauberkeit. Wir gehen zum nächsten Problem über, anstatt zurück zu gehen und die überfüllten Klassen in entkoppelte Einheiten mit Einzelverantwortung aufzuteilen. Gleichzeitig befürchten viele Entwickler, dass eine große Anzahl kleiner Einzweckklassen es schwieriger macht, das Gesamtbild zu verstehen. Sie machen sich Sorgen, dass sie von Klasse zu Klasse navigieren müssen, um herauszufinden, wie eine größere Arbeit erledigt wird.

promo image3
Auf der Suche nach einer neuen Herausforderung?

Interessante Projekte

Zuverlässige Arbeitgeber

Faire Vergütung


Ein System mit vielen kleinen Klassen hat jedoch nicht mehr bewegliche Teile als ein System mit wenigeren großen Klassen. Mit ein paar großen Klassen gibt es im System genauso viel zu lernen.

Open Closed Prinzip

Klassen sollten zur Erweiterung geöffnet, aber zur Änderung geschlossen sein. Die Klasse ist offen, um neue Funktionen durch Unterklassen zuzulassen. Sie kann diese Änderung jedoch vornehmen, während jede andere Klasse geschlossen bleibt. Schnittstellenspezifikationen können durch Vererbung wiederverwendet werden, die Implementierung muss jedoch nicht erfolgen. Die vorhandene Schnittstelle ist für Änderungen geschlossen, und neue Implementierungen müssen diese Schnittstelle mindestens implementieren.

Kommentar: Das bringt mehr Flexibilität und verhindert neue Fehler. Nachteile treten häufig bei den gleichen Entitäten mit unterschiedlichem Verhalten auf. Es scheint das gleiche Problem wie bei SRP zu sein, das früh beschrieben wurde. Wir empfehlen, dieses Prinzip zu verwenden, wenn neue Objekte ziemlich starke Unterschiede in den Objekteigenschaften aufweisen.

Liskov-Substitutionsprinzip

Abgeleitete Klassen müssen ihre Basisklassen ersetzen können.

Das Liskov-Substitutionsprinzip lautet, dass das Objekt einer abgeleiteten Klasse ein Objekt der Basisklasse ersetzen kann, ohne Fehler im System zu verursachen oder das Verhalten der Basisklasse zu ändern. Kurz gesagt: Wenn S eine Teilmenge von T ist, könnte ein Objekt von T durch ein Objekt von S ersetzt werden, ohne das Programm zu beeinträchtigen und Fehler im System zu verursachen.

Angenommen, Sie haben ein Klassenrechteck und ein anderes Klassenquadrat. Quadrat ist als Rechteck, oder mit anderen Worten, es erbt die Rechteckklasse. Wie das Liskov-Substitutionsprinzip besagt, sollten wir in der Lage sein, das Objekt des Rechtecks ​​durch das Objekt des Quadrats zu ersetzen, ohne unerwünschte Änderungen oder Fehler im System zu verursachen.

Das Liskov-Prinzip stellt einige Standardanforderungen an Signaturen, die in neueren objektorientierten Programmiersprachen übernommen wurden (normalerweise eher auf Klassen- als auf Typenebene; Unterscheidung zwischen nominaler und struktureller Untertypisierung):

  • Kontravarianz von Methodenargumenten im Subtyp.
  • Kovarianz der Rückgabetypen im Subtyp.
  • Von Methoden des Subtyps sollten keine neuen Ausnahmen ausgelöst werden, es sei denn, diese Ausnahmen sind selbst Subtypen von Ausnahmen, die von den Methoden des Supertyps ausgelöst warden

Zusätzlich zu den Signaturanforderungen muss der Subtyp eine Reihe von Verhaltensbedingungen erfüllen. Diese werden in einer Terminologie beschrieben, die der von Design by Contract Methodology ähnelt, was zu einigen Einschränkungen hinsichtlich der Interaktion von Verträgen mit Vererbung führt:

  • Preconditions können in einem Subtyp nicht verstärkt werden.
  • Postconditionskönnen in einem Subtyp nicht geschwächt werden.
  • Invarianten des Supertyps müssen in einem Subtyp erhalten bleiben.

Verlaufsbeschränkung (die „Verlaufsregel“). Objekte gelten nur durch ihre Methoden als modifizierbar (Kapselung). Da Subtypen Methoden einführen können, die im Supertyp nicht vorhanden sind, ermöglicht die Einführung dieser Methoden Statusänderungen im Subtyp, die im Supertyp nicht zulässig sind. Die Historienbeschränkung verbietet dies. Es war das neuartige Element, das von Liskov und Wing eingeführt wurde. Ein Verstoß gegen diese Einschränkung kann durch Definieren eines veränderlichen Punkts als Subtyp eines unveränderlichen Punkts veranschaulicht werden. Dies ist eine Verletzung der Verlaufsbeschränkung, da der Status in der Geschichte des unveränderlichen Punkts nach der Erstellung immer derselbe ist und daher die Geschichte eines veränderlichen Punkts im Allgemeinen nicht enthalten kann. Dem Subtyp hinzugefügte Felder können jedoch sicher geändert werden, da sie mit den Supertyp-Methoden nicht beobachtet werden können. Somit kann man einen Kreis mit festem Mittelpunkt, aber veränderlichem Radius vom unveränderlichen Punkt ableiten, ohne den LSP zu verletzen.

Dependency Inversion Prinzip

Wir analysieren die Klassenkopplung im vorherigen Artikel. Indem wir die Kopplung auf diese Weise minimieren, halten sich unsere Klassen an ein anderes Klassendesignprinzip, das als Dependency Inversion Principle (DIP) bekannt ist. DIP lautet, dass unsere Klassen von Abstraktionen abhängen sollten, nicht von konkreten Details.

Hinweis: Die Verwendung dieses Prinzips bietet viele Vorteile bei der App-Erstellung. Schauen wir aber nun die Nachteile an:

Es ist nicht sinnvoll, dieses Prinzip für bekannte Standardkomponenten zu verwenden. Wenn das Projekt auf Technologie X basiert, ist es nicht sinnvoll, Schnittstellen davon anstelle einer konkreten Implementierung zu verwenden. Dies wäre meistens einfach nicht möglich. Beispiel: GUI-Erstellung für Abstraktionen.

  • Das Debuggen einer solchen Anwendung ist schwieriger als die Verwendung konkreter Implementierungen.
  • Bei Verwendung von DI / IoC (Dependency Injection Container) soll auf die Leistung geachtet werden.

Interface Segregation Prinzip

Wenn wir eine Anwendung entwerfen, sollten wir darauf achten, wie wir ein Modul, das mehrere Submodule enthält, abstrakt machen. Gleich wie ein Modul implementiert von der Klasse, können wir eine Abstraktion des Systems in einer Schnittstelle durchführen lassen. Wenn wir unsere Anwendung jedoch um ein weiteres Modul erweitern möchten, das nur einige der Submodule des ursprünglichen Systems enthält, müssen wir die vollständige Schnittstelle implementieren und einige Dummy-Methoden schreiben. Eine solche Schnittstelle wird als „Fett“ – Schnittstelle bezeichnet. Das ist definitiv keine gute Lösung und kann zu unangemessenem Verhalten im System führen. Das Prinzip der Schnittstellentrennung lautet, dass Clients nicht gezwungen werden sollten, nicht verwendete Schnittstellen zu implementieren. Anstelle einer fetten Schnittstelle werden viele kleine Schnittstellen bevorzugt.

Klassengröße

Kleinere Klassen sind leichter zu erfassen. Klassen sollten kleiner als etwa 100 Codezeilen sein. Ansonsten ist es schwer zu erkennen, wie die Klasse funktoniert.

Kommentar: Wir folgen einer Regel in der Firma, dass die Methode kleiner als 50 Codezeilen sein sollte.

Der nächste Artikel befasst sich mit Klassenprinzipien: Lose Kopplung, hohe Kohäsion, Veränderung ist lokal, leicht zu entfernen.

Quellen:

R.Martin Clean Code: A Handbook of Agile Software Craftsmanship
BBV.ch
Wikipedia