Peter Bucher Ralf Westphal

Blog

Nutze den Augenblick
und teile der Welt mit, was Du zu sagen hast.

Abstraktion

Montag, 1. März 2010, 09:37 Uhr
Permalink | Kommentare (0) | Kommentare als RSSRSS

Am 13. Oktober 2008 haben Peter Bucher und ich unter dem Titel Noch Fragen, Bucher? Ja, Roden! angekündigt, jeweils zum ersten eines jeden Monats einen Kommentar zu einem vorab gemeinsam gewählten Thema verfassen zu wollen. Bisher sind in dieser Reihe folgende Kommentare erschienen:

Heute, am 1. März 2010, ist es nun wieder so weit, und unser Thema für diesen Monat lautet:

Abstraktion

So wohl Peter wie auch ich haben uns unabhängig voneinander im Vorfeld unsere Gedanken gemacht, wie wir diesem Thema gegenüberstehen. Außerdem nimmt diesen Monat auch Roberto Bez an unserem Streitgespräch teil.

Peters und Robertos Kommentare finden sich zeitgleich in den entsprechenden Blogs, folgend nun mein Kommentar zu diesem Thema:

Das Selbstverständnis der Informatik, anderen Fachgebieten eine Hilfswissenschaft zu sein, impliziert die Anwendung von Abstraktion bereits: Letztlich werden deren Objekte, Daten und Prozesse nämlich in geeinete informationstechnische Strukturen abstrahiert.

Abstraktion an sich wird dabei beispielsweise von Wikipedia als

induktiven Denkprozess des Weglassens von Einzelheiten und des Überführens auf etwas Allgemeineres oder Einfacheres

definiert. Es geht also letztlich darum, gegebene Probleme besser lösen zu können, indem für die Lösung unnötige Komplexität verringert wird – in der Regel, indem diese durch eine einfachere Darstellung verborgen wird.

Bereits im schulischen Mathematikunterricht findet Abstraktion in dieser Form statt, wenn beispielsweise die Lösung von Textaufgaben gefordert wird, die die eigentliche mathematische und abstrakte Aufgabe in einer mehr oder weniger anschaulichen Beschreibung verpacken.

Ohne Abstraktion wäre die Lösung mancher Aufgaben nicht nur bedeutend schwieriger und komplexer oder gar unmöglich, ohne sie wäre auch die Kommunikation mit anderen Disziplinen bedeutend erschwert.

Für einen Informatiker spielt es in der Regel nämlich zunächst keine Rolle, ob zum Beispiel Gepäckstücke an einem Flughafen oder aber Reagenzgläser in einem medizinischen Labor automatisiert befördert und verarbeitet werden sollen – beide Aufgaben können zu einem Logistikproblem abstrahiert werden, das mit entsprechenden Verfahren lösbar oder zumindest annäherbar ist.

Abstraktion nimmt also eine essenzielle Rolle in der Informatik ein – doch Abstraktion ist nicht per se als positiv zu bewerten. Abstraktionen verbergen nämlich nicht nur unnötige Komplexität, sondern neigen bewusst oder unbewusst auch dazu, Details zu verbergen, die für die Lösungsfindung allerdings relevant sind.

Joel Spolsky bezeichnet diesen Umstand als The Law of Leaky Abstractions. Er fasst seine Erkenntnisse bezüglich Abstraktion in dem Satz

All non-trivial abstractions, to some degree, are leaky.

zusammen, was letztlich so viel bedeutet, dass Abstraktionen nicht nur dabei helfen, Probleme zu lösen, sondern ihrerseits lecken und neue Probleme verursachen, weil die notwendigen Details nicht mehr bekannt sind – ironischerweise desto eher, je stärker abstrahiert wird.

Beispiele für Abstraktionen und die dazugehörigen Lecks sind – wie in der gesamten übrigen Entwicklung von Software – auch in .NET ohne weiteres zu finden:

So abstrahiert die Garbage Collection von .NET die Notwendigkeit, Speicher selbst zu verwalten – dies geschieht implizit. Doch sie löst nicht alle Probleme, zudem verursacht sie neue Probleme, derer sich Entwickler zumindest in kleinen Anwendungen nicht bewusst werden.

Denn nach wie vor haben Entwickler mit hängenden Referenzen zu kämpfen, die beispielsweise dadurch entstehen, dass Ereignisse nicht auch wieder abgemeldet werden.

Ebenfalls problematisch ist das vermeintlich günstige Erzeugen neuer Objekte im Verlass darauf, dass die Garbage Collection die erzeugten Objekte wieder entfernen wird – Speicherverschwendung und damit Performanceprobleme auf Grund falsch angewandter Instanziierung sind eher die Regel denn die Ausnahme.

Auch der grafische, in Visual Studio enthaltene Designer für WPF stellt eine solche Abstraktion dar: Er entledigt den Entwickler von der lästigen Aufgabe, WPF-Code per Hand schreiben zu müssen – kann aber natürlich nie die wahre Absicht des Entwicklers erahnen und muss daher gewisse Kompromisse zwischen Generalität und vernünftigem, das heißt performanten und wohlstrukturierten Code eingehen.

Last but not least stellt selbst der Konkatenationsoperator für Zeichenketten, dargestellt durch ein vermeintlich harmloses +, eine Abstraktion über die Vorgänge dar, die im Speicher stattfinden, um zwei Zeichenketten miteinander zu einer neuen zu verbinden. Die Speicher- und Performanceimplikationen dürften hinlänglich bekannt sein.

All diese Probleme werden durch Abstraktionen verursacht, die ursprünglich angetreten sind, bestehende Probleme zu lösen. Natürlich kann man auf Abstraktionen auch nicht verzichten – zu essenziell sind sie für die überschaubare Lösbarkeit eines gegebenen Problems.

So erspart beispielsweise ASP.NET als Abstraktion über das HTTP-Protokoll – das seinerseits übrigens nichts anderes als eine weitere Abstraktion über den Datenverkehr im Netzwerk darstellt – dem Entwickler eine ganze Menge an Aufgaben, die in der Regel ohnehin immer nach dem gleichen Schema erledigt werden.

Doch – wer nicht weiß, was bei ASP.NET unter der Haube geschieht, neigt schnell dazu, die vorgegebenen Pfade als das Nonplusultra anzusehen und dabei auftretende Probleme zu ignorieren, schlichtweg, weil sie auf Grund der Abstraktion nicht wahrgenommen werden.

Das Paradoxe an dieser Situation ist, dass es – je mehr und mächtigere Abstraktionen verfügbar sind, desto schwieriger wird es, zu wissen, was man eigentlich macht. Damit schließe ich mich nahtlos Joel Spolsky an, wenn er in seinem eingangs erwähnten Artikel schreibt:

[…] all this means that paradoxically, even as we have higher and higher level programming tools with better and better abstractions, becoming a proficient programmer is getting harder and harder.

Schlussendlich lässt sich zusammenfassen, dass es für hervorragende Entwickler nicht genügt, sich mit ihrer jeweils präferierten Technologie auseinanderzusetzen, sondern dass sie sich – um wirklich verstehen und nachvollziehen zu können, was geschieht – auch mit den zu Grunde liegenden Technologien auseinandersetzen müssen.

Das bedeutet, dass auch – oder erst recht – in Zeiten von Hochsprachen, Frameworks, Designern und Assistenten die Berechtigung oder gar die Pflicht gegeben ist, sich mit den Grundlagen zu beschäftigen.

Dass dies nicht zuletzt auch eine große Herausforderung für die Ausbildung darstellt, sei es in Firmen, an Hochschulen oder in sonstigen Formen, liegt auf der Hand.

Werden die Grundlagen nämlich zu Gunsten schnellerer und vermeintlich besserer Lernerfolge systematisch ignoriert, bedeutet das, Oberflächlichkeit zu fördern und verstehendes Wissen unter Anwendung der Root Cause Analysis, die unter anderem auch von der Clean Code Developer-Initiative wertgeschätzt wird, zu missachten.

Felder vs Eigenschaften

Montag, 1. Februar 2010, 09:37 Uhr
Permalink | Kommentare (0) | Kommentare als RSSRSS

Am 13. Oktober 2008 haben Peter Bucher und ich unter dem Titel Noch Fragen, Bucher? Ja, Roden! angekündigt, jeweils zum ersten eines jeden Monats einen Kommentar zu einem vorab gemeinsam gewählten Thema verfassen zu wollen. Bisher sind in dieser Reihe folgende Kommentare erschienen:

Heute, am 1. Februar 2010, ist es nun wieder so weit, und unser Thema für diesen Monat lautet:

Felder vs Eigenschaften

So wohl Peter wie auch ich haben uns unabhängig voneinander im Vorfeld unsere Gedanken gemacht, wie wir diesem Thema gegenüberstehen. Peters Kommentar findet sich zeitgleich in seinem Blog, folgend nun mein Kommentar zu diesem Thema:

Im Gegensatz zu klassischen Sprachen wie C++ oder Java verfügt C# seit der ersten Version über das Konzept der Eigenschaften: Ein für den Entwickler leichtgewichtiges und syntaktisch komfortables Konzept zum kontrollierten Zugriff auf Felder.

Gerade im Vergleich zu den in den genannten Sprachen üblichen zahlreichen Get- und Set-Methoden, die zum Zugriff auf Felder notwendig sind, bieten Eigenschaften einen enormen Vorteil, insbesondere im Hinblick auf die einfachere Lesbarkeit und damit auch bessere Verständlichkeit von Code.

Allerdings wird – beispielsweise in reinen Datenklassen – für den Zugriff auf Felder kein aufwändiger Zugriff benötigt, so dass die Definition einer Eigenschaft in der Regel zum einen immer nach dem gleichen Schema abläuft, zum anderen aber auch syntaktisch aufwändig und redundant ist.

Diesem Umstand trägt C# seit der Version 3.0 mit automatisch implementierten Eigenschaften Rechnungen, wodurch bereits deutlich weniger Code zu schreiben ist – allerdings immer noch deutlich mehr, als für ein Feld, das mit dem Zugriffsmodifizierer public ausgestattet wird.

Nun könnte man meinen, dass man an Stelle einer ohnehin leeren Eigenschaft durchaus ein öffentliches Feld verwenden könnte – doch es gibt einige Gründe, warum man dies unterlassen sollte:

  • MSIL-Code: Felder und Eigenschaften werden in unterschiedlichen MSIL-Code übersetzt, sind also binär nicht kompatibel zueinander. Im Nachhinein lässt sich ein Feld also nicht ohne weiteres durch eine gleichnamige Eigenschaft ersetzen, ohne abhängigen Code – beispielsweise in anderen Assemblies – zu brechen.
  • Referenzparameter: Felder können im Gegensatz zu Eigenschaften einer Methode als Referenzparameter übergeben werden. Deshalb kann ein Ersetzen eines Feldes durch eine Eigenschaft dazu führen, dass Methodenaufrufe nicht mehr kompiliert werden können, bei denen die Übersetzung zuvor ohne weiteres möglich war.
  • Reflection: Die Arbeit mit Feldern und Eigenschaften gestaltet sich per Reflection unterschiedlich, so dass ein Ersatz eines Feldes durch eine Eigenschaft ebenfalls wiederum abhängigen Code brechen könnte.
  • OOP: Es widerspricht dem objektorientierten Konzept der Informationskapselung, Felder als nicht-private zu markieren.
  • Debugger: Eigenschaften ermöglichen das Setzen eines Haltepunktes beim Auslesen beziehungsweise Schreiben, für Felder ist dies nicht möglich.
  • Datenbindung: Eigenschaften können im Gegensatz zu Feldern für die Datenbindung an Steuerelemente herangezogen werden.

Kurzum: Felder sollten ausschließlich mit dem Zugriffsmodifizierer private versehen werden, für alle anderen Anforderungen sind Eigenschaften in jedem Fall die bessere Wahl.

this oder kein this

Freitag, 1. Januar 2010, 09:37 Uhr
Permalink | Kommentare (2) | Kommentare als RSSRSS

Am 13. Oktober 2008 haben Peter Bucher und ich unter dem Titel Noch Fragen, Bucher? Ja, Roden! angekündigt, jeweils zum ersten eines jeden Monats einen Kommentar zu einem vorab gemeinsam gewählten Thema verfassen zu wollen. Bisher sind in dieser Reihe folgende Kommentare erschienen:

Heute, am 1. Januar 2010, ist es nun wieder so weit, und unser Thema für diesen Monat lautet:

this oder kein this

So wohl Peter wie auch ich haben uns unabhängig voneinander im Vorfeld unsere Gedanken gemacht, wie wir diesem Thema gegenüberstehen. Peters Kommentar findet sich zeitgleich in seinem Blog, folgend nun mein Kommentar zu diesem Thema:

Zumeist sind Schlüsselwörter innerhalb einer Programmiersprache mit einer eindeutigen Semantik belegt; auch C# stellt keine Ausnahme dieser Regel dar. Jedoch gilt dies zumindest in C# wohlgemerkt nicht für alle Schlüsselwörter, denn einige haben – je nach Kontext – verschiedene Bedeutungen.

