Mehr Freund als Feind!

Core Statische Klassen Statische Klassen
Mehr Freund als Feind!
Im September-Heft befasste sich David Tielke mit der Frage, ob statische Klassen eher
Freund oder Feind des Entwicklers sind. Wie Stefan Lieser das sieht, lesen Sie hier.
V
öllig zutreffend erläutert Autor David Tielke in
seinem Artikel [1] die technischen Unterschiede
zwischen Instanzmethoden und statischen Methoden.
Er weist ferner auf die Risiken statischer Klassen hin,
die im Zusammenhang mit der Garbage Collection
entstehen können. Im weiteren Verlauf des Artikels
zieht er den Schluss, dass „normale Klassen“ eher ge­
eignet seien, ein „qualitativ hochwertiges Software­
system“ zu bauen. Er weist auf die SOLID-Prinzipien
hin und lehnt, wie mir scheint, die statischen Klassen
vor allem mit dem Argument ab, dass sich damit kei­
ne wahre Objektorientierung realisieren lasse.
Da stellte sich mir die Frage, wie meine eigene Po­
sition zu statischen Klassen denn eigentlich ist. Da­
von will ich im Folgenden berichten – ich möchte ei­
ne Lanze für die statische Klasse brechen und ihre
Nützlichkeit herausstellen.
Am Anfang stand für mich die Frage, wozu wir über­
haupt Methoden und Klassen brauchen. Softwaresys­
teme bestehen in erster Linie aus Logik, also Berech­
nungen, Schleifen, bedingter Ausführung und so
weiter. Theoretisch können wir die gesamte Logik ei­
ner Anwendung in eine einzige oder wenigstens in
sehr wenige Methoden packen. Und auch Klassen be­
nötigen wir nicht, um die Funktionalität herzustellen.
Dann mag das System nicht objektorientiert aufgebaut
sein – na und? Was ist der Zweck der Objektorientierung? Sie
hat ja keinen Wert an sich. Worin besteht also der Wert für
den Kunden, wenn ein Softwaresystem in guter Weise in Me­
thoden und Klassen aufgeteilt ist? Der Überbegriff für Metho­
de, Klasse, Bibliothek (Assembly), Komponente (plattformab­
hängiger Kontrakt) und Service (plattformneutraler Kontrakt)
lautet für uns [2]: Modul.
Module sind Orte, an denen Logik ihren Platz finden kann.
Der Nutzen von Modulen besteht darin, Wandelbarkeit her­
zustellen. Wandelbarkeit steht für den Kunden für Investiti­
onsschutz. Er will auch noch in vielen Jahren an der Software
Änderungen und Fehlerkorrekturen vornehmen lassen, ohne
dass dabei die Kosten dramatisch ansteigen. Dazu bedarf es
einer Struktur innerhalb des Softwaresystems, die ein Ver­
ständnis der Zusammenhänge ermöglicht. Wir verwenden
­also Methoden und Klassen sowie die anderen Module, um
eine Struktur herzustellen, die Softwareentwickler nachvoll­
ziehen können. Daraus leiten sich Prinzipien ab, die wir für
www.dotnetpro.de 11.2015
Bild: shutterstock@Macrovector
Wozu Methoden?
Module in Anschlag bringen: So soll ein Modul beispielswei­
se für nur einen Aspekt (oder Responsibility oder Concern)
zuständig sein. Dem stimmt David Tielke zu, wenn er vom
SRP, dem Single Responsibility Principle, schreibt.
Wozu Objektorientierung?
Wir brauchen also Module, um wandelbare Softwaresysteme
herstellen zu können. Brauchen wir die Objektorientierung,
um Wandelbarkeit herzustellen? Ich meine nein. Sie ist ein
Paradigma, das in den letzten Jahrzehnten sehr in Mode ge­
kommen ist. Doch es gibt andere, wie etwa die funktionale
Programmierung oder Data Flows. Nur eines davon als das
„richtige“ hinzustellen wäre dogmatisch. Im Sinne der Wan­
delbarkeit geht es darum, in pragmatischer Weise geeignete
Paradigmen und Module auszuwählen, als Orte für Logik.
Und nun bin ich beim Punkt der statischen Methoden und
statischen Klassen: Im Detail mögen damit Herausforderun­
gen zum Beispiel bei der Speicherverwaltung verbunden
sein. Aber Ähnliches gilt für viele Werkzeuge: Man kann ▶
75
Core Statische Klassen
mit ihnen einen gewissen Nutzen erzie­
Das eigentliche Problem ist hier nicht
len, muss aber im Umgang mit ihnen ge­
die Abhängigkeit von A zu einer stati­
übt sein, um keinen Schaden anzurich­
schen Klasse S, sondern die Abhängig­
ten. An einer Schere kann ich mich
keit an sich. Im Kern geht es bei Davids
schneiden. Vermeide ich es deshalb, ei­
Beispiel um die Frage, was die Zustän­
ne Schere zu benutzen? Natürlich nicht.
digkeit von A und S ist. Beide enthalten
Die ALT.NET Community hat daher vor Das Logo der ALT.NET Community,
Logik (wenn auch, der Kürze des Bei­
einiger Zeit eine Schere in ihr Logo inte­ Quelle: [3] (Bild 1)
spiels geschuldet, keine komplizierte).
griert (Bild 1).
S produziert einen Wert. In einem
Insofern greifen die SOLID-Prinzipien
­realen Projekt käme der vielleicht aus
als Einwand gegen statische Klassen für mich nicht. Denn am
­einer Ressource oder würde berechnet. A nimmt diesen Wert
Ende steht dabei nur das Resultat, dass statische Klassen kei­
und transformiert ihn. Weil beide Klassen Logik enthalten,
ne Objektorientierung zulassen. Das mag sein, ist für mich
sind sie Operationen. A ist gleichzeitig aber auch für die In­
aber kein Argument, sie zum Feind zu erklären.
tegration zuständig.
Genau darin liegt das Kernproblem des Beispiels: Die Klas­
Wenn statische Methoden oder Klassen die Wandelbarkeit
se A verletzt das Integration Operation Segregation Principle
negativ beeinflussen würden, wäre das für mich ein Argu­
(IOSP). Trennt man nämlich Integration und Operation, sind
ment gegen ihre Verwendung. Doch sie sind wunderbar ge­
die Operationen, also die Teile, welche Logik enthalten, sehr
eignet für die Paradigmen Funktional oder Data Flow, und
gut automatisiert zu testen. Hier das umformulierte Beispiel:
mit beiden kann Wandelbarkeit hergestellt werden. Und
auch in einem objektorientiert angelegten Softwaresystem
class Integration
kann Logik in statischen Methoden abgelegt werden, sofern
{
sie nicht auf Zustand zugreifen muss.
public string DoSandA() {
Das wichtigste Kriterium scheint mir hier zu sein, ob Logik
var A = new A();
auf Zustand angewiesen ist oder nicht. Benötigt sie Zustand,
bieten sich Instanzmethoden an, weil der Zustand dann als
var fromS = S.DoS();
Feld der Klasse realisiert werden kann. Wird kein Zustand
var result = A.DoA(fromS);
benötigt, spricht für mich nichts gegen statische Klassen. Da­
zwischen liegen dann Fälle, in denen statischer Zustand be­
return result;
nötigt wird. Das Singleton Pattern hat ja durchaus Anwen­
}
dungsfälle. Muss also der Zustand über die gesamte Laufzeit
}
der Anwendung erhalten bleiben, kann dies mit einer stati­
schen Klasse oder einer Variante des Singletons implemen­
class A
tiert werden. Der Einsatz eines IoC-Containers schafft hier
{
ohnehin so viel Flexibilität, dass ein Singleton praktisch nicht
public string DoA(string fromS) {
selbst implementiert werden muss, sondern eine Frage der
return fromS.ToUpper();
IoC-Container-Konfiguration ist.
Automatisiertes Testen
Beim Thema Testen kommt David zu dem, wie ich meine, vor­
eiligen Schluss, dass mit statischen Klassen die Testbarkeit
nicht gegeben sei. Er führt als Beispiel eine Klasse A an, die
von einer statischen Klasse beziehungsweise Methode ab­
hängig ist. Sein Beispiel lautet:
}
}
static class S
{
public static string DoS() {
return "David";
}
class A
}
{
public string DoA() {
return S.DoS().ToUpper();
}
}
static class S
{
public static string DoS() {
return "David";
}
}
76
Die statische Klasse S ist unverändert. Sie war schon zuvor
gut testbar. In Klasse A habe ich die Methode DoS jedoch so
verändert, dass sie nun nicht mehr für Integration und Ope­
ration zuständig ist. Ich habe den Integrationsanteil heraus­
gezogen, in dem die Methode per Parameter den Wert erhält,
auf dem sie ihren Anteil Logik (ToUpper) ausführt. Die Inte­
gration ist in eine eigene Klasse gewandert. Die Klasse Integration ist nun ausschließlich für das gewünschte Zusammen­
spiel der beiden Operationen zuständig.
In der Methode DoSandA wird erst eine Instanz von A er­
zeugt. Anschließend werden die beiden Operationen nachei­
11.2015 www.dotnetpro.de
Core Statische Klassen nander aufgerufen, um das Resultat herzustellen. A und S
sind nun leicht automatisiert zu testen, da sie beide keinerlei
(!) Abhängigkeiten haben.
DoSandA ist im Sinne eines Unit-Tests schwer zu testen.
Das liegt daran, dass A und S für einen isolierten Unit-Test
durch eine Attrappe ersetzt werden müssen. Für A ginge das
per Dependency Injection mittels Interface. Für S ginge es
mittels Dependency Injection einer Func<string>:
class Integration
{
private readonly IA a;
private readonly Func<string> doS;
Allerdings bleibt auf diese Weise das Integration Operation­
Segregation Principle (IOSP) verletzt, weil die Methode DoA
nach wie vor S.DoS integriert und selbst Logik enthält.
Noch einmal SOLID: ISP
Das „I“ in SOLID steht für das Interface Segregation Princi­
ple (ISP). Es besagt, dass Interfaces so abzutrennen sind, dass
Konsumenten sich nur an den tatsächlich von ihnen benötig­
ten kleinsten Teil eines Interface binden.
Betrachten wir noch einmal den injizierten Funktionszeiger
im oben gezeigten Listing, dann wird klar, dass dies die ma­
ximale Form des ISP darstellt. Hier wird überhaupt kein In­
terface benötigt, weil sich der Konsument lediglich an eine
Signatur
public Integration() : this(new A(), S.DoS) {
}
void -> string
internal Integration(IA A, Func<string> DoS) {
bindet. Typische objektorientiert aufgebaute Systeme sind
voll von Interfaces, und es stellt sich die Frage, ob diese tat­
sächlich alle benötigt werden oder ob nicht bei extremerer
Auslegung des ISP viel häufiger Func<> und Action<> zum
Einsatz kommen sollten.
Natürlich soll auch das nicht zum Dogma werden, Inter­
faces haben ihre Berechtigung. Es geht um einen angemes­
senen Umgang. So ergibt es durchaus Sinn, Methoden und
Funktionen hoher Kohärenz in einem Interface zusammenzu­
fassen. Gleichzeitig sind Interfaces kein Selbstzweck oder
Allheilmittel.
Ohnehin steht für mich die Vermeidung von Abhängigkei­
ten wie oben gezeigt im Vordergrund. Wenn es keine Abhän­
gigkeiten zwischen Operationen gibt, also zwischen Funkti­
onseinheiten, die Logik enthalten, braucht nichts injiziert
werden. In der Folge ist der Einsatz von Interfaces auf selte­
ne Fälle begrenzt.
a = A;
doS = DoS;
}
public string DoSandA() {
var fromS = doS();
var result = a.DoA(fromS);
return result;
}
}
Nun kann die Klasse Integration isoliert getestet werden. Am
Ende des Tages benötigen wir zusätzlich zu Unit-Tests auch
Integrationstests, also solche, bei denen die Bestandteile des
Softwaresystems voll integriert im Zusammenspiel getestet
werden. Ich würde daher auf Unit-Tests an den Stellen ver­
zichten, an denen reine Integration stattfindet.
Wie auch immer Sie sich hier entscheiden, testbar ist der ge­
samte Code nun, obschon die unveränderte statische Klasse S
zum Einsatz kommt. Und natürlich kann auch die ursprüngli­
che Klasse A testbar gemacht werden, ohne den statischen
Methodenaufruf durch eine Instanzmethode zu ersetzen:
class A
{
private readonly Func<string> doS;
Fazit
Statische Klassen können bei ungeeigneter Verwendung
Schwierigkeiten bei der Speicherverwaltung verursachen.
Sie haben dennoch ihren Platz unter den Modulen. Am Ende
geht es um eine differenzierte Betrachtung. ◾
[1] David Tielke, Freund oder Feind?,
dotnetpro 9/2015, Seite 54,
www.dotnetpro.de/A1509DDD
[2] http://flow-design.org
[3] http://ayende.com/blog/3187/alt-net-logo
public A() : this(S.DoS) {
}
internal A(Func<string> doS) {
this.doS = doS;
}
public string DoA() {
return doS().ToUpper();
}
}
www.dotnetpro.de 11.2015
Stefan Lieser
sucht ständig nach Verbesserung und neuen
Wegen, um die i­nnere Qualität von Software zu
optimieren. Gemeinsam mit Ralf Westphal hat
er die Clean Code Developer Initiative (http://
clean-code-developer.de) ins Leben gerufen.
http://lieser-online.de
dnpCodeA1511StatischeKlassen
77