Peter Bucher Ralf Westphal

Blog

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

Accuracy und Präzision

Dienstag, 9. März 2010, 17:37 Uhr
Permalink | Kommentare (2) | Kommentare als RSSRSS

Im August 2009 habe ich Accuracy als Charakteristik für einen guten Entwickler gefordert.

Im Wesentlichen habe ich mich bei der Definition des Begriffs von dem Computerspiel Quake III Arena leiten lassen, in dem Accuracy für eine überdurchschnittlich hohe Treffgenauigkeit steht. Auf dieser Basis und übertragen auf die Softwareentwicklung habe ich Accuracy damals als

Die Liebe für Details, und die Fähigkeit, auch auf jeden Punkt und jedes Komma zu achten – und nicht nur oberflächlich mal eben schnell über Code hinweg zu gehen.

definiert. Nachdem ich diese Begriffsdefinition inzwischen eine Weile verwende, ist mir bewusst geworden, dass Accuracy alleine nicht genügt, sondern noch ein weiteres Maß erforderlich ist, um die Arbeitsweise eines Entwickler bewerten zu können: Präzision.

Auch – zumindest die englischsprachige – Wikipedia trennt zwischen Accuracy und Präzision, wobei dort wiederum die Analogie zur Waffenkunde aufgegriffen wird:

  • Accuracy bezeichnet die Treffgenauigkeit, wie weit Treffer also vom gewünschten Ziel abweichen.
  • Präzision hingegen bezeichnet die Wiederholbarkeit, wie groß die Varianz der Treffer also ist.

Natürlich sind beide Werte unabhängig voneinander, denn sie lassen sich in jeder beliebigen Ausprägung miteinander kombinieren – weder erfordert eine hohe Accuracy eine hohe Präzision, noch gilt dies umgekehrt.

Was bedeuten diese beiden Begriffe nun übertragen auf die Softwareentwicklung?

Accuracy bedeutet für mich, dass ein Entwickler der idealen Form von Quellcode nahe kommt, das heißt, akkurat geschriebener Code zeichnet sich durch die Abwesenheit von Formfehlern aus. Als Merkmale für derartigen Code können beispielsweise

  • die korrekte Einrückung sowie der korrekte Gebrauch von Leerzeilen,
  • die korrekte Klammerung,
  • die korrekte Schreibweise von Bezeichnern
  • und korrekte Rechtschreibung und Grammatik in Kommentaren

dienen. Entwicklern, die in der Lage sind, solchen Code zu erzeugen, kann man daher durchaus eine hohe Treffgenaugkeit zugestehen.

Nun stellt sich zusätzlich die Frage nach der Präzision. Dass Fehler vorkommen, kann kaum verhindert werden – die Frage ist aber, wie breit gestreut die Varianz der Arten von Fehlern ist.

Wird beispielsweise korrekt eingerückt, korrekt geklammert und es finden sich lediglich ab und an Kommafehler in Kommentaren, so kann man durchaus eine hohe Präzision zugestehen. Umgekehrt gilt – wenn die Arten von Fehlern weit gestreut sind, verfügt ein Entwickler über eine nur niedrig ausgepägte Präzision – wobei er dennoch, wenn er nur wenige Fehler macht, eine hohe Accuracy aufweisen kann.

Ich glaube, dass diese Trennung in Accuracy und Präzision wichtig ist, um die Qualität von Code und damit auch die Fähigkeiten des entsprechenden Entwicklers angemessen beurteilen zu können.

Natürlich beziehen sich beide Werte nicht nur auf die formale Gestalt von Code, sondern beispielsweise auch auf das Durchdenken dessen, was eigentlich codiert wird – auch hier können Ergebnisse mit beiden Maßen gemeinsam besser beurteilt werden als nur mit einem.

Dependency Injection ist keine Silberkugel

Montag, 7. Dezember 2009, 07:01 Uhr
Permalink | Kommentare (3) | Kommentare als RSSRSS

Das Inversion of Control-Paradigma folgt in der Regel einer von zwei Varianten: Entweder wird ein dedizierter Service Locator in Form eines Microkernels verwendet, oder es wird auf ein geeignetes Dependency Injection-Framework samt entsprechendem Container zurückgegriffen.