Ein bekanntes und weit verbreitetes Beispiel hierfür ist das Schlüsselwort using, das so wohl als Direktive wie auch als Anweisung verwendet werden kann:

  • Als Direktive dient es zunächst dazu, andere Namensräume bekannt zu machen, so dass die darin enthaltenen Typen ohne Angabe ihres vollqualifizierten Namens genutzt werden können.
  • Die Direktive dient zusätzlich jedoch auch der Bildung von Aliasnamen, wobei dies wiederum so wohl für Namensräume wie auch für einzelne Typen möglich ist.
  • Als Anweisung schließlich definiert using in Verbindung mit der IDisposable-Schnittstelle einen Gültigkeitsbereich, an dessen Ende automatisch die Dispose-Methode des gekapselten Objekts aufgerufen wird.

Doch nicht nur using ist mit verschiedenen Bedeutungen belegt – ein weiterer Kandidat mit zahlreichen Varianten ist das this-Schlüsselwort:

  • In Verbindung mit Konstruktoren dient das this-Schlüsselwort dazu, den Aufruf an einen überladenen Konstruktor der gleichen Klasse weiterzuleiten.
  • Mit this ist es zudem möglich, Indexer zu definieren – also Eigenschaften, die über keinen eigenen Namen verfügen, allerdings parametrisiert mit einem Index aufgerufen werden können.
  • Seit C# 3.0 dient this als Kennzeichner für Erweiterungsmethoden, indem mit Hilfe dieses Schlüsselworts festgelegt wird, welches Argument dem aufrufenden Objekt entspricht.
  • Schließlich dient this – wie bereits in C++ und Java – auch als Referenz auf das eigene Objekt. Auf diese Art kann auf Elemente der Klasse zugegriffen werden, deren Namen durch gleichnamige Parameter ansonsten ausgeblendet und damit nicht zugreifbar wären.

Im Gegensatz zu using, dessen Angabe immer erforderlich ist, kann this gegebenenfalls entfallen: Während seine Verwendung bei der Verkettung von Konstruktoren, Indexern und Erweiterungsmethoden obligatorisch ist, kann die Eigenreferenz entfallen – sofern der Name des betroffenen Elements nicht ausgeblendet wird.

Die Frage lautet allerdings, ob es sinnvoll ist, this in den optionalen Fällen zu streichen. Zunächst scheint es so, schließlich erspart man sich einige Zeichen zu tippen, zu lesen und vermeidet unnötige Redundanz. Auch ReSharper empfiehlt bei Verwendung der Standardeinstellungen, redundante this-Schlüsselwörter zu entfernen.

Neben diesen Vorteilen birgt das Entfernen von this jedoch auch einen essenziellen Nachteil: Es ist nicht mehr auf einen Blick ersichtlich, ob ein Zugriff auf ein Element des aktuellen Objekts stattfindet. So ist bei der Zeile

_foo = new Foo();

nicht klar, ob es sich um eine statisches Feld oder ein Instanzfeld handelt. Wird das Schlüsselwort this jedoch konsequent verwendet, ist auf den ersten Blick eindeutig, dass es sich um ein statisches Feld handelt, ansonsten müsste die Zeile nämlich

this._foo = new Foo();

lauten. Während diese Mehrdeutigkeit bei Feldern noch zu vertreten sein mag, wird es bei Eigenschaften bedeutend schwieriger: So bestehen für die Zeile

Foo.Bar();

bereits drei Möglichkeiten, welcher Aufruf sich dahinter verbirgt: Zum einen könnte Foo eine Eigenschaft sein, an der eine Methode namens Bar aufgerufen wird. Es ist jedoch nicht eindeutig, ob es sich bei der Eigenschaft um eine statische oder eine instanzbehaftete Eigenschaft handelt.

Neben diesen zwei Möglichkeiten könnte es zudem sein, dass Foo gar keine Eigenschaft, sondern eine statische Klasse bezeichnet, die ihrerseits wiederum eine Methode Bar() enthält.

Der Unterschied zwischen einer statischen Eigenschaft und einer statischen Klasse lässt sich ohne weiteres nicht auflösen – zumindest der erste Fall kann aber durch Verwendung von this explizit gemacht werden. Bei der Zeile

this.Foo.Bar();

ist nämlich auf den ersten Blick ersichtlich, dass eine Methode an einer Eigenschaft aufgerufen wird, die instanzbehaftet ist.

Diese Beispiele zeigen, dass die konsequente Verwendung von this durchaus ihren Teil zu einer besseren Verständlichkeit des Quellcodes beitragen kann.

Da Code in der Regel derart geschrieben wird, dass er für die Zukunft wie auch für andere Entwickler gut lesbar ist, kann es durchaus eine vernünftige Entscheidung sein, this generell zu verwenden – insbesondere auch in den redundanten Fällen, um die Semantik des Codes explizit zu machen.

Für welche Variante auch immer ein Entwickler beziehungsweise ein Team sich entscheidet, wichtig ist – wie so oft, wenn es um das Thema Coderichtlinien geht – dass die einmal getroffene Entscheidung konsequent eingehalten wird. Alles andere führt über kurz oder lang zu Missverständnissen.

Reflection – Fluch oder Segen?

Dienstag, 1. Dezember 2009, 09:37 Uhr
Permalink | Kommentare (1) | Kommentare als RSSRSS

Am 13. Oktober 2008 haben Peter Bucher und ich unter dem Titel Noch Fragen, Bucher? Ja, Roden! angekündigt, jeweils zum ersten eines jeden Monats einen Kommentar zu einem vorab gemeinsam gewählten Thema verfassen zu wollen. Bisher sind in dieser Reihe folgende Kommentare erschienen:

Heute, am 1. Dezember 2009, ist es nun wieder so weit, und unser Thema für diesen Monat lautet:

Reflection – Fluch oder Segen?

So wohl Peter wie auch ich haben uns unabhängig voneinander im Vorfeld unsere Gedanken gemacht, wie wir diesem Thema gegenüberstehen. Peters Kommentar findet sich zeitgleich in seinem Blog, folgend nun mein Kommentar zu diesem Thema:

Reflection ist ein zentrales Element in .NET: Die Tatsache, dass in MSIL eben nicht nur der eigentliche Code, sondern auch die dazugehörigen Metadaten enthalten sind, zeigt dies. Und – das ist auch gut so.

Denn Reflection bildet die Grundlage für zahlreiche Szenarien, die von vielen Entwicklern tagtäglich genutzt werden: Sei es der Einsatz eines Microkernels und damit verbunden das dynamische Laden und späte Instanziieren von Typen, sei es der Einsatz einer dynamischen Sprache oder der dynamischen Sprachfeatures von C# 4.0. All dies basiert letztlich auf Reflection.

Auch die Verwendung von Attributen zur Laufzeit wäre ohne Reflection nicht möglich, ebensowenig wie die Analyse von Assemblies, um an Hand der enthaltenen Typen und deren Methoden, Eigenschaften und Feldern gewisse Entscheidungen zu treffen.

Reflection ist also fraglos ein Segen – ohne sie wären all die genannten Punkte in .NET nicht oder zumindest nicht mit dermaßen geringem Aufwand möglich. Doch woher stammt dann der schlechte Ruf, der in Gesprächen über Reflection zugegebenermaßen häufig den Unterton bestimmt?

Prinzipiell sind gleich zwei Gründe für den schlechten Ruf von Reflection verantwortlich, wobei einer der beiden systemimmanent ist, der zweite jedoch zumindest teilweise nur die konkrete Implementierung in .NET betrifft:

  • Reflection ermöglicht den wahlfreien Zugriff auf Code, auch auf geschützte Elemente wie beispielsweise Variablen, die als private gekennzeichnet wurden.
  • Reflection ist langsam – so wohl die Analyse von Code wie auch dessen Ausführung betreffend.

Dass Reflection den wahlfreien Zugriff auf beliebigen Code ermöglicht, ist Fluch und Segen zugleich: Auf der einen Seite wird auf diese Art sämtliche objektorientierte Programmierung ausgehebelt, auf der anderen Seite kann es ungemein nützlich sein, von außen auch auf private Elemente zugreifen zu können – man denke nur an diverse Unittests.

Nebenbei bemerkt: Einen interessanten Ansatz, wie Unittests ohne den Einsatz von Reflection auskommen und dennoch Interna testen können, hat Ralf Westphal in seinem Blogeintrag Zustand als Abhängigkeit beschrieben.

Generell gilt für den Einsatz von Reflection zum Zugriff auf geschützte Elemente also die gängige Empfehlung: So viel wie nötig und so wenig wie möglich – mit Betonung auf zweiterem.

Doch all dies wohnt Reflection bereits vom Ansatz her inne: Dabei spielt es keine Rolle, ob es sich um Reflection auf Basis von .NET, Java oder einer beliebigen anderen Plattform handelt: Diese Nachteile und die damit einhergehenden Überlegungen gelten generell.

Mit dem Umstand, dass Reflection unter .NET ausgesprochen langsam ist, sieht es jedoch ein wenig anders aus: Reflection an sich ist immer langsamer als statisch kompilierter Code – das ist logisch, wenn man weiß, wie beispielsweise Aufrufe von Funktionen in beiden Fällen funktionieren: Der Lookup per Reflection kostet schlicht und ergreifend mehr Zeit.

Doch Reflection kann auch dazu genutzt werden, um Code zu analyiseren – und hier ist es tatsächlich die Implementierung von .NET, welche die Schuld dafür trägt. Zudem zeichnet sich die Reflection von .NET in diesem Zusammenhang auch dadurch aus, wenig flexibel zu sein.

So ist es beispielsweise nicht möglich, aus einer auf dem Desktop ausgeführten .NET-Anwendung die Assembly mscorlib.dll von Silverlight zu öffnen – es kann nämlich jeweils nur eine Instanz der mscorlib.dll in einen Prozessraum geladen werden, was die Analyse einer anderen als der eigenen mscorlib.dll unter .NET immer scheitern wird.

Abhilfe – so wohl für die Geschwindigkeit wie auch für die Flexibilität – schafft der Einsatz einer anderen Komponente für Reflection, wie beispielsweise Mono Cecil, die unter anderem auch von Werkzeugen wie dem Mono Migration Analyzer oder dem Mono Debugger eingesetzt wird.

Zusammenfassend kann man also sagen, dass Reflection per se so wohl Fluch wie auch Segen ist – und dass es vom konkreten Kontext abhängt: Für Latebinding, dynamische Sprachen und Analyse von Code ist Reflection unverzichtbar, doch für den darüber hinausgehenden Einsatz von Reflection sollte man sehr gute Gründe haben.

Wie viel Sinn machen Unittests?

Sonntag, 1. November 2009, 09:37 Uhr
Permalink | Kommentare (20) | Kommentare als RSSRSS

Am 13. Oktober 2008 haben Peter Bucher und ich unter dem Titel Noch Fragen, Bucher? Ja, Roden! angekündigt, jeweils zum ersten eines jeden Monats einen Kommentar zu einem vorab gemeinsam gewählten Thema verfassen zu wollen. Bisher sind in dieser Reihe folgende Kommentare erschienen:

Heute, am 1. November 2009, ist es nun wieder so weit, und unser Thema für diesen Monat lautet:

Wie viel Sinn machen Unittests?

So wohl Peter wie auch ich haben uns unabhängig voneinander im Vorfeld unsere Gedanken gemacht, wie wir diesem Thema gegenüberstehen. Außerdem nimmt diesen Monat auch Christian Wenz wieder an unserem Streitgespräch teil.

Peters und Christians Kommentare findet sich zeitgleich in den entsprechenden Blogs, folgend nun mein Kommentar zu diesem Thema:

Unittests sind sinnvoll – nicht ohne Grund wird ihr konsequenter Einsatz empfohlen, unter anderem im Rahmen der Clean Code Developer-Initiative und des Extreme Programmings, von der dediziert auf Tests optimierten Entwicklungsmethode Test-Driven Development (TDD) ganz zu schweigen.

In der Theorie hören sich diese Ansätze zunächst auch sehr vielversprechend an. Schließlich ermöglicht der konsequente, durchgängige Einsatz von Unittests eine gefahrlose Änderung von bestehendem Code.

Unbeabsichtigte Seiteneffekte werden durch die Ausführung der Unittests sofort aufgedeckt und können somit vermieden werden. Zudem können gefundene Bugs unmittelbar durch neu geschriebene Unittests abgesichert werden, so dass sie sich nicht erneut einschleichen können.

So weit die Theorie, die Praxis sieht leider ein wenig anders aus. So leicht Unittests nämlich erklärt sind, so kompliziert ist ihre tatsächliche Umsetzung – vorausgesetzt, man strebt tatsächlich eine 100%ige Abdeckung an.

Problematisch sind in der Regel drei Aspekte:

  • Der bestehende objektorientierte Aufbau ist unter OOP- und stilistischen Aspekten sauber umgesetzt, läuft einer guten Testbarkeit allerdings zuwider.
  • Es bestehen zahlreiche Abhängigkeiten zu externen Komponenten, die sich nicht oder nur mit sehr viel Aufwand simulieren lassen.
  • Es bestehen Abhängigkeiten zur konkreten Laufzeitumgebung, die sich nicht oder nur mit sehr viel Aufwand nachbilden lassen.

