Follow my new blog

Samstag, 30. Oktober 2010

Lesenswerte Widerlegung

Eine Allaussage geht um: “Es kann nur sein, wie es ist.”

image
Unternehmen geht es schlecht, weil der Markt halt so ist, wie er ist; böse Globalisierung. Oder die Mitarbeiter sind halt, wie sie sind; böse Ausbildungsdefizite und Konsumentenhaltung. Daran kann man kaum was ändern. Höchstens sollte man die bisherigen Anstrengungen verstärken: “Nachsitzen”, wenn das Release noch raus muss; mehr Incentives, damit endlich die Softwarequalität steigt; strengere Budgetschrauben, damit alles unter Kontrolle bleibt; weniger Investition in Mitarbeiterfortbildung, weil dadurch wertvolle Zeit zur Kundenwunscherfüllung verloren geht. Usw. usf. ad nauseam.

[Pause]

Mir ist ein wenig übel geworden, da ich diese Litanei geschrieben habe. Die komprimiert und verkürzt zwar, ist aber ein Abbild der Stimmung, die mir in Beratungen, Trainings oder auf Community-Veranstaltungen entgegenschlägt. Früher war vor allem Klagen über Tools oder Microsoft. Veteranengeschichten folgten dann schnell. Wie es damals mit dem C64 war… und die Lochkarten…

Heute scheint das weniger Thema. Vielleicht liegt es auch an meinem neuen Fokus, der nicht mehr auf Technologien, sondern auf Softwarequalität liegt. imageHeute höre ich Klagen über Arbeitsbedingungen. Immer wieder werden da Mauern beschrieben, gegen die Entwickler laufen, weil sie nicht so dürfen, wie sie wollen. ReSharper darf nicht angeschafft werden: zu teuer. Automatisiertes Testen darf nicht in Anschlag gebracht werden: dafür hat der Kunde nicht bezahlt. Eine Fortbildung darf nicht besucht werden: vier Monate Urlaubssperre, weil die Projekte weit jenseits des Plans liegen.

Dahinter – wie schon gesagt – der Glaubenssatz: Irgendwie kann es nicht anders sein. So ist die Welt halt. Vor allem ist sie kein Wunschkonzert. Dem Schicksal und den Marktgesetzen den ehernen sollte man sich daher ergeben.

Widerlegung

Die Allaussage, der unumstößliche Glaubenssatz wird ad absurdum geführt durch den Nachweis der Existenz auch nur eines Gegenbeispiels.

image
Wenn es auch nur einmal anders gehen kann, als die Überzeugung diktiert… dann ist die Welt anders als gedacht. Dann ist Hoffnung auch in schlimmsten Zeiten.

Und solche Hoffnung gibt es. Der campus Verlag hat eine mehr als 200 Seite starke Widerlegung des allgegenwärtigen Glaubenssatzes herausgebracht, die Unternehmensverhältnisse könnten kaum anders sein. Ihr Titel: “Nur Tote bleiben liegen” vom Autorenduo Förster und Kreuz.

image

Gelesen habe ich das Buch, weil Rezensionsexemplare davon im Blog der Autoren an andere Blogger vergeben wurden. Da konnte ich nicht nein sagen ;-)

Gefallen hat mir das Buch aber nicht wegen seiner Kostenlosigkeit für mich. Nein, keine Sorge. Es ist seine “Schwingung”, die gute Laune, der Optimismus, die Hoffnungsfülle, der Gegenentwurf, das Ja zu Neu und Anders, die es für mich zu einer Empfehlung machen.

Ich will deshalb auch gar nicht lange auf den Inhalt eingehen. Man muss es einfach aufschlagen und loslesen. Irgendwo. Jede Seite eine Widerlegung. (Naja, vielleicht übertreibe ich ein wenig ;-) Jede Seite ein Beispiel dafür, dass andere Arbeitsverhältnisse, andere Organisation von Unternehmen möglich ist. Und zwar ganz handfest. Dies ist kein Thesenbuch, sondern eine Beispielsammlung. Förster und Kreuz berichten aus der Realität. Die mag mal fern sein, auf anderen Kontinenten, aber sie ist immer relevant. Auch das ein Effekt der schlimmen Globalisierung. Oder ist die gar nicht so schlimm?

Wer glaub, man können nicht anders umgehen mit der Arbeitszeit oder der Führung oder der Aus-/Fortbildung oder der Kontrolle oder dem Kunden, der schlage dieses Buch auf uns lasse sich eines Besseren belehren. Es geht anders, als die allermeisten Unternehmen es heute tun und meinen nicht anders tun zu können. Irgendwo geschieht schon die kleine oder große Revolution, die Kunden zufrieden macht, Unternehmen prosperieren lässt und Mitarbeiter ganz einfach motiviert.

Internet, Globalisierung, neue Medien, anspruchsvolle Kunden, Digital Natives als Mitarbeiter… das alles und mehr muss keine Bedrohung althergebrachter Ruhe und Ordnung sein. Es liegt an uns, das Glas als halb voll und nicht als halb leer zu betrachten. Die Chancen auf etwas Besseres lauern überall. Das beweist “Nur Tote bleiben liegen” mit einem Feuerwerk an weltweit gesammelten Impressionen.

Im Maul vom Gaul

So empfehlenswert ich das Buch finde, man darf keine Tiefründigkeiten erwarten und auch keine Tipps für die Praxis. Wer von der Lektüre motiviert das Berichtete selbst ausprobieren möchte, ist auf sich gestellt.

Das soll keine Klage sein, sondern nur ein Hinweis, um falsche Erwartungen zu zerstreuen. Förster und Kreuz haben einen hochfrequenten Impulsgeber und Motivator geliefert, kein Handbuch.

Nein, eine tiefergehende Kritik am Inhalt habe ich nicht. Man nehme einfach das Buch für das, was es will und kann. Die knapp 25 EUR für das Hardcover sind nicht zuviel für die Portion Zuversicht, die sich daraus löffeln lässt.

Aber -- denn ohne Aber geht es nicht ;-) -- eines hat mir nicht gefallen. Es ist mir erst nach einiger Zeit aufgefallen, als ich nachfragte, ob es auch eine elektronische Version des Buches gäbe, die ich mit meinem iPad auf den prio.walk hätte nehmen können. Das, was fehlt, ist eben oft schwerer zu erkennen, als das, was da ist.

