Blog 4 Min. Lesezeit

Der Schlüssel zu einem erfolgreichen OOP-C++-Projekt: stabile Abstraktionen

Diesen Artikel teilen
The Key to a Successful OOP C++ Project: Stable Abstractions

Robert C. Martin verfasste einen interessanten Artikel über eine Reihe von Metriken, mit denen sich die Qualität eines objektorientierten Designs anhand der Abhängigkeiten zwischen seinen Subsystemen messen lässt.

Darin beschreibt er die Abhängigkeiten zwischen Modulen wie folgt:

What is it that makes a design rigid, fragile and difficult to reuse. It is the interdependence of the subsystems within that design. A design is rigid if it cannot be easily changed. Such rigidity is due to the fact that a single change to heavily interdependent software begins a cascade of changes in dependent modules. When the extent of that cascade of change cannot be predicted by the designers or maintainers the impact of the change cannot be estimated. This makes the cost of the change impossible to estimate. Managers, faced with such unpredictability, become reluctant to authorize changes. Thus the design becomes rigid.

Um Starrheit entgegenzuwirken, führt er Metriken wie afferente Kopplung, efferente Kopplung, Abstraktheit und Instabilität ein.

Afferente Kopplung: Die Anzahl der Typen außerhalb dieses Projekts, die von Typen innerhalb des Projekts abhängen.

Efferente Kopplung Die Anzahl der Typen außerhalb dieses Projekts, die von Typen dieses Projekts verwendet werden.

Efferente und afferente Kopplung lassen sich auch auf Namespaces und Typen anwenden. Die efferente Kopplung eines bestimmten Typs entspricht beispielsweise der Anzahl der Typen, von denen er direkt abhängt. Typen mit einem sehr hohen TypeCe-Wert hängen von zu vielen anderen Typen ab. Sie sind komplex und haben in der Regel mehr als eine Verantwortlichkeit.

Abstraktheit

Das Verhältnis der Anzahl interner abstrakter Typen (also abstrakter Klassen und Interfaces) zur Gesamtzahl interner Typen. Der Wertebereich reicht von 0 bis 1: A=0 steht für ein vollständig konkretes Projekt, A=1 für ein vollständig abstraktes Projekt.

A = Na / Nc

Where:

A = abstractness of a module
Zero is a completely concrete module. One is a completely abstract module.
Na = number of abstract classes in the module.
Nc = number of concrete classes in the module.

Instabilität

Das Verhältnis der efferenten Kopplung (Ce) zur Gesamtkopplung. I = Ce / (Ce + Ca). Diese Metrik gibt an, wie widerstandsfähig ein Projekt gegenüber Änderungen ist. Der Wertebereich reicht von 0 bis 1: I=0 bezeichnet ein vollständig stabiles, I=1 ein vollständig instabiles Projekt.

I = Ce/(Ce + Ca)
I represent the degree of instability associated with a project.
Ca represents the afferent coupling, or incoming dependencies, and
Ce represents the efferent coupling, or outgoing dependencies

Diagramm Abstraktheit vs. Instabilität und die „Zone of Pain“

Als Beispiel sehen Sie hier das Diagramm „Abstraktheit vs. Instabilität“ der C++-Bibliothek POCO.

Die Idee hinter diesem Diagramm lautet: Je stärker ein Codeelement innerhalb eines Programms genutzt wird, desto abstrakter sollte es sein. Anders gesagt: Vermeiden Sie eine zu starke Abhängigkeit von konkreten Implementierungen und setzen Sie stattdessen auf Abstraktionen. Mit einem stark genutzten Codeelement ist hier ein Projekt gemeint – das Prinzip gilt ebenso für Pakete und Typen –, das von vielen anderen Projekten des Programms verwendet wird.

Konkrete Typen, die in der gesamten Codebasis intensiv genutzt werden, sind problematisch. Dadurch entstehen sogenannte „Zones of Pain“: Bereiche, in denen Änderungen an Implementierungen potenziell große Teile des Programms betreffen. Und Implementierungen ändern sich bekanntlich häufiger als Abstraktionen.

Die gestrichelte „Main Sequence“-Linie im obigen Diagramm zeigt, wie Abstraktheit und Instabilität ausbalanciert sein sollten. Eine stabile Komponente befindet sich auf der linken Seite. An der Main Sequence lässt sich erkennen, dass eine solche Komponente einen hohen Abstraktionsgrad benötigt, um nahe an der gewünschten Linie zu liegen. Ist ihr Abstraktionsgrad dagegen niedrig, landet sie in dem Bereich, der als „Zone of Pain“ bezeichnet wird.

Wie lässt sich Starrheit beim OOP-Ansatz vermeiden?

Wie Robert C. Martin in seinem Artikel schreibt, sollten wir abstrakte Klassen und Interfaces einsetzen, um unsere Projekte flexibler zu gestalten und eine zu starke Kopplung zwischen Codeelementen zu reduzieren.

Kopplung kann in der OOP unter anderem durch Folgendes entstehen:

  • Vererbung: Beim Einsatz des OOP-Paradigmas wird sie häufig übermäßig verwendet und macht den Code in vielen Fällen leider starrer. Bestimmte Entwurfsmuster helfen, die dadurch entstehende Starrheit zu reduzieren, beispielsweise das Adapter-Pattern.
  • Direkte Verwendung einer konkreten Implementierung: Auch dadurch wird der Code starr, weil Änderungen schwierig werden, wenn aus irgendeinem Grund eine andere Bibliothek oder ein anderes Framework eingesetzt werden soll. Wie bei der Vererbung gibt es Entwurfsmuster, die diese Starrheit verringern, etwa Bridge oder Proxy.

Beim Einsatz von OOP empfiehlt es sich, die strukturellen GoF-Entwurfsmuster sicher zu beherrschen. Sie helfen dabei, die durch Kopplung verursachte Starrheit zu reduzieren.

Diesen Artikel teilen