Follow my new blog

Donnerstag, 30. Juli 2009

Mehr Termindruck braucht die Softwareentwicklung – aber den richtigen [OOP 2009]

heise Developer berichtet gerade über eine Studie in der Java-Welt – das 2. Java-Trendbarometer der expeso GmbH –, die die Verhältnisse bei der Projektabwicklung als nicht sehr rosig darstellt. Das ist mir als .NET-Entwickler ein kleiner Trost, denn ich dachte, in der Java-Welt sei das Gras grüner bzw. die Projektwelt rosig-erleuchteter. Nicht nur hüben, sondern auch drüben gibt es also einiges zu tun.

Die Begründung für die verbesserungswürdigen Zustände:

“Die Qualität leide[t] unter der häufig sehr stark termingetriebenen Entwicklung.”

Wer hätte das gedacht? Ja, alle denken und wissen das. Und viele ringen mit den Händen und fragen, “Ja, was sollen wir denn machen?”

Dabei ist die Lösung ganz, ganz einfach:

Softwareprojekte brauchen noch mehr Termindruck!

Wer hätte das gedacht? Wenige. Aber es ist so. Wir brauchen nicht weniger Termine, sondern mehr. Das ist der Kerngedanke der Agilität. Viele und ganz regelmäßige Termine sind nötig, damit es mit einem Softwareprojekt vorangeht. Die liegen dann 2 oder 3 oder vielleicht auch 4 Wochen auseinander. Und die Zeiträume zwischen den Terminen heißen Iteration oder Sprint.

image Diese Termine sind allerdings keine Meilensteine! Das (!) ist der entscheidende Unterschied zu den heutigen Terminen, die soviel Druck machen. Denn Meilensteine definieren eine Menge von Arbeit, die geschafft sein muss; bei Meilensteinen geht es um Inhalte. Iterationsendtermine drehen sich hingegen nur um verlässliche Zeitangaben.

Iterationen takten die Auslieferung. Sie garantieren, dass zu festgelegten und sehr häufigen Terminen etwas geliefert wird, das Feedback vom Kunden bzw.  seinem Vertreter braucht. Kommt solches Feedback nicht, dann wächst die Gefahr, dass Geld und Motivation schlicht verbrannt werden. Deshalb sind es auch immer mehr Iterationsendtermine als Meilensteintermine in einem Projekt. Softwareprojekte profitieren davon, wenn Entwickler und Kunden quasi ständig kurz vor einem Abgabetermin stehen. Das nächste Release – in welcher Größe auch immer – ist immer nur maximal 2-3 Wochen in der Zukunft. Es ist sozusagen immer “Meilensteinendphase” – allerdings ohne den Scope-Druck der Meilensteine.

Denn da liegt das wahre Übel bei der Projektabwicklung: Termindruck ist nur ein Symptom von Scope-Druck, d.h. vom Anspruch eine bestimmte Menge an Inhalten zu schaffen.

Und woher kommt diese Vorstellung, dass sich zu einem Termin eine vordefinierte Menge an Inhalten, an Features produzieren ließe? Die resultiert aus einem Grundmissverständis her. Es stammt aus einer Zeit, da Softwareentwicklung im Grunde nur Hardwaremanipulation war. Sie ist ein Relikt aus der Elektrotechnik und dem Maschinenbau, die beide ausgedehnte Produktionsphasen kennen.

Mit solchen Produktionsphasen wird Softwareentwicklung verwechselt. Für ein Auto oder einen Schaltkreis oder ein Gebäude können wir schon lange recht gut angeben, wann die Produktion zu welchem Prozentsatz abgeschlossen ist. Bauphasen lassen sich planen – vorbehaltlich auftretender Störungen z.B. durch Probleme mit Lieferanten oder am Produktionsort.

Doch Softwareentwicklung ist keine Produktion! Softwareentwicklung ist Entwicklung, Entwurf, Design, Kreativität, Problemlösung. Niemand sage also, er hätte es nicht gewusst. Im Deutschen wie im Englischen steckt die Natur der Sache schon im Begriff: Softwareentwicklung, software development. Nomen est omen.

image Jack W. Reeves hat das schon vor bald 20 Jahren deutlich herausgearbeitet. Er sieht “Code as Design”. Seine unmissverständlichen Worte sind hier zu lesen, und hier gibt es noch ein Wiki zum Thema. Dennoch scheint die Fehlwahrnehmung unausrottbar. Denn nicht anders ist zu erklären, dass immer noch Meilensteine als erreichbar gelten.

Wer hätte jedoch von Meilensteinen bei kreativen Aufgaben je gehört? Die Entwicklung (!) des iPod hat sicherlich nicht in festgelegten Meilensteinen stattgefunden. Die Entwicklung (!) des Smart hat sicher nicht in Meilensteinen stattgefunden. Nichts anderes ist aber auch die Entwicklung (!) einer Warenwirtschaft oder eines Dokumentenverwaltungssystems.

Entwicklung ist ein kreativer Prozess, der zwar ein grundsätzliches Ziel hat - aber bei dem man erstens nicht weiß, wie ganz haargenau das Ziel eigentlich aussieht, und zweitens wann es denn erreicht sein wird. Das ist ein fundamentales Gesetz der Softwareentwicklung. Sicherlich muss ein Entwicklungsprozess Fortschritte machen; das Gefühl einer stetigen Annäherung an das ungenaue Ziel muss da sein. Aber ansonsten ist nichts fix. Ab einer gewissen Reife der Entwicklung kann quasi immer jemand sagen, “Es ist genug! Ich bin zufrieden. Lassen wir es dabei. Wir gehen in Produktion.”

Entwicklung lässt sich nicht in Meilensteinpakete aus Inhalt+Termin verpacken. Entwicklung lässt sich höchstens zeitlich takten, um sicherzustellen, dass sie fortschreitet. Das ist alles. Das ist die Natur der Sache der Softwareentwicklung.

image Und deshalb braucht Softwareentwicklung mehr Termindruck, quasi den ständig drohenden Termin. Interessanterweise droht der dann aber gar nicht mehr, wenn ihm keine Inhaltslast mehr anhaftet. Der ständig in 2-3 Wochen liegende nächste kleine Abgabetermin kann vielmehr als eine produktive Rhythmisierung der Arbeit empfunden werden.

Nur eine kleine Voraussetzung ist dafür nötig: Vertrauen. Vertrauen in die Teammitglieder, dass sie bis zum nächsten Termin ihr Bestes geben, soviel wie mit vernünftigem Zeitaufwand eben möglich ist auch inhaltlich umzusetzen. Aber das ist ein anderes Thema.

Mittwoch, 29. Juli 2009

Hierarchische partielle Klassen [endlich-clean.net]

Neulich habe ich einen Weg beschrieben, um Code, der nach dem Single Level of Abstraction (SLA) Prinzip strukturiert ist, übersichtlicher zu machen – ohne gleich andere CCD-Bausteine in Anschlag zu bringen. Mein Vorschlag: Partielle Klassen. Damit kann man die durch SLA entstehende Unübersichtlichkeit einer Klasse auflösen, indem man sie partiell macht und auf mehrere Quelldateien verteilt. Ich finde, das funktioniert wunderbar.

Nur einen Nachteil gibt es: Dadurch steigt die Zahl der Quelldateien in einem Projekt. Also habe ich Teile einer Klasse, die sich mit niedrigeren Abstraktionsniveaus beschäftigen, in einen Projektordner ausgelagert:

Das war allerdings nur eine Krücke. Schön fand ich diesen Weg nicht, weil er die Klassenteile voneinander trennt. Viel besser wäre es, wenn man sie zusammenhalten könnte, indem sie dem höchsten Abstraktionsniveau echt untergeordnet werden (hier die Datei TextFileGeneratorSLAv2.cs). Meine Versuche, sie wie die Code-behind-Klassenteile “darunter zu ziehen”, schlugen leider fehl. Also habe ich mich mit dem Projektordner beschieden.

Dank Blog-Leser Karl (der Nachname ist leider mit einem Chatfenster verschwunden) kann ich diese Krücke nun jedoch wegwerfen. Karl hat mich auf dieses Visual Studio Add-In aufmerksam gemacht: VsCommands von Mokosh. Einfach runterladen, MSI-Datei auspacken und installieren; läuft mit VS 2008. Dann ist dies ganz einfach möglich:

image

Jetzt sind die Teile der partiellen Klasse auf einem niedrigeren Abstraktionsniveau direkt denen untergeordnet, die sie verfeinern. Mit VsCommands können partielle Klassen also quasi hierarchisch gemacht werden.

Um eine Datei einer anderen unterzuordnen, die beiden in der Reihenfolge “oben” - “unten” selektieren und dann im Kontextmenü “Group Items” aufrufen:

image

Sehr cooles kleines Tool! Es eröffnet eine neue Dimension der Codeorganisation in Visual Studio Projekten. Danke, Karl, für den Hinweis.

Sonntag, 26. Juli 2009

Wahrhaft modulare Software nur mit konsequent weniger Abhängigkeiten

Warum hat die Softwareentwicklung das mit der Modularität bisher nicht so richtig hingekriegt? Mir scheint eine wesentliche Ursache die zu große Abhängigkeit zwischen Modulen. Wir müssen anders über Abhängigkeiten nachdenken als bisher.