Entwickler, die sich an Clean Code Developer orientieren,und die auch ansonsten versuchen, beispielsweise mit Werkzeugen wie FxCop ihren Stil und ihre Codequalität zu verbessen, streben in der Regel ein sauberes und durchdachtes objektorientiertes Design an.

Leider steht manchmal genau ein solches Design der Testbarkeit gegenüber. Häufig müssen beispielsweise im Sinne der Testbarkeit weitere Konstruktoren, zusätzliche Eigenschaften oder Schnittstellen zu einer Klasse hinzugefügt werden – obwohl dies aus einer rein objektorientierten Sicht keinen Sinn ergibt.

Roy Osherove beschreibt in seinem Buch The Art of Unit Testing in dem Abschnitt Overcoming the encapsulation problem dieses Problem:

Some people feel that opening up the design to make it more testable is
a bad thing because it hurts the object-oriented principles the design is
based on. I can wholeheartedly say to those people, “Don’t be silly.”

Leider kann ich mich seiner leichtfertigen Aussage “Don’t be silly” nicht so einfach anschließen – denn ein Unittest ist eben nicht einfach nur ein weiterer Verwender der API. Für den Unittest ist es nämlich im Gegensatz zu realen Entwicklern gleichgültig, ob eine API sinnvoll strukturiert ist oder nicht.

Daher empfinde ich es zumindest als bedenklich, ein durchdachtes, objektorientiertes und sauberes Design zu Gunsten einer besseren Testbarkeit zu ändern, wenn es dafür keine weiteren Gründe gibt.

Neben dem potenziell wenig Unittest-tauglichen objektorientierten Design stellt die Abhängigkeit von externen Komponenten ein weiteres häufiges Problem dar. Zu nennen sind hierbei unter anderem:

  • Datenbanken
  • Dateisysteme
  • Registry

Natürlich können all diese Abhängigkeiten entfernt, umgangen oder verschleiert werden – die Frage ist aber, zu welchem Preis dies geschieht. Es genügt nämlich nicht, Stubs und Mocks einzuführen, statt dessen muss die Umgebung auch nach jedem Test potenziell wieder zurückgesetzt werden: Schließlich soll jeder Test isoliert, das heißt unabhängig von seinen Vorgängern, ausgeführt werden.

Eine Datenbank wieder und wieder zurückzusetzen, ist technisch zwar machbar, kostet allerdings sehr viel Zeit. Damit wird das Ziel, alle Unittests bei jedem Build automatisch auszuführen, ab einem gewissen Punkt unerreichbar: Die zur Ausführung der Unittests benötigte Zeit liegt schlichtweg zu hoch.

Zudem können gar nicht alle externen Abhängigkeiten ohne weiteres aufgelöst werden – vor .NET 3.5 SP1 und der Einführung von System.Web.Abstractions war es zum Beispiel nur eingeschränkt möglich, den HTTP-Kontext einer Webanwendung zu simulieren.

Auch die Abhängigkeit von der Laufzeitumgebung an sich kann problematisch sein. So können sich beispielsweise HTTP-Anforderungen, die an eine Webanwendung gesendet werden, voneinander unterscheiden – je nachdem, welcher Webbrowser auf dem Client eingesetzt wird.

Um Webbrowser-spezifisches Verhalten zu simulieren, müssten die Eigenheiten des jeweiligen Webbrowsers im Unittest nachgebildet werden. Alternativ könnten der entsprechenden gewünschte Webbrowser automatisiert werden – was seinerseits allerdings einen ziemlichen Aufwand nach sich zieht.

Ebenfalls problematisch ist multithreaded Code, sofern bestimmte Konstellationen in den einzelnen Threads getestet werden sollen – da das Umschalten zwischen den Threads vom aktuellen Kontext des Prozessors beziehungsweise des Betriebssystems abhängt, kann sich das Verhalten von Ausführung zu Ausführung unterscheiden.

All diese Beispiele zeigen, dass es unter Umständen sehr aufwändig oder gar unmöglich sein kann, einen Unittest zu schreiben, der unter realitätsnahen Bedingungen ausgeführt wird.

Die Frage ist also nicht, ob eine 100%ige Testabdeckung wünschenswert ist – dies gilt rein von der Theorie her ohne jegliche Zweifel – sondern ob eine 100%ige Testabdeckung mit vernünftigem und vertretbaren Aufwand erreicht werden kann: Diese Frage kann durchaus mit Nein beantwortet werden.

Nichtsdestotrotz gibt es natürlich auch Code, der sich perfekt für Unittests eignet: So fallen beispielsweise sämtliche Algorithmen in diese Kategorie, da diese in der Regel kaum oder gar keine Abhängigkeiten zu anderen Komponenten aufweisen.

Daher ergibt es durchaus Sinn, Unittests zu schreiben – die Frage ist lediglich, wofür. Eine 100%ige Abdeckung mit Unittests erreichen zu können klingt zwar verlockend, der zur Erreichung dieses Ziels notwendige Aufwand lohnt in der Regel jedoch nicht.

Insofern muss im konkreten Kontext abgewogen werden, welche Komponenten als kritisch zu betrachten sind und der Abdeckung durch Unittests bedürfen. Selbst für diese Komponenten ist eine 100%ige Abdeckung noch ein ausgesprochen ehrgeiziges Ziel – daher empfiehlt es sich eher, eine zwar hohe, aber nicht perfekte Abdeckung wie beispielsweise 85% anzustreben.

Wie viel Sinn machen Unittests also nun? Zusammengefasst kann man sagen, dass Unittests – an der richtigen Stelle eingesetzt – durchaus Sinn ergeben, dass diese Stellen aber explizit ausgewählt werden sollten.

Vererben oder aggregieren?

Donnerstag, 1. Oktober 2009, 09:37 Uhr
Permalink | Kommentare (2) | Kommentare als RSSRSS

Am 13. Oktober 2008 haben Peter Bucher und ich unter dem Titel Noch Fragen, Bucher? Ja, Roden! angekündigt, jeweils zum ersten eines jeden Monats einen Kommentar zu einem vorab gemeinsam gewählten Thema verfassen zu wollen. Bisher sind in dieser Reihe folgende Kommentare erschienen:

Heute, am 1. Oktober 2009, ist es nun wieder so weit, und unser Thema für diesen Monat lautet:

Vererben oder aggregieren?

So wohl Peter wie auch ich haben uns unabhängig voneinander im Vorfeld unsere Gedanken gemacht, wie wir diesem Thema gegenüberstehen. Peters Kommentar findet sich zeitgleich in seinem Blog, folgend nun mein Kommentar zu diesem Thema:

In der Welt der modernen Softwareentwicklung haben die objektorientierten Sprachen in den vergangenen Jahrzehnten die primäre Rolle eingenommen – von Sprachen wie C++, Java und C# bis hin zu neuen Entwicklung wie Scala: All diese Sprachen unterstützen die objektorientierte Programmierung, teilweise basieren sie sogar gänzlich darauf.

Auch die Literatur hat sich dieser Tendenz angenommen: Kein Einsteiger lernt heute zu programmieren, ohne nicht zumindest einige wenige Schritte in der objektorientierten Programmierung gewagt zu haben. Doch wie wird Objektorientierung üblicherweise vermittelt?

In der Regel werden zunächst die Begriffe Klasse und Objekt erläutert, um sich im Anschluss daran an fortgeschrittene Themen wie Vererbung und generische Typen zu wagen. Prinzipiell ist daran auch nichts auszusetzen – wenn an dieser Stelle nicht in der Regel auch Schluss wäre.

Für die objektorientierte Programmierung stellt Vererbung zwar ein Schlüsselkonzept dar, jedoch wird es in der Praxis bei weitem nicht dermaßen häufig eingesetzt wie einem die Theorie weismachen will: Der Grund hierfür liegt schlichtweg darin, dass Vererbung für zahlreiche Szenarien wenn überhaupt, dann nur bedingt geeignet ist.

Als Begründung hierfür sei das Liskovsche Substitutionsprinzip angeführt, das im Jahr 1993 von Barbara Liskov und Jeanette Wing formuliert wurde, und auch den von Clean Code Developer propagierten Prinzipien angehört.

Frei formuliert besagt dieses Prinzip, dass abgeleitete Typen das gleiche Verhalten aufweisen müssen wie der Typ, von dem sie abgeleitet wurden. Obwohl diese Forderung zunächst trivial und logisch erscheint, wird sie in der Praxis häufig verletzt: Nämlich dann, wenn der abgeleitete Typ die Funktionalität nicht erweitert, sondern einschränkt.

Das Standardbeispiel für eine Verletzung des Liskovschen Substitutionsprinzips ist die Ableitung einer Kreis- von einer Ellipsenklasse – oder analog dazu die Ableitung einer Quadrat- von einer Rechteckklasse. In beiden Fällen schränkt die abgeleitete Klasse die Möglichkeiten zur Manipulation des Objekts semantisch ein.

Auf Grund der Anschaulichkeit und der Verbreitung dieses Beispiels trägt es sogar einen eigenen Namen, so ist es nämlich als Kreis-Ellipse-Problem bekannt.

Ein Beispiel für den gelungenen Einsatz von Vererbung stellen hingegen die diversen grafischen Steuerelemente in .NET dar. Unabhängig davon, welcher technologische Teilbereich betrachtet wird (ASP.NET, Silverlight, WPF, …), das Prinzip ist immer das gleiche: Alle grafischen Steuerelemente sind von einer gemeinsamen Basisklasse Control abgeleitet, die alle allgemeinen Eigenschaften und Methoden definiert.

Generell gilt jedoch, dass Vererbung sparsam eingesetzt werden sollte: Gerade weil es dermaßen leicht ist, eine Klasse von einer anderen abzuleiten, sollte man als Entwickler besondere Vorsicht walten lassen.

Was aber, wenn Vererbung prinzipiell tatsächlich Sinn ergeben würde, der abgeleitete Typ jedoch in seiner Funktionalität eingeschränkt werden müsste?

Als Beispiel hierfür sei eine Rechnungsverwaltung genannt, in der Rechnungen zwar hinzugefügt werden können, aus rechtlichen Gründen jedoch nicht mehr gelöscht werden dürfen, wenn sie erst einmal gebucht sind.

Sicherlich bietet es sich an, zur Datenhaltung der Rechnungen von dem generischen Typ List<T> abzuleiten, schließlich erhält man damit auf einfache Weise nicht nur typsicheren Zugriff auf die einzelnen Rechnungen, sondern erhält quasi zum Nulltarif auch diverse Methoden zum Verwalten, Durchsuchen und Sortieren dieser Liste.

Problematisch ist jedoch, dass nun auch eine Methode Remove zur Verfügung steht, mit der einmal gebuchte Rechnungen wieder entfernt werden könnten – die Aufgabe lautet also, diese Möglichkeit zu eliminieren. Da es in der Vererbung keine Möglichkeit gibt, eine Methode der Basisklasse in einer abgeleiteten Klasse zu entfernen, gibt es de facto nur zwei Möglichkeiten:

  • Zum einen kann die Methode überschrieben werden. In diesem Fall gibt es wiederum zwei Varianten, denn einerseits kann die Methode schlichtweg mit einer leeren Methode überschrieben werden, andererseits könnte in der überschriebenen Variante auch eine NotImplementedException geworden werden. In beiden Fällen wäre das Problem insofern gelöst, dass die Remove-Methode zwar noch aufgerufen werden kann, der Aufruf jedoch keine Auswirkungen auf die Liste hat.
  • Zum anderen kann auf Vererbung verzichtet werden. In diesem Fall stellt sich aber die Frage, wie die Datenstruktur implementiert werden soll – schließlich kann die Lösung aus naheliegenden Gründen auch nicht lauten, dass für solche Fälle eine eigene Liste entwickelt werden muss.

Die Lösung lautet: Es sollte ein eigener Datentyp kreiert werden, der eine Liste als internen Datenspeicher enthält – den Zugriff darauf jedoch über eigene Methoden reguliert. Dieses Verfahren wird als Aggregation bezeichnet, An die Stelle einer is-a-Beziehung tritt also eine is-part-of-Beziehung.

Welche Variante sich für welchen Fall empfiehlt, hängt vom jeweiligen konkreten Kontext ab. Als Faustregel kann jedoch gelten: Wenn der abgeleitete Typ den Basistyp ausschließlich erweitert, sich in semantischer Hinsicht wie der zugehörige Basistyp verhält und es sich tatsächlich um eine is-a-Beziehung handelt, dann kann Vererbung in Betracht gezogen werden.

Im Zweifelsfall wie auch für alle anderen Fälle empfiehlt sich jedoch der Einsatz von Aggregation.

Alles var – oder nicht?

Dienstag, 1. September 2009, 09:37 Uhr
Permalink | Kommentare (4) | Kommentare als RSSRSS

Am 13. Oktober 2008 haben Peter Bucher und ich unter dem Titel Noch Fragen, Bucher? Ja, Roden! angekündigt, jeweils zum ersten eines jeden Monats einen Kommentar zu einem vorab gemeinsam gewählten Thema verfassen zu wollen. Bisher sind in dieser Reihe folgende Kommentare erschienen:

Heute, am 1. September 2009, ist es nun wieder so weit, und unser Thema für diesen Monat lautet:

Alles var – oder nicht?