Der wesentliche Unterschied zwischen diesen Varianten besteht in unterschiedlichen Graden der Explikation beziehungsweise Implikation, und in der Abhängigkeit von der Infrastruktur:

  • Während der Service Locator eine generische Factory darstellt und explizit beauftragt werden muss, eine Instanz zu einem gegebenen Kontrakt zu erzeugen, geschieht dies beim Einsatz eines Dependency Injection-Containers implizit.
  • Da der Aufruf des Service Locators explizit geschieht, bedeutet dies in Konsequenz, dass eine Referenz auf den Service Locator vorhanden sein muss – diese Referenz entfällt beim Einsatz eines Dependency Injection-Containers.

Aus diesen grundlegenden Unterschieden ergeben sich einige spezifische Vor- und Nachteile, interessant ist jedoch, dass der Service Locator in der Regel von Einsteigern, denen das Inversion of Control-Paradigma noch nicht vertraut ist, bevorzugt wird. Die Gründe hierfür liegen auf der Hand: Der explizite Aufruf einer Factory zur Erzeugung einer Instanz ist näher am klassischen Aufruf von new.

Je ausgiebiger sich ein Entwickler danach mit Inversion of Control auseinandersetzt, desto eher lernt er die Vorzüge von Dependency Injection schätzen. Dazu zählen unter anderem:

  • Es ist keine Referenz auf einen Service Locator oder ein ähnliches Konstrukt notwendig – der Dependency Injection-Container bleibt verborgen, so dass die Abhängigkeit von externen Komponenten vermindert wird.
  • Da die einzelnen Abhängigkeiten von außen über den Konstruktor oder entsprechende Eigenschaften aufgelöst werden können, ist es ein Leichtes, innerhalb eines Unittests passende Mockobjekte in eine Komponente hineinzureichen. Darüber hinaus kann in Unittests gänzlich auf den Dependency Injection-Container verzichtet werden, was den Aufwand zur Bereitstellung einer passenden Infrastruktur deutlich senkt.
  • Da die gesamte Verdrahtung der Anwendung implizit geschieht, sinkt der Aufwand für den einzelnen Entwickler deutlich.

Vor allem auf Basis dieser Gründe erscheint Dependency Injection dann häufig als die besser durchdachte Alternative zu einem Service Locator, so dass man als Entwickler versucht ist, sämtliche Abhängigkeiten mit Hilfe von Dependency Injection aufzulösen.

Doch Dependency Injection ist keine Silberkugel. Anders als häufig behauptet, verfügt Dependency Injection nämlich auch über einige Nachteile:

  • Zum einen ist es mit Dependency Injection nicht möglich, lokale Variablen zu instanziieren: Zugriff besteht – entweder über den Konstruktor oder über passende Eigenschaften – nur auf Felder. Der vermeintliche Ausweg, jede lokale Variable in ein Feld zu überführen, erweist sich als Irrweg: Schließlich weisen lokale Variablen und Felder eine unterschiedliche Semantik auf und sind nicht per se gegeneinander austauschbar.
  • Zum anderen ist die dynamische Erzeugung von Instanzen per Dependency Injection nicht möglich: Der Lebenszyklus einer injizierten Komponente ist weitestgehend an den der enthaltenden Komponente gebunden. Für dynamische Instanziierung – beispielsweise in Abhängigkeit von anderem Code – eignet sich Dependency Injection somit nicht.

Das heißt, dass immer dann, wenn lokale Variablen gefüllt oder mehrfach vorhandene Abhängigkeiten dynamisch aufgelöst werden sollen, Dependency Injection an ihre Grenzen stößt: In diesem Fall bleibt nichts anderes, als auf einen Service Locator oder ein ähnliches Konstrukt zu setzen.

Dies bedeutet jedoch, dass Dependency Injection nicht alle Probleme auf einen Schlag löst, ebenso wenig wie ein Service Locator dies macht. Vielmehr liegt die ideale Lösung darin, beide Ansätze miteinander zu vereinen, um das Beste aus beiden Welten nutzen zu können.

Kurzum: Dependency Injection ist kein Allheilmittel. Die Nutzung empfiehlt sich immer dann, wenn genau eine einzige Komponente verwendet werden soll, deren Lebenszyklus dem des enthaltenden Objekts gleicht. Für alle anderen Fälle eignet sich ein Service Locator besser.

Die beiden Konzepte konkurrieren also nicht exklusiv miteinander, sondern ergänzen sich gegenseitig: Dies kann sogar so weit gehen, dass der Service Locator mit Hilfe von Dependency Injection in die entsprechenden Komponenten injiziert wird, um die sonst vorhandene Abhängigkeit zu vermeiden.

Ein Ordnungssystem für Unittests