image Darüber habe ich auch schonmal geschrieben, z.B. grad neulich hier. Doch mein Denken kreis immer wieder um das Problem. Deshalb hier nochmal eine etwas andere Perspektive, über die ich schon lange schreiben wollte. Anstoß hat jetzt ein Artikel in der Geo über synthetische Biologie gegeben. Dabei geht es darum, neue Lebewesen (Bakterien) zu produzieren, indem man ihre DNA aus Bausteinen (BioBricks) zusammensetzt, die jeder eine gewünschte Eigenschaft beschreiben. Ein Comic erklärt recht plakativ, wie das zu verstehen ist und funktioniert. Nein, das ist kein Science Fiction! Es wird schon getan.

Die Elektroniker können es also, die Bauingenieure, ebenso die Möbelbauer können es – und nun auch noch die Biologen. Alle können ihre Produkte aus Bausteinen, Bricks, Komponenten zusammensetzen.  Nur die Softwareentwicklung tut sich immer noch schwer. Warum?

Naja, ganz grundsätzlich können wir das natürlich auch. Softwarebausteine gibt es schon lange. Früher mussten sie statisch gelinkt werden, heute geht das dynamisch. DLLs oder Bibliotheksassemblies sind kein Hexenwerk. Allemal die User Control Industrie hat das in den letzten 18 Jahren seit VB 1.0 bewiesen.

Und dennoch: irgendwas fehlt, habe ich das Gefühl.

Was das ist, ist mir jetzt nochmal wieder bewusst geworden bei Lektüre des BioBricks-Artikels. Es fehlt uns der rechte Umgang mit Abhängigkeiten.

Ich stelle mal ein paar ganz unterschiedliche erfolgreiche Bauteile nebeneinander:

image imageimage 

image image

Warum sind Bauteile von Lego über Moto bis Steuerelement so erfolgreich? Warum funktioniert Wiederverwendung hier?

Ich glaube, erfolgskritisch ist die Entkopplung von ihrer Umwelt. Klingt nicht neu, dass zur Modularisierung irgendwie lose Kopplung gehört. Aber es hilft ja nichts: Wenn wir wider diesen Wissens es nicht tun, dann kommen wir nicht dort an, wo wir hin wollen.

All den erfolgreichen Bauteilen ist gemeinsam, dass sie keine internen funktionalen Abhängigkeiten haben. Was meine ich mit internen funktionalen Abhängigkeiten?

Ich unterscheide zwischen Abhängigkeiten. Natürlich ist die Funktionalität eines Motors davon abhängig, dass er mit Treibstoff versorgt wird. Ein UI Control muss mit Daten gefüttert werden. In einen Transistor muss Strom fließen.

Das sind für mich aber externe oder gar triviale Abhängigkeiten. Ich möchte sie mit dem Begriff Input belegen. Ohne Input erfüllt ein Baustein seine Zweck nicht. Doch dieser Input kommt von außen. Er fließt sozusagen in den Baustein hinein. An der Tonerkartusche wird von außen ein Zahnrad bewegt, ein Legostein wird auf eine Platte gesetzt, ein Treeview Control wird von außen mit Daten befüllt.

Solchem Input, d.h. äußerer Abhängigkeit, stelle ich interne Abhängigkeit gegenüber. Intern ist eine Abhängigkeit, wenn ein Baustein beim Einbau intern verändert werden muss. Dann muss er nämlich seine Umgebung kennen.

Ein unabhängiger, entkoppelter, echter und erfolgreicher Baustein sieht so aus:

image

Alle oben angeführten Bausteine entsprechen diesem Bild. Man steckt sie mit anderen zusammen zu einem größeren Ganzen – das dann wieder ein Baustein auf einer höheren Abstraktionsebene sein kann. Ein Motor als Baustein eines Autos besteht z.B. aus Zündkerzen, Zylindern, Rohren, Kolben usw. die alle für sich wiederum Bausteine in Bezug auf den Motor sind.

Dieses Zusammenstecken geschieht entweder direkt wie beim Motor oder indirekt über ein Medium wie eine Leiterplatte:

image

Indirekter Zusammenbau ist flexibler. Die Bauteile müssen nicht so gut aufeinander abgestimmt sein wie beim direkten Zusammenbau, wo wirklich eines in das andere passen muss.

Was aber tun wir, wenn wir unsere “Bausteine”, die Klassen und Assemblies unserer Software entwerfen? Wir koppeln sie ganz fundamental eng, weil wir sie intern (!) voneinander abhängig machen. Da machen auch Interfaces und Adapter keinen Unterschied. Das sieht dann so aus:

image

Unsere Bausteine sind allermeistens keine Black Boxes, sondern höchstens Grey Boxes. Denn wo wir etwas über die Interna wissen, da herrscht zumindest etwas Durchsicht durch die Bausteinwand. Und nichs anderes als Interna sind es, wenn wir wissen, dass ein Baustein zur Adressdublettenprüfung von einem Persistenzbaustein abhängig ist:

image

Früher war diese Abhängigkeit statisch und dynamisch in der Klasse, heute ist sie nur noch statisch, weil die konkrete Implementation des Persistenzbausteins dynamisch zur Laufzeit per Dependency Injection (DI) in die Dublettenprüfung “gespritzt” wird. Ein Baustein A ist nicht mehr von einem konkreten Baustein B abhängig, sondern nur noch von Bausteinen, die irgendwie wie B aussehen und funktionieren.

class EinfacheDublettenprüfung : IDublettenprüfung
{
    private IPersistenz db;

    public Einfache Dublettenprüfung(IPersistenz db) { this.db = db; }

    …
}

Das ist ein netter Fortschritt, der uns schonmal erlaubt, die Dublettenprüfung isoliert zu bauen und zu testen. Eine Komponentenwerkbank ist damit möglich, auf der ich immer nur an einem Baustein zur Zeit konzentriert arbeite.

Je länger ich darüber jedoch nachdenken, desto mehr komme ich zu der Überzeugung, dass das nur eine Symtomkur ist oder ein Übergangsstadium. Wir sollten bei DI nicht stehenbleiben. Früher waren statische Abhängigkeiten, jetzt sind noch dynamische Abhängigkeiten. Morgen sollten es keine (internen) Abhängigkeiten mehr sein.

image Ja, das meine ich so. Wenn ich die erfolgreichen Bausteine anschaue, dann lehren sie uns, dass sie nur praktikabel sind, weil sie eben keine internen Abhängigkeiten haben. Wir stecken nichts in sie hinein. Es gibt keine Dependency Injection in eine Tonerkartusche hinein. Alle Abhängigkeiten sind an der Oberfläche und heißen Input. Ihnen stehen gegenüber andere “Oberflächlichkeiten”, nämlich der Output – der wiederum Input für andere Bauteile ist.

Solange wir unsere Softwarebausteine noch so entwerfen, dass wir sie (dynamisch) immer wieder anpassen müssen, solange haben wir ein Kompositionsproblem. Denn solange ist der Zusammenbau, die Komposition von etwas größerem immer schwierig. Solange gibt es nämlich kein Halten was die Kompliziertheit der “Steckverbindungen” angeht. Eine interne Abhängigkeit ist – so möchte ich mal sagen – per se eine enge Kopplung.

Sobald wir aber dahinkommen, unsere Bausteine nur über Input- und Output-“Pins” zu verbinden, werden wir erleben, dass die Entkopplung steigt und die Wiederverwendbarkeit. Dann werden wir nämlich anfangen, wirklich hierarchische System mit Bausteinen auf unterschiedlichen Ebenen zu bauen, die insbesondere auf niederer Abstraktionsebene besser wiederverwendbar sind.

Und dass sie das auf höherer Ebene nicht sind, macht dann nichts. Das liegt erstens in der Natur der Sache – ein Motherboard kann in weniger Zusammenhängen gebraucht werden als ein Kondensator, der darauf steckt – und zweitens können wir auf einer neuen “Leiterplatte” viel einfacher eine neue Baugruppe zusammenstecken.

Für mich ich also eine fundamentale Separation of Concerns (SoC) überfällig, die noch klarer als bisher mit DI Containern zwischen Baustein und Leiterplatte unterscheidet. Voraussetzung dafür: die Bausteine werden intern unabhängig.

Was könnte das für die Dublettenprüfung bedeuten? Sie wüsste nichts mehr von ihrer konkreten Umgebung. Sie würde von keinem Persistenzbaustein mehr abhängen:

image

Stattdessen hätte sie weitere oder anders gestaltete Inputs und Outputs, die dann mit beliebigen Inputquellen und Outputsenken verbinden ließen. Die Dublettenprüfung hätte damit die volle Hoheit über ihre “Oberfläche”. Sie wäre von keiner weiteren Schnittstelle abhängig.

image

Die Veränderung des Programmier- bzw. Denkmodells gegenüber dem heutigen mit seinen internen Abhängigkeiten wird noch deutlicher, wenn ich das obigen Zusammenspiel zwischen Dublettenprüfung und Persistenz “auseinanderziehe”:

image