So wohl Peter wie auch ich haben uns unabhängig voneinander im Vorfeld unsere Gedanken gemacht, wie wir diesem Thema gegenüberstehen. Peters Kommentar findet sich zeitgleich in seinem Blog, folgend nun mein Kommentar zu diesem Thema:

Im Grunde lassen sich die Neuerungen in C# 3.0 auf einen einzigen Aspekt reduzieren: LINQ. Alle Änderungen und Erweiterungen, die C# außer LINQ erfahren hat, sind durch LINQ begründet. Anders formuliert: Wäre LINQ nicht eingeführt worden, wäre kein Bedarf für die anderen Features von C# 3.0 gegeben gewesen.

Obwohl dies nicht der ursprünglichen Intention entspricht, lassen sich diese anderen Features jedoch auch ohne LINQ nutzen:

  • Lambdaausdrücke sind eine kompakte und komfortable Möglichkeit, anonyme Methoden und Eventhandler zu implementieren.
  • Erweiterungsmethoden eignen sich wunderbar, um Methoden für generische Zwecke wie beispielsweise Konvertierung auf elegante Weise an bestehenden Datentypen zur Verfügung zu stellen.
  • Automatisch implementierte Eigenschaften und Objekt- wie auch Listeninitialisierer ersparen dem Entwickler einiges an unnötiger Tipparbeit.

Das einzige Feature, das ohne LINQ nur selten genutzt wird, sind anonyme Typen. Allerdings wird das zugehörige Schlüsselwort var umso häufiger auch abseits von LINQ genutzt. Daraus ergibt sich relativ schnell die Frage, wann var eigentlich genutzt werden sollte und wann nicht: Was sind die Best Practices für den Einsatz von var?

Zunächst einmal bleibt festzuhalten, dass die Verwendung von var nichts an der starken Typisierung von C# ändert: Anders als beispielsweise der weit verbreitete untypisierte Datentyp Variant entbindet das Schlüsselwort var nicht von der starken Typisierung – im Gegenteil, die Typisierung findet ebenso stark statt wie zuvor, nur eben implizit statt explizit.

Was var intern nämlich macht, ist, den Typ des Ausdrucks auf der rechten Seite einer Zuweisung auszuwerten, und diesen Typ für die Variablen auf der linken Seite der Zuweisung zu verwenden. Diese Technik wird – da der Typ hergeleitet wird – auch als Type Inferring bezeichnet.

Prinzipiell kann man also nichts grundlegend falsch machen, wenn man alle explizit angegebenen Typen durch das Schlüsselwort var ersetzen würde. Trotzdem empfiehlt es sich nicht immer: Auf var sollte nämlich dann verzichtet werden, wenn die Lesbarkeit leiden würde, und es nicht mehr auf den ersten Blick offensichtlich wäre, um welchen Typ es sich handelt.

Während beispielsweise die Zuweisung eines generischen Datentyps

var dictionary = new Dictionary<string, int>();

auch ohne explizite Angabe des zu verwendenden Typs problemlos lesbar ist, sieht dies bei den eingebauten Datentypen bereits ganz anders aus:

var number = 2147483746;

Es liegt auf der Hand, dass bei einigen Werten nicht mehr auf den ersten Blick eindeutig zugeordnet werden kann,um welchen Datentyp es sich handelt. Anfällig sind dabei vor allem die folgenden Kombinationen:

  • int oder long?
  • float oder double?

Zieht man außerdem auch noch die nicht-vorzeichenbehafteten Datentypen – wie beispielsweise short oder ulong – hinzu, wird das Chaos perfekt.

Für eingebaute Datentypen sollte das Schlüsselwort var also offensichtlich möglichst nicht verwendet werden. Dies ist der eine Extremfall. Der andere Extremfall sind die anonymen Typen, deren Verwendung ohne das Schlüsselwort var gar nicht erst möglich ist.

Doch wie sieht es zwischen diesen beiden Extremen aus?

Ein Beispiel wurde bereits genannt: Die Lesbarkeit von generischen Datentypen kann durch die Verwendung des Schlüsselwortes var durchaus verbessert werden, immerhin wird durch var an dieser Stelle Redundanz vermieden. Diese Feststellung kann man dabei durchaus zur Regel erheben:

Das Schlüsselwort var kann immer dann guten Gewissens angewendet werden, wenn es redundante Typangaben verhindert – sofern mindestens ein Vorkommen des Typs nach wie vor bestehen bleibt.

Mit dieser Regel hat man bereits die meisten Anwendungsfälle abgedeckt, ansonsten sollte man von var Abstand nehmen.

Eine Ausnahme gibt es jedoch noch: Innerhalb von Schleifen – und hierzu zählen insbesondere auch die foreach-Schleifen – kann für die Definition der Schleifenvariable in der Regel das Schlüsselwort var verwendet werden, sofern auch dann noch auf den ersten Blick ersichtlich ist, von welchem Typ die Schleifenvariable ist.

Zusammengefasst lässt sich also sagen, dass man auf den Einsatz von var zu Gunsten einer besseren Lesbarkeit verzichten sollte – es sei denn, redundante Typangaben werden vermieden. Für Schleifenvariablen kann var verwendet werden, sofern der zugehörige Typ nach wie vor auf einen Blick ersichtlich ist.

C# oder VB: Welche Sprache soll ich lernen?

Samstag, 1. August 2009, 09:37 Uhr
Permalink | Kommentare (4) | Kommentare als RSSRSS

Am 13. Oktober 2008 haben Peter Bucher und ich unter dem Titel Noch Fragen, Bucher? Ja, Roden! angekündigt, jeweils zum ersten eines jeden Monats einen Kommentar zu einem vorab gemeinsam gewählten Thema verfassen zu wollen. Bisher sind in dieser Reihe folgende Kommentare erschienen:

Heute, am 1. August 2009, ist es nun wieder so weit, und unser Thema für diesen Monat lautet:

C# oder VB: Welche Sprache soll ich lernen?

So wohl Peter wie auch ich haben uns unabhängig voneinander im Vorfeld unsere Gedanken gemacht, wie wir diesem Thema gegenüberstehen. Außerdem nimmt diesen Monat auch Christian Wenz an unserem Streitgespräch teil.

Peters und Christians Kommentare finden sich zeitgleich in den entsprechenden Blogs, folgend nun mein Kommentar zu diesem Thema:

Die Frage, ob C# oder Visual Basic die bessere Sprache ist, kann pauschal wohl kaum beantwortet werden. Wie so oft hängt die Antwort vom konkreten Kontext ab, in dem die Frage gestellt wird.

Zudem ist man zunächst geneigt, C# und Visual Basic als zwei syntaktische Varianten einer gemeinsamen Sprache zu sehen – und wenn man MSIL als diese Sprache nimmt, ist diese Sichtweise aus rein technischer Sicht auch gar nicht so abwegig.

Es gibt nur wenige Sprachfeatures, auf Grund deren man C# über Visual Basic oder umgekehrt Visual Basic über C# favorisieren könnte: Eines davon könnte die integrierte Unterstützung von XML in Visual Basic sein.

Ob ein derart einzelnes Feature jedoch genügt, um eine Sprache als Grundlage für ein gesamtes Projekt auszuwählen, sei noch einmal dahin gestellt.

Auch die historische Entwicklung, die beispielsweise in Not Another C# Versus VB Article auf CodeProject beschrieben wird, ist nur bedingt geeignet, eine Entscheidung zu fällen: Denn Historie ist für all jene irrelevant, die sich erstmalig mit Programmierung beschäftigen.

Werde ich nach meiner persönlichen Empfehlung gefragt, so rate ich fast durchwegs immer zu C# – jedoch bin ich auf Grund von Projekten wie guide to C# auch nicht unbelastet. zudem fehlt mir die Erfahrung in Visual Basic.

Was also empfehlen, wenn man zumindest ein wenig Objektivität wahren möchte?

Als die Entwicklung für Windows noch nicht auf .NET, sondern auf der Win32-API basierte, gab es für die Frage, ob Visual C++ oder Visual Basic Classic eingesetzt werden sollte, eine relativ einfach anwendbare Richtlinie: Professionelle Entwickler sollten Visual C++ einsetzen, alle anderen Visual Basic Classic:

  • Visual C++ hatte für professionelle Entwickler einige entscheidende Vorteile: So war nicht nur der Zugriff auf die gesamte Win32-API möglich, auch die Integration in COM war erstklassig. Diese Vorteile erkaufte man sich mit einer ausgesprochen komplexen Entwicklung.
  • Visual Basic Classic hingegen war einfach zu erlernen und einfach anzuwenden. Zwar gab es Grenzen, diese spielten jedoch abseits der professionellen Entwicklung nur selten eine entscheidende Rolle.

Nun haben so wohl Visual C++ wie auch Visual Basic Classic ihren Einfluss verloren und C# und Visual Basic sind die neuen Sterne am Programmiersprachenhimmel. Die spannende Frage lautet nun: Gilt die damalige Rollenverteilung nach wie vor?

Ich meine: Ja, sie gilt nach wie vor. C# ist besser geeignet für professionelle Entwickler, Visual Basic hingegen ist nach wie vor besser geeignet für alle anderen Entwickler. Wie komme ich zu dieser Einschätzung – wo doch so oft zu hören und zu lesen ist, dass es sich faktisch nur um zwei verschiedene syntaktische Varianten einer Sprache handelt? Dass die Entscheidung somit letztlich keine Rolle spielt? Dass es sich eigentlich nur um einen Glaubenskrieg handelt?

Der entscheidende Punkt ist, dass C# von vornherein darauf ausgelegt wurde, eine akademische Sprache zu sein. Eine Sprache, in der alle enthaltenen Sprachkonzepte und -ideen sauber umgesetzt sind. Man könnte auch sagen, C# ist die Sprache des Elfenbeinturms.

Visual Basic hingegen ist auf ein komfortables Entwicklungerlebnis ausgerichtet: Man muss nicht zwingend alle Konzepte von .NET verstehen, um erfolgreich in Visual Basic programmieren zu können – für C# ist ein gutes Verständnis von .NET essenziell.

Dieser Komfort manifestiert sich in Visual Basic in vielen Kleinigkeiten, unter anderem in den folgenden Konzepten:

  • Visual Basic unterstützt nach wie vor das On Error Goto-Konstrukt, das an Stelle des wesentlich besser strukturierten try / catch eingesetzt werden kann.
  • Visual Basic unterstützt optionale Parameter. Dieses Feature ist zwar nun für eine verbesserte COM-Interoperabilität auch in C# 4.0 enthalten, wird dort allerdings nicht als unproblematisch angesehen, wie beispielsweise in Für und wider C# 4.0 beschrieben.
  • Visual Basic unterstützt zahlreiche der Funktionen aus Visual Basic Classic nach wie vor, indem diese in der Assembly Microsoft.VisualBasic.dll nachgebildet worden sind. Im Vergleich zu den moderneren .NET-Varianten lassen diese Funktionen allerdings einiges zu wünschen übrig – insbesondere im Hinblick auf Performance.

Nun kann man all diesen Punkten ankreiden, dass es sich nur um Kleinigkeiten handelt, die zudem aus Gründen der Abwärtskompatibilität enthalten sind. Jedoch hat Microsoft im Rahmen von Visual Studio 2005 ein komplett neues Feature eingeführt, das jedoch interessanterweise nur für Visual Basic zur Verfügung steht: Den Namensraum My.

Auch Uwe Baumann hat bereits im April 2005 mit My, ist das geil! eine Lanze für den Namensraum My gebrochen – nicht jedoch ohne die entsprechende Kritik, nachzulesen als Zitat in Die Freiheit der abweichenden Mynung:

My ist nicht geil. Nicht einmal ansatzweise. Denn My untergräbt die klare Struktur von .NET, die viele Entwickler so lieben, wie es bei PHP auch geschieht. Unter dem Motto “Alles soll einfacher werden” wird ein zusätzlicher Namensraum eingeführt, der Abkürzungen bietet. Diese aber mit eingeschränkter Funktionalität, und nicht mehr nach den klassischen Namensräumen sortiert, es wird also neben der Einführung einer zweiten Schreibweise auch noch die Beschäftigung mit den klassischen Namensräumen vermindert.

Letztlich führt das meiner Meinung nach zum gleichen Ziel wie in PHP, nämlich, dass Einsteiger sich auf My stürzen werden, dann aber irgendwann an die Grenzen stoßen oder fremden Code bearbeiten müssen, und sich dann doch mit den klassischen Strukturen auseinander setzen müssen.

Letztlich wird so wieder ein Parallelweg eingeführt, der eigentlich überflüssig ist, und am Ende die Produktivität eher senkt als steigert, da redundante Abläufe erlernt werden müssen.

In seiner Antwort referenziert Uwe auf genau jene Unterscheidung, die ich in diesem Eintrag auch getroffen habe: C# ist eine Sprache aus dem Elfenbeinturm, akademisch sauber und elegant. Visual Basic hingegen ist die Sprache für den Praktiker, der sein Ziel erreichen will – wobei ihm der Weg relativ gleich ist, und der unter Umständen auch gar kein Interesse an Weiterentwicklung hat.

Aufbauend auf meiner Argumentation und gestützt durch Uwes Antwort möchte ich daher folgendes Fazit ziehen: Wer sich professionell mit .NET beschäftigen will, wissen will, wie .NET intern funktioniert, und bereit ist, Zeit und Aufwand zu investieren, der ist mit C# besser beraten.