Sonntag, 22. November 2009, 07:06 Uhr
Permalink | Kommentare (2) | Kommentare als RSSRSS

Auf den Tag drei Wochen sind vergangen, seit Ralf Westphal mich in Reaktion auf den Blogeintrag Wie viel Sinn machen Unittests? ermuntert hat, mich nochmals intensiv mit diesem Thema auseinanderzusetzen:

Unit Tests […] konsequent und kompetent mal für einen Monat einzusetzen. Mit der Kompetenz mag es da schwierig sein, weil man sich dann eine Veränderung der Handlungs- und Denkgewohnheit selbst beibringen muss... aber das ist zumindest ein Anfang.

Ich bin seinem Rat gefolgt und habe bemerkt, dass die wesentlichen Probleme für mich nicht technischer, sondern gedanklicher Natur waren. Zu viele unbeantwortete Fragen standen im Raum – Fragen, die zunächst einfach erscheinen, deren Beantwortung aber viel Nachdenken voraussetzt.

Man könnte mein Problem als die Herausforderung bezeichnen, ein tragfähiges Konzept für Unittests zu finden, das sich nahtlos in meine bisherige Arbeitsweise integriert, sie zwar erweitert, aber nicht von Grund auf umkrempelt.

Bemerkenswert fand ich, dass die wenigsten Entwickler, die ich mit diesen Fragen konfrontiert habe, ihre Antworten fundiert begründen konnten. Einige haben sich sogar daran gestört, dass ich diese Fragen überhaupt stelle, und haben eher dazu geraten, einfach loszulegen – frei nach dem Motto: TDD ist keine Wissenschaft!

Doch ohne fundierte Antworten auf grundlegende Fragen zu haben, die den eigenen Wissensdurst stillen, verbleibt immer ein ungutes Gefühl – ob das, was man macht, tragfähig für zukünftige Erweiterungen sein wird. Diese zukünftige Tragfähigkeit bedeutet mir sehr viel – anders wäre ich vermutlich auch von Extreme Programming nicht so begeistert.

Außerdem ist die Beantwortung solcher Fragen entscheidend, um klare Regeln und Richtlinien zu haben, auf deren Basis man Entscheidungen treffen kann: Je genauer diese Regeln und Richtlinien formuliert werden, desto größer wird die persönliche Liebe zum Detail, desto besser wird die Accuracy – meines Erachtens eine essenzielle Eigenschaft eines guten Enwicklers.

Die erste dieser Fragen, ob Code zum Zwecke einer besseren Testbarkeit angepasst werden darf, wurde bereits in Auch Kirchen haben Kragsteine diskutiert: Wie so oft ist es Ralf Westphal gelungen, eine anschauliche Analogie zur realen Welt zu finden, die das digitale Problem schlagartig löst.

Die zweite dieser Fragen, wie Unittests sinnvoll und übersichtlich organisiert werden können, wurde inzwischen auch beantwortet: Auch hierzu hat Ralf Westphal einen wesentlichen Teil beigetragen, aber auch Neno Loje, Bernd Marquardt und Peter Bucher haben mein nun gefundenes System beeinflusst.

Die grundlegende Erkenntnis ist zunächst, dass sich eine Testklasse nicht zwingend auf eine zu testende Klasse beziehen muss – dass also nicht zwingend eine 1:1-Beziehung zwischen beiden vorliegt. Statt dessen bezieht sich eine Testklasse auf ein sogenanntes System under test (SUT). Ein solches SUT kann durchaus eine Klasse sein – genausogut kann es aber auch eine einzelne Methode sein.

Die zweite Erkenntnis ist, dass ein Unittest ein SUT immer in einem gegebenen Kontext testet. Bei einer Methode ist das fast immer der Aufruf derselben, bei einer Klasse hingegen kann es verschiedene Ausgangssituationen geben: Ein Kontext wäre das Initialisieren der Klasse, ein weiterer die Arbeit mit der gerade initialisierten Klasse, wieder ein weiterer das Disposen einer bereits verwendeten Klasse.

Da sich in der Regel mehrere Unittests auf einen solchen Kontext beziehen, macht es durchaus Sinn, diese in einer Testklasse zusammenzufassen, deren Name den Kontext beschreibt, und deren Namespace den Platz des SUTs angibt.

Auf diese Art könnten Unittests, die den Aufruf der Methode ToDictionary in der Klasse goloroden.de.Common.ExtensionMethods testen, beispielsweise in der Testklasse

  • goloroden.de.Tests.Common.ExtensionMethods.WhenToDictionaryIsCalled