Nein, ich meine nicht eine eBook-Version oder ein Hörbuch. Beides gibt es.

Ich meine die Abwesenheit von Innovation bei Verlag und Autoren.

Es hat einen Moment gedauert, weil auch ich in Jahrzehnten konditioniert wurde, bei Büchern ersteinmal an Papier zu denken. Aber was für ein Quatsch! Bücher müssen weder aus Papier bestehen noch als zusammenhängendes PDF ausgeliefert werden. Sie müssen auch nicht daheim am Schreibtisch verfasst, zu einem Verlag getragen und dann von einem Vertriebler an den Buchhändler gebracht werden. All das inklusive eBook und Hörbuch ist so Buch 1.0, dass es kaum auszudenken ist. Das ist so un-innovativ und so un-mutig, dass es mich bei diesem Buch erschreckt und enttäuscht hat. Schade.

Warum haben Förster und Kreuz, die als Erfolgsautorenduo genügend Reichweite auch ohne Verlag haben, ihr neues Buch “im stillen Kämmerlein” geschrieben? Sie haben natürlich ein Blog, dessen Inhalte früher oder später wahrscheinlich in der einen oder anderen Weise auch wieder in einem Buch auftauchen werden. Dennoch findet das Schreiben ab von der Öffentlichkeit und somit ab vom Feedback statt.

Wie Buch 2.0 ist dagegen dieser Titel:

image

Der Autor hat sich schon beim Schreiben dem detaillierten Feedback der Community gestellt, indem er den kompletten Text online veröffentlicht hat – mit der Möglichkeit, absatzweise Kommentare zu geben. Hier ein Auszug:

image

Das ist Web 2.0 gelebt. Vom Autor wie vom Verlag.

Oder warum haben Förster und Kreuz den potenziellen Rezensenten überhaupt als Default ein Papierbuch angeboten? Warum nicht einen Link auf ein PDF oder einen Kindle-Gutschein? Der ganze Aufwand mit Verpackung und Porto ist überflüssig. Allemal für Rezensenten. Mich interessiert doch nur am äußersten Rand, ob das Papierbuch geschmeidig in der Hand liegt. Auch hier also eine merkwürdige Zurückhaltung in Bezug auf das, was möglich und zukunftsweisend ist.

Wo ist auch das virale Marketing? Warum nicht das Buch launchen im Blog mit einem befristeten kostenlosen Download für jedermann? Von mir aus kann es dabei ja durch Einbinden des Namens des Herunterladenden personalisiert werden.

Oder wenn schon nicht ganz umsonst für eine gewisse Zeit, dann zumindest Zahlen mit einem Tweet. Das ist ja trivial aufzusetzen: http://www.paywithatweet.com/

Nett, dass die Blogeinträge der Rezensenten am Ende von den Autoren zusammengefasst werden. Aber auch das ist letztlich Buch 1.0, weil es auf Zentralisierung setzt.

imageOder wie wäre es, das Buch sofort in “Module” zu zerlegen. Und nicht nur dieses Buch, sondern auch die bisherigen von Förster und Kreuz? Dann die Module mit Tags versehen oder in eine Concept Map o.ä. einbinden und ab ins Internet damit.

Dann könnte der Inspiration suchende Leser sich durch das Förster und Kreuz Universum bewegen, online lesen z.B. mit Scribd, sich am Ende sein persönliches Buch aus den interessantesten Modulen zusammenstellen, alles in ein PDF “zusammenschweißen” und das womöglich noch ad hoc in der Auflage 1 für sich z.B. durch www.epubli.de drucken lassen. Das (!) wäre Buch 2.0. Das würde dem amerikanischen Sprichwort “Put your money where your mouth is!” entsprechen.

Aber ich will nicht abschweifen. Dies ist ja nur eine Buchrezension und keine Verlagsberatung ;-) Eine Beratung wäre für viele Verlage wohl auch eher das falsche Mittel, um aus deren Klagehaltung herauszukommen. Da scheint mir eher eine Therapie angezeit. Aber das ist ein anderes Thema…

Also komme ich mal zum Schluss:

“Nur Tote bleiben liegen” finde ich lesenswert, weil kurzweilig und inspirierend. Lockere Schreibe, lockerer Inhalt, macht gute Laune. Einfach mal auf den Wunschzettel für Weihnachten setzen – oder gleich dem Chef schenken ;-)

Denn wer sagt, es ginge nicht anders in den Unternehmen, Erfolg und Zufriedenheit seien nur mit Verstärkung tradierter Maßnahmen – Management 1.0 – zu erreichen oder gar nicht, der findet in diesem Buch einen bunten Strauß an Widerlegungen.

Und ganz vielleicht sind Förster und Kreuz ja beim nächsten Buch auch selbst mutiger. Raus aus der Komfortzone gilt nicht nur für die, über die sie berichten, würd´ ich mal sagen.

Dienstag, 26. Oktober 2010

Private Methoden einfach testen

In einer Session auf der ADC fragte Stefan Lieser mich heute, ob nicht die dynamischen Features von C# helfen könnten, private Methoden von Objekten zu testen. Erst war ich skeptisch – doch dann half Google. Es geht tatsächlich.

Wer das also dringend tun will, der kann es so wie folgt beschrieben tun. Besonders mag das in Brownfield-Projekten helfen. Denn ansonsten ziehe ich es vor, Privates privat sein zu lassen und nicht direkt zu testen. Und Internes mache ich für Tests mit InternalsVisibleTo zugänglich.

Gegeben dieses Systen under Test:

public class MyFizzBuzzer
{
   
public static IEnumerable<string
> FizzBuzz()
    {
       
return Enumerable
.Range(1, 100).Select(FizzBuzzThis);
    }

   
private string FizzBuzzThis(int
i)
    {
       
if (IsFizzBuzz(i)) return "FizzBuzz"
;
       
if (IsFizz(i)) return "Fizz"
;
       
if (IsBuzz(i)) return "Buzz"
;
       
return
i.ToString();
    }

   
private static bool IsFizz(int
i)
    {
       
return
i % 3 == 0;
    }
…


Dann können Sie in Zukunft solche Tests schreiben für die privaten Methoden:

[TestFixture]
public class testMyFizzBuzzer
{
    [
Test
]
   
public void
Check_fizz_numbers()
    {
       
dynamic sut = typeof(MyFizzBuzzer
).Undress();
       
Assert
.IsTrue(sut.IsFizz(3));
       
Assert
.IsTrue(sut.IsFizz(12));
       
Assert
.IsFalse(sut.IsFizz(5));
    }