Wer jedoch nur gelegentlich eine kleine Anwendung zusammenbasteln will, wem das Erreichen des Ziels wichtiger ist als der dafür eingeschlagene Weg, der ist mit Visual Basic besser beraten.

Primärschlüssel: GUID vs Identity

Mittwoch, 1. Juli 2009, 09:37 Uhr
Permalink | Kommentare (3) | Kommentare als RSSRSS

Am 13. Oktober 2008 haben Peter Bucher und ich unter dem Titel Noch Fragen, Bucher? Ja, Roden! angekündigt, jeweils zum ersten eines jeden Monats einen Kommentar zu einem vorab gemeinsam gewählten Thema verfassen zu wollen. Bisher sind in dieser Reihe folgende Kommentare erschienen:

Heute, am 1. Juli 2009, ist es nun wieder so weit, und unser Thema für diesen Monat lautet:

Primärschlüssel: GUID vs Identity

So wohl Peter wie auch ich haben uns unabhängig voneinander im Vorfeld unsere Gedanken gemacht, wie wir diesem Thema gegenüberstehen. Peters Kommentar findet sich zeitgleich in seinem Blog, folgend nun mein Kommentar zu diesem Thema:

Die Aufgabe, eine Datenbankstruktur zu entwickeln, ist nicht leicht. Auf der einen Seite ist in der Regel eine möglichst hohe Normalisierung gewünscht, auf der anderen Seite darf die Performance nicht unter einer Vielzahl von Joins leiden.

Aus diesem Grund trifft man die fünfte Normalform – so wünschenswert ihre Umsetzung in der Theorie auch sein mag – in der Praxis nur ausgesprochen selten an: Der in der Datenbank notwendige Aufwand zur Bearbeitung einer Abfrage ist schlichtweg zu hoch.

In der Praxis trifft man daher zumeist auf Strukturen, die sich zwischen dritter, vierter und der sogenannten Boyce-Codd-Normalform bewegen. Essenziell ist in allen Fällen jedoch die Wahl eines geeigneten Primärschlüssels.

Ein Schlüssel ist dabei wie folgt definiert:

  • Ein Superschlüssel ist eine Menge von Feldern einer Tabelle, deren Zusammenspiel einen Datensatz eindeutig identifizieren. Beispielsweise könnte in vielen Fällen die Kombination aus Name, Vorname, Geburtsdatum und Adresse als Superschlüssel dienen, da es ausgesprochen unwahrscheinlich ist, dass zwei Personen gleichen Namens am gleichen Tag geboren sind und zudem noch an der gleichen Adresse wohnhaft sind.
  • Als Schlüsselkandidat bezeichnet man alle Superschlüssel, bei denen die Anzahl der verwendeten Felder minimal sind. Angenommen, beim vorigen Beispiel ließe sich das Geburtsdatum entfernen, ohne zugleich die Eindeutigkeit zu verlieren, dann wäre auch die Kombination aus Name, Vorname und Adresse ein Superschlüssel. Diese Version verwendet jedoch ein Feld weniger als die vorige. Sobald kein Feld mehr entfernt werden kann, ohne die Eindeutigkeit zu verlieren, spricht man von einem Schlüsselkandidaten.
  • Der Primärschlüssel ist schließlich ein (willkürlich) ausgewählter Schlüsselkandidat, der als Hauptidentifikationsmerkmal dient.

Eine interessante Frage ist, wie ein solcher Primärschlüssel aussehen sollte. Prinzipiell gibt es hierzu zwei Möglichkeiten: Entweder verwendet man lediglich Felder, die ohnehin als Bestandteil der Daten vorhanden sind – in diesem Fall nennt man den Schlüssel einen natürlichen Schlüssel, da alle enthaltenen Felder auch über eine natürliche Bedeutung in den Daten verfügen.

Oder man verwendet ein dediziertes, künstlich hinzugefügtes Feld, das zwar keine semantische Beziehung zu den Daten aufweist, dafür allerdings auf den Einsatz als Schlüssel optimiert wurde – in diesem Fall spricht man von einem Surrogatschlüssel.

Die Vorteile von Surrogatschlüsseln liegen auf der Hand: Da bei natürlichen Schlüsseln gegebenenfalls mehrere Felder zu einem Verbundschlüssel zusammengefasst werden müssen, ist die Arbeit mit ihnen arbeits- und zeitaufwändig. Surrogatschlüssel hingegen können auf das jeweilige Einsatzgebiet optimiert werden.

Die Frage lautet also: Wie sollte ein Surrogatschlüssel aussehen?

In der Praxis haben sich hierzu zwei Ansätze etabliert. Das erste Verfahren verwendet einen nummerischen Schlüssel, der im Regelfall durch die Datenbank hochgezählt und automatisch vergeben wird. Das zweite Verfahren nutzt sogenannte GUIDs – global eindeutige IDs.

Historisch bedingt sind nummerische Schlüssel wesentlich stärker verbreitet als GUIDs. Zudem verfügen GUIDs über einige immanente Nachteile:

  • Eine GUID belegt 16 Byte in der Datenbank, ein int hingegen nur 4 Byte. Abgesehen von dem zusätzlichen Speicherbedarf, der durch die Verwendung von GUIDs erzeugt wird, sind GUIDs aus diesem Grund auch nicht so performant wie int-Felder: Ein Vergleich auf 16 Bytes dauert logischerweise länger als ein Vergleich auf 4 Bytes.
  • Eine GUID ist schlechter lesbar als ein int. Das macht sich insbesondere beim Debuggen und bei der Fehlersuche bemerkbar.
  • Verschiedene GUIDs implizieren im Gegensatz zu fortlaufend vergebenen int-Werten keine Reihenfolge, sodass nicht nur eine natürliche Sortierung fehlt, sondern auch die Clusteringfähigkeit der Datenbank leidet.

Doch wo Schatten ist, ist auch Licht:

  • Eine GUID ist garantiert eindeutig – nicht nur in der Tabelle, sondern auch in Bezug auf die gesamte Datenbank und andere (!) Datenbanken. Das bedeutet, dass das Mergen von Datenbanken problemlos möglich ist, da es zu keinen Konflikten von Primärschlüsseln kommt, wie dies bei int-Werten häufig der Fall ist. Auch die Replikation von Datenbanken erfordert in der Regel die Verwendung von GUIDs – andernfalls wäre die Konsequenz ein äußerst aufwändiges Nachziehen von zu ändernden int-Werten.
  • Eine GUID kann vom Client erzeugt werden, der nächsthöhere int-Wert muss durch den Server erzeugt werden. Dies bedeutet im schlechtesten Fall, dass nach dem Einfügen eines Datensatzes ein weiterer Roundtrip zum Server nötig ist, um den erzeugten Schlüssel zu erhalten. Hierbei tritt bei der Verwendung von int-Werten häufig auch das Problem auf, wie mit nahezu zeitgleich eingefügten Datensätzen umgegangen wird – die Schlüssel müssen verbindungsbezogen zurückgegeben werden.

Ein besonderes Feature, das zwar nicht auf der Verwendung von GUIDs basiert, aber eindrucksvoll beweist, wie nützlich ein datenbankweit eindeutiger Schlüssel ist, enthält die Datenbank PostgreSQL: Die sogenannte OID (Object ID) ist ein automatisch von PostgreSQL vergebener eindeutiger Schlüssel, der in allen Datenbanken auf dem verwendeten Server eindeutig ist.

Die Besonderheit in PostgreSQL liegt nun darin, dass diese OID in Abfragen verwendet werden kann, um auf Datensätze zuzugreifen, ohne zu wissen, in welcher konkreten Datenbank oder Tabelle diese enthalten sind.

Aus all diesen Überlegungen kann als Fazit gezogen werden, dass die Verwendung von GUIDs generell empfohlen werden kann: Die genannten Vorteile überwiegen die wenigen Nachteile bei weitem, insbesondere auch im Hinblick auf die Zukunftssicherheit: Eine Datenbank, die ohnehin GUIDs verwendet, lässt sich ohne weiteres replizieren – eine Datenbank, die auf int-Werten basiert, nicht.

Als einzige Ausnahme dieser Regel sei der Fall genannt, wenn es auf das noch so kleinste Quentchen Performance ankommt: In diesem Fall sind int-Werte eventuell eine Option – allerdings stellt sich dann ohnehin die Frage, ob die Datenbank nicht per Scale-up oder Scale-out anders aufgebaut werden sollte.

Heißt die Zukunft RIA?

Montag, 1. Juni 2009, 09:37 Uhr
Permalink | Kommentare (1) | Kommentare als RSSRSS

Am 13. Oktober 2008 haben Peter Bucher und ich unter dem Titel Noch Fragen, Bucher? Ja, Roden! angekündigt, jeweils zum ersten eines jeden Monats einen Kommentar zu einem vorab gemeinsam gewählten Thema verfassen zu wollen. Bisher sind in dieser Reihe folgende Kommentare erschienen:

Heute, am 1. Juni 2009, ist es nun wieder so weit, und unser Thema für diesen Monat lautet:

Heißt die Zukunft RIA?

So wohl Peter wie auch ich haben uns unabhängig voneinander im Vorfeld unsere Gedanken gemacht, wie wir diesem Thema gegenüberstehen. Peters Kommentar findet sich zeitgleich in seinem Blog, folgend nun mein Kommentar zu diesem Thema:

Bevor man sich der Frage widmet, ob die Zukunft RIA heißt, gilt es zu klären, was sich überhaupt hinter diesem Begriff verbirgt: Laut Wikipedia handelt es sich bei einer RIA um

eine Anwendung, die Internet-Techniken benutzt und eine intuitive Benutzeroberfläche bietet.

Das Akronym RIA steht dabei für Rich Internet Application. Der englische Begriff Rich steht dabei laut Wikipedia für

die reichhaltigen Möglichkeiten wie zum Beispiel Drag-und-Drop-Fähigkeit oder Bedienbarkeit über Tastenkürzel, suggeriert dabei aber auch einen “Mehrwert” gegenüber herkömmlichen Webanwendungen.

Da die klassischen Webtechnologien die Erstellung solcher Benutzeroberflächen auf Grund von technischen Limitierungen nicht zulassen, assoziieren viele Entwickler RIAs mit solchen Anwendungen, die zwar innerhalb des Browsers laufen, in der Regel jedoch auf zusätzlichen und zumeist proprietären Addins wie beispielsweise Silverlight oder Flash basieren.

Der Einsatz eines solchen Addins ist jedoch keine zwingende Voraussetzung für die Entwicklung einer RIA. So können entsprechende UIs beispielsweise auch nur auf Basis von AJAX erstellt werden. Als Vorreiter für eine AJAX-basierte RIA kann dabei Outlook Web Access gelten, das einen webbasierten Zugriff auf Exchange Server ermöglicht und sich in seiner Bedienung an das Desktopprodukt Outlook anlehnt.

Die Addin-basierte Entwicklung von Anwendunen verfügt jedoch im Vergleich zu AJAX-basierten Varianten über einige Vorteile:

  • Mächtigkeit: Da die Hersteller der Addins nicht an die von den Browsern gesetzten Grenzen gebunden sind, verfügen auf diesen Addins basierende Anwendungen häufig über erweiterte Fähigkeiten. Diese betreffen – da eine solche Anwendung in der Regel nur das webbasierte Frontend einer mehrschichtigen Anwendung darstellt – meistens grafische Aspekte einer Anwendung, wie beispielsweise Animationen, 3D-Grafik, Multimediafähigkeiten, …
  • Plattformübergreifend: Da es zudem Aufgabe des Herstellers eines Addins ist, dafür zu sorgen, dass das Addin in allen verbreiteten Browsern funktioniert, stellt sich für den Entwickler die Frage nach der plattformübergreifenden Entwicklung nicht mehr. Da das Addin im Idealfall auf allen Browserplattformen gleich funktioniert, gibt es für den Entwickler keine Kompatibilitätsprobleme mit den verschiedenen Browsern mehr: Entweder läuft die Anwendung ganz oder gar nicht.
  • Out of Browser: Mit zunehmender Reife ermöglichen es die Addins, für das Web konzipierte Anwendungen mit wenig Aufwand auf den Desktop zu portieren. Hierbei werden unter Umständen gänzlich verschiedene Ansätze verfolgt, wie beispielsweise ein Vergleich der Out of Browser-Features von Silverlight und AIR zeigt.
  • Offline: Spätestens, sobald RIAs auch außerhalb eines Browsers ausgeführt werden können, stellt sich die Frage, inwiefern sie auch ohne bestehende Verbindung zum Internet lauffähig sind. Viele Frameworks unterstützen daher diese Offline-Fähigkeit durch entsprechende APIs und ermöglichen es einer Anwendung, die offline erstellten und geänderten Daten mit einem Server zu synchronisieren, sobald die Anwendung wieder über eine Internetverbindung verfügt.
  • RAD: Im Vergleich zu AJAX-basierten Anwendungen können Addin-basierte Anwendungen wesentlich schneller entwickelt werden, da sich der Entwickler auf die eigentliche Anwendung konzentrieren kann, ohne sich zusätzlich noch um einen Teil der Infrastruktur kümmern zu müssen.