abgelegt werden. Die einzelnen Unittests beschreiben dann mit ihrem Namen innerhalb dieser Klasse nur noch die übergebenen Parameter sowie das erwartete Ergebnis:

  • WithNull_AnArgumentNullExceptionIsThrown()
  • WithAnEmptyString_AnArgumentExceptionIsThrown()

Der Vorteil dieser Terminologie liegt klar auf der Hand: Schlägt ein Unittest fehl, lassen sich aus Testklasse und –methode auf einen Blick alle relevanten Informationen ablesen: Der Aufruf welcher Codestelle verursacht in welchem Kontext mit welchen Parametern ein Problem?

Zudem fördert diese Terminologie das Prinzip von TDD, pro Unittest nur einen einzelnen Aspekt zu testen – versucht man, mehrere Aspekte in einem Unittest zusammen zu fassen, führt dies unweigerlich zu unhandlichen Methodennamen.

Nachdem ich auf dieser Basis nun eine Reihe von Unittests geschrieben habe, bemerke ich, dass sie mir bereits sehr ans Herz gewachsen sind. Interessant fand ich vor allem, dass ich allein durch das nachträgliche Schreiben von Unittests einen Fehler in einer zehnzeiligen Methode entdeckt habe, der trotz dem häufigen Einsatz dieser Methode in den vergangenen zwölf Monaten nicht aufgefallen ist.

Allein das gute Gefühl, zukünftig Änderungen und Erweiterungen an dieser Methode durchführen zu können, ohne Angst haben zu müssen, diesen Fehler unbemerkt wieder einzubauen, war die vergangenen drei Wochen allemal wert.

Insofern kann ich – auch wenn das Schreiben von Unittests für mich noch nicht intuitiv geschieht – guten Gewissens behaupten, dass Unittests sehr wohl Sinn machen. Dies zugebenermaßen sogar mehr, als ich noch vor vier Wochen gedacht hätte.

Auch Kirchen haben Kragsteine

Samstag, 7. November 2009, 18:41 Uhr
Permalink | Kommentare (3) | Kommentare als RSSRSS

Vor knapp einer Woche habe ich mit meinem Blogeintrag Wie viel Sinn machen Unittests? – der im Rahmen der monatlichen Streitgespräche von Peter Bucher und mir erschienen ist – für einige Verwunderung gesorgt. Viele haben sich gefragt, warum gerade ich Unittests dermaßen skeptisch gegenüberstehe.

Ralf Westphal hat in seinem Kommentar vermutet, dass

etwas hinter den drei “problematischen Aspekten” das Grundproblem zu sein [scheint]

und ich muss zugeben, dass diese Vermutung nicht von der Hand zu weisen ist. Als problematisch habe ich an Unittests im Wesentlichen die Tatsache betrachtet, dass Code zum Zweck einer besseren Testbarkeit potenziell angepasst werden muss, da sich einige Konstrukte ansonsten nur schlecht oder gar nicht testen lassen.

Als Beispiel seien an dieser Stelle abstrakte Klassen angeführt, die per Definition nicht instanziiert und deshalb nicht direkt getestet werden können. Prinzipiell kann dieses Problem auf zwei Arten gelöst werden:

  • Entweder werden die bereits bestehenden abgeleiteten Klassen getestet – immerhin ist die abstrakte Basisklasse in diesen implizit enthalten.
  • Alternativ wird eine dedizierte abgeleitete Klasse erstellt, welche die abstrakte Basisklasse beerbt – die Basisklasse ist also auch in dieser Variante implizit enthalten.

Der relevante Unterschied zwischen beiden Varianten liegt in der Tatsache, dass die bereits bestehenden abgeleiteten Klassen eigene Funktionalität mitbringen, die den Test nicht unbedingt verfälschen, aber zumindest beeinflussen könnte: Es wird schwierig, den Test auf die abstrakte Basisklasse zu beschränken.

Eine dedizierte Klasse verfügt nicht über diese Nachteile: Sie sorgt lediglich dafür, dass die abstrakte Basisklasse in möglichst natürlicher Form instanziiert werden kann.

Technisch ist dieses Vorgehen unproblematisch, aber mental bereitete es mir Probleme: Seit Jahren wird Entwicklern vor allem in der akademischen Welt impliziert, dass Code nur dann “gut” ist, wenn er kompakt, elegant und im mathematischen Sinne schön ist.

Wie sind nun Änderungen, die nur um einer besseren Testbarkeit durchgeführt werden, mit diesem Paradigma vereinbar? Die kurze, aber schmerzhafte Antwort lautet: Gar nicht.