    [
Test
]
    [
TestCase(1, "1"
)]
    [
TestCase(2, "2"
)]
    [
TestCase(3, "Fizz"
)]
    [
TestCase(5, "Buzz"
)]
    [
TestCase(15, "FizzBuzz"
)]
   
public void FizzBuzz_number(int number, string
expected)
    {
       
dynamic sut = new MyFizzBuzzer
().Undress();
       
Assert
.AreEqual(expected, sut.FizzBuzzThis(number));
    }
…

Dazu ist es nur nötig, dass Sie diese Klassen ins Testprojekt einbinden:

namespace System.Dynamic
{
   
public static class UndressObjects
    {
       
public static dynamic Undress(this Type
type)
        {
           
return new UndressedObject
(type);
        }

       
public static dynamic Undress(this object
obj)
        {
           
return new UndressedObject
(obj);
        }
    }


   
public class UndressedObject : DynamicObject
    {
       
private const BindingFlags MEMBERS_FLAGS =
BindingFlags.Instance | BindingFlags
.Static |
               
BindingFlags.Public | BindingFlags
.NonPublic;

       
private readonly object
_dressedObject;


       
public UndressedObject(object
o)
        {
            _dressedObject = o;
        }


       
public override bool TryInvokeMember(InvokeMemberBinder binder,
object[] args, out object
result)
        {
           
var
method = TypeOfDressed().GetMethod(binder.Name,
                                                   MEMBERS_FLAGS,
                                                  
null
,
                                                   GetParameterTypes(args).ToArray(),
                                                  
null
);

           
if (method == null) return base.TryInvokeMember(binder, args, out
result);

            result = method.Invoke(_dressedObject, args);
           
return true
;
        }


       
private IEnumerable<Type> GetParameterTypes(object
[] args)
        {
           
return from a in args select
GetActualType(a);
        }


       
private Type GetActualType(object
t)
        {
           
return (t is ParameterInfo) ? (t as ParameterInfo
).ParameterType
: t.GetType();
        }


       
private Type
TypeOfDressed()
        {
           
return (_dressedObject is Type) ? (Type
)_dressedObject
: _dressedObject.GetType();
        }
    }
}

Die Idee stammt aus diesem Artikel. Ich hab sie lediglich etwas, hm, fokussiert.


Getestet werden können also private statische Methoden oder Instanzmethoden.


Enjoy your brownfield! ;-)

Freitag, 22. Oktober 2010

Wider die Softwarearchaeologie

imageAuf der prio.conference habe ich mal die Event-Based Components Dru Sellers vorgestellt, einem der Entwickler des MassTransit Busses. Das war ein sehr interessantes Gespräch in vielerlei Hinsicht.

Zu einem Aspekt daraus bin ich dann auch gleich heute zurückgekehrt: der Archaeologie der Software.

Frage: Was ist der Unterschied zwischen dem Steinkreis von Stonehenge und einem Klassendiagramm?

image

Meine Antwort: Es gibt keinen Unterschied.
Naja, außer dem offensichtlichen Zwinkerndes Smiley

Sowohl der Steinkreis wie das Klassendiagramm sind archaeologische Artefakte. Das heißt für mich hier, sie sind Menschengemachtes mit einem Zweck, den es zu entschlüsseln gilt. Er lässt sich nicht einfach ablesen, sondern muss durch langwieriges Studium rekonstruiert werden. Stonehenge wie Klassendiagramm zeigen nicht an, wie sie funktionieren.

Für Stonehenge mögen Sie mir da auch zustimmen. Beim Klassendiagramm jedoch schütteln Sie sehr wahrscheinlich den Kopf. Dennoch: Ich meine es genau so. Ein Klassendiagramm zeigt nicht, wie eine Software funktioniert. Die Prozesse, für die es steht, sind aus ihm nicht abzulesen.

Ist das nun ein Mangel des Klassendiagramms? Sicher nicht. Sein Zweck ist allein die Darstellung von Struktur, es beschreibt Beziehungen zwischen Funktionseinheiten.

Meine Kritik richtet sich daher nicht an das Klassendiagramm, sondern an die, die es zum Symbol für die rechte Softwareentwicklung gemacht haben.

Unter den Diagrammarten ist es wohl die heute am wohl häufigsten verwendete – aber sie sagt am wenigsten über Software aus. Oder anders: Ein Klassendiagramm kümmert sich nicht um das Wesentliche von Software und doch hält die Branche es hoch als Symbol für zeitgemäße Softwareentwicklung à la Objektorientierung.

Denn was ist das Wesentliche an Software? Seine Funktion, die Verarbeitung von Eingaben zu Ausgaben, EVA. Laufende Software ist ein Prozess, der knetet und transformiert Daten in vielen Schritten.

Wäre es da nicht naheliegend, dass es die vornehmste Aufgabe eines Softwareentwicklers sei, diese Schritte klar sichtbar zu machen, für jedermann nachvollziehbar?

Aber nein, die Softwareentwicklung tut alles, um die Funktionsweise von Software zu verschleiern. Einem Klassendiagramm können Sie nicht ansehen, wie die Prozesse ablaufen, für die es steht. Ebenso sind Paket- und Komponentendiagramme archaeologische Artefakte, die man lange studieren muss, um irgendetwas über die Funktionsweise einer Software herauszubekommen.

image

Das ist wieder kein Mangel an den Diagrammen, denn sie wollen Funktionsweise auch nicht repräsentieren. Wieder gehören sie aber zu den meist verwendeten Diagrammarten.

Was aber mit den Sequenzdiagrammen? Stehen die denn nicht für Funktionsweise?

image

Sicher, Sequenzdiagramme beschreiben Funktionsweise. Doch sie tun das in immer noch schwer verständlicher Weise. Das Problem ist die Schachtelung. Sequenzdiagramme visualisieren Callstacks.

Wenn eines aber schwer für Menschen zu verstehen ist, dann ist es Schachtelung. Wie man es dreht oder wendet, wir kommen damit nur schwer zurecht. Simples Beispiel: Beobachten Sie sich beim nächsten Gespräch mit Freunden oder Kollegen. Sie beginnen ein Thema, dann wirft jemand ein anderes Thema auf, ohne dass das erste abgeschlossen wäre… was nun? Sie werden entweder dafür sorgen, dass das erste Thema beendet wird, bevor Sie mit dem zweiten weitermachen. Oder Sie werden das zweite aufgreifen – und am Ende das erste nicht zuende führen.

Die dritte Alternative – Sie behandeln das zweite Thema als Einschub und kehren am Ende zurück zum ersten Thema – werden Sie nur sehr selten realisieren. Denn dafür braucht es ein gutes Gedächtnis und einigen Willen. Von einem weiteren Einschub während der Behandlung des zweiten Themas ganz zu schweigen.

Schachtelungen sind schwer zu verstehen. So ist das nun mal.

f(g(h(x)));

ist schwerer zu verstehen als

h(x).g().f();

oder

var rh = h(x);
var rg = g(rh);
f(rg);

Und so sind Sequenzdiagramme ebenfalls schwer zu verstehen. Sie zeigen zwar das Wesentliche einer Software, doch sie helfen nur suboptimal beim Verständnis, weil sie immer noch dem von-Neumann-Modell von Software folgen.

Vier der hauptsächlich verwendeten Diagramme der Softwareentwicklung verschleiern damit, worum es eigentlich geht. Undv om Code wir gar nicht erst reden. Aus dem Funktionsweise ablesen zu wollen, ist ein mühseliges Unterfangen, wie jeder bezeugen kann, der schonmal an Brownfieldcode arbeiten musste.

image

Das ist es noch einfacher, ein Sequenzdiagramm zu lesen. Nicht umsonst wurden die auch entwickelt, nämlich als Analyseinstrumente und nicht als Entwurfswerkzeuge.

Und das bedeutet?

Die Softwareentwicklung leidet unter selbstverschuldeter Verschleierung. Mit jedem Diagramm, das nicht die Funktionsweise leicht verständlich offenbart, mit jeder Zeile Code, die einschachtelt, macht sie es Softwareentwicklern schwer, das Wesentliche von Code zu verstehen. Sie erzwingt wieder und wieder, Softwarearchaeologie zu betreiben. Wohl dem, der dann noch mit den “Altvorderen” sprechen kann, die ein Artefakt hinterlassen haben.

Hochgepriesene visuelle und nicht visuelle formale Sprachen helfen also nicht, die Prozesse, die als Problemlösungen erdacht und dann in ihnen codiert wurden, anschließend aus ihnen abzulesen.

Das kann nicht anders beschrieben werden als Vernichtung von Information. Dadurch entsteht ungeheurer betriebswirtschaftlicher und am Ende auch volkswirtschaftlicher Schaden. Um den in Zukunft zu vermeiden, müssen wir den Fokus ändern.

Software ist fundamental dynamisch. Sie tut etwas. Das ist ihr ganzer Zweck. Sie ist kein Regal oder Bild, sondern eine Maschine. Dafür braucht sie Input, Daten. Aber noch wichtiger ist, wie diese Daten verarbeitet werden. Verständliche Beschreibungen von Funktionsweisen haben deshalb für mich Vorrang vor Datenbeschreibungen.

Am Ende müssen beide sein, doch die Funktionalität ist wichtiger, weil sie der Zweck von Software ist. Daten gibt es durchaus auch ohne Software. Funktionalität nicht. Dafür wird sie entwickelt.

Und wie sollte die geforderte Beschreibung von Funktionalität aussehen?

Sie sollte visuell sein und Schachtelung nur zum Zwecke der Abstraktion verwenden.

