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 innere 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
© Copyright 2024 ExpyDoc