Wie bereits in Wie viel Sinn machen Unittests? angesprochen, beschreibt auch Roy Osherove 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.”

Seine zugegebenermaßen sehr pragmatische Lösung

“Don’t be silly.”

dieses Problems stellt für mich keine zufriedenstellende Antwort dar – schließlich kommen die Bedenken nicht von ungefähr, und diese Antwort spiegelt dies in keinerlei Hinsicht wieder.

Auch die im Gespräch mit einigen anderen Entwicklern geäußerte Ansicht

Das ist halt so.

ist nicht zufriedenstellend. Diese Aussage beschreibt nämlich lediglich den Status Quo, begründet aber nicht, warum es in Ordnung oder vielleicht sogar gut ist, auf diese Art vorzugehen. Letztlich spiegelt diese Aussage lediglich das resignierte Abfinden mit einer – potenziell – unbequemen Tatsache dar.

Doch nun hat Ralf Westphal eine Begründung geliefert, die den Ansprüchen der Skeptiker – zumindest aus meiner Sicht – genügt:

das ist völlig ok. maschinen haben ja auch revisionsklappen für wartungsarbeiten. die haben sonst keine funktionalität. oder denk an kragsteine an alten kirchen, damit man gerüste anbringen kann.

Nach einigen Jahren des Zweifels und der Skepsis empfinde ich diese Analogie als eine wahre Erlösung – endlich wird begründet, warum ein solches Vorgehen in der Software in Ordnung ist.

Man mag einwenden, dass dieser Vergleich zur realen Welt doch offensichtlich und naheliegend sei, doch war in den vergangenen Jahren sonst niemand, mit dem ich mich über dieses Thema unterhalten habe, in der Lage, einen ähnlich klaren, einleuchtenden Vergleich zu ziehen.

Persönlich bemerke ich, dass diese Erkenntnis eine Art Wendepunkt darstellt – die mentale Barriere, mit der ich mich bislang gegen Unittests gesträubt habe, weil sie Anpassung von Code erfordern, ist aufgelöst. Alles, was bleibt, sind technische Probleme – und diese können mit Sicherheit gelöst werden.

Als Konsequenz werde ich das umsetzen, was Ralf Westphal vorgeschlagen und empfohlen hat:

Unit Tests […] konsequent und kompetent mal für einen Monat einzusetzen. Mit der Kompetenz mag es da schwierig sein, weil man sich dann eine Veränderung der Handlungs- und Denkgewohnheit selbst beibringen muss... aber das ist zumindest ein Anfang.

Wie es mir damit ergeht – darüber werde ich berichten …

Stirnrunzeln über “Bugtracking ist sinnlos”

Mittwoch, 21. Oktober 2009, 09:33 Uhr
Permalink | Kommentare (5) | Kommentare als RSSRSS

Ilker Cetinkaya hinterfragt in seinem aktuellen Blogeintrag den Sinn von Bugtacking und schließt mit einem provokanten Fazit:

Bugtracking ist sinnlos!

Für ihn steht der Verwaltungsaufwand, der für ein entsprechendes System notwendig ist, in keinem Verhältnis zum Nutzen – zumindest dann nicht, wenn man ohnehin agile Methoden wie XP oder Scrum einsetzt.

Sein Vorschlag zum Umgang mit Bugs lautet:

Wenn ein Bug wirklich ein wichtiger Bug ist, dann wird er sofort gefixed. Alles andere ist entweder nicht wichtig oder wird dann erst wichtig, wenn die wichtigeren Dinge schon erledigt sind.

Doch nicht nur das Bugtracking, auch das –reporting hinterfragt Ilker. So schlägt er zusätzlich vor,

ein leichtgewichtiges Bugreporting [zu] etablieren und die Entscheidung über den Fix oder No-Fix des Bugs schnell herbei[zu]führen

Insgesamt verwundern mich diese Vorschläge sehr, denn es gibt einige Aspekte, die Ilker vollständig ignoriert: Die Entscheidbarkeit, die Nachvollziehbarkeit und zuletzt auch die Transparenz.

Er geht davon aus, dass für jeden Bug sofort entschieden werden kann, ob dessen Relevanz genügt, um zeitnah behoben zu werden. Die Entscheidung, ob ein Bug dermaßen relevant ist, dass er sofort beseitigt wird, wird in der Regel möglich sein.