  • Eine visuelle Beschreibung ist leichter zu überschauen als eine textuelle derselben Funktionalität – außer in trivialen Fällen.
  • Und Abstraktion ist nötig, um Kompliziertes überhaupt überschaubar zu machen. Es kann dann auf verschiedenen Abstraktionsebenen dargestellt werden.

Eine visuelle Sprache, die die Funktionsweise einer Software auf verschiedenen Abstraktionsebenen darstellen kann, sollte das Hauptwerkzeug des Softwareentwicklers sein.

Nur so vermeiden wir teure Softwarearchaeologie. Davon bin ich fest überzeugt, auch wenn damit zwei Jahrzehnte Objektorientierung als Verirrung dastehen mögen. Denn die de facto Objektorientierung hat nie den Prozess im Fokus gehabt, sondern immer die Struktur.

Dass es die auch geben muss, ist unzweifelhaft. Aber die Objektorientierung hat sie auf ein Podest gehoben und angebetet. Jedes Domänenobjektmodell legt dafür lebendiges Zeugnis ab.

Die Zukunft der Software liegt daher aus meiner Sicht nicht in der Objektorientierung, sondern einzig in der Prozessorientierung. Ohne Fokus auf den Prozess und seine einfache Implementierung wie verständliche Darstellung auf verschiedenen Ebenen – von kleinen Funktionen bis zu großen Services –, kommen wir nicht aus dem Brownfieldmatsch heraus.

PS: Es gibt natürlich Hoffnung. Aktivitätsdiagramme, Datenflussdiagramme oder BPMN sind Mittel, um Prozesse darzustellen. Wir müssen ihnen nur den gebührenden Platz im Zentrum der Softwareentwicklung einräumen. Und natürlich gehören auch Event-based Components dorthin.

Freitag, 17. September 2010

Rein in die Matrix!

Sind Event-Based Components (EBC) ne super Sache oder “krank” (wie gerade in einem Diskussionsforum behauptet wurde)? Tja… ich glaube natürlich, dass sie ziemlich cool sind und was bringen. Der Entwurf von Software wird damit einfacher, die Wartbarkeit, äh, Evolvierbarkeit steigt, die Verständlichkeit ebenfalls und automatisierte Tests brauchen keine Mock-Frameworks mehr.

image Es gibt auch eine kleine Community um EBC, die dasselbe denkt. Die Majorität der Entwickler ist jedoch – wenn sie denn EBC überhaupt kennen – mindestens indifferent. Ist es deshalb aber besser, weiterzumachen wie bisher? Oder wären EBC ein Gewinn für sie? Wer hat nun recht? Und in welchen Fällen?

Ich glaube, da können wir Überzeugte uns noch lang den Mund fusselig reden und schreiben, am Ende zählt immer nur eine Gegenüberstellung. Wer die aber nicht kundig selbst macht oder machen kann, der wird nicht überzeugt. Also muss die Gegenüberstellung anders laufen. Sie muss präsentiert werden.

Deshalb möchte ich ein Angebot machen:

Wer glaubt, EBC brächten nichts oder würden irgendwie die Softwareentwicklung sogar noch behindern, der kann sich mit seinem Ansatz zur Planung von Software im fairen, harten aber herzlichen Wettstreit mit mir messen. Wie wäre das?

Das ist dann natürlich kein wissenschaftliches Experiment. Dennoch ließe sich was lernen. Auf beiden Seiten. Da bin ich mir sicher.

image

Ich schlage vor, dass ein solcher “Shoot Out” in einer .NET User Group stattfindet. Sicher gibt es eine, die den Event hosten würde. Diese User Group würde beiden Parteien – EBC und Non-EBC – eine Aufgabe stellen, die die dann in einer Timebox umsetzen müssen. Zeitaufwand ca. 3 Stunden von der Anforderungspräsentation bis zur Auslieferung.

Am Ende würde mit vorgefertigten automatisierten Akzeptanztests geprüft, inwieweit die Anforderungen erfüllt sind. Zwischendurch – vielleicht nach 1,5 bis 2 Stunden – würden noch neue Anforderungen nachgereicht. Und natürlich würde jede Partei ihre Lösung in einem Review vorstellen.

Die Akzeptanztests machen eine Aussage über die Korrektheit des Codes, den ein Ansatz produziert. Die Erfüllung auch der nachgereichte Aufgabe machen eine Aussage über die Fähigkeit, den Code zu verändern, d.h. die Evolvierbarkeit.

Die Größe der Aufgabe ist fast egal, würde ich sagen. Sie muss nicht mal komplett zu schaffen sein. Es reicht, wenn die Zahl der Akzeptanztests nicht zu klein ist und in der Timebox davon eine ganze Reihe schaffbar sind. Dann ließen sich sich Unterschiede im Erfüllungsgrad ablesen. Die nachgereichte Anforderung wäre allerdings eine Pflichtanforderung, die zumindest angegangen werden muss.

imageWas der Ansatz der anderen Partei ist, ist egal. Sie sollte nur mit C# programmieren. Ob dann streng OOP oder Cowboy Programmierung oder Komponentenorientierung oder Funktionale Programmierung oder sonstwas gemacht wird… alles ist recht.

Hm… einen Aspekt würde ich allerdings noch gern reinbringen: Entwicklung im Team. Wäre also vielleicht gut, wenn auf jeder Seite 3 Leute stünden. Das ist etwas realitätsnäher und ließe größere Aufgaben zu.

Wäre das nicht mal ein Coding Dojo der besonderen Art? Ein “Shoot Out” Dojo oder Contest Dojo.  EBC gegen den Rest der Welt :-)