Nimmt man all diese Aspekte zusammen, und beachtet zusätzlich den seit Jahren bestehenden Trend, Anwendungen in das Internet zu verlagern, dann liegt auf der Hand, dass RIAs eine große Zukunft bevorsteht. Es fragt sich nur: Wofür?

Auf Grund der oben genannten Fähigkeiten zu glauben, RIAs seien die universelle Lösung und die Zukunft schlechthin, ist reichlich naiv: Egal, für welche Plattform man sich als Entwickler entscheidet – RIAs haben systembedingte, immanente Nachteile, die man mit gleich welcher RIA-Technologie nicht lösen können wird.

Allen voran sei hier die Sandbox genannt, welche die Addins den Anwendungen in der Regel setzen: Weder besteht direkter Zugriff auf die unter Umständen sehr spezielle Hardware des Clients, noch kann eine RIA Arbeits- und Festplattenspeicher in dem Maße nutzen wie dies für eine klassische Desktopanwendung möglich ist.

Führt man sich vor Augen, dass für viele Aufgaben Bedarf an spezieller Hardware besteht – man denke nur an Lesegeräte für Smartcards oder Chipkarten – oder dass viele Berechnungen große Mengen an Speicher und / oder Prozessorleistung benötigen, wird offensichtlich, dass RIAs hierfür weniger geeignet sind.

In der Praxis werden sich RIAs daher für komplexe Anwendungen insbesondere für die UI etablieren, denn genau hierin liegen die Stärken. Der Middletier sowie das Backend werden sich allerdings nach wie vor auf einem dedizierten Webserver und nicht auf dem lokalen System befinden – schlichtweg, weil dort mehr Möglichkeiten gegeben sind, die für die Businesslogik und ähnliche Bereiche einer Anwendung interessant sind.

Tritt man nun einen Schritt zurück, so erkennt man, dass weder RIAs noch einzelne Addins per se die Zukunft sind, sondern dass sie lediglich eine weitere Option für die Implementierung des Frontends und damit der UI einer Anwendung darstellen.

Und selbst hierfür muss je nach Kontext entschieden werden, ob eine RIA überhaupt Sinn ergibt: Nicht für jede Webseite und nicht für jede Anwendung ist es sinnvoll, diese zukünftig als RIA zu bauen – bloß, weil man nun die Möglichkeit dazu hat.

Langer Rede kurzer Sinn: Die Zukunft heißt nicht pauschal RIA – dafür sind die Konzepte zu sehr auf die Entwicklung von UIs ausgelegt. Doch beschränkt man die ursprüngliche Frage auf den Bereich der Frontends, so können RIAs ihre Fähigkeiten dort – den entsprechenden Kontext und damit den Sinn für den Einsatz einer RIA vorausgesetzt – perfekt ausspielen.

Woran erkennt man einen guten Entwickler?

Freitag, 1. Mai 2009, 09:37 Uhr
Permalink | Kommentare (2) | Kommentare als RSSRSS

Am 13. Oktober 2008 haben Peter Bucher und ich unter dem Titel Noch Fragen, Bucher? Ja, Roden! angekündigt, jeweils zum ersten eines jeden Monats einen Kommentar zu einem vorab gemeinsam gewählten Thema verfassen zu wollen. Bisher sind in dieser Reihe folgende Kommentare erschienen:

Heute, am 1. Mai 2009, ist es nun wieder so weit, und unser Thema für diesen Monat lautet:

Woran erkennt man einen guten Entwickler

So wohl Peter wie auch ich haben uns unabhängig voneinander im Vorfeld unsere Gedanken gemacht, wie wir diesem Thema gegenüberstehen. Peters Kommentar findet sich zeitgleich in seinem Blog, folgend nun mein Kommentar zu diesem Thema:

So sehr sich die IT-Branche von anderen Branchen unterscheiden mag, eines haben sie alle gemeinsam – es müssen gewisse Voraussetzungen erfüllt werden, damit qualitativ hochwertige Produkte entstehen können:

  • Was: Zunächst werden hochwertige Rohstoffe und -materialien benötigt – denn wenn bereits eine Komponente eines Produktes minderwertig ist, wird es schwierig, darauf ein hochwertiges Gesamtprodukt aufzubauen.
  • Wie: Des weiteren ist ein gewisses Know-How und Handwerkszeug erforderlich, um zu wissen, wie die zu Grunde liegenden Rohstoffe und -materialien miteinander verarbeitet werden müssen.
  • Wer: Schließlich muss dieses Know-How auch von irgendjemandem ein- und umgesetzt werden. Hierbei gilt: Je besser jemand sein Handwerkszeug versteht, desto eher ist er auch in der Lage, es umzusetzen.

Die Behauptung, dass gute Entwickler also essenziell für den Erfolg einer Firma oder eines Projektes sind, liegt auf der Hand. Die spannende Frage ist nun jedoch: Woran erkennt man einen guten Entwickler?

Interessanterweise haben die meisten Menschen eine Vorstellung von einem guten Handwerker: Er arbeitet ordentlich, zuverlässig, kennt sich in seiner Domäne aus und weiß, welche Konsequenzen sein Handeln auf das Gesamtprodukt und potenziell sogar dessen Einsatzgebiet hat.

Nun könnte man annehmen, dass sich diese Merkmale auch auf Entwickler übertragen lassen. Genau das ist es, was Ralf Westphal und Stefan Lieser in ihrer Initiative Clean Code Developer in Worte versucht haben zu fassen: Sie haben Prinzipien, Regeln und Praktiken zusammengetragen, die gemeinsam ein Wertesystem bilden.

An Hand dieses Wertesystems können sich Entwickler zum einen weiterbilden, zum anderen sind Auftraggeber aber auch in der Lage, bestehende Leistungen und die dahinter stehenden Entwickler zu bewerten.

Das Projekt Clean Code Developer hat in den vergangenen Wochen sehr viel Aufmerksamkeit erfahren – zu Recht, wie ich finde. Allerdings sollte man sich davor hüten, zu glauben, damit wäre alles gesagt oder erfasst, was einen guten Entwickler ausmacht.

Es geht in diesem Projekt bewusst nämlich lediglich um das Wie, also um das oben bereits angesprochene Know-How und das Handwerkszeug. Eine Frage bleibt jedoch gänzlich unbeachtet und unbeantwortet: Was zeichnet einen guten Entwickler im Hinblick auf den Entwickler als Person aus?

Anders formuliert: Über welche persönlichen und charakteristischen Eigenschaften muss ein Entwickler verfügen, um erfolgreich und gut sein zu können?

Mit dieser Frage hat sich unter anderem auch Joel Spolsky beschäftigt, der darüber ein äußerst lesenswertes und – wie immer bei Joel – sehr amüsant geschriebenes und dennoch lehrreiches Buch mit dem Titel Smart and Gets Things Done: Joel Spolky’s Concise Guide to Finding the Best Technical Talent. geschrieben hat.

Die Quintessenz lautet: Ein guter Entwickler muss im Wesentlichen zwei Merkmale aufweisen:

  • Smart: Zum einen muss er “smart” sein. Joel Spolsky meint damit, dass ein guter Entwickler kreativ, aufgeschlossen und neugierig sein muss, dass er zudem bereit sein muss, ungewöhnliche Wege zu gehen, und dabei gleichzeitig in der Lage, gegebenenfalls um eine Ecke mehr zu denken als ein durchschnittlicher Entwickler.
  • Gets Things Done: Zum anderen muss er seine Aufgaben allerdings auch erledigt bekommen – und zwar in der vorgegebenen Zeit. Denn all die zuvor genannten positiven Eigenschaften nützen nichts, wenn es einem Entwickler nicht gelingt, auf den Punkt zu kommen und seine Aufgaben zeitgemäß abzuschließen.

Dieser Vorschlag kommt meiner persönlichen Vorstellung von einem guten Entwickler schon recht nahe, und trotzdem fehlt mir noch ein essenzieller Aspekt: Der innere Drang zur Weiterentwicklung.

Ein Aspekt, in dem sich die IT-Branche von jeder anderen Branche ganz gravierend unterscheidet, ist ihre Schnelllebigkeit: Was heute noch elitär und akademisch ist, gehört morgen schon als etablierte Commodity zum Mainstream und ist übermorgen hoffnungslos veraltet.

Nur derjenige, der in der Lage ist, der zunehmend schneller werdenden Entwicklung der IT-Branche zu folgen, und sich immer auf dem Laufenden hält, der bereit ist, stetig zu lernen und Altes wieder über Bord zu werfen – nur der wird in der Lage sein, dauerhaft smart sein zu können.

Die erste schlechte Nachricht lautet: Zu viele Entwickler finden sich selbst in dieser Beschreibung nicht wieder. Zu viele Entwickler verfolgen nicht, was auf dem Markt geschieht – zumeist mit der Begründung, dass es doch bisher auch funktioniert habe, und man durchaus auch ohne den Einsatz neuer Technologien entwickeln könne.

Die zweite schlechte Nachricht lautet: Als Auftrag- oder Arbeitgeber hat man schlechte Karten, daran etwas zu ändern. Der Drang, Veränderung und neue Entwicklungen zu verfolgen und mitzunehmen, besteht entweder in jemandem – oder eben nicht. Und wenn nicht, dann bekommt man ihn auch von außen nicht dazu, so zu werden.

Interfaces vs abstrakte Basisklassen

Mittwoch, 1. April 2009, 09:37 Uhr
Permalink | Kommentare (10) | Kommentare als RSSRSS

Am 13. Oktober 2008 haben Peter Bucher und ich unter dem Titel Noch Fragen, Bucher? Ja, Roden! angekündigt, jeweils zum ersten eines jeden Monats einen Kommentar zu einem vorab gemeinsam gewählten Thema verfassen zu wollen. Bisher sind in dieser Reihe folgende Kommentare erschienen:

Heute, am 1. April 2009, ist es nun wieder so weit, und unser Thema für diesen Monat lautet:

Interfaces vs abstrakte Basisklassen

So wohl Peter wie auch ich haben uns unabhängig voneinander im Vorfeld unsere Gedanken gemacht, wie wir diesem Thema gegenüberstehen. Peters Kommentar findet sich zeitgleich in seinem Blog, folgend nun mein Kommentar zu diesem Thema:

Interfaces haben in den vergangenen fünf Jahren einen ungeahnten Aufschwung erlebt. Ein – wenn nicht der – Grund hierfür liegt sicherlich in der zunehmenden Verbreitung von Microkernel-Architekturen, denen unter .NET mit Projekten wie beispielsweise Unity und autofac Rechnung getragen wird.

Allerdings ist auch die Entwicklung eines Microkernel im Eigenbau inzwischen keine hochwissenschaftliche Angelegenheit mehr – Microkernel sind also ein weiteres Thema, das nach und nach seinen elitären Status verliert und zu einer Commodity wird.

Bei all der Beschäftigung mit Interfaces verlässt ein anderes Sprachkonstrukt allerdings häufig all zu leicht das eigene Blickfeld: Abstrakte Basisklassen. Diese haben – der zunehmenden Fokussierung auf Schnittstellen zum Trotz – nach wie vor ihre berechtigte Existenz.

Die Frage lautet also: Wann bietet sich der Einsatz von Interfaces an, wann der einer abstrakten Basisklasse?

Um diese Frage beantworten zu können, empfiehlt es sich, sich zunächst einen Überblick über die jeweiligen Vor- und Nachteile zu verschaffen. Die Vorteile abstrakter Basisklassen liegen auf der Hand:

  • Polymorphie: Da abstrakte Basisklassen selbst nicht instanziiert werden können, müssen davon abgeleitete konkrete Klassen erstellt werden, die daraufhin ihrerseits instanziiert werden können. Da all diese konkreten Klassen aber über die gleiche abstrakte Basisklasse verfügen, kann die abstrakte Basisklasse zur Laufzeit an Stelle einer konkreten Klasse verwendet werden. Dieses Verhalten wird als Polymorphie bezeichnet und geht einher mit dem zweiten Vorteil.
  • Schnittstelle: Eine abstrakte Basisklasse definiert einen gemeinsamen Kontrakt für sich und die von ihr abgeleiteten Klassen. In jedem Fall liegt also eine wohldefinierte Schnittstelle vor. Sofern die abstrakte Basisklasse ihrerseits Code enthält, ist diese Schnittstelle potenziell nicht nur syntaktischer, sondern sogar semantischer Natur.
  • Codebasis: Da es in einer abstrakten Basisklasse möglich ist, nicht nur die Definition einer Schnittstelle vorzunehmen, sondern auch Code zu hinterlegen, bietet es sich an, Code, der in allen abgeleiteten Klassen gemeinsam genutzt werden soll, in einer abstrakten Basisklasse zu platzieren, um Redundanzen zu vermeiden. Zudem steht es dem Entwickler der abstrakten Basisklasse frei, nur die Klasse selbst als abstrakt zu markieren, nicht jedoch die darin enthaltenen Methoden. Das bedeutet, dass für abgeleitete Klassen kein Vererbungszwang besteht, was sich wiederum positiv auf die Versionierbarkeit niederschlägt.