Im Endeffekt bedeutet das nämlich, dass all jene Bugs gefixt werden, die bei Verwendung eines Bugtrackingsystems mit oberster Priorität und als höchst kritisch gekennzeichnet worden wären.

Die Frage ist aber, was mit einem Bug geschehen soll, der diese Kriterien nicht erfüllt. Dazu schlägt Ilker vor:

Ist er nicht wichtig genug (also ist entweder ein anderer Bug oder ein anstehendes Feature wichtiger), so wird der Report weggeworfen.

Interessanterweise schreibt er direkt im nachfolgenden Satz:

Der Benutzer wird benachrichtigt dass der Fehler momentan nicht korrigiert wird.

Beachtenswert an diesem Satz ist für mich das Wort momentan – das heißt, Ilker räumt ein, dass es sinnvoll sein könnte, den Bug zu einem späteren Zeitpunkt zu fixen. Doch wie merken sich die Entwickler diesen Bug und dessen Details, wenn sie ihn nirgends notieren dürfen?

Es liegt auf der Hand, dass dabei über kurz oder lang Bugs vergessen werden, oder dass zumindest Details verloren gehen, die zur Behebung potenziell wichtig gewesen wären.

Interessant finde ich auch, welches Resultat Ilker anführt: Durch den Verzicht auf Bugtracking und die sofortige Entscheidung wird direkt an den Benutzer kommuniziert, ob der von ihm gemeldete Bug behoben wird oder nicht. Dies nennt er:

Ehrlich, sachlich, klar.

Ohne Frage wird die Transparenz über die sofortige Entscheidung erhöht – jegliche sonstige Transparenz geht allerdings verloren – nicht nur für die Entwickler, auch für den Benutzer:

  • Ohne Bugtracking weiß niemand, welche Bugs vor langer Zeit abgelehnt wurden, insbesondere aus welchen Gründen dies geschehen ist.
  • Ohne Bugtracking weiß niemand, wie oft welcher Bug auftritt – dies kann man nur noch nach persönlichem Gutdünken schätzen.
  • Ohne Bugtracking bekommt der Benutzer nur die Aussage, dass sein Bug momentan nicht behoben wird – was damit zukünftig geschieht, kann er nicht nachverfolgen.
  • Ohne Bugtracking weiß niemand, wie viele gefundene, aber ungefixte Bugs es in einer Software gibt.

Diese Liste ließe sich noch lang fortsetzen. Worauf ich hinauswill, wird aber deutlich: Bugtracking ist nicht nur nicht sinnlos, es ist sogar enorm wichtig für den Erfolg eines Projekts!

In einem einzigen Punkt kann ich Ilker allerdings dennoch zustimmen:

[…] wenn man wirklich das Produkt stetig weiterentwickeln möchte auch immer sich zum Ziel setzen muss, ein möglichst fehlerfreies Produkt auszuliefern.

Dieser Aussage stimme ich vollkommen zu – lediglich die Konsequenzen, die er daraus zieht, kann ich weder nachvollziehen noch gutheißen.

Wozu überhaupt Programmierstandards?

Donnerstag, 27. August 2009, 09:45 Uhr
Permalink | Kommentare (1) | Kommentare als RSSRSS

Gestern habe ich in meinem Blogeintrag Accuracy unter anderem versucht, die Frage zu beantworten, warum Peter Bucher und mir Programmierstandards und deren strikte Einhaltung so wichtig sind:

Aus diesem Grund erachten auch Peter und ich Programmierstandards als dermaßen wichtig: Sie erfüllen eben nicht nur die im Extreme Programming verfolgte Intention, gemeinsame Verantwortlichkeit zu etablieren, sondern ermöglichen uns auch, den Code des anderen schneller zu verstehen und uns gegenseitig zu helfen.

Im Grunde steckt darin auch schon die Antwort auf die im Titel dieses Blogeintrages gestellte Frage: Wozu sollte man sich als Entwickler an Programmierstandards halten?

Nun mag der ein oder andere einwenden, dass er ohnehin nicht im Team, sondern nur alleine entwickelt, dass für ihn also das Argument der Etablierung einer gemeinsamen Verantwortlichkeit nicht zählt. Doch selbst dann bin ich der Meinung, dass es durchaus Sinn ergibt, sich an Programmierstandards zu halten.