Wer nimmt die Herausforderung an?

image

Dienstag, 7. September 2010

Die Power von Rx für Event-Based Components

Ob Event-Based Components (EBC) auch mit den Reactive Extensions, dem Rx Framework von Microsoft implementiert werden könnten, stand als Frage schon länger im Raum. Jetzt hab ich mich mal daran versucht.

Als Beispiel habe ich die FizzBuzz Kata gewählt. Der EBC-Entwurf ist denkbar simpel:

image

Bisher wurde dann eine EBC-Aktion wie Zahl transformieren so übersetzt:

   1: class Zahl_transformieren_früher
   2: {
   3:     public void In_Zahl(int zahl)
   4:     {
   5:         ...
   6:     }
   7:  
   8:     public event Action<string> Out_Symbol;
   9: }

Output-Pins von EBC-Aktionen waren Events, Input-Pins Methoden. Das ist eine geradlinige Übersetzung. Funktioniert in der Praxis tadellos.

Und doch bleibt etwas zu wünschen übrig. Es gibt einfach keine Unterstützung für Operationen auf Folgen von Events, also Nachrichtenströme. Natürlich kann man Aktionen wie Filter in einen Draht “einklemmen” – doch die müssen relativ umständlich erst entwickelt werden.

Mit Rx geht das jedoch viel einfacher. Damit kann man einen Event-Strom filtern oder verzögern oder zwei Ströme zusammenführen usw. usf. Viele schöne Operationen auf Events – oder eben Observables – gibt es dort. Warum das nicht für EBC nutzen?

Deshalb hier mein Vorschlag für eine Übersetzung von EBC-Flows mit Rx-Mitteln:

* Ein Output-Pin wird in ein IObservable<T> übersetzt
* Ein Input-Pin wird in einen IObserver<T> übersetzt

Mit diesen Interfaces kann man dann wunderschöne Sachen machen.