Neben all diesen positiven Aspekten gibt es jedoch auch zwei essenzielle Nachteile, die den Einsatz einer abstrakten Basisklasse potenziell von vornherein verbieten können:

  • Einfachvererbung: Da .NET (und damit auch C#) keine Mehrfachvererbung unterstützen, schließt der Einsatz einer abstrakten Basisklasse den Einsatz einer anderen Basisklasse aus. Gelegentlich ist dies jedoch aus technischen Gründen zwingend erforderlich, so dass eine abstrakte Basisklasse nicht genutzt werden kann. Dies ist beispielsweise bei der Framework-eigenen Klasse MarshalByRefObject der Fall – muss von ihr abgeleitet werden, hat man als Entwickler mit einer eigenen abstrakten Basisklasse schlechte Karten.
  • is-a: Auch für abstrakte Basisklassen gilt, dass es sich bei der Ableitung von ihnen um eine is-a-Relation handelt, deren Einsatz im objektorientierten Paradigma wohlüberlegt sein will. Beispielsweise ist ein Quadrat im objektorientierten Sinn eben kein Rechteck, da sich die Semantik in einigen Punkten unterscheidet.

Beide Nachteile können durch Interfaces als leichtgewichtige Alternative ausgeräumt werden:

  • Mehrfachvererbung: Da Interfaces nur eine syntaktische, in keinem Fall jedoch eine semantische Schnittstelle definieren, kann eine Klasse beliebig viele Interfaces zugleich implementieren. Falls tatsächlich zwei Interfaces eine Methode mit gleicher Signatur enthalten sollten, kann die Eindeutigkeit durch explizite Implementierung wiederhergestellt werden.
  • has-a: Im Gegensatz zu einer Vererbung von einer Klasse beschreibt ein Interface lediglich eine Eigenschaft oder eine Fähigkeit, über die eine implementierende Klasse verfügt. Es handelt sich also nur um eine (semantisch schwächere) has-a-Relation, was den Aufbau komplexer Klassenhierarchien deutlich vereinfacht.

Doch auch Interfaces haben einen essenziellen Nachteil:

  • Codebasis: Interfaces können keinen Code enthalten, was zum einen wiederum Redundanzen und Codeduplizierung fördert. Zum anderen können Methoden in Interfaces nur ausschließlich als abstrakt definiert werden. In einer implementierenden Klasse besteht also Vererbungszwang, was die Versionierbarkeit eines Interfaces häufig unmöglich macht, in jedem Fall aber zumindest stark einschränkt.

Um nach all diesen Vorüberlegungen auf die ursprüngliche Frage zurückzukommen: Wann empfiehlt sich nun der Einsatz von Interfaces, wann der einer abstrakten Basisklasse?

Spielen die jeweiligen Nachteile keine Rolle, fällt die Entscheidung wenig überraschend nicht sonderlich schwer. Vermeintlich schwierig wird es, wenn sie eine Rolle spielen – doch wie gesagt, dies ist nur vermeintlich so: Der entscheidende Punkt ist nämlich, dass sich der Einsatz eines Interfaces und einer abstrakten Basisklasse nicht gegenseitig ausschließen.

Statt sich also auf eine der beiden Möglichkeiten einzuschränken, werden einfach beide Varianten genutzt: Aus einem oder wird ein und.

Zunächst wird dementsprechend ein Interface entwickelt, das als grundlegende syntaktische Basis dient. Darauf aufbauend wird dann eine abstrakte Basisklasse entwickelt, die eine Standardimplementierung dieses Interfaces enthält.

Konkrete Klassen haben nun die Wahl, ob sie die Freiheit eines Interfaces oder den Komfort einer abstrakten Basisklasse benötigen, und können je nach Bedarf so oder so ableiten. Die Polymorphie bleibt dabei über das Interface in jedem Fall erhalten, so dass zu einem späteren Zeitpunkt die Implementierung eines Interfaces sogar vollkommen problemlos gegen die Ableitung von einer Basisklasse (oder umgekehrt) ausgetauscht werden kann.

Sinn und Zweck von AOP

Sonntag, 1. März 2009, 09:37 Uhr
Permalink | Kommentare (6) | Kommentare als RSSRSS

Am 13. Oktober 2008 haben Peter Bucher und ich unter dem Titel Noch Fragen, Bucher? Ja, Roden! angekündigt, jeweils zum ersten eines jeden Monats einen Kommentar zu einem vorab gemeinsam gewählten Thema verfassen zu wollen. Bisher sind in dieser Reihe folgende Kommentare erschienen:

Heute, am 1. März 2009, ist es nun wieder so weit, und unser Thema für diesen Monat lautet:

Sinn und Zweck von AOP

So wohl Peter wie auch ich haben uns unabhängig voneinander im Vorfeld unsere Gedanken gemacht, wie wir diesem Thema gegenüberstehen. Peters Kommentar findet sich zeitgleich in seinem Blog, folgend nun mein Kommentar zu diesem Thema:

Die aspektorientierte Programmierung (AOP) ist ein verhältnismäßig junges Programmierparadigma, das in gewisser Weise als Weiterentwicklung und Ergänzung der objektorientierten Programmierung gelten kann. Die der aspektorientierten Programmierung zu Grunde liegende Erkenntnis ist, dass eine Anwendung nicht nur solchen Code enthält, der sich direkt auf funktionale Anforderungen bezieht.

Statt dessen enthält jede Anwendung auch Infrastrukturcode, der den funktionalen Code um weitere Aspekte ergänzt. Diese Aspekte ergeben sich häufig aus den nicht-funktionalen Anforderungen und betreffen in der Regel Themen wie beispielsweise Sicherheit, Validierung, Transaktionen, Logging, Caching, ...

All diesen Themen ist gemein, dass sie für eine qualitativ hochwertige Anwendung notwendig sind, dass sie ohne den funktionalen Code jedoch nicht genügen, um eine eigenständige Anwendung zu formen. Als weitere Gemeinsamkeit kann genannt werden, dass es häufig nicht möglich ist, diese Aspekte genau einer Schicht zuzuordnen.

Da diese Aspekte also die vorhandenen Schichten vertikal schneiden, werden sie auch als Crosscutting Concerns bezeichnet, während der funktionale Code die Core Concerns abbildet. Die Frage, die mit Hilfe der aspektorientierten Programmierung beantwortet wird, lautet, wie diese Crosscutting Concerns isoliert entwickelt und an entsprechender Stelle effizient in die Anwendung injiziert werden können.

Die Sicht auf die aspektorientierte Programmierung hat sich dabei in den vergangenen Jahren drastisch verändert. Galt das Thema noch vor wenigen Jahren als ausgesprochen elitär, ist es inzwischen dank entsprechender Werkzeuge auf dem besten Weg, zu einer Commodity zu werden: In meinem Eintrag Architektur lernen habe ich bereits auf PostSharp und das zugehörige LAOS-Framework verwiesen, die .NET um einen Postcompiler und ein entsprechendes Framework zur aspektorientierten Programmierung erweitern.

Warum lohnt also die Beschäftigung mit aspektorientierter Programmierung? Die Antwort liegt auf der Hand, denn die aspektorientierte Programmierung vereinfacht und verkürzt die Entwicklung von Software in verschiedenen Phasen:

  • Planung: Um aspektorientierte Programmierung effizient einsetzen zu können, müssen potenzielle Aspekte zunächst identifiziert werden. Die Beschäftigung mit dieser Frage führt damit automatisch zu Überlegungen über die Architektur der zu entwickelnden Anwendung, potenziell allerdings unter einem neuen Blickwinkel, wodurch weitere Erkenntnisse über die Tragfähigkeit einer Architektur gewonnen werden können.
  • Entwicklung: Da die einzelnen Aspekte isoliert und unabhängig von der eigentlichen Anwendung entwickelt werden, sinkt der Entwicklungs- und Integrationsaufwand. Zudem können die Aspekte in eine eigenständige Komponente verpackt und für verschiedene Anwendungen wiederverwendet werden. Last but not least muss ein Aspekt unabhängig davon, wie oft er in einer Anwendung genutzt wird, nur einmal entwickelt werden, und kommt somit der Einhaltung des DRY-Prinzips entgegen.
  • Test: Aus den gleichen Gründen sinkt auch der Testaufwand für Aspekte, denn diese können unabhängig von einer Anwendung isoliert getestet werden. Da Aspekte zudem nur einmal entwickelt, aber mehrfach in die Anwendung injiziert werden, reichen einige wenige Tests aus, die in Konsequenz aber weite Bereiche der Anwendung abdecken.
  • Wartung: Schlussendlich muss bei einer Änderung eines Aspekts nur eine Codestelle geändert werden, was wiederum den vorigen Punkten zu Gute kommt - lediglich in einer neuen Iteration.

Doch wo Licht ist, gibt es bekanntlich auch Schatten: Eines der größten Probleme der aspektorientierten Programmierung ist derzeit, dass es (noch) keinen standardisierten Weg gibt, wie sie umgesetzt werden soll. Es fehlen etablierte Prinzipien, wie sie beispielsweise für die objektorientierte Programmierung existieren, die bei der Umsetzung helfen. Hier wird die Zukunft zeigen müssen, wie Best Practices aussehen werden und welche Ideen tragfähig sind.

Diesen Nachteilen zum Trotz überwiegen dennoch die Vorteile dermaßen deutlich, dass die Beschäftigung mit der aspektorientierten Programmierung in jedem Fall bedenkenlos empfohlen werden kann.

Die Forderung nach Softwarequalität

Sonntag, 1. Februar 2009, 09:37 Uhr
Permalink | Kommentare (4) | Kommentare als RSSRSS

Am 13. Oktober 2008 haben Peter Bucher und ich unter dem Titel Noch Fragen, Bucher? Ja, Roden! angekündigt, jeweils zum ersten eines jeden Monats einen Kommentar zu einem vorab gemeinsam gewählten Thema verfassen zu wollen. Bisher sind in dieser Reihe folgende Kommentare erschienen:

Heute, am 1. Februar 2009, ist es nun wieder so weit, und unser Thema für diesen Monat lautet:

Die Forderung nach Softwarequalität

So wohl Peter wie auch ich haben uns unabhängig voneinander im Vorfeld unsere Gedanken gemacht, wie wir diesem Thema gegenüberstehen. Peters Kommentar findet sich zeitgleich in seinem Blog, folgend nun mein Kommentar zu diesem Thema:

Den Begriff Softwarequalität zu definieren, ist keine große Herausforderung. Qualität ist bei einem Softwareprodukt dann gegeben, wenn die Software nicht nur ihre funktionalen, sondern auch ihre nicht-funktionalen Anforderungen ausreichend erfüllt. Je höher diese Anforderungen gelegt werden, desto besser die Qualität.

Wie wichtig das Thema ist, hat bereits im November 2007 die dotnetpro mit ihrer Konzeptkonferenz prio bewiesen, als sie den Begriff Softwarequalität als Leitthema für die Konferenz gewählt hat.

Außerdem kennt man die Forderung nach Softwarequalität so wohl von Entwicklern wie auch von Managern – in fast jeder Softwarefirma spielt dieses Thema eine gewichtige Rolle.

Man könnte meinen, dass ein Thema, dass zum einen von so großer Bedeutung und zum anderen auch von so vielen gefordert wird, nicht nur ausreichend erforscht, sondern vor allem auch mehr als ausreichend gelebt wird. Leider ist in der Praxis häufig weder das eine noch das andere der Fall.

Das Paradoxe an Softwarequalität ist nämlich, dass sie gleichzeitig Geld und Zeit kostet, aber auch Geld und Zeit spart: Wird während der Entwicklung bereits auf die Einhaltung gewisser Qualitätsregeln geachtet, ist das natürlich aufwändiger, als wenn dies nicht geschieht.

Leider wird die Argumentation in der Praxis häufig an dieser Stelle beendet, denn sobald Auslieferungstermine oder Kosten die Entwicklung beeinträchtigen könnten, wird in der Regel als erstes an der Qualität gespart, denn der Code könnte zu einem späteren Zeitpunkt noch überarbeitet werden.

Das Problem ist nur: Dieser Zeitpunkt wird so gut wie nie erreicht. Denn nach der Auslieferung der Software beginnen zumeist die Arbeiten an der nächsten Version – Zeit, die vorherige erst noch zu bereinigen, findet sich nicht.

Vordergründig mag dieses Vorgehen auch in Ordnung sein, denn auf den ersten Blick funktioniert alles, und es wurden sogar Zeit und Geld gespart. Zugleich wird akzeptiert, dass der ausgelieferte Code nicht fehlerfrei ist, weshalb später Zeit in die Behebung von Bugs fließen muss. Doch genau an dieser Stelle liegt der Hund begraben.

Auch und sogar speziell in der Informatik gilt: Je später ein Fehler behoben wird, desto teurer ist seine Behebung (warum das so ist, habe ich vor einigen Monaten bereits in dem Eintrag Architektur = Planen + Entwurfsmuster erläutert). Genau an dieser Stelle kostet die Entwicklung dann nämlich wiederum übermäßig viel Zeit und Geld: Es handelt sich also um einen dieser typischen Fälle, in denen sich Dinge desto stärker rächen, je mehr man sie hinausschiebt.

Hat man diese Problematik erst einmal erkannt, akzeptiert und beschlossen, etwas zu unternehmen, stellt sich die Frage, was genau man denn eigentlich machen könnte. Hierzu gibt es meiner Meinung nach in jeder Phase der Entwicklung ausreichend Möglichkeiten, aktiv zu werden – selbst, wenn man in der Firma ansonsten nicht wirklich etwas zu sagen hat: Getting Things Done When You’re Only a Grunt.

In der Anforderungsphase kann man als Entwickler zwar die eigentlichen Anforderungen nicht oder nur kaum beeinflussen, aber man hat die Möglichkeit, eigene Aufwandsabschätzungen durchzuführen und darauf hinzuweisen, dass ein bestimmtes Feature eventuell nicht so schnell umgesetzt werden kann, wie es der Kunde gerne hätte.

Eine meiner Meinung nach ausgezeichnete Methode, wie man Aufwände überhaupt schätzen kann, ist das sogenannte Evidence Based Scheduling. Neben der Beschreibung der eigentlichen Methode enthält diese Webseite auch Tipps, wie man dem eigenen Zeitplan die Aufmerksamkeit verschafft, die er verdient hat.

In der Designphase kann ein Entwickler dann mitreden, wenn er sich nicht nur als einfacher Programmierer sieht, sondern sich auch aktiv mit dem Thema Softwarearchitektur auseinandersetzt. An dieser Stelle zeigt sich der essenzielle Unterschied zwischen Programmierern, Entwicklern und Architekten:

Während sich die Programmierung mit dem reinen Was auseinandersetzt, beschäftigt sich Architektur mit dem Wie. Und die Schnittstelle zwischen beidem - quasi die architekturbewusste Programmierung - ist die Entwicklung.

Den größten Einfluss hat ein Entwickler selbstverständlich auf die Entwicklungsphase einer Software, denn dies ist die Phase, mit der er selbst die meiste Zeit zubringt, und in der er sich am besten auskennt. Doch auch hier wissen viele Entwickler nicht recht, wie sie vorgehen sollen, um die Qualität ihres Codes zu verbessern (mir selbst ging es vor einigen Jahren ebenso).

Obwohl es kein Patentrezept für die Verbesserung der Qualität des eigenen Codes gibt, haben sich in der Praxis doch einige Vorgehensweisen als sinnvoll erwiesen, die ich im Folgenden noch ein bisschen näher beschreiben möchte:

  • Coderichtlinien: Der Einsatz von Coderichtlinien hilft, konsistenten Code zu schreiben, was nicht nur im Team sinnvoll ist. Auch als Einzelkämpfer profitiert man von solchen Richtlinien, denn man beginnt, sich aktiv mit dem Code auseinanderzusetzen, was zu mehr Disziplin während der Entwicklung führt Speziell zu .NET hat Microsoft in der MSDN zahlreiche Coderichtlinien veröffentlicht – angefangen bei Namensgebung über Klassendesign bis hin zu Best Practices, wie gewisse Konstrukte in .NET optimal eingesetzt werden. Ein ausgesprochen gutes und sehr empfehlenswertes Buch zu dem Thema ist Framework Design Guidelines, zu dem ich im Dezember 2008 einen Review geschrieben habe.
  • Werkzeuge: Coderichtlinien nützen nur dann etwas, wenn sie eingehalten werden. Außerdem stellt sich immer die Frage, welche Richtlinien man befolgen sollte und welche nicht. Aus Gründen der Kompatibilität zu anderen Entwicklern und auf Grund ihrer Konsistenz bietet es sich an, sich an die von Microsoft gegebenen Richtlinien zu halten. Visual Studio hilft hierbei schon ein wenig, indem es beispielsweise die Codeformatierung weitestgehend automatisch durchführt. Dennoch ist es hilfreich, ein dediziertes Programm zu nutzen, wobei hier an erster Stelle FxCop zu nennen ist. Der Einsatz von FxCop kann zunächst sehr frustrierend sein, nimmt man die Warnungen dieses Werkzeugs jedoch ernst und beschäftigt sich vor allem mit den Begründungen der Warnungen, dann lernt man im Lauf der Zeit, gewisse Regeln zu beachten.
  • Unittests: Codequalität zeichnet sich nicht nur durch formal ausgezeichneten Code aus, sondern auch dadurch, dass Code über die Zeit funktional stabil bleibt – sprich, dass sich seine Semantik nicht unabsichtlich verändert, weil Seiteneffekte unerwartete Auswirkungen haben: Dies kann zu guten Teilen mit Unittests sichergestellt werden. Dazu empfiehlt sich der Einsatz eines entsprechenden Testframeworks wie beispielsweise MBUnit, und zusätzlich die Beschäftigung mit mindestens einem Teststil, wie beispielsweise TDD oder BDD. Interessant ist, dass sich diese beiden nicht gegenseitig ausschließen, sondern sich sogar gegebenenfalls wunderbar ergänzen können.
  • Code Reviews: Vier Augen sehen bekanntlich mehr als zwei. Deshalb ist es fast immer hilfreich, eigenen Code noch einmal von einem zweiten Entwickler auf Schwachstellen überprüfen zu lassen. Hierzu sind weder spezielle Werkzeuge noch eine besondere Vorgehensweise notwendig – ein einfacher Austausch per E-Mail über den Code genügt im einfachsten Fall bereits.
  • Pair Programming: Pair Programming ist das Konzept der Code Reviews auf die Spitze getrieben. Man reviewed Code nicht nur gemeinsam, sondern man schreibt ihn direkt gemeinsam. Pair Programming ist eines der Kernkonzepte des sogenannten Extreme Programmings und bedeutet schlussendlich, dass zwei Entwickler gemeinsam vor einem Computer arbeiten – der eine entwickelt, der andere denkt aktiv mit und macht gegebenenfalls Verbesserungsvorschläge oder hinterfragt den geschriebenen Code. Das typische Argument gegen Pair Programming ist, dass zwei Entwickler gemeinsam nicht so viel Code schreiben wie ein Entwickler alleine. Rein an der Zeilenanzahl gemessen, stimmt das – die Qualität ist jedoch um Längen besser, und es gibt auf Grund der gegenseitigen Motivation weniger Leerlauf, was sich letzten Endes in weniger Zeitaufwand niederschlägt.
  • Architektur: Dass die Beschäftigung mit Softwarearchitektur ausgesprochen lohnend für eine Verbesserung der eigenen Codequalität sein kann, habe ich bereits angesprochen. Da die Frage, wie man als Entwickler denn einen Einstieg in das Thema Architektur finden, häufig gestellt wird, sei an dieser Stelle noch einmal explizit auf meinen Eintrag Architektur lernen hingewiesen.

Das Schöne an diesen Punkten ist, dass man sie größtenteils sogar in eigener Initiative durchführen kann – selbst, wenn die eigene Firma diese nicht explizit fordert oder unterstützt.

In diesem Sinne: Auf geht’s!

SOA vs WOA

Donnerstag, 1. Januar 2009, 09:37 Uhr
Permalink | Kommentare (1) | Kommentare als RSSRSS

Nachdem Peter Bucher und ich vor rund vier Wochen unser erstes Streitgespräch unter dem Titel Dynamic Language Runtime: .NET, quo vadis? veröffentlicht haben, haben wir für dieses Mal ein Thema ausgewählt, das in der IT durchaus für Furore sorgt:

SOA vs WOA

Die erste Herausforderung im Hinblick auf diese Begriffe ist, sie überhaupt zu definieren. Ich für meinen Teil kann es mir diesbezüglich verhältnismäßig leicht machen, da sich in der aktuellen Ausgabe der dotnetpro ein Artikel von mir findet, der sich mit genau dieser Frage beschäftigt. Daher werde ich im folgenden darauf Bezug nehmen. Allerdings bin ich auf die Definition von Peter gespannt, die sich zeitgleich in seinem Blog findet. Folgend nun mein Kommentar zu diesem Thema:

Der Terminus SOA (serviceorientierte Architektur) geht auf das Marktforschungsunternehmen Gartner zurück, das ihn erstmals 1996 verwendete. Obwohl Gartner die Möglichkeit gehabt hätte, den Begriff mit einer Definition zu hinterlegen, hat das Unternehmen diese Herausforderung anderen überlassen.

Durchsucht man das Web, so stößt man - je nachdem, wen man fragt - auf unterschiedliche Definitionen. Laut Wikipedia wird jedoch häufig die Definition von OASIS zitiert, die SOA als "ein Paradigma für die Strukturierung und Nutzung verteilter Funktionalität, die von unterschiedlichen Benutzern verantwortet wird" beschreibt.

Offensichtlich geht es bei SOA also um das Vernetzen von Diensten verschiedener Anbieter. Die naheliegende Frage ist dabei, was dabei unter einem Dienst zu verstehen ist. Prinzipiell ist ein Dienst mit einer Komponente vergleichbar, denn für beide gelten:

  • Ein Dienst verfügt über eine wohldefinierte Schnittstelle, die zur Kommunikation mit diesem genutzt wird. Der Dienst fungiert dabei als Blackbox, die Implementierung bleibt für die den Dienst nutzende Anwendung also verborgen.
  • Ein Dienst steht prinzipiell allen Anwendungen zur Verfügung, potenziell auch über ein lokales Netzwerk oder das Internet. Anwendungen müssen in diesem Fall mit Latenz und potenziell auch mit Nichtverfügbarkeit umgehen können.

Anders als eine Komponente wird ein Dienst jedoch autonom ausgeführt - das heißt, er wird nicht im Kontext einer Anwendung ausgeführt, sondern unabhängig von dieser. Man könnte sagen, ein Dienst ist eine ausgelagerte, eigenständig ausführbare Komponente.

Auf dieser Basis ist es bereits sehr einfach, den Begriff SOA anschaulich zu erklären: Eine serviceorientierte Architektur ist ein Ansatz für die Architektur einer Anwendung, bei der die Funktionalität nicht ausschließlich innerhalb einer Anwendung positioniert wird, sondern auf die Anwendung und entsprechende Dienste verteilt wird.

Eine ausgesprochen wichtige Frage für SOA ist, wie die einzelnen Dienste oder die Anwendung und die Dienste miteinander kommunizieren. Hierzu gibt es zahlreiche Möglichkeiten. Prinzipiell ist jede asynchrone, streambasierte Kommunikation denkbar. Häufig wird SOA mit Webservices gleichgesetzt, allerdings sind Webservices nach den WS*-Standards nur eine von vielen möglichen Implementierungen von SOA.

Ebenfalls wird dabei deutlich, dass es sich bei SOA nicht um eine Blaupause für eine Architektur handelt, sondern lediglich um einen Denkansatz - ein Paradigma eben -, das für jede Anwendung in ihrem Kontext neu zu überdenken und auszuarbeiten ist.

Im vergangenen Jahr hat sich ein weiterer Begriff aus dem Hause Gartner verbreitet: WOA (weborientierte Architektur). Die Namensähnlichkeit von WOA zu SOA ist mit Sicherheit kein Zufall, und tatsächlich hängen beide Begriffe auch eng zusammen: Obwohl SOA nur eine abstrakte Idee, ein Paradigma einer Architektur beschreibt, wird es in der Praxis häufig synonym mit Webservices und den entsprechenden WS*-Standards verwendet.

Prinzipiell ist daran auf den ersten Blick nichts auszusetzen, schließlich bieten Webservices eine äußerst flexible Plattform für serviceorientierte Architekturen. Trotzdem gibt es einige Punkte, an denen auf Webservices basierende Architekturen mit Problemen zu kämpfen haben:

  • Zum einen erfordert die Arbeit mit ihnen sehr gute Kenntnisse von zahlreichen Standards. Obwohl im Bereich der Webtechnologien
    bereits seit Jahren entsprechende etablierte Technologien existieren, gibt es für Webservices häufig eine eigene Variante. Dies erhöht den Einarbeitungsaufwand und erschwert die Migration.
  • Zum anderen ist das SOAP-Protokoll, das den Webservices zugrunde liegt, verhältnismäßig ausladend. Selbst das Übertragen einer kleinen Nachricht erfordert eine umfangreiche XML-Struktur, sodass die Kommunikation häufig ein wenig schwerfällig wird und schlecht skaliert.

Als Ausweg bietet sich der Einsatz der bereits seit Jahren etablierten Webstandards an, um auf deren Grundlage einfach formulierte XML-Nachrichten zu versenden. Genau dieses Ansinnen befriedigt REST. Dessen Ansatz verfolgt die gleichen Ziele wie die WS*-Standards, ist aber deutlich einfacher aufgebaut. Als wesentliche Technologien liegen REST das HTTP-Protokoll, URLs und XML zugrunde - also einfache, erprobte Technologien, die zahlreichen Webentwicklern bereits geläufig sind.

Da REST in den vergangenen Jahren zunehmend an Bedeutung gewonnen hat und zahlreiche Webservices inzwischen auch parallel als REST-Service zur Verfügung stehen, steht dem Aufbau einer WOA-basierten Anwendung mit den dazugehörigen Diensten nichts im Wege.

WOA ist also eine auf das Web optimierte und auf REST basierende konkrete Version von SOA, und somit lediglich eine mögliche Ausprägung einer serviceorientierten Architektur, ebenso wie Webservices eine solche mögliche Ausprägung darstellen.