Warum? Dafür gibt es mehrere Gründe:

  • Verstehen: Auch einem allein arbeitenden Entwickler helfen Programmierstandards, sich auf das Wesentliche zu konzentrieren und der äußeren Form zunehmend weniger Aufmerksamkeit schenken zu müssen, da er sie nach und nach verinnerlicht. Dennoch degeneriert die Form nicht, so dass der Code auch mittel- und langfristig gut lesbar und damit verständlich bleibt.
  • Fragen: Viele Entwickler suchen – auch wenn sie prinzipiell alleine arbeiten – ab und an Hilfe in der Community, sei es in Foren oder in Newsgroups. Sofern dann Code gepostet werden muss, ist es für die Antwortenden ausgesprochen hilfreich, wenn sich der Autor an gängige Programmierstandards gehalten hat – das Verständnis fällt leichter, als wenn man sich erst durch die Syntax kämpfen muss, bevor man die eigentliche Frage nachvollziehen kann. Die Einhaltung der Programmierstandards erleichtert in diesem Fall also die Kommunikation nach außen.
  • Lernen: Analog dazu vereinfacht die Gewöhnung an Programmierstandards auch die Kommunikation nach innen, denn die meisten Autoren halten sich ebenfalls an gängige und etablierte Standards.

Unabhängig von der Frage, ob man sich an solche Standards halten sollte, ist übrigens die Frage, welche Standards denn als Referenz gelten sollen: Hier gibt es schließlich – je nachdem wen man fragt oder in welchem Projekt man beteiligt ist – ausgesprochen unterschiedliche Auffassungen.

Als Beispiel hierzu kann ein einfaches Hallo Welt-Programm dienen, einmal in C# und einmal in Java. Obwohl es sich bei C# und Java um objektorientierte Sprachen handelt und sich das grundlegende Vorgehen prinzipiell sehr ähnelt, weisen beide Quellcodes doch einige Unterschiede auf.

Während es in C# beispielsweise etabliert ist, geschweifte Klammern in einer eigenen Zeile zu positionieren und Methodennamen groß zu schreiben, gilt in Java das genaue Gegenteil. Zweifelsohne kann ein Entwickler, der C# beherrscht, das Java-Programm problemlos lesen und verstehen – ebenso umgekehrt.

Sobald jedoch Modifikationen vorgenommen werden sollen, fällt die Einhaltung der jeweiligen Standards schwer: Es wird vorkommen, dass geschweifte Klammern falsch gesetzt werden, was dann letztlich die Lesbarkeit verschlechtert.

Natürlich kann man nicht sagen, dass das eine richtig und das andere falsch wäre – es handelt sich bei Programmierstandards immer nur um formale und vor allem willkürliche Übereinkünfte zwischen mehreren Entwicklern: Man hätte es ebensogut anders machen können, hat sich aber für eine Variante entschieden.

Daraus kann man dann die erste Regel im Zusammenhang mit Programmierstandards herleiten: Prinzipiell ist es egal, wie Code formatiert wird – so lange die Formatierung einheitlich durchgeführt wird.

Nichtsdestotrotz haben sich einige Programmierstandards mehr und andere minder etabliert. Microsoft selbst schlägt für C# entsprechende Regeln vor, die im Kontext von .NET etabliert sind und als allgemein anerkannt gelten: Sofern von diesen Best Practices abgewichen wird, sollte dies wohlüberlegt und begründet geschehen.

Peter und ich haben uns daher für die nächsten Wochen vorgenommen, die von uns verwendeten Programmierstandards vorzustellen, zu erläutern, warum wir gewisse Dinge so und nicht anders machen, und hoffen, das Thema Programmierstandards auf diesem Wege dem ein oder anderen Entwickler schmackhaft machen zu können.

Wie bereits erwähnt sind natürlich auch die von uns vorgestellten Programmierstandards nur eine mögliche Variante von vielen – sie haben sich jedoch in unserer tagtäglichen Praxis als nützlich erwiesen und etabliert.

Extreme Programming

Dienstag, 18. August 2009, 22:58 Uhr
Permalink | Kommentare (0) | Kommentare als RSSRSS

Keine tausend Meter von der Technischen Universität Kaiserslautern entfernt liegt das Fraunhofer-Institut für Experimentelles Software Engineering (IESE). Obgleich der Name auf Grund des Wortes experimentell zunächst unwissenschaftlich wirkt, befasst sich das IESE mit einer ausgesprochen anspruchsvollen Thematik: Der Erforschung von Methoden und Prozessen zur industriellen Softwareentwicklung.

Der Leiter des IESE, Prof. Dr. Dieter Rombach, zählt zu den von mir – und nicht nur während meines Studiums – am meisten geschätzten Professoren: Er hat meine Sicht auf Softwareentwicklung im Allgemeinen und das ingenieursmäßige Vorgehen dabei wesentlich geprägt.