Aber es sind eben nur Interfaces und so brauchen wir noch Implementationen. Ich nenne die mal OutputPin<T> und InputPin<T>. Ist doch irgendwie naheliegend, oder?


   1: class Zahl_transformieren
   2: {
   3:     public InputPin<int> In_Zahl { get; private set; }
   4:     public OutputPin<string> Out_Symbol { get; private set; }
   5:  
   6:     public Zahl_transformieren()
   7:     {
   8:         this.In_Zahl = new InputPin<int>(Transform);
   9:         this.Out_Symbol = new OutputPin<string>();
  10:     }
  11:  
  12:  
  13:     private void Transform(int zahl)
  14:     {
  15:         if (IsFizzBuzz(zahl))
  16:             this.Out_Symbol.Post("fizzbuzz");
  17:         ...
  18:     }

Um die eigentlichen Transformationsroutine Transform() wird nun ein IObserver<T> gewickelt in Form eines InputPin<T>. Und die Ergebnisse werden an einen OutputPin<T> geschickt, der ein IObservable<T> ist. Den können dann folgende Aktionen abonnieren.

Die Zahlengenerierung sieht analog aus. Kein Hexenwerk. Beide Aktionen verdrahte ich dann noch in einer Aktionsgruppe zu einem Flow, um die Details der Verarbeitung gegenüber einem Client zu verbergen:


   1: public class FizzBuzzer
   2: {
   3:     public InputPin<Unit> In_Generate { get; private set; }
   4:     public OutputPin<string> Out_Symbole { get; private set; }
   5:  
   6:  
   7:     public FizzBuzzer()
   8:     {
   9:         var ze = new Zahlen_erzeugen();
  10:         var zt = new Zahl_transformieren();
  11:  
  12:         this.In_Generate = ze.In_Generate;
  13:         ze.Out_Zahl.Subscribe(zt.In_Zahl);
  14:         this.Out_Symbole = zt.Out_Symbol;
  15:     }
  16: }

Dem Input-Pin der Aktionsgruppe kann der Input-Pin von Zahlen erzeugen zugewiesen werden. Sie braucht also keinen eigenen. Dito für den Output-Pin der Aktionsgruppe.

Und die konsumierende Aktion abonniert die produzierende Aktion, s. Zeile 13. Das entspricht der bisherigen Zuweisung einer Input-Pin Methode als Eventhandler an einen Output-Pin Event.

Formal ist der Unterschied zwischen bisheriger Übersetzung und der nach Rx minimal, finde ich. Das setzt sich dann bis zum Client fort, der die Aktionsgruppe nutzt:


   1: var fb = new FizzBuzzer();
   2:  
   3: fb.Out_Symbole.Subscribe(Console.WriteLine);
   4: fb.In_Generate.OnNext(new Unit());

Ein kleinwenig gewöhnungsbedürftig mag allerdings sein, dass Rx keine Kommunikation ohne Wert kennt. Deshalb muss die Zahlengenerierung mit einem Parameter angestoßen werden. Dankenswerterweise gibt es dafür Unit, sozusagen einen “wertlosen” Typ. Er ist der Funktionalen Programmierung entlehnt und findet sich auch in F# wieder.

Und jetzt zum Gewinn durch eine Umstellung der Übersetzung von EBC mit Rx:


   1: fb.Out_Symbole
   2:   .Where(s => s[0] < '@')
   3:   .Select(s => int.Parse(s))
   4:   .SkipWhile(i => i < 42)
   5:   .Subscribe(Console.WriteLine);

Wir können ganz leicht in den “Eventfluss” eingreifen. Es gibt viele Standardoperationen dafür, allen voran die bekannten und beliebten Linq-Operatoren.

Wie klingt das? Ich finde das sehr vielversprechend. Damit experimentiere ich weiter. Mein Gefühl ist, dass mit Rx die Übersetzung noch systematischer wird und gleichzeitig die Möglichkeiten zum Umgang mit Event-Strömen wachsen. Also ein doppelter Gewinn.

Abhängigkeiten bewusster wahrnehmen

Abhängigkeiten sind eine der größten Geißeln der Softwareentwicklung. Wenn es ein allgemein anerkanntes Prinzip gibt, dann ist es, Abhängigkeiten zu minimieren. Jede Hilfe ist da willkommen. Was können Sie also tun, um Abhängigkeiten im Code los zu werden?
Für die Suche nach einer Lösung und die Beurteilung von Hilfsangeboten ist es nützlich, Abhängigkeiten zu kategorisieren. Nicht alle Abhängigkeiten sind gleich. Worüber reden wir also eigentlich?

Abhängigkeit

Eine Abhängigkeit ist vorhanden, wenn einer etwas braucht, ohne dass er seine Arbeit nicht verrichten kann. Eine Funktionseinheit kann eine andere Funktionseinheit brauchen; oder sie braucht nur eine Datei an einem bestimmten Ort. Oder sie ist von einem bestimmten Format von Daten abhängig, die als Zeichenkette an sie übergeben werden.
Abhängigkeiten lauern also überall.
Mir geht es allerdings vor allem um Abhängigkeiten zwischen den grundlegenden Funktionseinheiten Assembly, Klasse und Methode.

Statische Abhängigkeit

Wenn eine Funktionseinheit (FE) schon zur Compilezeit von einer anderen abhängig ist, dann nennt man das statische Abhängigkeit oder auch statische Kopplung. Hier ein Beispiel: Klasse Client ist von Klasse Service statisch abhängig.

   1: class Client
   2: {
   3:     private Service s;
   4: }
   5:  
   6: class Service
   7: {}

Oder hier eine Methode, die mit einer anderen statisch gekoppelt ist:



   1: void Foo()
   2: {
   3:     Bar();
   4: }
   5:  
   6: void Bar()
   7: {}

Oder hier eine statische Abhängigkeit von Client nicht zur Klasse Service, sondern auch noch zu einem Member von Service:



   1: class Client
   2: {
   3:     private Service s; 
   4:     
   5:     void Foo()
   6:     {
   7:         s.Text = "hello";
   8:     }
   9: }
  10:  
  11: class Service
  12: {
  13:     public string Text;
  14: }


Oder hier statische Kopplung zwischen Assemblies:

image

Ohne, dass die Funktionseinheiten, zu denen eine Kopplung besteht, bei der Compilation vorhanden sind, ist keine fehlerfreie Übersetzung möglich. Das macht statische Abhängigkeiten vergleichsweise zahm. Es fällt einfach schnell auf, ob eine Abhängigkeit nicht erfüllt ist.

Ted Faison hat in seinem Buch “Event-based Programming” für Abhängigkeiten eine Notation eingeführt, die ich hier auch benutzen möchte. Abhängigkeiten bezeichnet er dabei so:

image

Der Pfeil zeigt vom Abhängigen zum Unabhängigen und das Symbol (Zeichen Ou des erweiterten Lateinischen Alphabets) macht klar, dass der Pfeil ein Abhängigkeitspfeil ist.


Eine statische Abhängigkeit kann dann so qualifiziert werden:

image 


Dynamische Abhängigkeit


Dynamisch ist eine Abhängigkeit, wenn erst zur Laufzeit die eigentliche unabhängige Funktionseinheit verfügbar ist; der abhängige Code kann zur Compilezeit dann nämlich nur Annahmen darüber treffen, wie das zur Laufzeit “nachgereichte” Unabhängige aussieht.

Ein typsiches Beispiel sind Abhängigkeiten von Klassen, die zur Compilezeit durch Interfaces vertreten werden:

   1: class Client
   2: {
   3:     public IService s; 
   4:     
   5:     void Foo()
   6:     {
   7:         s.Text = "hello";
   8:     }
   9: }
  10:  
  11: interface IService
  12: {
  13:     string Text { get; set; }
  14: }
  15:  
  16: class Service : IService
  17: {
  18:     public string Text
  19:     {
  20:         get { ...  } 
  21:         set { ... }
  22:     }
  23: }

Hier ist die Klasse Client von der Klasse Service dynamisch abhängig. Vom Interface IService und dessen Property Text hingegen ist sie statisch abhängig.

image

Das macht klar, wie Komponentenorientierung funktioniert: Die Assembly, die Client implementiert, muss die referenzieren, die IService implementiert – aber nicht die Assembly von Service. Client und Service können also getrennt von einander entwickelt werden. Aber die IService-Assembly, der Kontrakt von Service,muss vorher da sein, weil die Client-Assembly wie die Service-Assembly daran statisch gekoppelt sind.

Statische Abhängigkeiten erfordern mithin Colocation von Code in derselben Assembly oder zumindest, dass Assemblies einander statisch referenzieren. Die unabhängige Funktionseinheit muss dann vor der abhängigen implementiert werden.

Ein typisches Muster, um diese Form der Abhängigkeitskonstellation zwischen Klassen und Interfaces auszudrücken, ist die Inversion of Control mit der ctor-Injection:

   1: class Client
   2:     {
   3:         private IService s; 
   4:         public Client(IService s)
   5:         {
   6:             this.s = s;
   7:         } 
   8:         
   9:         void Foo()
  10:         {
  11:             s.Text = "hello";
  12:         }
  13:     }
  14:  
  15:     class Program
  16:     {
  17:         static void Main(string[] args)
  18:         {
  19:             Client c = new Client(new Service());
  20:         }
  21:     }

So werden Klassen entkoppelt, um einfacher testbar zu werden, um Implementationen austauschen zu können und um produktiver bei der Entwicklung zu sein.

Dynamische Kopplung ist loser als statische – das ist vorteilhaft. Allerdings werden Fehler erst zur Laufzeit sichtbar. Die Entscheidung zwischen statischen und dynamischen Abhängigkeiten ist also eine zwischen Flexibilität/Entkopplung und Gewissheit.

Wer flexibel sein will, der setzt z.B. die neue dynamische Typisierung ein:

   1: class Client
   2: {
   3:     public dynamic s; 
   4:     
   5:     public void Foo()
   6:     {
   7:         s.Text = "hello";
   8:     }
   9: }

Zur Entwicklungszeit muss man sich dann nicht entscheiden, wie die Instanzen von s aussehen sollen. Sie müssen lediglich eine Property oder ein Feld Text bieten.

Das funktioniert dann gut mit dem bisherigen Service:

   1: static void Main(string[] args)
   2: {
   3:     Client c = new Client(); 
   4:     c.s = new Service(); 
   5:     c.Foo();
   6: }

Ohne Kontrolle durch den Compiler kann aber auch jeder andere Typ zugewiesen werden:

   1: static void Main(string[] args)
   2: {
   3:     Client c = new Client(); 
   4:     c.s = 42; 
   5:     c.Foo();
   6: }

Und das funktioniert dann gar nicht mehr gut.

Also: Vorsicht mit dynamischen Abhängigkeiten. Flexibilität, Entkopplung hat ihren Preis. Dynamische Abhängigkeiten lassen sich schlechter kontrollieren als statische. Sollte sich am Unabhängigen bzw. bei der Bedienung einer Abhängigkeit etwas ändern, ist erst viel später klar, auf welche Abhängigen das eine Auswirkung hat.


Logische Abhängigkeit


Der Compiler meldet kein Problem und Sie haben auch Ihre dynamischen Abhängigkeiten im Griff? Das ist gut – aber Ihre Software ist dann noch nicht in trockenen Tüchern, was die Abhängigkeiten angeht. Denn da sind noch die logischen Abhängigkeiten. Und das sind die fiesesten.

Hier eine kleine Denksportaufgabe. Welche Abhängigkeiten enthält diese Funktion, die die Nachkommastellen einer Zahl abtrennen soll:

   1: static double Trunc(double n)
   2: {
   3:     var s = n.ToString(); 
   4:     s = s.Substring(0, s.IndexOf(',')); 
   5:     return double.Parse(s);
   6: }

Gibt es statische Abhängigkeiten? Klar. Der Code ist ja streng typisiert.

Gibt es dynamische Abhängigkeiten? Nein. Hier wird zur Laufzeit nichts konkretisiert, nachgereicht.

Dennoch gibt es im Code eine Abhängigkeit. Und ob die Erwartung daran erfüllt wird, zeigt sich auch erst zur Laufzeit.

Trunc() ist davon abhängig, dass die String-Repräsentation  einer double-Zahl ein Komma enthält, das die Nachkommastellen abtrennt. Zahlen müssen diesem Format folgen:

Zahl ::= Vorkommastellen [ “,” Nachkommastellen ].

Vorkommastellen, Nachkommastellen ::= Ziffer { Ziffer }.
Das ist eine legitime Annahme für eine Software, die nur in Deutschland laufen soll – für eine internationale Software hingegen sollte sie nicht getroffen werden.

Eine solche Abhängigkeit nennt man logische Abhängigkeit. Sie besteht immer dann, wenn zwei Funktionseinheiten Annahmen übereinander machen, insbesondere Annahmen über ihre Funktionsweise. Deshalb drücken sich logische Abhängigkeiten oft in Daten aus, da die das Bindeglied zwischen Funktionseinheiten sind.

Hier besteht die Annahme der Funktionseinheit Trunc() darin, dass die Funktionseinheit double.ToString() ein Komma vor die Nachkommastellen setzt.

image

Als Kontrast ein Beispiel logischer Unabhängigkeit:

   1: static int GetIndexOf(string item, string[] list)
   2: {
   3:     for (var i = 0; i < list.Length; i++)            
   4:         if (list[i] == item) return i; 
   5:     return -1;
   6: }

Die Funktion ist nicht (!) abhängig davon, dass die Einträge in der Liste in einer bestimmten Reihenfolge stehen. Sie bestimmt mit und ohne Ordnung der Listenelemente den Index des gesuchten korrekt. So ist diese Funktion logisch unabhängig von potenziellen Quellen für Listen.

Keine Annahme über die Reihenfolge der Einträge zu machen, dient also der Entkopplung. Das ist gut für die Evolvierbarkeit (oder auch Wiederverwendbarkeit), hat jedoch seinen Preis. Die Performance dieses Verfahrens ist nicht optimal. Ob das allerdings schlimm ist… das hängt vom Verwendungszusammenhang ab. Im Sinne der Prinzipien KISS und Beware-of-Premature-Optimization (BoPO) mag es sinnvoll sein, keine Annahmen zu machen und die lose Kopplung einzustreichen – bis das an eine Grenze stößt.

Logische Abhängigkeiten machen die Softwareentwicklung wahrhaft komplex. Denn erstens wird erst zur Laufzeit sichtbar, ob sie erfüllt werden. Und zweitens führt ihre Nichterfüllung nicht immer und sofort zu einem Fehler.

Trunc() kann während der Entwicklung alle automatisierten Tests bestehen. Software, die die Funktion einsetzt, kann bei Hunderten Kunden fehlerfrei laufen. Doch dann, eines Tages, kommt es zu einem Fehler. Warum? Weil ein Anwender erstmalig auf die Idee gekommen ist, die Software auf einem Rechner mit anderer default Einstellung für das Dezimaltrennzeichen laufen zu lassen.

Logische Abhängigkeiten können sehr subtil sein. Kein Compiler zeigt sie an. Kein Laufzeitsystem deckt sie auf. Womöglich werden sie nur sporadisch nicht erfüllt.

Vorsicht also an den Schnittstellen zwischen Funktionseinheiten. Dort lauern immer wieder Annahmen über empfangene Daten, die die Funktionseinheiten mehr oder weniger offensichtlich und oft unerwartet stark miteinander logisch koppeln.


Zusammenschau


Abhängigkeiten machen das Softwareentwicklerleben schwer. Denn wo Abhängigkeiten bestehen, besteht immer die Gefahr, dass sich Änderungen am Unabhängigen auf Abhängige auswirken.

Sind die Abhängigkeiten nur statisch, dann zeigt ein Übersetzungslauf an, wo Erwartungen bei Abhängigen enttäuscht werden. Insofern sind statische Abhängigkeiten unkritisch, was die Entdeckung von Nichterfüllung angeht. Diese Effizienz, diese Sicherheit wird jedoch durch Inflexibilität erkauft. Statische Abhängigkeit bedeutet immer enge Kopplung.

Dynamische Abhängigkeiten koppeln loser – lassen sich jedoch erst zur Laufzeit entdecken. Durch die heutzutage sehr einfach mögliche Automatisierung von Tests lassen sich Probleme bei der Erfüllung dynamischer Abhängigkeiten jedoch auch fast so schnell erkennen wie bei statischen Abhängigkeiten.

Programme geschrieben in dynamischen Sprachen wie Python oder Ruby leiden daher auch nicht unter größerer Fehlerhäufigkeit als Programme geschrieben in einer streng typisierten Sprache wie C#. Wer mit einer dynamischen Sprache entwickelt, stützt sich einfach nur weniger auf den Compiler und setzt stattdessen mehr auf eine Sammlung von automatisierten Tests.

Die Komponentenorientierung hilft ebenfalls weiter, da sie dynamische Abhängigkeiten bewusst plant und Kontrakte auf beiden Seiten der Abhängigkeit für Stabilität sorgen. So kann lose Kopplung als Vorteil dynamischer Abhängigkeit eingestrichen werden, ohne die Sicherheit statischer Kopplung aufzugeben.

Logische Abhängigkeiten teilen mit den dynamischen Abhängigkeiten, dass sie erst zur Laufzeit festgestellt werden können. Sie gehen in ihrer Gefährlichkeit jedoch darüber hinaus. Sie sind oft unsichtbar, sie sind oft subtil, sie machen womöglich nur sporadisch Probleme. Es hilft aber nichts: Wir müssen mit ihnen leben.

Ohne logische Abhängigkeiten keine Zusammenarbeit. Wer sich nicht mindestens logische abhängig macht, steht allein.

Bei Planung, Test und Review Ihres Codes achten Sie daher besonders auf logische Abhängigkeiten. Wo Sie sie erkennen, dokumentieren Sie sie. Am besten mit einem Test. Beispiel:

   1: [Test]
   2: public void GetIndexOf_does_not_require_the_list_to_be_sorted()
   3: {
   4:     Assert.AreEqual(2, 
   5:                     Helpers.GetIndexOf("x", 
   6:                                        new[] { "k", "f", "x", "s" }));
   7: }

Das kann ein Unit Test wie hier sein. Das kann aber auch ein Integrationstest sein, der live die Funktionseinheiten, die Annahmen übereinander machen, zusammenspielen lässt.

.NET 4 Code Contracts könnten auch helfen, um logische Abhängigkeiten zu dokumentieren.

In jedem Fall gilt jedoch: zentralisieren Sie logische Abhängigkeiten. Lassen Sie möglichst nur eine Funktionseinheit in einer bestimmten Hinsicht logisch von einer anderen abhängig sein.

Ein Beispiel dafür ist ein Proxy in der verteilten Kommunikation. Der zentralisiert die logische Abhängigkeit zwischen Client und Server in Bezug auf das Datenformat auf der Leitung. Der Client nimmt z.B. an, dass der Server SOAP-Nachrichten versteht. Statt nun aber diese Annahme an vielen Stellen im Code zu treffen, zentralisiert man sie im Proxy. Sollte sich das Datenformat ändern, trifft die Annahme also nicht mehr zu, dann ist nur eine Funktionseinheit betroffen.

Der Proxy ist sozusagen dafür zuständig, eine logische Abhängigkeit in eine statische zu verwandeln. Denn wo der Proxy von SOAP-Nachrichten logisch abhängt, da hängt Code, der ihn nutzt, z.B. nur von einem POCO ab, das der Proxy umwandelt in einen Teil einer SOAP-Nachricht.

Seien Sie also wachsam, was die Abhängigkeiten in Ihrer Software angeht. Unterscheiden Sie zwischen statischen, dynamischen und logischen. Werden Sie sich der Abhängigkeiten in Ihrem Code bewusst. Wählen Sie die eine oder andere Form mit Bedacht. Prüfen Sie die Erfüllung von Abhängigkeiten automatisiert. Dann sind Sie auf einem guten Weg, die Komplexität Ihrer Software zu verringern.