Das bedeutet vielleicht etwas mehr “Leiterplattenaufwand”, aber das finde ich nicht schlimm. Der Gewinn an Entkopplung und Wiederverwendbarkeit und Klarheit (SoC) wiegt ihn mehr als auf.

Allerdings: dafür ist ein Umdenken nötig. Das will geübt sein. Ich fange grad auch erst damit an. Aber ich bin guter Dinge, dass in dieser Richtung Erfolg zu finden ist. Mehr Evolvierbarkeit tut Not. Denn wenn die biologischen System evolvierbar sind und jetzt auch noch mit echten Bausteinen “programmiert” werden können, dann sollte das doch auch ein Rezept für die Softwareentwicklung sein.

image

Samstag, 25. Juli 2009

Partial Classes helfen dem Single Level of Abstraction Prinzip [endlich-clean.net]

Eines meiner Clean Code Developer (CCD) Lieblingsprinzipien ist Single Level of Abstration (SLA). Wenn man es befolgt, dann wird Code ganz einfach viel lesbarer.

Eigentlich. Denn auch wenn bei einer einzelnen Methode durch Fokussierung auf ein Abstraktionslevel die Übersichtlichkeit steigt, so kann sie für eine ganze Klasse sinken. Das habe ich immer wieder bemerkt und auf Abhilfe gesonnen. Nicht immer besteht die nämlich im Single Responsibility Principle (SRP), nach dem eine Klasse bei anhaltender Unübersichtlichkeit vielleicht besser in mehrere zerlegt werden sollte.

Aber jetzt habe ich eine Lösung im Rahmen der verfügbaren Sprach- und IDE-Mittel gefunden, glaube ich. Als Beispiel eine Methode, die eine Textdatei erzeugt und mit Zeilen aus Dummy-Worten füllt. Solche Textdateien brauchte ich neulich mal für Tests eines kleinen Flow-Frameworks.

V0 – Unrefaktorisiert

In der ersten Runde habe ich den Textfile-Generator natürlich einfach erstmal so runtergeschrieben. Nicht sehr clean, wie diese Abbildung zeigt:

image

Ich wollte möglichst schnell Funktionalität auf die Straße bekommen. Und da die Lösung einfach ist, war innere Qualität zunächst nicht so wichtig. Wer kennt das nicht? :-)

Die zentrale Methode Generate() sieht daher sehr typisch aus. Es steckt alles drin, was ein Mann zur Textfile-Generierung braucht ;-) Da werden solange Zeilen bestehend aus Worten erzeugt, bis eine festgesetzte Gesamtzeichenzahl (max_number_of_chars) erreicht ist. Und die Zeilen haben auch eine ähnliche Länge (MAX_CHARS_IN_LINE), die sich aus der Summe von mehreren Worten unterschiedlicher Länge (von MIN_WORD_LEN bis MAX_WORD_LEN) ergibt, die durch Leerzeichen getrennt sind. Alle soundsoviele Zeilen feuert die Routine einen Event, den der Aufrufer für eine Fortschrittsanzeige benutzen kann.

Das ist alles nicht schwer zu verstehen – aber wenn Sie in den Code blicken, müssen Sie schon einen Moment hinschauen, um die Algorithmusbestandteile zu identifizieren. Sie müssen sozusagen ihr geistiges Auge erst scharfstellen, bevor sie den Algorithmus wirklich sehen/verstehen.

Auch mit der obigen Beschreibung dauert das etwas, weil die Bestandteile/Verarbeitungsschritte nicht klar getrennt sind im Quelltext. Ein wenig helfen die Einrückungen der Schleifen. Doch letztlich zeigen sie nur an, dass etwas zusammengehört, aber nicht was es ist.

V1 – Refaktorisieren mit SLA

Viel leichter haben Sie es, wenn die Methode Generate() dem SLA Prinzip folgt:

image

Ah, jetzt sehen Sie klar! Die Dateierzeugung besteht aus drei Schritten: Initialisierung einer Statistik, der eigentlichen Textfile-Generierung und dann einem Abschlussbericht über die Statistik. Diese Methode liegen alle auf demselben Abstraktionsniveau – auch wenn unterschiedlichen Verantwortlichkeiten angehören (Statistik, Textfile-Generierung).

Wenn Sie dann Details interessieren, dann schauen Sie sich die Methoden genauer an, z.B. GenerateFile():

image

Ah, jetzt sehen Sie auch sofort klar! Geschrieben wird über einen StreamWriter() solange die Datei noch nicht groß genug ist. Eine Zeile nach der anderen wird erzeugt und zur Statistik hinzugefügt.

Und wie wird eine Zeile erzeugt? Drill-down in die Methode GenerateLine() hinein:

image

Ah, alles klar! Eine Zeile wird aus Worten zusammengebaut und dann weggeschrieben.

Und so weiter und so fort… SLA bietet Ihnen auf jeder Ebene einen schnellen Überblick des Zusammenspiels von Bestandteilen (ungefähr) gleicher Abstraktheit. Sie müssen sich nur soweit mit Details konfrontieren, wie für Ihr Informationsbedürfnis in einer Situation nötig ist.

SLA-Problem Unübersichtlichkeit im Großen

Wo Licht ist, da ist auch Schatten. So auch bei SLA. Die Übersichtlichkeit im Kleinen, in der Methode, führt relativ schnell zu einer Unübersichtlichkeit im Großen, in der Klasse. Denn aus einer überschaubaren Klasse mit einer Methode

image

ist eine geworden, die viele Methoden auf unterschiedlichem Abstraktionsniveau hat:

image

Nun ist es einfach zu verstehen, was Generate() tut – aber die Klasse zu betrachten verwirrt eher, auch wenn die Sichtbarkeitsangaben helfen, einen Einstieg in die Interpretation zu finden.

Schlimmer wird es mit der Unübersichtlichkeit, wenn Sie in den Quellcode schauen:

image

Von Abstraktionsebenen keine Spur mehr. Methoden weiter oben mögen auf einem höheren Abstraktionslevel liegen als solche weiter unten. Aber klare Grenzen sind nicht zu erkennen. Ich habe mit Einrückungen experimentiert, aber die bleiben nicht immer erhalten.

Was also tun? Muss ich denn für mehr Übersichtlichkeit gleich SRP in Anschlag bringen und neue Klassen definieren, z.B. eine für die Statistik, eine zum Erzeugen einer neuen Zeile usw. Das fände ich misslich. Denn eine weitere Klasse bedeutet immer mehr Aufwand als eine weitere Methode.

V2 – Partial Classes für mehr Übersicht

Jetzt habe ich allerdings eine Idee gehabt, die als Zwischenstufe helfen mag, die Übersichtlichkeit bei Anwendung von SLA zu erhalten, ohne auf SRP übergehen zu müssen. Ich zerlege meine methodenreichen SLA-Klassen in Partial Classes, die sich jeweils auf ein Abstraktionslevel konzentrieren.

Der “oberste” Klassenbestandteil für das Beispiel sieht dann z.B. so aus:

image

Er zeigt die wesentliche Methode auf höchstem Abstraktionsniveau. Wenn Sie dann mehr wissen wollen, schauen Sie sich den “darunterliegenden” Klassenbestandteil an:

image

Hier finden Sie nun zwei Abstraktionsniveaus vereint. GenerateFile() liegt höher als GenerateLine(). Doch da der Klassenbestandteil insgesamt nicht zu lang ist (1 Bildschirmseite) und die drei Methoden eng zueinander gehören, habe ich keine weitere Aufteilung für nötig befunden.

Insgesamt besteht der TextFileGenerator nun aus 4 Klassenbestandteilen, von denen drei die Textfile-Generierung schrittweise verfeinern und einer die auf verschiedenen Ebenen genutzten Statistikfunktionen zusammenfasst. Im Solution Explorer bekommen Sie also auch schnell einen Überblick über eine Klasse:

image

Ein weiterer Vorteil: Indem ich mit dem simplen SLA anfange, meinen Code zu refaktorisieren, und mit Partial Classes ein unaufwändiges Mittel nutzen kann, um die Übersichtlichkeit zu erhalten, merke ich ganz natürlich und konkret, wann sich das SRP lohnt. In diesem Szenario kristallisiert sich z.B. die Statistik als Kandidat für eine Auslagerung in eine eigene Klasse heraus. Aber das ist ein anderes Thema…

Erstmal bin ich zufrieden, in einfacher Weise Übersichtlichkeit in Klassen mit Methoden auf unterschiedlichem Abstraktionsniveau herstellen zu können. Partial Classes to the rescue. SLA kann also eines meiner Lieblingsprinzipien bleiben.

Donnerstag, 23. Juli 2009

Romantik rettet die Softwareentwicklung? [OOP 2009]

image

Jetzt stehen mir meine wenigen Haare doch zu Berge. Jeff Atwood hat das geschafft. Aus dem witzigen Titel “Coding Horror” seines Blog – das ich eigentlich sehr schätze – ist für mich bitterer Ernst geworden. Sein Beitrag “Software Engineering: Dead?” lässt mir kalte Horror-Schauer den Rücken herunterkriechen. Wie das sein kann? Er meint das Fragezeichen in seinem Beitragstitel ernst:

“what we do is craftsmanship, not engineering. And I can say this proudly, unashamedly, with nary a shred of self-doubt.” (Atwood)

Software Engineering ist tot, es lebe die Handwerkskunst! Das ist der Schlachtruf eines Bloggers, der von Zehntausenden gelesen wird. Mit Verlaub, ich kann es nicht fassen. Schon lang er das Handwerkerherz in seiner Brust schlagen hören, aber mochte sich nicht so platt outen. Nun jedoch, anlässlich eines IEEE-Beitrags von Tom DeMarco, da fühlt er sich erleichtert und bestätigt in seinem Fühlen und lässt es raus. Und nicht nur er mag sich erleichtert fühlen, sondern eine ganze Bewegung, die des Softwareware Craftsmanship. Tom DeMarco erteilt den Software Craftsmen, den Handwerkern für die Softwareentwicklung, seine Absolution für ihre Bewegung gegen das Software Engineering:

“I’m gradually coming to the conclusion that software engineering is an idea whose time has come and gone.” (DeMarco)

Aus, Ende, tot, bye bye, Software Engineering. Sagt Tom DeMarco. Anscheinend.

Dass Jeff davon jetzt erst so freudig erregt wird, liegt am IEEE-Artikel, der der Selbstverortung von Tom DeMarco kaum ein Jahr hinterherhinkt. Denn spräche Jeff Deutsch, dann hätte er schon im Objektspektrum 6/2008 viele für ihn neue Aussagen DeMarcos schon lesen können.

Nur weil DeMarco dem Software Engineering neuerdings skeptisch gegenübersteht, sehe ich jedoch nicht, dass er das Software Craftsmanship Manifest mit Freuden unterschreibt. Er schreibt ja auch deutlich:

“I still believe it makes excellent sense to engineer software.” (DeMarco)

Seine Kritik gilt also nicht ingenieursmäßigem Denken im Allgemeinen, sondern einer konkreten Ausprägung.

“But that isn’t exactly what software engineering has come to mean. The term encompasses a specific set of disciplines including defined process, inspections and walkthroughs, requirements engineering, traceability matrices, metrics, precise quality control, rigorous planning and tracking, and coding and documentation standards. All these strive for consistency of practice and predictability.” (DeMarco)

DeMacro stört sich vor allem an der Kontrollillusion des Software Engineering, die sich in einem Berg an Messinstrumenten und Planungsvorgaben ausdrückt. Der begräbt die simple Wahrheit, die er nun erkannt hat:

“Software development is and always will be somewhat experimental.” (DeMarco)

Bravo, würd ich sagen. Hört, hört! Das (!) scheint mir – auch weil es am Ende des Beitrags steht – die wesentliche Einsicht, aus der es Konsequenzen zu ziehen gälte. Bedeutet das jedoch den Tod des Software Engineering?

Auch ich bin kein großer Freund “mächtiger Methoden” und “allumfassender Ansätze” oder “zwangsweiser Normierung”. Dennoch halte ich es für ein ganz falsches Signal für eine Branche, die zu einem Großteil auf Autodidaktentum gegründet ist, das Bisschen Ingenieurskunst, mit dem sie inzwischen aufgeladen wurde, so zu torpedieren. Der Mann, der Bärentango geschrieben hat, würde ihr damit einen Bärendienst erweisen.

Definiere Software Engineer

Was ist denn eigentlich das Problem mit dem Bild des Software-Ingenieurs, des Softwaretechnikers, des Software Engineer? Hier die grundlegenden Definitionen aus Wikipedia für Ingenieur bzw. Engineer:

“Der Begriff Ingenieur […] umfasst im herkömmlichen deutschen Sprachgebrauch im weiteren Sinne ein Berufsbild, welches durch die systematische Aneignung, Beherrschung und Anwendung von wissenschaftlich-theoretisch fundierten und empirisch gesicherten technischen Erkenntnissen und Methoden gekennzeichnet ist. […] Ingenieure sollten sich durch analytisches Denken, gute theoretische und anwendungsorientierte Fachkenntnisse, verbunden mit praxisorientierten und auf termingerechte Umsetzung bedachte Vorgehensweisen auszeichnen; Grundlage für erfolgreiches Arbeiten ist ein fundiertes Fachwissen und eine gute technische Allgemeinbildung. Die Hauptaufgabe des Ingenieurs stellt der Entwurf von Systemen dar. Dabei handelt es sich um einen komplexen Prozess, bei dem sowohl analytische Fähigkeiten als auch Kreativität eine große Rolle spielen. Die Entwurfstätigkeit ist eine schöpferische Tätigkeit, bei der der Ingenieur sein Wissen einsetzt, einem System eine bestimmte Funktion, Form oder Materialeigenschaft zu geben. Ein wichtiger Faktor bei der Entwicklung eines Systems ist aus Wettbewerbsgründen die Zeit. So muss sich der Ingenieur in der Praxis häufig mit einer nicht optimalen Lösung zufrieden geben, die aber dennoch als gut einstufbar ist.”, http://de.wikipedia.org/wiki/Ingenieur

“Engineers are concerned with developing economical and safe solutions to practical problems, by applying mathematics and scientific knowledge while considering technical constraints. The term is derived from the Latin root "ingenium," meaning "cleverness". The industrial revolution and continuing technological developments of the last few centuries have changed the connotation of the term slightly, resulting in the perception of engineers as applied scientists. The work of engineers is the link between perceived needs of society and commercial applications”, http://en.wikipedia.org/wiki/Engineer

Wo ist das sch… Problem mit diesen Definitionen? Warum sollten wir Softwareentwickler Anstoß daran nehmen? Warum sollten wir nicht danach streben, so zu arbeiten wie die hier beschriebenen Ingenieure?

Ja, gut, Software ist komplexer als ein Auto. Die Anforderungen für eine Software lassen sich schwieriger erheben als für eine Brücke. Und? So what? Sollten wir deshalb verzichten auf “systematische Aneignung, Beherrschung und Anwendung von wissenschaftlich-theoretisch fundierten und empirisch gesicherten technischen Erkenntnissen und Methoden”? Das kann weder Jeffs noch DeMarcos Ernst sein.

Gut, wir haben immer wieder Probleme mit dem Vorgehen bei der Softwareentwicklung und der Kommunikation mit Kunden und anderen Stakeholdern. Und? So what? Sollten wir deshalb nicht immer noch als Ziel haben “economical and safe solutions to practical problems, by applying mathematics and scientific knowledge while considering technical constraints”? Das können beide doch auch nicht ernsthaft meinen.

imageDie Softwaretechnik – deutscher Begriff für Softwareengineering – mag etwas aufgebläht sein und hier und da noch zu sehr versuchen, ihren Geschwistern einer mechanischen und elektronischen Welt nachzueifern. Sie mag noch zu sehr an einer Kontrollillusion festhalten. Aber muss sie deshalb für tot erklärt werden? Komplett tot? Da kann ich nicht anders als Quatsch! auszurufen. Da schütten Leute grad das Kind mit dem Bade aus. Und ich frage mich, was sie dazu treibt. Wie groß müssen Unsicherheit und Frust für solch eine pauschale Proklamation sein?

Sicherlich täte die Softwaretechnik gut daran, etwas aufzutauen und durchlässiger zu werden. Sie könnte von einem akademischen Dünkel entstaubt werden. Das wäre eine Integration eines Traditionsbegriffs, der nicht so falsch ist, wie Jeff und DeMarco Glauben machen wollen. Stattdessen: Ausgrenzung, Verdrängung, Amputation. Au weia!

Definiere Software Craftsmanship

Und was bietet Jeff als Alternative? Ein Hurra für das Handwerkertum. Webster´s definiert craftsman so:

“An artificer; a mechanic; one skilled in a manual occupation.”, http://1828.sorabji.com/1828/words/c/craftsman.html

Oder hier eine andere Definition:

“1. a worker in a skilled trade; artisan
2. any highly skilled, painstaking, technically dexterous worker, specif. in the manual arts”
, http://www.yourdictionary.com/craftsman

Da steckt dann auch noch der artisan, der Kunsthandwerker drin:

“Der Begriff Kunsthandwerk steht für das Handwerk für dessen Ausübung künstlerische Fähigkeiten maßgebend und erforderlich sind. Die Produkte des Kunsthandwerks sind in eigenständiger, handwerklicher Arbeit und nach eigenen Entwürfen gefertigte Unikate (Kleinkunst/Autorenprodukte').”, http://de.wikipedia.org/wiki/Kunsthandwerk

Ist der Software Kunsthandwerker oder der Software Handwerker (von mir aus auch der Software Facharbeiter) nun eine soviel passendere Bezeichnung für das, wonach wir streben? Ich zumindest fühle mich nicht besser beschrieben als jmd, der “skilled in a manual occupation” ist. Und ich glaube auch nicht, dass ein Entwickler, der an einer Warenwirtschaft oder einer Hochregallagersoftware oder an einer Triebwerkssteuerung sitzt, danach streben sollte, “künstlerische Fähigkeitheit” auszubilden.

Zugegeben, wir produzieren meist Unikate. Und? So what? Ein Brückenbauingenieur tut das auch. Sind wir deshalb gleich (Kunst)Handwerker? Und auch zugegeben, wir vertun uns mit unseren Schätzungen und sollten daher ganz anders damit umgehen, agil zum Beispiel. Iterationen sind für ein Projekt wie den Potsdamer Platz eher nicht das akzeptierte Vorgehen und auch nicht nötig, wie wir sehen, wenn wir ihn besuchen. Für eine Anzeigenlayoutsoftware oder eine Gemeindeverwaltungsanwendung aber schon. Und? So what? Deshalb sollten wir uns unser Wissen nicht systematisch aneignen und nach wissenschaftlicher Erkennnis über unser Metier streben?

Termingerecht muss für uns anders definiert werden als für einen Maschinenbauer. Ok. Aber deshalb gilt doch auch für uns:

“Die Hauptaufgabe des [Softwareentwicklers] stellt der Entwurf von Systemen dar. Dabei handelt es sich um einen komplexen Prozess, bei dem sowohl analytische Fähigkeiten als auch Kreativität eine große Rolle spielen. Die Entwurfstätigkeit ist eine schöpferische Tätigkeit, bei der der Ingenieur sein Wissen einsetzt, einem System eine bestimmte Funktion, Form […] zu geben.”

Wenn die Software Craftsmanship Bewegung das nicht unterschreiben will, dann fällt mir nichts mehr ein. Wenn sie sich so gegen ein zugegeben überladenes Bild vom Software Engineer wendet, dass sie dessen Fundament - die Systematik, das Analytische, die Kreativität, die Gründung in der Wissenschaftlichkeit – aus dem Blick verliert, dann sehe ich nicht, wie sie der Branche einen Dienst erweist.

Damit behaupte ich nicht, dass die Software Craftsmen in allem falsch liegen. Keineswegs! Aber ihre plakative Botschaft, die seitenweise Zustimmung zu Jeffs Posting generiert, die halte ich für ab-so-lut kontraproduktiv.

image Craftsman, d.h. Handwerker oder gar Kunsthandwerker, beschwört eine romantische Vorstellung herauf von Überschaubarkeit, Gemütlichkeit, Nähe, Ernsthaftigkeit, Stolz… dass es nur so eine Freude ist. Da riecht man förmlich den Holzleim und hört den Hobel; von Ferne tönt der Schlag des Schmieds im Verein mit dem Generalbass seines Blasebalgs; und da ein munteres Lied auf den Lippen des Schneidermeisters wie er zusammen auf dem Tisch mit seinen Lehrlingen sitzt.

Software Craftsmen sitzen natürlich nicht auf Tischen und schitzen auch nicht so wie Schmiede. Dafür wenden sie sich im Pair Programming väterlich ihren Adepten zu, lernen am liebsten auf der Walz, stehen über den technologischen Moden, bringen sich voll mit ihrer Kreativität ein und geben sich ganz der Produktqualität hin. Nicht die Tranfunzel erhellt ihre Hightech Büros, sondern die Lavalampe im Grün der korrekten Tests vom letzten automatischen Build. Zum Mittag treffen sie sich dann alle im Refektorium ihres Entwicklerklosters und lauschen der Lesung aus einer ihrer Bibeln, zum Beispiel “Clean Code” oder “Software Craftsmanship”. Anschließend zurück an die Softwarewerkbank oder in die Codeschreibstube.

Mir wird schon ganz warm ums Herz. Ich will auch…

Nein, natürlich nicht. So schön die Vorstellung ist, so verständlich ich den Wunsch nach einem anderem Leitbild als dem gescheiterten finde, das Handwerkertum halte ich für ab-so-lut ungeeignet als Vision für die Zukunft der Branche. Das ist etwas für Romantiker und überlastete Gekränkte. Kein Wunder auch in einer Branche, die am Burnout vorbeischrammt.

Fazit

Mir ist eigentlich egal, was genau “Software Engineering” bedeutet, wie es von den allgemeinen Definitionen von Ingenieur oder Engineer abweicht. Wenn ich lese, was Ingenieur bzw. Engineer im Allgemeinen tun, dann meine ich, dass wir weiterhin oder gar vermehrt danach streben sollten, so wie sie zu werden.

Die Softwareentwicklung muss noch einiges lernen. Ohne Frage. Doch dass sie mehr und besser lernt, wenn wir sie den Kunden oder nachwachsenden Generationen als Handwerk verkaufen, das kann ich nicht glauben. Software Craftsmanship, wenn es denn seinem Namen gerecht werden will, ist eine romantische Vorstellung; ich würde sogar fast schon sagen, Software Craftsmanship ist eine Regression eines Teil des kollektiven Psyche unserer Branche.

Alskönnten Ingenieure nicht mit Unwägbarkeiten umgehen, als würden sie nicht experimentieren, als würden sie nicht Stolz für ihre Arbeit empfinden, als strebten sie nicht nach Qualität, ja, und als seien sie nicht kreativ. Wasfürein Humbug, wenn Software Craftsmanship all das den Ingenieuren abspricht. Ein Elektrotechnik Ingenieur mag anders vorgehen als ein Softwareentwickler. Aber das tut auch ein Maschinenbauer oder Motorkonstrukteuer im Vergleich zu einem Elektrotechniker. Software Engineers müssen nicht in allem mit anderen Ingenieursdisziplinen gleichziehen.

Auch müssen nicht alle Softwareentwickler Software Engineers werden. Es bleibt Raum für Software Handwerker wie es Raum für Heizungsbauer und Klempner und Tischler gibt. Doch wir alle wissen, was wir einem Maurer zutrauen im Vergleich zu einem Bauingenieur. Und wir alle wissen, was wir dem korbflechtenden Kunsthandwerker zutrauen im Vergleich zu einem Webmaschineningenieur. Kunsthandwerk ist eine schöne Sache – im wahrsten Sinn des Wortes. Wenn die Schnitzerei aus schlechtem Holz ist, die getöpferte Schale nicht spülmaschinenfest… dann ist das nervig, aber nicht wirklich schlimm. Kunsthandwerkliche Produkte treiben uns selten in den Runin, wenn ihre Qualität nicht stimmt.

Wer möchte aber Hunderttausende Euro einem Kunsthandwerker anvertrauen? Oder wer lässt einen Eiffelturm von Handwerkern planen? Hand hoch!

Nein, nein, so einfach sollte es sich Software Craftsmanship oder zumindest Jeff Atwood nicht machen. Die Softwarewelt wird nicht durch mehr Romantik genesen. “Mehr Licht!” wie Goethe schon an seinem Ende sagte und vielleicht doch damit die Aufklärung meinte, das ist eher die Lösung.

Zu schade also, dass ansonsten sehr hell Köpfe wie Jeff Atwood und Tom DeMarco (der allerdings eher unfreiwillig) solchem Rückfall in “voraufklärerische Zeit” der Softwareentwicklung Vorschub leisten. Auch Robert C. Martin ist da kräftig tätig – was Clean Code Developer nicht davon abhält, seine Verdienste um “Clean Code” anzuerkennen.

Um meine Haare jetzt wieder zu glätten, lese ich am besten erstmal ein Werk über softwaretechnische Ingenieurskunst wie z.B. “das Drachenbuch” (gibt es auch in neuerer Auflage) oder dies hier.

Montag, 20. Juli 2009

Klage eines ungebackenen Entwicklers

Mehr Ausbildung zum Softwareentwickler in den Betrieben, das scheint mir ein wichtiger und gangbarer Weg für die Zukunft der Branche, die vom IT-Fachkräftemangel gebeutelt ist. So habe ich es in der dotnetpro 8/2009 in meiner Sandbox-Kolumne geschrieben. Daraufhin schreibt mir nun ein junger Fachinformatiker, wie sehr ich (für ihn) mit diesem Artikel und anderen zum Thema Ausbildung den Finger in die Wunde gelegt habe:

Hallo Herr Westphal,
mit großem Interesse habe ich Ihren Artikel "Entwickler selbst backen"
in der aktuelle dotnetpro gelesen.
Ich hoffe sehr das sich einige Firmen den Artikel zu Herzen nehmen und
auch den nicht Vollprofis eine Chance geben.
Ich selbst bin gelernter Fachinformatiker Fachrichtung
Anwendungsentwicklung und habe meine Ausbildung im Februar 
[des Jahres 2009] abgeschlossen. Die defizite der Ausbildung sind mir also bestens
bekannt, zum einen wurde bei uns in der Berufsschule
keinerlei Vorgehensweiße unterrichtet, weder Analyse noch Design
geschweigedenn irgendwelche Modelierungen mit UML
.
Meine zweieinhalb Jahre Ausbildung habe ich in der Schule mit
Struktogrammen und dem Borland Builder 6 mit C++ verbracht, wo wir
es nach zwei Jahren tatsächlich geschafft haben eigene Klassen und
Methoden
zu erstellen (Fehlerbehandlung, Debugging usw. waren alles
Fremdwörter
).

Die Berufsschule hate nicht die Fachkräfte die nötig wären und auch
garnicht die Zeit, der überwiegende Teil wird in Systemintegration
ausgebildet und hat keinerlei Interesse an der Programmierung allgemein,
somit sind die Themen auch nicht gut zu vermitteln.
Somit bleibt noch der Betrieb, ich hatte das Pech das ich einem Betrieb
gelandet bin der nur eine EDV Abteilung besitzt und keine
Dienstleistungen in dem Bereich erbringt. Schulungen oder Einweisungen in richtige
Vorgehensweißen oder z.B. die Verwendung von Visual Studio gab es hier
nicht.

Wer sich nicht selbst weitergebildet hat blieb auf einem sehr niedrigen
Level. Ich bin sehr interessiert daran es richtig zu lernen, ich
arbeite sehr gerne mit
ASP.NET und mit dem CompactFramework und möchte
ein möglichst hohes Level erreichen, leider scheitere ich an den Firmen
die scheinbar durchweg kein Interesse haben Ihre Mitarbeiter zu guten
Entwicklern auszubilden
, und in Eigenarbeit mangelt es mir an Zeit und
manchmal auch an der Motivation (Die Firma dankt es mir eh nicht).

Deshalb habe ich auch kurz nach meinem Ausbildungsende die Firma
gewechselt, ich habe eine Firma gesucht die mir auch Weiterbildungen
anbietet, die verspricht das dass ausprobieren von neuen Technologien
Pflicht ist. Ich bin für diese Firma 200km umgezogen, und nehme weniger
Gehalt in Kauf. Leider musste ich feststellen das es hier keine
Weiterbildung gibt bzw. ich kann mir einmal im Monat mal eine Stunde zu
einem Thema, welches ich im übrigen in meiner täglichen Arbeit garnicht
einsetzen kann/darf, was anhören
. Weitere Themen stehen dann wieder nur
Senior Software Developern zur Verfügung, welcher ich hier in dieser
Firma erst nach 5 Jahren sein werde. Das ausprobieren der neuen
Technologien findet hier garnicht statt
, das Projekt läuft immernoch
auf dem 2.0 Framework und wie was umgesetzt wird, wird vorgegeben. Ich
habe mich im großen und ganzen selbst von Anwendungsentwickler zum
einfachen Programmierer heruntergestuft der den ganzen Tag irgendwelche
Support anfragen bearbeitet. Nun bin ich wieder auf der Suche nach
einer Firma die eventuell Interesse daran hat
sich selbst einen guten Mitarbeiter auszubilden. Ich bin nicht dumm und
eigentlich hochmotiviert ein "guter" .NET Entwickler zu werden, leider
muss ich wohl erst zwei bis fünf Jahre "Berufserfahrung" (meines
Erachtens ist das keine Berufserfahrung wenn ich Tag ein Tag aus
dasselbe mache ohne mich dabei weiterzubilden) sammlen bevor ich eine
Firma finde dem gerecht wird was ich suche.
Ich möchte Ihnen auf diesem Weg für diesen Artikel danken! Und ich hoffe
sehr das es auch in meiner Region Firmen gibt die diesen lesen und ihn
sich zu Herzen nehmen und vieleicht finde ich dann doch eine Firma bei
der mir meine Arbeit, die eigentlich auch mein Hobby ist, mir wieder
Spass macht. Ich hoffe das Konzept School of .NET wird in die Tat
umgesetzt, ich wäre sofort dabei das zu unterstützen!
[…]

Er würde so gern gebacken werden, aber niemand schiebt ihn wirklich in den Ofen. Die Firmen, bei denen die junge und zumindest motivierte Entwickler arbeitet, sehen ihn als fertig an, nur weil er eine Fachinformatiker-Ausbildung durchlaufen hat. Die aber ist, wie sich bei allem Verständnis für den Willen zu einer “plattformneutralen Ausbildung” zeigt, von nur zweifelhaftem Nutzen. State-of-the-art wird dort nicht vermittelt – was eher weniger an der Ausbildungssprache Borland C++ liegt.

Wie kann es sein, dass die Betriebe das nicht merken? 1. Es findet keine Qualitätskontrolle des in der Ausbildung Vermittelten bei seinem Lehrherren und auch später nicht statt. 2. Allemal als Auszubildender ist er wahrscheinlich so billig gewesen, dass niemand aufgefallen ist, dass er mit dem Ausbildungswissen nichts anstellen konnte. Seine Unproduktivität fiel nicht ins Gewicht. Denn unproduktiv muss er gewesen sein bei dem, was auf dem Lehrplan gestanden hat. Wieviel mehr hätte er schaffen können, wenn die Ausbildung zum einen der Plattform seines Lehrbetriebs entsprochen hätte und zweitens auf der höhe der Zeit stattgefunden hätte? Er hätte seinem Lehrbetrieb geradezu neue Impulse geben können. Nicht auszudenken wäre es doch, wenn ein Auszubildender aus dem Blockunterricht käme und z.B. sagte: “Wow, wir können viel korrekter arbeiten, wenn wir Unit Tests einsetzen.”

Stattdessen setzt man den motivierten Entwickler in eine Ecke und lässt ihn mokeln. Ich spekuliere mal, denn geschrieben hat er davon nichts: Seine Arbeit wurde nicht mit ihm nach Abgabe durchgesprochen. Nicht nur hat man ihm keine weitere Ausbildung/Fortbildung in den Betrieben zugestanden, auch fand sicherlich keine “Förderung im Kleinen” durch Kollegen statt. Es würde ja helfen, wenn sich jemand mit einem Junior-Programme in der Woche 2-3 Mal für ein Stündchen zusammensetzte, um seinen Code echt durchzugehen. Oder mal mit ihm Pair Programming machen. Davon jedoch keine Spur.

Betriebe nehmen klaglos, fraglos einfach an, was da aus der Ausbildung kommt. Und sie haben keinen eigenen Anspruch, wie es dann weitergeht. Außer einem Anspruch: irgendwie muss die Arbeit geschafft werden.

Was sie nicht sehen: vor 150 Jahren wurde die Arbeit auf den Feldern auch geschafft. Dafür war ein Heer von Landarbeitern zuständig. Doch heute schaffen wir es mit einem kleinen Bruchteil an Menschen, eine viel größere Zahl zu ernähren. Die Produktivität in der Landwirtschaft ist explodiert.

Dass das auch in der Softwareentwicklung mit etwas besserer Ausbildung, etwas besserer Kommunikation, etwas mehr Blick auf innere Qualität (Clean Code Developer lässt grüßen) und etwas besseren Prozessen auch der Fall sein könnte… das sehen viele nicht. Und das bedeutet, sie krebsen so dahin. Und das bedeutet, es wird für sie nicht besser, sondern eigentlich immer nur schlimmer. Darüber geht dann die Motivation der Leute verloren.

Der junge Entwickler hat zum Glück die Konsequenz gezogen und versucht, in eine bessere Firma zu kommen. Leider ist er nur vom Regen in die Traufe geraten. Doch er lässt nicht locker. Er will wieder einen Sprung wagen. Er glaubt noch, dass es Firmen mit mehr Interesse an ihren Entwicklern gibt. Ja, die gibt es. Ich wünsche ihm viel Erfolg bei der Suche!

Und den anderen Firmen wünsche ich einen Moment der Ruhe, des Abstands, um darüber nachzudenken, wie sie intern die Ausbildungs- und Arbeitssituation verbessern könnten. Es gilt ungehobene Produktivitätsschätze zu heben. Von höherer Motivation und Zufriedenheit mal ganz zu schweigen. Also, auf zum Entwicklerbacken!

Sonntag, 19. Juli 2009

Verständnisvorteil für Flows – Funktionale Programmierung lässt grüßen

Flows sind verständlicher als Schachtelungen, glaube ich inzwischen. Und zwar nicht nur asynchrone Flows, sondern auch synchrone.

Hier hatte ich ja schonmal über Flows sinniert. Inzwischen habe ich dann auch eine kleine Bibliothek für asynchrone Flows gebaut, die CCR Flows (http://ccrflows.codeplex.com). Doch neulich hat ein Engagierter Entwickler mit darauf hingewiesen, dass solche asynchronen Flows womöglich bei kleineren Aufgaben einen Performanceoverhead durch die Asynchronizität erzeugen, der gegen sie spricht. Da war ich erstmal ein wenig geknickt. Ja, das stimmt wohl. Es ist wie bei der Verteilung von Code. Verteilung erzeugt auch einen Overhead bei der Kommunikation gegenüber dem lokalen Stack. Doch ab einer gewissen Aufgabengröße überwiegen die Vorteile von Verteilung und Asynchronizität natürlich auch. Bis dahin ist synchrone lokale Programmierung vorzuziehen. Klar.

Machen deshalb aber Flows ebenfalls bis dahin keinen Sinn? Dazu habe ich ein wenig experimentiert. Hier mein Szenario:

Eine Textdatei mit Zeilen bestehend aus Worten ist umzuformatieren. Es soll eine neue Textdatei mit anderer Zeichenzahl pro Zeile erzeugt werden. Die Worte müssen also neu umgebrochen werden.

Übliche synchrone Lösung

Wenn ich für dieses Szenario mal eine Lösung einfach so hinschreibe, dann sieht sie z.B. so aus:

using(var sr = new StreamReader("quelldatei.txt", Encoding.Default))

using(var sw = new StreamWriter("zieldatei.txt", false, Encoding.Default))

{

    LineBuilder lb = new LineBuilder(sw, 40);

 

    while(!sr.EndOfStream)

    {

        string line = sr.ReadLine();

 

        foreach (var word in line.Split(' '))

            lb.Add(word);

    }

    lb.Emit();

}

Das ist nicht super clean, aber ja auch nicht so umfangreich. Quelldatei zeilenweise lesen, Zeilen in Worte splitten, neue Zeilen aus den Worten zusammebauen und in Zieldatei schreiben.

Für den Zusammenbau und das Wegschreiben lohnt eine Hilfsklasse, finde ich. Über die Worte hinweg muss etwas Zustand gehalten werden (die neue Zeile). Das würde mir den obigen Code zu sehr aufblähen. Die Aufgabe scheint mit eine genügend große Verantwortlichkeit, um eine eigene Klasse dafür zu rechtfertigen:

class LineBuilder

{

    private StreamWriter sw;

    private int max_line_length;

 

    private StringBuilder line = new StringBuilder();

 

 

    public LineBuilder(StreamWriter sw, int max_line_length)

    {

        this.sw = sw;

        this.max_line_length = max_line_length;

    }

 

 

    public void Add(string word)

    {

        if (line.Length + word.Length + 1 > max_line_length)

            Emit();

 

        if (line.Length > 0) line.Append(" ");

        line.Append(word);

    }

 

 

    public void Emit()

    {

        this.sw.WriteLine(this.line);

        this.line = new StringBuilder();

    }

}

Mit 60-70 Zeilen habe ich also eine Lösung für das Problem. Die sieht “normal” aus, finde ich. Ist sie aber deshalb auch verständlich? Hm… joa, so “normal verständlich”, oder?

Flow-basierte synchrone Lösung

Jetzt dagegen eine Lösung auf der Basis von synchronen Flows:

var flow = new SyncFlow<string, string>(SplitFileIntoLines)

    .Do<string>(SplitLineIntoWords)

    .Do<string>(new LineBuilder(40).AddWord)

    .Do(new FileAssembler("zieldatei.txt").WriteLine);

 

flow.Execute("quelldatei.txt");

Wie ist das? Ist finde es viel besser verständlich. Die Verantwortlichkeiten sind deutlicher getrennt. Das Abstraktionsniveau ist einheitlicher. Klar, das wäre irgendwie auch “normal” gegangen, aber auf dem “normalen” Weg muss ich dafür mehr Selbstdisziplin aufbringen, finde ich.

Die Flow-basierte Lösung hingegen zwingt mich dazu, die einzelnen Schritte zu verpacken und damit zu “entwirren”. In der synchronen Lösung sind Lesen und Splitting und Zeilenerzeugung miteinander stark verwoben. Hier hingegen stehen sie sauber nacheinander gelistet als “Stages” in einem Flow. Mit einer anderen Sprache hätte ich vielleicht auch so schreiben können:

“quelldatei.txt” | SplitFileIntoLines | SplitLineIntoWords
      | new LineBuilder(40).AddWord | new FileAssembler("zieldatei.txt").WriteLine

Aber mit C# geht das halt nicht. Die obige Formulierung finde ich allerdings auch nicht so schlimm. Die Do<>()-Aufrufe verbergen den generellen Fluss des Prozesses nicht.

Insgesamt brauche ich für diese Flow-Lösung zwar ein paar mehr Zeilen Code. Aber das finde ich vernachlässigbar. Hier der Rest:

IEnumerable<string> SplitFileIntoLines(string filename)

{

    using (var sr = new StreamReader(filename, Encoding.Default))

    {

        while (!sr.EndOfStream)

            yield return sr.ReadLine();

        yield return null;

    }

}

 

 

IEnumerable<string> SplitLineIntoWords(string line)

{

    if (line == null)

        yield return null;

    else

        foreach (var word in line.Split(' '))

            yield return word;

}

 

 

class LineBuilder

{

    private int max_line_length;

 

    private StringBuilder line = new StringBuilder();

 

 

    public LineBuilder(int max_line_length)

    {

        this.max_line_length = max_line_length;

    }

 

 

    public IEnumerable<string> AddWord(string word)

    {

        if (word == null)

        {

            yield return this.line.ToString();

            yield return null;

        }

        else

        {

            if (line.Length + word.Length + 1 > max_line_length)

            {

                yield return this.line.ToString();

                this.line = new StringBuilder();

            }

 

            if (line.Length > 0) line.Append(" ");

            line.Append(word);

        }

    }

}

 

 

class FileAssembler

{

    private StreamWriter sw;

 

    public FileAssembler(string filename)

    {

        this.sw = new StreamWriter(filename, false, Encoding.Default);

    }

 

    public void WriteLine(string line)

    {

        if (line == null)

        {

            this.sw.Close();

        }

        else

            this.sw.WriteLine(line);

    }

}

Der entscheidende Vorteil liegt für mich in dem Zwang zur Strukturierung der Lösung. Flows geben mir ein Denkmodell: Formuliere jeden Arbeitsschritt so, dass er keine Abhängigkeiten hat. Verlasse dich in einem Arbeitsschritt nur auf den Input und den eigenen Zustand.

Ich denke, das ist Funktionale Programmierung. Und damit habe ich für mich nun verstanden, glaube ich, wo deren Vorteil liegt. Die Zustandslosigkeit finde ich da gar nicht so wichtig. Die ist nett insbesondere für eine Parallelisierung. Unmittelbar relevanter und hilfreicher finde ich jedoch den Flow-Gedanken, den Funktionale Programmiersprachen nahelegen. F# enthält nicht umsonst den |> Operator.

Doch wer will schon auf F# umsteigen müssen, um Verarbeitung mit Flows leichter verständlich zu strukturieren? Wie der Code oben zeigt, geht es auch mit C#. Dafür ist etwas Umdenken nötig – aber es winken höhere Verständlichkeit und auch bessere Evolvierbarkeit als Gewinn auch schon für synchrone Programme.

Montag, 13. Juli 2009

Programme schrittweise aushärten

Wieviel strenge Typisierung brauchen wir eigentlich? Wieviel Schema tut unseren Anwendungen eigentlich gut? Je länger ich darüber nachdenke, desto mehr scheint mir, dass strenge Typisierung und explizite Schemata überbewertet oder gar kontraproduktiv sind.

Wozu brauchen wir eine strenge Typisierung, wozu Schemata? Sie machen effizient. Wenn der Compiler den Typ eines Feldes kennt, kann er maximal schnellen Code für die Zugriffe darauf erzeugen. Und wir brauchen den Code nur zu übersetzen, um zu wissen, ob z.B. eine Zuweisung korrekt ist. Diese Compilation kann sogar im Hintergrund in der IDE stattfinden.

Bei Datenbankschemata ist es ähnlich. Sie optimieren den Platzverbrauch und die Zugriffsgeschwindigkeit. Und, ja, auch automatische Datenkonsistenz ist ein Gewinn von expliziten Datenbankschemata.

Strenge Typisierung und Schemata sind Kinder einer Zeit, als Ressourcen noch knapp waren. Von den 1950er bis Anfang der 1980er  Jahren war Rechenpower teuer, so dass man spätestens nach einem (nächtlichen) Compilerlauf wissen wollte, ob ein Programm korrekt war. Fehlerhafte Probeläufe galt es zu vermeiden. Ebenso war an Speicherplatz zu sparen, was gespart werden konnte. Und jedes Quentchen Performance wollte herausgequetscht sein, um überhaupt annehmbare Laufzeiten zu bekommen.

Strenge Typisierung und explizite Schemata sind also allzu verständliche Entwicklungen. Doch haben Sie sich vielleicht überlebt? Ich denke, wir müssen sie im Kontext ihrer Geschichte sehen. Die damaligen Bedingungen sind nicht zu vernachlässigen. Sie zu vergessen und einfach Typisierung und Schemata absolut setzen, wäre eine Dogmatisierung.

Gerade in den letzten 10 Jahren hat sich nun aber einiges getan. Speicherplatz ist für die meisten Anwendungen keine wirklich knappe Ressource mehr, dito die Prozessorpower – die heute allerdings nicht mehr so einfach wie früher wächst; 2, 4 oder 8 mal 2-3 GHz durch mehrere Kerne ist etwas anderes als weiter wachsende Taktfrequenzen.

Dazu kommt, dass Software immer komplexer wird. Die Anforderungen steigen, die Vielfalt der Geräte steigt, die Technologien werden mächtiger und facettenreicher…

Das Resultat: Es ist immer weniger klar zu erkennen, was eine Anwendung genau können soll. Aber die Ressourcen sind nicht mehr wirklich knapp.

Ich denke, das hört sich nicht mehr danach an, dass wir unheimlich effizient sein müssen, sondern eher flexibel. Flexibel sind von der ersten Codezeile an explizite Klassen und Datenbankschemata aber nicht. Das Gegenteil ist der Fall. Unsere Fixierung auf Schemata für Daten im Speicher und auf der Platte zwingt uns sehr schnell in ein Korsett, dass Änderungen an Software schwierig macht.

Deshalb glaube ich, dass wir von den quasi absolut gesetzten Schemata abrücken müssen. Sie haben ihren Zweck, aber wir sollten nicht glauben, dass wir ohne sie nicht können. Wir müssen vielmehr dahin zu kommen, sie gezielt und zweckmäßig, statt zwanghaft einzusetzen. Explizite, statische Schemata machen Sinn, wo wir genau wissen, wirklich genau!, wie Datenstrukturen aussehen.

Wo wir das aber nicht wissen, wo noch Unklarheit herrscht, da sollten wir uns noch nicht so festlegen. Da sollten wir schemalos arbeiten.

In den 1980ern wurde der Begriff vom “Stepwise Refinement” populär. Programme sollten schrittweise detaillierter formuliert werden. Top-down sollte man vorgehen.

Ich möchte diesem Begriff ein “Stepwise Hardening”, eine schrittweise Aushärtung, hinzufügen. Wir sollten Programme weich beginnen, mit flexiblen Strukturen – und sie dann und nur dann verhärten, wenn wir sicher sind, dass wir mehr Effizienz brauchen. Dynamische Sprachen und schemalose Datenbanken scheinen mir da richtige Schritte auf dem Weg zu einer Balance zwischen Effizienz und Flexibilität.

Heute lassen wir unsere Programme vom ersten Moment an aus hart schematisierten Bausteinen bestehen. Je größer sie werden, desto mehr “harte Brocken” enthalten sie, die sich Änderungen widersetzen:

image

Aber könnte es nicht auch anders sein? Warum fangen wir nicht weich an? Warum härten wir nicht schrittweise aus, wo wir im Verlauf der Entwicklung immer sicherer werden, dass sich etwas nicht mehr ändert? Den Rest lassen wir bis auf Weiteres flexibel, weich:

image

Wie wäre das? Kämen wir nicht schneller voran? Wären wir nicht weniger genervt bei Änderungswünschen? Ich glaube, es lohnt sich, darüber nachzudenken.

Strenge Typisierung und Schemadenken haben ihren Platz; sie sind zurecht gegen einen allzu laxen Umgang mit Speicherplatz angetreten. Aber nun ist es Zeit, die Synthese einzuleiten. Wir brauchen dringend mehr Flexibilität im Inneren, um unsere Software evolvierbar zu halten.

Donnerstag, 2. Juli 2009

CCR Flows - Asynchrone Prozesse mit der CCR verdrahten

Neulich habe ich eine Lanze dafür gebrochen, zwei Probleme der Softwareentwicklung auf einen Streich zu lösen. Das würde Software zukunftsfähiger machen. Denn Abhängigkeiten und Synchronizität sind Behinderungen auf dem Weg in eine glückliche Projektzukunft.

Das Mittel für diese “Wundertat”? Asynchrone Flows, d.h. Funktionseinheiten nicht mehr statisch voneinander abhängig machen und auch nicht mehr synchron miteinander kommunizieren lassen. Stattdessen Verarbeitungsschritte in einem expliziten, getrennten “Bereich” (Separation of Concerns) lose mit eigenständigen Verbindungsgliedern “zusammenstöpseln”.

image

Dazu hatte ich dann ein wenig über einen API spekuliert, der das möglich machen könnte. Damit lag ich – wie sich nun herausgestellt hat – wohl nicht ganz daneben. Denn nach einigen Versuchen habe ich nun so einen Flow API implementiert. Ich nenne ihn CCR Flows, weil er intern auf CCR (Microsoft Concurrency Coordination Runtime) Ports als “Verbindungsglieder” zwischen Prozessschritten setzt.

Die CCR Flows sind jetzt Open Source (sogar inkl. Dokumentation sowie Unit Tests) und liegen bei CodePlex:

http://ccrflows.codeplex.com

Über Feedback und Diskussion dort im Forum würde ich mich freuen. Es gibt natürlich noch etwas daran zu tun. Aber als Einstieg in ein anderes Programmiermodell finde ich den API nicht ganz schlecht. Ein Beispielprogramm in den Sourcen realisiert auch den Beispielprozess meines vorherigen Blogartikels. An dieser Stelle zum Schnuppern aber nur ein kleiner Prozess, der die Worte eines Textes extrahier und dann in zwei Schritten transformiert:

Flow<string>.Do<string>(SplitTextIntoWords).Do<string>(w=>w.ToUpper()).Do<string>(Reverse)

Meine Vermutung, solcher Code lässt sich besser weiterentwickeln, weil schon bei jeder Prozessstufe (stage) viel entkoppelter gedacht wird. Denn diese Stufen kennen ihren Vorgänger und Nachfolger nicht! Sie haben keine Abhängigkeiten.

Wie das genau geht, erklärt die Doku bei CodePlex. Ansonsten fragt mich einfach.

Viel Spaß damit!

Wie gut ist Ihr Job? – Jetzt Umfrage mit Gewinn [OOP 2009]

Vor einigen Tagen hatte ich darüber spekuliert, wie zufrieden wir Softwareentwickler wohl so mit unseren Jobs sind. Daraufhin gab es einige Einsendungen von Fragebogenergebnissen nach dem DGB-Fragebogen zur Jobzufriedenheit. Das hat mich ermutigt und ich habe mit dem Professional Developer College nun die Aktion etwas erweitert:

Jetzt gibt es eine “echte” Umfrage mit Gewinnen, die unter den Einsendern verlost werden, und einer Veröffentlichung der Ergebnisse in der dotnetpro.

Wäre toll, wenn möglichst viele mitmachten, damit wir ein halbwegs repräsentatives Bild von der Jobzufriedenheit in der Branche bekommen. Der Aufwand ist 5 Minuten, der Nutzen hoch für die Gemeinschaft.

Also, auf geht´s. Klickt hier…

Mittwoch, 1. Juli 2009

Blinder Fleck Change Tracking

Habe grad ein paar Postings zum Thema Distributed Domain Driven Design (DDDD oder “D4”?) gelesen. Dabei ist mir wieder der Gedanke gekommen, dass wir uns das Leben noch schwerer machen als nötig. Ich glaube nämlich, zu unserem Glück fehlt uns noch eine deutliche Separation of Concerns (SoC).

Über das DataSet kann man sagen, was man will, es hat eins ganz wunderbar getan: Änderungen verfolgen. DataSet mit Daten füllen, irgendwohin schicken, dort ändern – und dann nur die Änderungen zurückschicken, um sie zu persistieren. Sehr cool!

Und dann kam O/R Mapping – und wir haben für die Änderungsverfolgung (Change Tracking) einen Blinden Fleck entwickelt. Denn entweder haben O/R Mapper kein Change Tracking betrieben, sondern Daten geladen, Objekte befüllt und dann einfach immer komplette Objekte auch wieder gespeichert, wenn man ihnen sagte, dass sich daran etwas geändert hat. Oder sie haben Objekte befüllt, die intern selbst Buch führen über Änderungen an sich. Dann konnte der O/R Mapper später ohne manuelle Meldungen über Änderungen sich selbst geänderte Objekte herauspicken und mit minimalen SQL DML-Statements persistieren.

Klingt bequem, ist bequem. Aber Change Tracking liegt im Blinden Fleck. Wir sehen das nicht. Meistens. Deshalb machen wir uns darüber keine Gedanken. Sollten wir aber. Denn letztlich ist das ein Thema, das immer relevant ist, wenn Mapping ins Spiel kommt.

Deshalb hat mich ja auch D4 wieder darauf gebracht. In verteilten Systemen erzeugen wir nämlich Objekte z.B. in einem Server, die wir zum Client schicken. Ob der Server die aus einer Datenbank befüllt oder nicht, ist egal. Änderungen an diesen Objekten im Client sollen dann wieder zurück zum Server. Aber wie? Geänderte Objekte können in Form “dummer DTOs” eigentlich nur komplett zurückfließen. Aber warum soviel Traffic? Warum können wir nicht nur die Änderungen zurückschicken und in die serverseitigen Instanzen einspielen?

Klar, das geht. Kann man programmieren. Kostet aber Mühe. Event sourcing ist dazu ein Stichwort. Aber warum sollten wir das immer wieder programmieren? Das ist ein so grundsätzliches Problem, dass ich finde, dafür sollte es eine allgemeine Lösung geben.

Und die steckt für mich im Change Tracking, das manche O/R Mapper eh schon tun. Warum wird dieses Change Tracking nicht dort rausgezogen und separat implementiert? Warum gibt es nicht eine Change Tracking Infrastruktur (z.B. auf einem AOP-Fundament), die dann beim O/R Mapping oder auch bei sonstigem Mapping oder Versand von Daten einsetzen kann?

image Für mich sind Change Tracking und Persistenz inzw. ganz klar orthogonale Belange. Sie sollten deshalb auch technologiemäßig ganz klar getrennt werden.  Change Tracking darf nicht unser Blinder Fleck sein.

Wer baut also als erstes eine allgemeine Change Tracking Infrastruktur z.B. auf der Basis von PostSharp? Wer baut einen O/R Mapper, der dann darauf aufsetzt? Das macht dem O/R Mapper-Hersteller weniger Mühe. Und das verschafft dem Change Tracking-Hersteller eine viel größere Zielgruppe. Alle würden profitieren.