Das Studium der Angewandten Informatik an der TU Kaiserslautern war – zumindest während meiner Studienzeit – derart strukturiert, dass das Grundstudium um eine Serie von drei Vorlesungen namens Entwicklung von Softwaresystemen aufgebaut war: Darin wurden die Grundlagen der Programming an Hand von Java vermittelt – angefangen bei den grundlegenden OOP-Konzepten bis hin zu Multithreading.

Was Entwicklung von Softwaresystemen für die Entwicklung im Kleinen war, waren die darauf aufbauenden Vorlesungen Software Engineering während des Hauptstudiums für die Entwicklung von großen Softwaresystemen. Wurde der Schwerpunkt während des Grundstudiums noch auf das Schreiben von Code gelegt, ging es nun um Methoden und Prozesse der Softwareentwicklung.

In einer dieser Vorlesungen habe ich den Begriff Extreme Programming zum ersten Mal gehört: Damals entwickelte sich an Hand dieses Begriffs eine interessante und spannende Diskussion über das Für und Wider agiler Vorgehensweisen.

Für lange Zeit danach war Extreme Programming für mich jedoch nicht mehr als ein Schlagwort, dessen nähere Beschäftigung nicht lohnte – wurden agile Methoden doch häufig als unseriös oder kaschierende Bezeichnung für bewusst in Kauf genommenes Chaos abgetan.

In den vergangenen Jahren habe ich nun immer wieder festgestellt, dass sich meine Art zu entwickeln verändert hat: Man könnte sagen, dass eine gewisse Professionalisierung eingetreten ist – zumindest zu einem bestimmten Grad. Allerdings ist das Ende der Fahnenstange dabei noch lange nicht in Sicht, nach wie vor lerne ich täglich neues dazu.

Interessant an dieser Entwicklung ist, dass es nicht einen einzelnen Faktor gibt, an dem ich dies festmachen könnte – statt dessen gibt es eine ganze Reihe von Faktoren, die mich beeinflusst haben: Das Spektrum reicht dabei von Büchern wie C# in Depth oder Framework Design Guidelines über Personen wie Ralf Westphal oder Peter Bucher bis hin zu Initiativen wie Clean Code Developer.

Vergangene Woche habe ich nun das Buch eXtreme Programming von Henning Wolf, Stefan Roock und Martin Lippert gelesen, weil ich zum einen endlich wissen wollte, was genau sich hinter diesem Begriff verbirgt, und weil ich zum anderen einen Einstieg in das Thema agiler Methoden gesucht habe.

Dabei habe ich festgestellt, dass Extreme Programming ziemlich genau dem Vorgehen entspricht, das ich mir in den vergangenen Jahren angeeignet und für mich als meine persönliche Best Practice erkoren habe. Die meisten der im Extreme Programming vorhandenen Methoden setze ich bereits ein, insbesondere auch in Zusammenarbeit mit Peter Bucher:

  • Programmierstandards, gemeinsame Verantwortlichkeit, fortlaufende Integration – all das sind für uns Selbstverständlichkeiten in unserer Entwicklung geworden. Da wir auf Grund der räumlichen Distanz kein Programmieren in Paaren durchführen können, haben wir Reviews als ständigen Prozess für uns etabliert – was seinerseits wiederum der gemeinsamen Verantwortlichkeit zu Gute kommt.
  • Auch den Punkt des einfachen oder besser gesagt des iterativen Designs schätzen wir enorm: Wir gestalten unsere Architekturen derart, dass sie nicht mehr erfüllen als das derzeit benötigte, dass sie aber für Erweiterungen offen sind. Gerade dieser Aspekt spielt nicht nur für uns eine sehr große Rolle, sondern auch für das Extreme Programming.
  • Einige der in Extreme Programming genannten Methoden wie Retrospektive, Testen oder Planungsspiel setzen wir derzeit noch zu wenig ein – wir sind uns dessen aber bewusst und steuern dieses Ziel an.

Nimmt man all dies zusammen, so kann ich sagen, dass Extreme Programming sehr gut funktioniert – wenn man das richtige Team hat, und das Projekt die erforderliche Agilität zulässt, und zudem eine gewisse Größe nicht übersteigt.

Aus dieser Erkenntnis ziehe ich für mich den Schluss, auf dem richtigen Weg zu sein und diesen Weg auch in Zukunft fortzuschreiten und auszubauen.