Follow my new blog

Donnerstag, 14. Februar 2013

Experiment Selbstverlag - Mein erstes Kindle-Buch

imageMein neues Buch ist endlich erschienen:

Systematisch produktiver und zufriedener
Pragmatische Schritte raus aus der Überlastung am Arbeitsplatz

Als Kindle-Buch bei amazon. Hurra, geschafft, endlich, nach ein paar Anläufen.

Vor Jahren hatte ich ja schon einige Bücher geschrieben, Bücher auf Papier, pBooks. Da gab es allenfalls zaghafte Versuche, die auch mal in PDF herauszubringen. Aber jetzt, knapp 10 Jahre später, ist alles anders. Heute sind Bücher elektronisch gleichwertig, eben echte eBooks. Und deshalb sind Produktion und Veröffentlichung anders möglich. Einen Verlag braucht man dafür nicht mehr – höchstens für das Marketing.

Und so habe ich mich mal aufgemacht, ein eBook im Selbstverlag herauszubringen. Bei diesem Experiment geht es mir um die Form und den Prozess, weniger um den Inhalt. Deshalb ist der Inhalt auch eine Zweitverwertung von Blogartikeln. Das soll aber natürlich den Wert des eBooks nicht schmälern, denn als Zusammenfassung für bequemes Lesen in einem Stück und aufbereitet für eReader bietet es etwas fürs Geld.

Insofern ist das eBook natürlich auch ein Experiment in puncto Monetarisierung von Inhalten. Ja, das will ich nicht verschweigen. Es geht auch ums Geld. Warum auch nicht?

Allerdings finde ich die Monetarisierung über Werbung sehr unschön. Sie mag funktionieren, doch hat sie für mich den Beigeschmack des Unehrlichen. In ihr steckt auch immer der latente Keim von Prostitution. Aber ich will nicht abschweifen… ;-)

Ohne Einnahme von Geld durch Werbung bleibt für die Monetarisierung nur eine direkte Transaktion mit dem Leser. Aber wie? Für Blogbeiträge ist das auch im Jahr 2012 immer noch schwierig. Das hat weniger mit Technik zu tun, würde ich sagen, sondern eher mit mangelndem “Standard”. Paypal mögen viele nicht, bei Micropayment-Diensten wie Flattr will sich niemand so recht auch noch anmelden…

Und dann stellt sich auch noch die Frage, ob jemand für das Medium “HTML-Seite” etwas bezahlen möchte? Das fühlt sich nicht sehr wertig an, oder? Jedenfalls nicht, wenn man für einzelne Inhalte zahlen soll und für andere wieder nicht. Als Abo mag das irgendwie noch funktionieren und so können Zeitungen sich langsam hinter Paywalls zurückziehen. Für Autoren ist das jedoch keine Option.

amazon als Veröffentlichungsplattform

Aber es gibt ja amazon! Da kommt für Autoren dreierlei zusammen:

  • Erstens haben wahrscheinlich 99% aller Leser schon einmal bei amazon gekauft. Es gibt also keine Anmeldehürde zu überwinden, um Geld fließen zu lassen.
  • Zweitens hat amazon es Autoren vergleichsweise sehr leicht gemacht, Inhalte anzubieten.
  • Und drittens ist das Kindle-eBook-Format derzeit nach meinem Empfinden das für den Leser einfachste [1].

Bis sich einfache (Micro)Payment-Lösungen per Smartphone durchgesetzt haben, scheint mir amazon für Autoren der einfachste Weg, zu einem direkten, ehrlich Austauschverhältnis mit dem Leser.

Für einen Beitrag wie diesen, ist Monetarisierung natürlich keine Option. Abhängig von Umfang und Inhalt wird es immer Inhalte geben, die Blogger/Autoren im Allgemeinen und ich im Speziellen kostenlos anbieten. Für Umfangreicheres jedoch glaube ich daran, dass der Trend zu einer Monetarisierung gehen wird. Das schmeckt dann nicht jedem Leser – aber das ist ok. Mit denen, die übrig bleiben und sich darauf einlassen, habe ich gern ein engeres Verhältnis, das mehr auf Gegenseitigkeit beruht.

Doch das ist derzeit noch weitgehend Theorie. Zuerst mal ausprobieren, wie das überhaupt technisch und vom Prozess her geht mit dem Selbstverlegen.

Vorgehen bei der Veröffentlichung

imageamazon macht das wirklich sehr simpel. Einfach bei Kindle Direct Publishing (KDP) anmelden und Manuskript hochladen.

Das Manuskript kann in Word geschrieben werden. Einfach ein paar simple Formatierungsregeln beachten [2]. Dann ist das kein Problem.

Allerdings aufpassen mit Bildern. Da mögen ein paar Experimente angezeigt sein, je nachdem um was für Abbildungen es sich handelt, Grafiken oder Fotos. Am Anfang habe ich dafür ein kleines Testdokument mit Bildern in verschiedenen Auflösungen zusammengestellt und bei KDP immer wieder nach Veränderungen hochgeladen.

Auf der KDP-Seite gibt es eine Vorschau, die verschiedene Kindle-Reader simuliert, und einen Download für das Manuskript als .mobi-Datei. Statt auf die Simulationen habe ich mich dann lieber auf mein iPad verlassen :-) Damit kann ich auch in Abbildungen reinzoomen, um die Qualität zu überprüfen.

Am Ende habe ich die meiste Zeit in Word für Mac OS X gearbeitet und die Grafiken nur mit 72dpi Auflösung eingebunden. Nur am Schluss habe ich Word für Windows angeworfen, um das Inhaltsverzeichnis einzusetzen – das macht WinWord nämlich besser mit Verweisen auf die Kapitel – und für den Export nach HTML – auch der scheint subtil besser zu sein als bei MacWord.

Beim Titelbild ist dann noch etwas Kreativität gefragt. Kann man selbst machen, wie ich es getan habe. Aber ein Profigrafiker würde dafür auch kein Vermögen in Rechnung stellen. Mit einem einfarbigen Titelbild sollte man sich in jedem Fall nicht zufrieden geben ;-)

Nach dem Hochladen des finalen Manuskripts hat es dann nochmal 2 Tage gedauert, bis das Buch im amazon Shop zu sehen war. Das mag daran gelegen haben, dass ich es an einem Sonntag abgeschickt hatte. Dazu kam dann allerdings auch noch eine Nachfrage von amazon, die Material im Buch entdeckt hatten, das frei im Web verfügbar ist. Ja, wie denn auch nicht. Es sind ja meine Blogartikel :-) Da musste ich nochmal bestätigen, dass ich der Copyright-Inhaber bin.

Preisfindung

Ein bisschen hat mich noch die Preisfrage umgetrieben. Wie viel “darf” so ein eBook kosten? 0,99 EUR oder 1,49 EUR oder 2,68 EUR oder 5,79 EUR oder 8,45 EUR? Ich habe mich für den niedrigsten Preis entschieden, der mir als Autor noch 70% vom Nettoverkaufspreis bringt. Das ist ein Experiment. Runtergehen kann ich eher mit dem Preis, als ihn rauf zu setzen.

Einen einzelnen Artikel kann man bei vielen Zeitschriften online als PDF für 0,99 EUR oder 1,50 EUR kaufen. Das sind dann vielleicht 4-5 Heftseiten, also ca. 8-9 A4 Seiten. Mein eBook enthält aber rund 30 A4 Seiten, also ca. 3-4 Mal so viel wie ein Artikel. Deshalb denke ich, dass 2,68 EUR Verkaufspreis angemessen sind.

Ein anderer Vergleichsmaßstab ist für mich – wie auch im Klappentext angedeutet – der Preis eines Stückes Kuchen oder ein Heißgetränk im Coffeeshop. Da geben wir alle ohne zu zucken 2,50 EUR oder auch weit mehr aus, um uns einen sehr flüchtigen Genuss zu verschaffen. Insbesondere wenn wir “to go” wählen, leistet der Coffeeshop nicht mehr, als einen Becher auszugeben. Wir nutzen keine weitere Infrastruktur. So schlappen wir durch die Gegend mit 2,50 – 4,90 EUR in der Hand, die in 15 Minuten im Magen verschwunden sind. Für eine ganze Stunde “Genuss” würden wir mithin 15,00 – 20,00 EUR ausgeben – das ist mehr als im Kino. Selbst “Rauchgenuss” ist viel billiger.

Ich gebe auch Geld aus für solche modernen Heißgetränke. Dabei sitze ich jedoch meist in meinem Lieblings-Coffeeshop. Ich bezahle deshalb eher für die “Aufenthaltsmöglichkeit” als das Getränk. Aber einerlei. Wenn ich bereit bin, für solch flüchtigen Genuss soviel Geld auszugeben, dann finde ich es nicht zu hoch gegriffen, für 45 – 60 Minuten Lesestoff 2,68 EUR anzusetzen. Vor allem, wenn der Lesestoff auch noch den Anspruch hat nachzuwirken; ich hoffe ja, beim Leser etwas mit dem eBook-Inhalt zu bewirken.

Aber, wie gesagt, das mit dem Preis ist ein Experiment. Für Musik haben sich inzwischen 0,99 EUR pro Titel eingebürgert. Dafür bekommt man 3 – 6 Minuten Ohrenschmaus. Bei eBooks müssen wir noch herausfinden, wo Käufer und Leser möglichst reibungsfrei zusammenfinden.

Nach dem Experiment…

…ist vor dem Experiment. Mein Kindle-Experiment habe ich erstmal abgeschlossen. Von der Idee bis zur Verfügbarkeit des eBooks im amazon Shop hat es 6 Tage gedauert – davon 2 bei amazon.

Aber das Selbstverlegen ist damit nicht zuende. So ist das Buch nur bei amazon erhältlich. Wie kriege ich es denn aber auch noch auf andere Plattformen oder auf Papier – für die, die das dringend wollen?

Es sind also noch einige weitere Experimente nötig. Mit CreateSpace bietet amazon einen Weg zum Papier. Alternativ könnte ich mich aber auch für lulu.com oder epubli.de entscheiden. Und was ist mit anderen eBook-Plattformen? Wer den lokalen Buchhandel unterstützen möchte, hätte es lieber, ein eBook über einen anderen Kanal zu kaufen, bei dem auch sein Buchhändler etwas davon hat. Vielleicht probiere ich mal bookrix.de als Vermittler aus.

Und natürlich werde ich beim Inhalt experimentieren. Zweitverwertung als eBook finde ich völlig in Ordnung. Doch ich will auch neue Inhalte für eBooks schreiben.

Endnoten

[1] Kindle-Reader gibt es als Gerät und Apps für jede Plattform. Die Übertragung des eBooks auf Gerät/App ist ein no brainer. Und der Lesefortschritt sowie die Notizen werden über Gerät/App hinweg synchronisiert. Diese Einfachheit bietet derzeit sonst kein Anbieter, nicht Thalia mit textunes und auch nicht libri mit seiner eBook Reader App. Und iTunes? Ist zwar für den Leser einfach, doch für den Autor schwierig. Ohne Vermittler, der mir das Administrative und auch die Abrechnung einfacher macht, würde ich da nichts einstellen.

[2] Es gibt einige Literatur im Web und bei amazon dazu, was es bei Kindle-Büchern zu beachten gibt, z.B.

Dienstag, 12. Februar 2013

Wenn Agilität abhebt

Ich bin ja ein großer Freund von agilem und schlankem Vorgehen in der Softwareentwicklung. Aber derzeit scheint mir die Agilität ein bisschen unbescheiden. Sie hebt immer mal wieder ab. So auch in diesem Artikel von Henrik Kniberg (meine Hervorhebung):

image

Was soll das denn? Vor 2000 wussten Softwareunternehmen nicht, wie man Software ausliefert? Ja, ist das so gewesen?

Und vor 2000 haben Unternehmen keine nützliche, funktionierende Software (“working software”) ausgeliefert, falls sie es denn überhaupt geschafft haben, welche aus der Tür zu bekommen? Ja, ist das so gewesen?

Ich hatte in den 1980ern und 1990ernn schon den Eindruck, mit “working software” zu arbeiten. Und ich habe mit meiner Branchensoftwarefirma 10 Jahre lang selbst “working software” ausgeliefert. Sonst hätten mein Partner und ich uns und unseren Angestellten kein Gehalt zahlen können. (Er liefert übrigens immer noch “working software” aus und lebt davon – obwohl er ganz unagil arbeitet.)

Dass etwas im Argen war mit der Art, wie wir in unserer Branchensoftwarebude Software entwickelt und ausgeliefert haben, ist unbenommen. Und es liegt auch heute noch viel im Argen bei dem, wie Software entwickelt wird. Natürlich bin ich dafür, dass mehr Teams, agil und/oder schlank werden.

Aber wir sollten doch bitteschön nicht verkennen, wie viel geleistet wird ganz ohne Agilität und Schlankheit. Die Softwareentwicklung hat nicht erst mit der Agilität angefangen. Und Agilität ist bei weitem nicht in alle Köpfe, Teams und Unternehmen bisher vorgedrungen. Sind deshalb die, die noch nicht agil oder schlank sind, Hinterwäldler und Dummköpfe? Kriegen die nichts gebacken?

Ein bisschen Bescheidenheit stünde der Agilitätsbewegung gut zu Gesicht, finde ich. Mit solchen Aussagen gewinnt man keine neuen Freunde. Eine abgehobene, arrogante Agilität nützt niemandem.

Und wie kann das Abheben vermieden werden? Ich glaube weiterhin, dass dafür eine glasklare Definition von Agilität nötig ist. Henrik Kniberg bleibt Sie trotz Link auf das agile Manifest schuldig. Und Martin Fowler weiß es nicht besser, sondern erkennt sie einfach, wenn er ihr mal begegnen sollte.

Ob Sie nun meiner persönlichen Definition von Agilität zustimmen oder ihre eigene haben… Wir sollten uns darüber unterhalten, was Agilität im Kern ausmacht. Dass mit ihr “working software” hergestellt werden kann, ist nichts Neues und nichts Besonderes. Ein bisschen mehr Butter bei die Fische darf schon sein.

Was ist also das klar umrissene Nutzenversprechen der Agilität, das sie vom Bisherigen abhebt? Wie kann Agilität einem gestandenen Geschäftsführer, der seit 20 Jahren nützliche und funktionierende Software an den Markt bringt und 20 Leute damit ernährt, in nicht arroganter Weise nahegebracht werden?

Und dann: Mit welchen Merkmalen, Hilfsmitteln, Methoden, Konzepten, Paradigmen versucht Agilität dieses Nutzenversprechen einzulösen? Warum sollen die funktionieren und nicht anderes?

Mein Empfinden ist, dass es bei all der Literatur zu Agilität noch Erklärungsbedarf gibt. Die Marketing-Arbeit der Agilisten ist nicht vorbei. Die Zeit für siegerhafte Arroganz ist noch nicht gekommen. Und sollte am besten auch nie kommen.

 

PS: Und dass das heutige Problem vor allem sei, das falsche Produkt zu bauen, halte ich auch noch nicht für ausgemacht. Sicherlich ist das ein nicht zu leugnendes Problem – nur hat das nicht speziell mit Softwareentwicklung zu tun. Für viel dringender halte ich das Problem mangelnder Evolvierbarkeit. Die Unwartbarkeit, das Brownfield, die Legacy Code Berge… sie drohen überall. Denn was will man denn machen, wenn man denn nun endlich weiß, welches Produkt man bauen will – und man kriegt es nicht hin? Dann ist das Händeringen umso größer. Deshalb: Ich bin dafür, zuerst die Bedingung für die Möglichkeit des richtigen Produktes anzugehen. Und das ist eine Codebasis, die sich in welche Richtung auch immer geschmeidig anpassen lässt.

Donnerstag, 7. Februar 2013

Performance vom Schwanz her aufzäumen

Wer ist es eigentlich, der höhere Performance fordert? Der Kunde, ist doch klar. Den meine ich aber nicht. Mir geht es um etwas Grundlegenderes.

Wenn wir über Performance reden, dann haben wir ein Delta im Hinterkopf, eine Dauer zwischen zwei Ereignissen. Es geht um ein auslösendes Ereignis, an dem Input hängt, und ein abschließendes Ereignis, an dem Output hängt.

Das kann die Dauer zwischen der Eingabe einer URL und der Anzeige der Webseite sein; das kann die Dauer zwischen der Eingabe von Zahlen und der Anzeige einer Funktionskurve sein; das kann die Dauer zwischen dem Bewegen eines Mauszeigers und der Reaktion einer Figur auf dem Bildschirm sein.

Immer geht es um Delta = T_Ergebnis – T_Auslöser. Höhere Performance bedeutet, dass Delta kleiner wird.

Jetzt zurück zur Ausgangsfrage: Wer fordert denn aber, dass Delta kleiner wird?

Es sind zwei Rollen im Spiel: der Auslöser und der Empfänger. Der Auslöser startet die Verarbeitung, deren Performance in Frage steht. Er liefert den Input oder zumindest Teile davon; der Rest kann aus Zustand kommen, auf den die Verarbeitung Zugriff hat.

Der Empfänger andererseits ist derjenige, den das Ergebnis der vom Auslöser gestarteten Verarbeitung interessiert. Er betrachtet den Output und fällt daraufhin Entscheidungen. Output ist ein Unterschied, der für ihn einen Unterschied macht, d.h. Information.

image

Nun zum dritten Mal: Und wer interessiert sich nun für ein kleineres Delta? Immer nur der Empfänger.

Das ist mir heute aufgegangen. Es ist immer nur der Empfänger von Output, dem daran gelegen sein kann, den Abstand zum auslösenden Ereignis zu reduzieren, weil das irgendeinen Vorteil für ihn hat.

Es hat lange für mich gedauert, bis ich das begriffen habe. Bis heute. Jahr um Jahr hab ich geglaubt es sei der Auslöser, den Performance interessiert. Oder genauer: Ich habe Auslöser und Empfänger nicht einmal unterschieden. Sie sind oft dieselbe Person oder ich habe vor allem mit dem Auslöser als Kunde gesprochen.

Aber es gibt in Performancefragen immer beide Rollen und nur der Empfänger ist maßgeblich.

Das hat nun Auswirkungen auf die Performancediskussion, würde ich sagen. Denn eine Steigerung der Performance ist nur da relevant, wo ein Empfänger erstens eine Ahnung davon hat, wann das auslösende Ereignis vor dem Empfang stattgefunden hat. Oder es ist generell von Wert, dass das Delta zwischen Auslöser und Empfang klein ist, auch wenn unklar ist, wann das auslösende Ereignis stattgefunden hat. Beispiele:

  • Sie sind Empfänger von Informationen über Bücher. Die Performance ist für Sie dabei vor allem wichtig, wenn Sie Auslöser der Beschaffung dieser Informationen sind. Sie stöbern bei amazon und wünschen sich natürlich möglichst zügig ein Abfrageergebnis. In dieser Doppelrolle ist Performance für Sie wichtig.
  • Sie sind wieder Empfänger von Informationen über Bücher. Dieses Mal haben Sie jedoch keine Kontrolle über eine Auslösung. Das initiale Ereignis für eine Verarbeitung ist nicht eine Abfrage, sondern das Einpflegen von Titelinformationen. In diesem Fall interessiert Sie die Performance des online Shops nicht. Wenn überhaupt, machen Sie sich Gedanken über die Performance von amazon als Organisation mit ihren Prozessen.
  • Jetzt sind Sie Empfänger von Adressinformationen in einer Faktura-Anwendung. Sie müssen Kunden mahnen. Was ist das für Sie auslösende Ereignis in Bezug auf Adressen? Das Einpflegen. Damit haben Sie nichts zu tun. Das macht eine andere Abteilung, z.B. der Verkauf, jedenfalls nicht die Buchhaltung. Wie lange das Softwaresystem also braucht, um eine Adresse “zu verarbeiten”, ist für Sie nicht wichtig.
  • Auf der anderen Seite sitzt nun der Auslöser einer Adressveränderung. Ist der an der Performance einer Adressenspeicherung interessiert? Nein. Er ist ja nur Auslöser. Wenn er eine Datenänderung erfasst hat, will er nur schnell weiter zur nächsten Datenerfassung. Wie lange es dauert, bis eine Adressenänderung überall wirksam wird, ist ihm einerlei, da er kein Empfänger ist.
  • Schließlich sind Sie Geschäftsführer einer Pizzakette. Sie sind Empfänger von Verkaufszahlen. Was ist Ihre Erwartung an die Performance der Unternehmenssoftware? Auf Ihre Abfrage wollen Sie schnell eine Antwort. Da sind sie sowohl Auslöser wie Empfänger. Aber der Auslöser einer Veränderung im Datenbestand sind Sie nicht. Sekunden- oder auch nur stundengenaue Auskunft müssen Sie nicht über die Zahlenentwicklung haben. Ihnen reichen die Zahlen des Vortages oder gar der Vorwoche.

Sie ahnen, worauf ich hinaus will. Ich möchte eine Lanze für mehr asynchrone Verarbeitung brechen. Und ich möchte dafür sensibilisieren, dass Performance im Auge des Empfängers liegt. Die Frage ist immer, was der für ein Verhältnis zu welchem auslösenden Ereignis hat.

Ist das auslösende Ereignis eine Query oder die Ankunft von Daten im System?

Mein Empfinden ist, dass das zu oft nicht unterschieden wird. Der Auslöser wird naiv gleichgesetzt mit dem Empfänger. Und beim Empfänger denkt man nicht an den Unterschied zwischen Datenankunft und Abfrageauslösung.

Solange hohe Performance billig herzustellen ist, macht die fehlende Unterscheidung auch nichts. Wenn Performance aber anfängt die Diskussion zu dominieren, wenn sie sich immer wieder in den Weg der Arbeit an anderen Aspekten wirft, dann sollten Sie genauer hinschauen.

Mir scheinen drei Begriffe dafür interessant: Änderungsfrequenz, Abtastfrequenz und Reaktionszeit.

Die Änderungsfrequenz gibt an, wie häufig sich Daten in relevanter Weise ändern. Mein Lieblingsautor schreibt jedes Jahr ein Buch. Der Benzinpreis ändert sich vielleicht 5 Mal am Tag bei meiner Tankstelle. Die Temperatur draußen ändert sich unter Umständen stündlich.

Wenn für mich diese Ereignisse grundsätzlich interessant sind, so ist nicht unbedingt jedes Ereignis auch relevant. Ich möchte zwar keine Neuerscheinung meines Lieblingsautors verpassen, interessiert mich der Benzinpreis nur alle paar Tage und die Temperatur auch nur gelegentlich während des Tages.

Während die Änderungsfrequenzen 1/Jahr, 5/Tag und 24/Tag sind, sind meine Abtastfrequenzen vielleicht 12/Jahr, 1/Woche und 2/Tag.

Wenn ich häufiger abtaste als das Ereignis eintritt, dann ist mir an möglichst kleinem Verzug gelegen. Besser als solches Polling wäre natürlich eine Benachrichtigung. Leider bietet amazon so etwas bisher nicht an, wenn ich es recht sehe. Beim Buch möchte ich im Mittel also nicht später als 2 Wochen nach Erscheinen (halbe Periode der Abtastfrequenz) darüber informiert sein. amazons Performance muss nur so gut sein, wie mein Anspruch an meine Reaktionszeit ist.

Wenn sich Daten jedoch häufiger ändern als ich abtaste… dann ist mir die Aktualität offensichtlich nicht so wichtig. Zwischen zwei Abtastungen kann sich ja viel ändern. Der Benzinpreis könnte um 50% schwanken, ohne dass ich es merke, auch ohne dass es für mich relevant wäre, weil mein Tank noch so voll ist, dass sich nachtanken nicht lohnte. Was bedeutet das für die Performance der Datenpflege? Ich denke, die Performance ist in diesem Fall nicht so wichtig. Denn ob ich den tatsächlich aktuellen Wert erfahre, wo mich doch zwischenzeitliche Werte nicht interessiert haben, oder einen etwas älteren, weil die Verarbeitung des aktuellen noch nicht abgeschlossen ist, das scheint mir einerlei.

Daraus ergibt sich für mich zusammenfassend:

In Performancefragen ist zuerst zu klären, wer Auslöser und Empfänger einer Datenveränderung sind.

Dass sowohl Auslöser wie Empfänger in Bezug auf ihre unmittelbare Interaktion mit der Software immer beides sind, ist trivial. Dadurch darf man sich nicht verwirren lassen. Wenn einer von beiden einen Befehl absetzt, dann will er schnellstmöglich danach weitermachen. Die Software soll nicht einfrieren. Das ist jedoch unabhängig von den dahinter stehenden Datenänderungen im System!

Wer Daten einpflegt, will nur sicher sein, dass die Daten der weiteren Verarbeitung verlässlich übergeben wurden. Das muss schnellstmöglich geschehen.

Und wer Daten abfragt, der will schnellstmöglich eine Antwort. Wie aktuell die Daten jedoch sind, die in die Antwort eingehen, das ist eine ganz andere Sache.

image

Zwischen Übergabe an die Verarbeitung und Abfrage kann eine lange Zeit liegen. Nur diese Zeit ist maßgeblich für die Verarbeitungsgeschwindigkeit. Ausschlaggebend ist die gewünschte Reaktionszeit. Denn danach wird sich ein Empfänger für seine Abtastfrequenz richten; die muss seinem Anspruch an Datenaktualität entsprechen.

Performanceoptimierung ist mithin eine Sache des Zugs: Wie oft zieht ein Interessent an den Daten? Wie ist seine Abtastfrequenz in Bezug auf die Änderungsfrequenz der Daten?

Zäumen Sie das Performance-Pferd von diesem Schwanz her auf. Nur wenn die Abtastfrequenz über der Änderungsfrequenz liegt, scheint mir Tuningaufwand gerechtfertigt. Und unterscheiden Sie deutlich zwischen dem, der Datenänderungen anstößt (Einpfleger) und dem, der sie empfängt (Abfrager). Meine Vermutung ist: Sie werden viele Möglichkeiten finden, die Datenverarbeitung asynchron zu machen. Und das ist eine sehr entspannende Aussicht, finde ich.

Freitag, 1. Februar 2013

Softwareentwurf in der hohlen Hand

Gerade komme ich wieder von drei Trainingstagen zum Thema agiler Softwareentwurf. Ein inhouse Training für ehemalige C bzw. C++ Entwickler, die sich noch an .NET gewöhnen müssen. Aber es hat vielleicht gerade deshalb besonders Spaß gemacht, weil sie noch nicht so “objektorientiert verdorben” sind. Ihnen steckt vor allem die C-Praxis in den Knochen, die sie anscheinend offen für den Softwareentwurf mit Softwareuniversum und Flow-Design gemacht hat. Denn wenn die prozedurale Programmierung in einem Recht hatte, dann darin, dass es zuerst und vor allem um Funktionalität und nicht um Daten geht.

Aber von dem positiven Feedback, das Stefan Lieser und ich dort bekommen haben, wollte ich gar nicht sprechen. Die gute Stimmung bei den Trainingsteilnehmern hat vielmehr mich selbst nochmal unseren Ansatz frisch erleben lassen.

Abends saß ich nämlich im Hotel bei einem gemütlichen Kaminfeuerchen und habe an einer Anwendungsidee herumgebastelt. Eine Web-Anwendung, die helfen soll, eine Klippe des Kennenlernens über online Partnerbörsen zu umschiffen. Wie das gehen soll wird die dotnetpro demnächst berichten ;-) Technologisch mit von der Partie werden jedenfalls AppHarbor, Nancy und iron.io sein.

Da saß ich also und habe darüber nachgedacht, wie die Idee in Software funktionieren könnte. Welche Seiten sollte so ein Website haben? Wie sollten die Seitenübergänge sein? Was sollte dabei passieren? Welche Klassen/Komponenten sollte es geben? Wie würden Feature ineinander greifen?

Im Kopf kann ich so etwas bewegen – aber nur bis zu einer gewissen Grenze. Dann muss ich einen Dump machen. Irgendwie muss ich meine Gedanken und Ergebnisse manifestieren. Visual Studio hatte ich vor dem Kamin aber nicht zur Hand. Warum auch? Ich brauche keinen Code, um Software zu entwerfen.

So hat mir denn ein kleiner Notizblock des Hotels geholfen. Der war nur handtellergroß mit schmalen Blättern. Aber er hat mir gereicht wie diese Bilder zeigen:

image

image

Wenn Sie nicht wissen, was die Symbole bedeuten und meine Schrift nicht lesen können, macht das nichts. Ich will Ihnen ja nicht erklären, was die Anwendung tut und wie ich gedenke, das zu realisieren.

An dieser Stelle soll nur rüberkommen, dass ich mit zwei kleinen Zetteln die Software entwerfen konnte. Ich weiß nun, welche Klassen ist brauche. Ich weiß, welche Methoden die haben. Ich weiß, wie alles zusammenhängt und die Funktionalität herstellt. Ich weiß, welche Daten fließen. Ich weiß, wie ich APIs kapseln muss. Ich weiß sogar schon, wie ein guter Teil des Codes aussehen muss.

Den Entwurf der beiden Bilder kann ich “so runtercodieren”. Da ist alles drin, was ich brauche. Nur das Layout der Seiten fehlt. Aber das gehört ja auch nicht zum funktionalen Entwurf. Wichtig ist dafür nur, welche Interaktionen der Benutzer mit Dialogen hat. Und die stecken in dem Modell drin.

Durch das Training geöffnet habe ich frisch erlebt, wie effektiv Softwareentwurf mit Papier und Stift sein kann – wenn man eine geeignete Methode hat ;-) Ich muss nicht darauf warten, dass Softwarestrukturen durch Tests wachsen. Ich brauche keine IDE, um bei einer Anwendung voran zu kommen.

Klar, am Ende muss implementiert werden. Bubbles don´t crash. Ich weiß nicht, ob mein Entwurf 100% korrekt/ausreichend ist. Aber ich bin ein gutes Stück voran gekommen. Ich habe mit “systematischer Kritzelei” das Codieren solide vorbereitet. Wenn ich dann Details mit TDD austreiben will, weiß ich, wo ich ansetzen kann.

Und all das steckt in zwei Blättchen mit Flow-Designs. Das fand selbst ich, der ich täglich mit Flow-Design arbeite, irgendwie bemerkenswert cool.

Samstag, 26. Januar 2013

Softwareentwurf als Video – Ein Experiment

Wie entwirft man Software? Wie kann man über Softwarestrukturen nachdenken und reden, ohne am Code zu kleben?

Dazu entwickeln Stefan Lieser und ich als die Clean Code Advisors seit Jahren einen Ansatz unter wechselndem Namen: alles begann mit Softwarezellen, dann kamen Feature Streams dazu, die haben Event-Based Components auf den Kopf gestellt und im Augenblick steht im Kern Flow-Design. Alles fließt – nicht nur in der Software, sondern auch bei unserer Methode. Wir voran gekommen, haben einen pragmatischen Weg gefunden – aber wir sind sicher noch nicht am Ende [1].

Wie dieser Weg beschritten werden kann, thematisieren wir in Zeitschriftenartikeln und Blogpostings. Und wir führen in Trainings darin ganz konkret ein, zum Beispiel in der Clean Code Developer Akademie oder auch inhouse. Immer wieder werden wir aber auch gefragt, ob es nicht etwas “zum Zuschauen” gäbe, bei dem man verfolgen kann, wie die Anwendung funktioniert.
Bisher musste wir verneinen. Außer einem Vortragsmitschnitt hier und da, in dem ich mal Modellierung präsentiert habe, gibt es keine “Bewegtbilder” zum Softwareentwurf, wie wir ihn verstehen.

Das könnte sich jetzt aber ändern. Stefan und ich haben nämlich ein Experiment gemacht. In Regensburg haben wir uns um ein SMART Board versammelt und mitgeschnitten, wie wir eine Anwendung entwerfen und beginnen zu implementieren.

Das Ergebnis sehen Sie hier:



Wie gesagt, es ist ein Experiment. Alles steht letztlich zur Disposition. Wir freuen uns über Feedback.

Das Video gliedert sich in mehrere Abschnitte:
  • 00:18:18: Erklärung der Aufgabe
  • 02:13:14: Überblick über den Lösungsansatz verschaffen
  • 05:30:11: UI skizzieren
  • 09:24:03: Interaktionen identifizieren
  • Modellierung von…
    • 12:12:17: Inkrement #1
    • 15:58:21: Inkrement #2
    • 24:02:14: Inkrement #3
    • 28:24:05: Inkrement #4
  • 39:25:03: Kontrakte definieren, um zu zweit komponentenorientiert entwickeln zu können
  • 50:27:13: Review der Kontraktcodierung
  • 51:42:09: Review der Implementation des ersten Inkrements
  • Den Code können Sie im Github Repo einsehen. Dort liegen auch die Entwürfe als PDF.
Ob das zu viel oder zu wenig ist, werden Ihre Rückmeldungen ergeben. Aber wir haben uns auch schon eine Meinung gebildet ;-)

Doch nun: Vorhang auf!

Schauen Sie sich das Video an. Danach können Sie unsere Retrospektive zur Produktion lesen.

Retrospektive

Der Dreh hat Laune gemacht. Das war schon mal wichtig :-) Ganz grundsätzlich hat alles ziemlich gut geklappt. Abgesehen von kleinen technischen Problemen haben alle Werkzeuge geschnurrt. Auch der Videoschnitt verlief reibungslos – allerdings muss ich mal über eine Firewire Festplatte und/oder einen Rechner mit mehr Kernen nachdenken. Es wehte einige Stunden ein deftiger Lüfterwind in meiner Bude.

Ob wir das SMART Board allerdings weiter einsetzen, wissen wir noch nicht. Wie die Totale im Video zeigt, ist es mit dem Licht nicht so einfach. Die Projektionsfläche ist hell – aber Stefan und ich erscheinen als Schattenspieler davor. Die Raumhelligkeit hat für uns nicht ausgereicht. Das nehmen Kameras anders wahr als menschliche Beobachter im selben Raum. Das nächste Videoexperiment wird sicherlich ein anderes Darstellungsmittel ausprobieren.

Auch beim Ton müssen wir nochmal schauen, was wir tun können. Einerseits könnten wir etwas lauter sprechen. Andererseits darf die Akustik etwas besser werden. Das Nuscheln müssen wir uns aber in jedem Fall abgewöhnen ;-)

Mit mehreren Kameras aufzunehmen und zwischen ihnen zu schneiden, war in jedem Fall eine Leichtigkeit. Das behalten wir bei. Dadurch ergibt sich eine natürliche Lebendigkeit im Video ohne großen Aufwand. Wechsel zwischen Küche, Fahrrad, Auto, Schreibtisch usw. ohne inhaltlichen Bezug, wie bei anderen Videoproduktionen, können wir uns sparen ;-)

Nicht zufrieden sind wir jedoch mit der Präsentation des Inhalts. Da sind wir einen Kompromiss eingegangen, weil wir organisatorischen Aufwand befürchtet haben, der das Ergebnis suboptimal ausfallen lässt.

Der Kompromiss besteht darin, dass wir die Inkremente im Sinne eines agilen Vorgehens zuerst alle modellieren, um sie erst anschließend eines nach dem anderen zu implementieren. Das entspricht nicht unserem wahren Vorgehen, das ist nicht, was wir empfehlen. Doch wir dachten, so sei es einfacher aufzunehmen, um nicht immer wieder zwischen Modellierung und Implementierung umbauen zu müssen.

Wie sich dann aber herausgestellt hat, war es gar nicht nötig, für die Implementierung bzw. den Review von Code umzubauen. Wir haben einfach mit den drei Kameraperspektiven weitergemacht und am SMART Board über den Code gesprochen. Kein live coding, kein Screenrecording, wie zunächst gedacht.

Das werden wir beim nächsten Mal auf jeden Fall anders machen: design a little, code a little. So muss der Rhythmus sein. Sonst läuft man mit dem Modell bei allen Vorteilen eines expliziten Entwurfs in die Irre. Bubbles don´t crash behält seine Relevanz. Das zeigt das letzte Inkrement im Video. Die Diskussion darüber ist verhältnismäßig lang – und trotzdem kommt etwas heraus, das nicht voll funktionstüchtig ist. Wir haben schlicht nicht alle Eventualitäten durchdacht. Erst “Anwendertests” nach der Implementierung von Inkrement #4 haben gezeigt, dass es eine Lücke in der Funktionalität gab.

Das Problem ist beim letzten Inkrement aufgetreten, da war es dann nicht so schlimm. Aber es hätte uns auch früher passieren können; dann wären die Modelle weiterer Inkremente auf falschen Modellen aufgebaut worden. Dagegen schützt nur, Modellinkremente sofort zu implementieren.

Eine weitere Frage, die wir uns gestellt haben, ist die nach dem Single Responsibility Principle. Haben wir das mit dem Video eingehalten? Ist der Fokus eng genug? Oder haben wir auch hier schon zuviel auf einmal gewollt? Bewusst haben wir unseren Ansatz schon nicht erklärt, sondern ihn einfach unter uns angewandt. Aber ist es vielleicht zuviel, dann auch noch zu implementieren? Die Kontraktdefinition scheint uns in jedem Fall zu ausführlich geraten.

Ausblick

Soweit mal unser erstes Videoexperiment mit Reflexion. Nun kommen Sie. Was ist Ihr Feedback? Was gefällt Ihnen, was sollen wir anders machen? Haben Sie Interesse an weiteren Videodarstellung zum Thema Softwareentwurf? Wünschen Sie sich inhaltlich etwas? Worauf sollten wir näher eingehen? Was weglassen? Können wir den Entwurf in bestimmten Domänen oder von konkreten Szenarien demonstrieren?

Wir sind gespannt, was Sie uns schreiben.

Fußnoten

[1] Dass wir überhaupt an einem Ansatz arbeiten und nicht schlicht OOA/D und UML benutzen ist kurz gesagt: Wir sehen in der Praxis nicht, dass die existierenden Ansätze erstens überhaupt relevante Verbreitung gefunden hätten. Es regiert das Durchwursteln. Zweitens halten wir diese Ansätze im Sinne von Methode bzw. Standarddarstellung für unzureichend und allemal schwer erlernbar.
Unser Bemühen richtet sich daher auf eine pragmatischere, auch im Projektalltag handhabbare Methode ohne Firlefanz, die einen Großteil der Softwarerealität abdeckt.

Wie diese Methode aussieht, haben wir in unterschiedlichen Publikationen immer wieder in Facetten dargestellt. Eine umfassende Beschreibung fehlt allerdings. Auch dieses Video liefert sie nicht. Es bietet nur einen Ausschnitt. Aber vielleicht kommen wir ja noch dazu, ein Buch zu schreiben… ;-) Einstweilen entsteht die derzeit systematischste Beschreibung gerade in einer Artikelserie für die Zeitschrift web & mobile developer.

Montag, 7. Januar 2013

Objektorientierung an der Quelle

Was viele Entwickler so jeden Tag betreiben, soll ja Objektorientierung sein. Java, C#, C++ und einige Sprachen mehr firmieren als objektorientiert. Sogar F# sucht den Anschluss als hybride Sprache.

Und wenn es dann in diesen Sprachen Klassen gibt, aus denen zur Laufzeit Objekte gemacht werden, dann ist das doch auch richtig – oder?

Natürlich kann jeder unter Objektorientierung im Grunde verstehen, was er will. Oder Objektorientierung ist einfach der kleinste gemeinsame Nenner dessen, was irgendwie so genannt wird. Ja, so kann man die Kunst betreiben.

Aber man kann es auch anders tun und sich fragen, was diese Objektorientierung denn ursprünglich mal sollte, wie sie eigentlich gedacht war. Was da herauskommt, finde ich spannend.

Wer hat´s erfunden? Der Alan Kay. Und der sagt zu seiner Erfindung in einer Emailkonversation zum Beispiel das Folgende:

“I thought of objects being like biological cells and/or individual computers on a network, only able to communicate with messages (so messaging came at the very beginning -- it took a while to see how to do messaging in a programming language efficiently enough to be useful).”

“So I decided to leave out inheritance as a built-in feature until I understood it better.”

“OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.”

An anderer Stelle ist von ihm dann auch noch zu hören gewesen:

“I invented the term Object-Oriented, and I can tell you I did not have C++ in mind.”

Das Internet: “Possibly the only real object-oriented system in working order.”

Nun frage ich mich, was der entscheidende Unterschied ist zwischen dem, was Alan Kay mal visionierte und dem, was wir heute haben? Denn ganz offensichtlich ist die heutige Objektorientierung nicht zu dem herangewachsen, was er gewollt hatte.

Mir scheint, der Hauptunterschied liegt im Begriff Message. Bestätigung finde ich dafür auch hier in der Bemerkung über Smalltalk-80, wo der Begriff Message irreführenderweise weiter verwendet wurde, obwohl es nur noch um synchrone bidirektionale Kommunikation zwischen Objekten ging.

Heutige objektorientierte Sprachen betreiben schlicht kein Messaging zwischen Objekten. Sie haben damit den Begriff aus dem Blick verloren, der am Anfang aller Objektorientierung stand (s.o. das erste Zitat).

Im folgenden Code wird also keine (!) Message vom Aufrufer an Objekt o gesendet:

var z = o.f(x, y);

o.p = “…”;

Das erste ist nur ein Unterprogrammaufruf, ein procedure call. Das zweite ist ein Zugriff auf Daten eines Objektes.

Keine Frage, so etwas ist nützlich. Ich denke nicht, dass Alan Kay das abschaffen wollte. Aber er hätte nicht mit der Objektorientierung begonnen, wenn er nicht darüber hätte hinausgehen wollen.

Messaging ist unidirektional. Und Objekte sind mindestens abgeschlossene, zustandsbehaftete Kapseln, die keine Struktur verraten, wenn nicht sogar autonom, d.h. auf eigenem Thread laufend.

Klassen, Vererbung, Polymorphie (über Basisklasse oder Interface)… das sehe ich daher ganz und gar nicht im Kern von Objektorientierung. Alan Kay hat eher an dynamische Programmierung gedacht, wenn er “extreme late-binding of all things” forderte.

Und was bedeutet das? Wenn wir Alan Kay ernst nehmen, dann sollten wir erstens bei dem, was wir so üblicherweise tun, den OO-Mund nicht so voll nehmen. Zweitens sollten wir endlich beginnen, mal richtig objektorientiert zu denken und zu codieren. Manche Sprachen machen das sicher leichter als andere. Und dann schauen wir mal, wohin wir kommen, wenn wir unsere Software intern mehr nach dem Internet modellieren, das Alan Kay für das einzige funktionstüchtige echt objektorientierte System hält.

In diesem Sinne: Merry encapsulation, and happy messaging!

Donnerstag, 3. Januar 2013

Beispielhafte Nichtbeachtung

Wie könnte eine Anwendung aussehen, die dem Prinzip der gegenseitigen Nichtbeachtung folgt? Ich versuche das mal anhand eines simplen Szenarios im Kontrast zu einer mehrschichtigen Anwendung darzustellen.

Das Beispielszenario

Eine Anwendung soll den Index aller Worte in Dokumenten aufbauen. Die Dokumente sind Textdateien (Endung auf .txt) unterhalb eines Wurzelverzeichnisses. Und Worte sind Zeichenketten ohne Leerzeichen, d.h. sie werden durch Leerzeichen von einander getrennt.

Die Anwendung wird mit dem Wurzelverzeichnis gestartet, z.B.

wordindex f:\docs

und liefert den Index in folgender Form auf dem Standard-Output ab:

image

Zu jedem Wort gelistet in alphabetischer Reihenfolge werden die Dateinamen der Dokumente gelistet, in denen es vorkommt.

Das ist alles natürlich keine große Sache. Aber ich denke, es gibt genug her, um zwei Ansätze zu vergleichen. Der Code für beide liegt in einem github Repository. Wer ihn also komplett sehen möchte, ist herzlich eingeladen.

Ansatz #1: Mehrschichtige Anwendung

Bei der mehrschichtigen Variante will ich mich hier gar nicht lange mit Entwurfsschritten aufhalten. Ist doch durch das Muster “Mehrschichtige Architektur” schon alles vorgegeben, oder?

Da gibt es ein Frontend – das hier zugegebenermaßen dünn ausfällt –, da gibt es Domänenlogik und dann gibt es noch Datenzugriff. Alles simpel, aber vorhanden.

image

Für jede Schicht mache ich eine Komponente auf, d.h. es gibt einen separaten Kontrakt und eine binäre Implementation (Assembly). In Visual Studio drücke ich das durch mehrere Projekte aus:

image

Es gibt also drei Projekte – je eines für jede Schicht – und dazu noch eines für die Kontrakte und eines für den Programmstart und den Zusammenbau des Ganzen zur Laufzeit.

Die Interfaces (funktionale Kontrakte) sind simpel. Woher die kommen, lasse ich mal außen vor. Hier fallen sie vom Himmel :-)

image

Dass ich mich für die Schichten für Interfaces entschieden habe, sollte keine Fragen aufwerfen. Aber bei den Daten… Warum ein Interface für den Index?

Ich erwarte, dass der Index selbst noch wieder eine gewisse Funktionalität enthält. Die will ich aber zum Zeitpunkt der Kontraktlegung nicht festschreiben müssen. Was weiß ich, wie die Domäne die Indizierung implementiert? Sie kann Funktionalität zwischen Indexer und Index verteilen, wie sie mag.

Indem ich also den Index auch nur als Interface in den Kontrakte angebe, sind die Kontrakte schnell geschrieben und die parallele Entwicklung an allen Schichtenkomponenten kann losgehen.

Das mag für manche neu sein – aber am Ende ist das ein alter Hut. Eben Komponentenorientierung.

Abhängigkeiten

Die aus meiner Sicht unseligen Abhängigkeiten bestehen nun zwischen den Implementation der funktionalen Kontrakte. Zur Compilezeit sind sie von Interfaces abhängig, zur Laufzeit von deren Implementationen.

image

Wenn ich Console testen will, muss ich eine IIndexer-Attrappe haben. Und wenn ich den Indexer testen will, muss ich eine Attrappe für IDocuments haben. So ist das halt. Hört sich ganz normal an, oder? Quasi unvermeidbar. Dafür gibt es ja schließlich Mock-Frameworks.

Bei Programmstart werden die Implementationen dann zusammengesteckt. Das passiert über Dependency Injection:

image

Auch normal. Dass ich hier keinen DI-Container benutze, ist vernachlässigbar.

Das funktioniert alles tadellos. Aspekte sind erkannt (Schichten) und für produktive Entwicklung und Wiederverwendung in Komponenten herausgelöst. Über Interfaces wird entkoppelt. Funktionale Abhängigkeiten sind sauber ausgerichtet von oben nach unten. Jede Schicht ruft nur die unmittelbar darunter liegende.

Das funktioniert tadellos. Wer mag, probiert es mit dem Code im Repo aus.

Gedanken zum Entwurf

Oben wollte ich noch nichts zum Thema Entwurf sagen. Und auch jetzt möchte ich nicht den Entwurf nachholen. Aber eines Kommentares kann ich mich nicht enthalten:

Als ich die Anwendung entworfen habe, habe ich mich schwer getan. Nicht sehr schwer natürlich. Ist ja alles total triviales Zeugs. Doch ich habe gemerkt, dass ich einen Moment geschwommen bin.

Die Schichten zu finden, war nicht schwer. Die sind ja durch das Muster vorgegeben und für das Szenario auch klar erkennbar. Aber wie dann die Interfaces genau aussehen sollten… dazu fiel mir erstmal nichts ein.

IIndexer war simpel. Da geht es zentral um die Zweck der Anwendung. Also bin ich gleich auf die obige Definition verfallen. Ich habe einfach Wünsch-dir-was gespielt :-)

Aber wie sollte IConsole aussehen? Vor allem aber: Wie sollte IDocuments aussehen? Was würde ein Indexer von IDocuments wollen?

Bei diesem Entwurfsansatz habe ich mich da sehr gehemmt gefühlt. Ich hätte eigentlich mit TDD loslegen müssen zu implementieren, um dabei schrittweise herauszufinden, was ich von IDocuments brauche.

Das habe ich mir angesichts des einfachen Beispiels dann aber gespart und habe einfach mal getippt ;-) Wie sich herausstellte, lag ich da nicht falsch.

In Summe ist mein Eindruck also, dass mir der Ansatz der Mehrschichtigkeit zwar im gaaanz Groben irgendwie hilft – nur dann wird es schnell schwierig. Ich werde nicht geleitet bei der Verfeinerung (Wie sehen die Schichten denn konkret aus?). Und die Abhängigkeiten machen dann bei der Implementation keinen Spaß: Entweder muss ich bei Tests Attrappen einsetzen oder ich muss von unten nach oben entwickeln. Beides macht keine Laune.

Ansatz #2: Nichtbeachtende Anwendung

Jetzt gilt es. Wie sieht eine Anwendung aus, die nicht mehrschichtig ist? Wie kann das Problem mit den funktionalen Abhängigkeiten vermieden werden? Im Repo finden Sie dazu auch einen Vorschlag, die Solution obliviouswordindex:

image

Von Grundaufbau her sieht sie gar nicht so anders aus. Es gibt wieder drei Projekte für die Aspekte Frontend, Domäne und Persistenz. Warum auch nicht? Es sind Aspekte, die es lohnt, deutlich separat zu implementieren.

Und es gibt wieder ein “Hauptprogramm” und ein Projekt für das, was den Aspekten gemein ist: Daten.

Die Beziehungen zwischen diesen Teilen sehen allerdings anders aus!

Zunächst einmal ist zu bemerken, dass es keine funktionalen Kontrakte gibt. Interfaces für die Aspekte sind unnötig. Es gibt zwischen ihnen ja keine funktionalen Abhängigkeiten. Das Frontend kennt keine Domäne und die Domäne weiß nichts von Persistenz.

Die Implementationen liegen funktional “nichtbeachtend” nebeneinander:

image

Es gibt lediglich gemeinsame Erwartungen an Daten. Und das ist ja auch völlig ok. Daten sind vergleichsweise “ungefährlich”, wenn sie keine großartige Funktionalität enthalten. Dann können sich Änderungen an Logik nicht so leicht fortpflanzen.

Daten stehen für die unvermeidliche logische Kopplung. Aber nur weil es die gibt, ja, geben muss, heißt das ja nicht, dass darüber hinaus auch noch funktional gekoppelt werden muss.

Das Hauptprogramm ist wieder von allen Implementationen abhängig, weil es sie irgendwie zusammenstecken muss. Aber das ist auch sein einziger Job: die Integration.

Die Komponenten implementieren hingegen Operationen, d.h. Logik.

Das ist die Basis der Architektur des Prinzips der gegenseitigen Nichtbeachtung: die Trennung der Aspekte Integration und Operation. Diese Aspekte gibt es immer. Sie sind domänenunabhängig. Auch das ist anders als bei der Mehrschichtigkeit. Die trennt ebenfalls Aspekte – allerdings sind das inhaltliche.

Vielleicht ist es gar nicht so schlecht, das mal wie folgt gegenüber zu stellen:

image

Soweit die Grundarchitektur. Da gibt es auch immer noch Abhängigkeiten bei der “Nichtbeachtung”. Sowas, sowas ;-)

Dennoch ist die rein praktische Erfahrung, dass es simpler ist, die Operationsschicht mit den nicht funktional abhängigen Operationen zu implementieren. Es sind schlicht keine Attrappen nötig.

Entwurf

Wie komme ich nun zu diesen Operationen? Das geht über einen schrittweisen Entwurf. Ich muss dafür keinen Test schreiben, sondern einfach das Problem verstehen und einen Lösungsansatz formulieren können.

Es ist im Grunde wie die Erarbeitung einer Sprache. Es geht um Sätze und Worte. Die Operationen entsprechen den Worten, sie sind das Grundvokabular für Sätze. Größeres kann ich dann aus diesem Vokabular und schon gebildeten Sätzen zusammenbauen.

Beim Entwurf für das Beispielszenario könnte das so aussehen: Am Anfang stelle ich mich ganz dumm. Ich weiß nur, dass ich Input von der Kommandozeile bekomme und dass der Index auf dem Standard-Output rauskommen soll. Dazwischen passiert “es” dann irgendwie:

image

Bei der Ausgabe bin ich mir sicher, dass ich die nicht weiter verfeinern will. Eine IIndex-Datenstruktur letztlich in einen Text zu serialisieren, ist eine Sache von wenigen Zeilen.

Und wie ist es mit der Dokumentenverarbeitung? Da spielt jetzt die Musik. Die einfach so ohne weitere Verfeinerung zu implementieren, wäre mir zuviel. Kann man machen – muss man aber nicht ;-)

Also zoome ich mal rein und denke mir die Verarbeitung wie folgt:

image

Zuerst werden Dokumente geladen – eines nach dem anderen. Und mit jedem wird der Index erweitert. Aus Load docs fließen also einzelne Dokumente an Build index.

Jetzt wieder die Frage: so lassen oder weiter verfeinern? Jede dieser Funktionseinheiten wäre schon wieder einfacher zu implementieren. Vor allem, weil sie ja auch für sich, also nichtbeachtend realisiert würden. Ich könnte eine Methode Load_docs() entwickeln, die nichts mit einer Methode Build_index() zu tun hätte – und umgekehrt. Das war bei der Mehrschichtigkeit anders.

Aber ich finde die Funktionalität immer noch zu grob. Also verfeinere ich weiter. Dokumente laden, erfolgt in drei Schritten:

image

Zuerst einmal muss ich den Wurzelpfad aus der Kommandozeile herausfischen, dann suche ich alle überhaupt relevanten Dokumente, also die .txt-Dateien, und schließlich lade ich für jeden Dateinamen den Dateiinhalt.

Bitte beachten Sie, wie aus 1 Input-Wert, dem Wurzelpfad, n Output-Werte, die Dateinamen werden.

Anschließend wird zu jedem Dateinamen aber wieder nur 1 Dokument produziert.

Document wie IIndex sind im “Datenkontrakt” definiert, da sie relevant zwischen Aspekten sind. Andere Datentypen gibt es nicht auf globaler Ebene; innerhalb von Aspekten benutze ich gern dynamische Typen.

Mit den Funktionseinheiten auf dieser Zoomebene bin ich jetzt zufrieden. Eine weitere Zerlegung ist nicht nötig; ich bin bei den Operationen angekommen.

Aber der Indexbau muss noch näher betrachtet werden. Auch dort sehe ich eine Schrittfolge vom Dokument bis zum Index:

image

Zuerst wird jedes Dokument in seine Worte zerlegt, die ich Funde (occurrence) nenne. Das sind Tupel bestehend aus Wort und Dateiname. Wie angedeutet, definiere ich dafür aber keinen eigenen Typ. Das wäre Overkill. Alle Operationen gehören ja zum Aspekt Domäne. Da kann man sich untereinander so gut kennen, dass man sich auf die Struktur dynamischer Typen verlassen kann.

Der Filterschritt unterdrückt Worte mit weniger als 3 Zeichen. Und bei der Registrierung wird der Fund dem Index hinzugefügt.

Wenn es keine Funde mehr gibt, fließt am Ende der Index hinaus.

Angezeigt wird das Ende eines Stromes durch eine fließende Null. find docs schließt damit den Strom der Dateinamen ab, load doc fährt damit fort nach dem letzten Dokument usw. Das ist ein probates Mittel für die stromorientierte Verbindung von Operationen. Letztlich macht es Rx auch so – allerdings über einen eigenen Event, der das Ende eines Stromes anzeigt. Ein Endesignal muss aber sein. Insofern unterscheiden sich Ströme von Listen (zu denen auch IEnumerable<T> gehört).

Ich hoffe, mit dieser schrittweisen Verfeinerung habe ich niemandes Phantasie strapaziert. Dass so ein Indexaufbau funktioniert, sollte auf der Hand liegen.

Wie weit ich verfeinere, hängt von meinem Vorstellungsvermögen ab. Diese drei Zerlegungsebenen finde ich einfach naheliegend. Aber Sie können das Problem natürlich auch anders angehen. Entscheidend ist nicht, wie Sie konkret zerlegen, sondern dass sie es überhaupt tun. Damit unterscheiden Sie nämlich Integration und Operationen.

Die Operationen get root path, output index, parse doc usw. stellen jetzt das Grundvokabular dar. Das sind alles black boxes. Wie die implementiert werden, ist egal für die Integration. Alle sind aber unabhängig von einander. Sie definieren sehr kompakte Leistungen, wenn man ihnen passenden Input liefert.

Mit diesem Grundvokabular habe ich “Sätze” gebildet, z.B. Load docs. Die liegen auf einem höheren Abstraktionsniveau. Die Zerlegungsebenen sind insofern keine Schichten, sondern Strata im Sinne von Sussmans & Abelsons “Structure and Interpretation of Computer Programs”.

Das kann man von IConsole oder IIndexer bei der mehrschichtigen Architektur nicht sagen. Die Domain ist nicht soetwas wie die Persistence auf höherer Abstraktionsebene, sondern etwas anderes. Eine solche Zerlegung hilft deshalb nur wenig bei der Problemlösung.

Eine schrittweise Verfeinerung wie hier vorgenommen jedoch, die hilft. Es geht auf jeder Ebene nämlich um das Ganze – nur eben mit immer feingranularerem Vokabular.

Implementation

So viele Bildchen, wann kommt denn endlich Code? Bubbles don´t crash, heißt es doch. Bisher wurde nur geredet und nichts substanzielles geschafft – oder?

Weit gefehlt. Alles, was Sie bisher an Bubbles verbunden mit Pfeilen gesehen haben, ist schon Code. Hier der Beweis:

image

Das sind die “Blubberdiagramme” übersetzt in eine texttuelle Notation (DSL). Die hätte ich auch gleich hinschreiben können – aber Bilder sind übersichtlicher, lassen sich schneller auf einem Blatt notieren im kreativen Prozess und machen es einfacher, einen Entwurf anderen zu erklären.

Aber keine Angst, die hübschen Bildchen sind nicht weg, nur weil ich sie in Text übersetzt habe. Sie lassen sich jederzeit aus dem Text generieren:

image

Das funktioniert sogar nicht nur in der Entwicklungsumgebung, sondern auch auf dem Binärcode. Falls mal also nur eine .EXE vorliegt, kann der hier eingesetzte Visualizer auch aus ihr diese Integrationen lesen und in Bilder rückübersetzen. Aber das nur am Rande.

Wichtig ist, dass ich rüberbringe, dass die Diagramme nicht nur eine optionale Nettigkeit sind, sondern Code darstellen. Sie entwerfen und Implementieren letztlich gleichzeitig. Entwurf ist damit keine verschwendete Zeit. Sie tun etwas für Ihr Problemverständnis, Sie tun damit etwas für eine saubere Lösungsstruktur – und nun haben Sie auch schon einen wichtigen Teil der Lösung codiert: die Integration.

Die nun noch nötige Implementation der Operationen ist aber auch nicht schwierig. Dafür benutze ich jetzt auch eine 3GL, z.B. C#. Damit können Operationen in Methoden übersetzt werden oder ganze Klassen. Wie es mir von Fall zu Fall am besten passt.

Beispiel TextfileDocuments: Die Operationen find docs und load doc lassen sich schön in statische Methoden übersetzen. Warum auch nicht? Ein Interface ist ja nicht mehr nötig. Zustand gibt es auch nicht. Also spricht nichts gegen statische Methoden:

image

Da load doc bei jedem Aufruf, also für jeden Dateinamen, ein Dokument erzeugt, ist eine Funktion angebracht. Anders bei find docs. Für 1 Wurzelpfad werden n Dateinamen erzeugt. Da ich die nicht als Liste mit einem Mal zur Weiterverarbeitung leiten möchte, sondern die Dokument für Dokument einzeln stattfinden soll, implementiere ich die Operation als Prozedur mit Continuation für die Dateinamen.

Dass Operationen leichter zu testen sind, zeigt bei diesem Aspekt jedoch noch nicht so sehr. Auch in der mehrschichtigen Anwendung war diese Funktionalität unabhängig.

Anders ist das bei der Domäne. Die ist in der mehrschichtigen Architektur abhängig von der Persistenz. Jetzt aber nicht mehr:

image

Hier habe ich mich für Instanz-Operationen entschieden, weil die Domäne einen Zustand hat: den Index. Der wird ja langsam aufgebaut (s. Register_occurrence()).

Keine der Operationen kennt nun aber eine Persistenzoperation. Es gibt keine Request/Response-Kommunikation, sondern nur message passing. Klingt für mich übrigens total objektorientiert ;-)

Beachten Sie, wie ich hier dynamische Typen einsetze. Die Operationen vertrauen darauf, dass ihr Input eine passende Struktur hat. Von wo der kommt und wohin ihr Output geht… das wissen sie jedoch nicht. Deshalb sind die einfach zu testen.

Die Abwesenheit von Request/Response lässt Methoden auch tendenziell kleiner sein. Denn wenn Sie merken, dass Sie in einer Operation etwas brauchen, dann müssen Sie sie im Grunde beenden, einen Output erzeugen – und eine andere Operation aufmachen, in die das, was Sie brauchen, als Input einfließt. Aus dem üblichen

void f() {
  // vorher
  string r = g(42);
  // nachher
}

würde

void f_vorher(Action<int> continueWith) {
  // vorher
  continueWith(42);
}

void f_nachher(string r) {
  // nachher
}

Wenn Sie das so sehen, wirkt es natürlich irgendwie umständlich und künstlich. Aber mit ein bisschen Übung, verliert sich diese mentale Reibung. Dann ist das eine ganz natürliche Denkweise; dann können Sie nicht mehr anders. Oder besser: dann wollen Sie nicht mehr anders – müssen jedoch manchmal.

Aber solches Vorgehen ist natürlich kein Zwang. Sie können beim Design entscheiden, bis zu welcher Granularitätsebene Sie so denken wollen – und wann Sie damit aufhören. Wenn Sie durch Entwurf auf Ihre Operationen gekommen sind, dann können Sie sich in deren Implementation auch austoben. Da ist quasi wieder alles erlaubt.

Dennoch sollten Sie die Integration-Operation-Trennung (und den Verzicht auf Request/Response) immer im Hinterkopf behalten. Sie sind es, die zu den kleineren Methoden führen. Eine Methode integriert entweder oder sie operiert. Und wenn sie integriert, dann sind darin keine Kontrollstrukturen und keine Ausdrücke erlaubt. Wie lang kann dann eine Methode noch werden? ;-)

Deshalb ist es auch nicht schwer, eine textuelle oder grafische DSL für die Integration wie oben gezeigt zu realisieren. In der Integration passiert einfach nichts. Keine Logik. Nur “Zusammenstecken”.

Komposition

Da gibt es nun eine Integration notiert in einer obskuren DLS. Und es fliegen eine Menge Operationen im Raum herum. Wie kommt beides zusammen zu einer lauffähigen Anwendung, wie sieht der Kompositionsschritt bei diesem Ansatz aus.

Der ist ganz einfach. Dependency Injection gibt es ja nicht. Allerdings müssen die Integrationsbeschreibung und die Operationen bei einer Runtime registriert werden. (Das ist aber nur eine Möglichkeit, wenn Sie sich eben für die DSL entscheiden. Sie können diese Art der Strukturierung auch anders umsetzen.)

image

Die Konfiguration der Runtime sucht sich die Operationen aus den angegebenen Assemblies selbst heraus; sie sind ja mit einem Attribut versehen.

Und dann geht es auch schon los. Der Einfachheit halber und weil nicht mehr gebraucht wird, schalte ich die Runtime zur Verarbeitung der Datenfluss-Integration auf synchrone Verarbeitung. Mit den Kommandozeilenargumenten geschickt an den Port .run beginnt die erste Operation ihre Arbeit.

Zusammenschau

Den Unterschied in den zwei Ansätzen sehen Sie hoffentlich:

Erstens: Bei der Mehrschichtigkeit verteilt sich Logik (Kontrollstrukturen und Ausdrücke) über alle Ebenen. Das ist in diesem Beispiel noch gar nicht so deutlich geworden, weil die Schichten so dünn sind. Aber in größeren Szenarien gibt es ja auch innerhalb Schichten Abhängigkeitshierarchien.

Solch verschmierte Logik macht es schwer, schnell und einfach und präzise den Ort zu finden, wo man für eine Verhaltensänderung eingreifen muss.

Und wenn man ihn gefunden hat, dann ist der Test nicht so leicht, weil es in Abhängigkeitshierarchien eigentlich keine Unit Tests gibt, sondern immer nur Integrationstests. Denn wenn Funktionseinheit A von B abhängt und ich für den Test von A eine Attrappe_B brauche, dann teste ich nicht nur, ob in A die richtigen Entscheidungen getroffen werden, sondern auch noch, ob B erwartungsgemäß aufgerufen wird. Ein korrekter Umgang mit B oder gar auch noch mit C und D stellt jedoch eine Integrationsleistung dar: Logik in A wird mit Logik in B und C und D zu etwas größerem Integriert.

Zweitens: Zumindest für mich ist es schwer, Abhängigkeitshierarchien zu entwerfen. Wenn sie einmal da sind, dann kann ich sie malen. Aber wie komme ich dahin? Und vor allem, wie komme ich in agiler Weise dahin? Wenn Agilität bedeutet, in Inkrementen zu denken, wie kann ich solche Hierarchien in Inkrementen wachsen lassen? Und bleiben dann diese Inkremente sichtbar?

Das alles finde ich schwierig, schwierig. Nicht, dass ich nicht selbst lange Jahre so versucht hätte zu entwickeln. Das habe ich wahrlich. Letztlich ist das jedoch im Vergleich zu dem, wie einfach es mir heute fällt, alles ein Krampf gewesen. Den sehe ich auch regelmäßig, wenn Teams versuchen, solche Beispiele im Rahmen von Assessments zu lösen. Das ist dann für mich ein ganz praktischer Beweis dafür, dass in den Köpfen eine Menge Konzepte herumschwirren, wie das mit dem Entwurf gehen könnte und sollte… Nur kommt dann kein Gummi auf die Straße. Aus “Mehrschichtige Architektur, MVC und Entwurfsmuster plus SOLID” resultiert kein systematisches Vorgehen, das erstens zu verständlichen und evolvierbaren Strukturen führt, die zweitens dann auch noch im Team effizient implementiert werden können. Leider, leider ist das so.

Deshalb erlaube ich mir, ernsthafte Zweifel anzumelden, dass das mit der Mehrschichtigkeit großen Wert hat. Nicht zuletzt deshalb, weil wir das ja nun auch schon mehr als ein Jahrzehnt probieren und die Softwaresysteme immer noch nach kurzer Zeit unwartbar werden.

Klar, es gibt Fortschritte. IoC und DI-Container und Mock-Frameworks haben etwas gebracht. Heute wird schon mehr entkoppelt als früher. Aber “mehr” bedeutet noch nicht gleich “genug”. Ich sehe da noch deutlichen room for improvement.

Zwei Beispiele dafür sind die strikte Trennung von Integration und Operation, also ein Integration Operation Separation Principle (IOSP) ;-), sowie das Principle of Mutual Oblivion (PoMO). (Ein bisschen Englisch zwischendurch darf schon mal sein ;-)

Dass Schritte über das Gewohnte hinaus dann erstmal mühsam sind, ist halt so. So ist es immer. Auch von prozeduraler zu objektorientierter Denke war der Übergang mühsam. Oder von Objektorientierung zur Funktionalen Programmierung. Bei Veränderungen knirscht es – was ja aber nicht bedeutet, dass es hinterher nicht besser sein kann. Wenn ich heute 20 Liegestützt kann und mal 25 oder 30 mache, dann tut das auch weh. Aber wenn ich es morgen und übermorgen usw. wieder tue, dann geht der Schmerz vorbei; ich werde kräftiger. Und in einem Monat kann ich 50 Liegestütz. Eine vorher ungeahnte Menge ist möglich, weil ich mich durch Müh und Plage durchgebissen habe.

So ist es mit vielen anderen Fähigkeiten auch, körperlichen wie geistigen. Ob man diesen Weg geht, ist natürlich eine Frage der persönlichen Ökonomie. Lohnt es sich, den Veränderungsschmerz auszuhalten? Das muss letztlich jeder für sich entscheiden. Wer schon 20 Liegestützt kann, mag abwinken. Kraft für mehr braucht er nicht. Und aus dem Alter, wo er Mädels beeindrucken wollte, ist er auch raus ;-) Aber wer sich noch schwer tut beim Entwurf von Software, wer noch vor einem unwartbaren Codeverhau sitzt… kann der sich leisten, Veränderungsschmerz zu vermeiden, der auf dem Weg zu einer anderen Denke entstehen würde?

Naja, ist vielleicht eine Frage für ein längeres Gespräch bei einem Kaltgetränk.

Abhängigkeiten im close-up

An dieser Stelle abschließend noch ein Wort zum Thema Abhängigkeiten, das für mich schon im vorhergehenden Artikel so wichtig war. Dabei sehe ich ja einen großen Unterschied, der Ihnen vielleicht nicht so groß erscheint. Deshalb nochmal durch die Lupe darauf geschaut:

Was ist der Unterschied zwischen

string[] Split(string text) {…}

und

void Split(string text, Action<string[]> parts) {…}

Keiner. Beide Methoden zerlegen einen Text in irgendwelche Teile, z.B. Worte oder Zeilen. Beide Methoden liefern diese Teile in Form eines Array ab.

Dass die erste Methode ihr Ergebnis über den Rückgabewert meldet und die zweite über einen Delegaten, ist im Grunde nur syntactic sugar für den Aufrufer.


var parts = Split(“…”);

vs


string[] parts = null;
Split(“…”, _ => parts = _);

Der marginale Unterschied ist jedoch nur oberflächlich. Darunter lauert ein viel größerer. Der besteht nämlich in der Flexibilität der prozeduralen Variante mit dem Delegaten. Dort kann man sich nämlich entscheiden, ob jedes Teil einzeln oder alle Teile zusammen abgeliefert werden. Man kann wählen, ob man eine Liste oder einen Stream produziert. Die Prozedur kann sogar entscheiden, gar kein Ergebnis zu liefern; dann ruft sie den Delegaten einfach nicht auf.

void Split(string text, Action<string[]> parts) {…}
void Split_to_stream(string text, Action<string> part) {…}

Bei der Funktion hingegen gibt es immer ein Ergebnis und nur eines.

Aus meiner Sicht ist es damit schwerer, den Schritt von synchroner zu asynchroner Verarbeitung zu machen. Der Delegatenaufruf stellt ein Ereignis dar und kann leicht asynchron oder gar für jeden Teil parallel verarbeitet werden:

Split_to_stream(“…”, part => new Task(()=>Process(part)).Start());

Das ist für mich der erste Nachteil unserer Fixierung auf Request/Response-Kommunikation. Die hat uns das Leben schon vor 15 Jahren schwer gemacht, als wir versucht haben, objektorientierte Denke einfach so auf verteilte Systeme auszudehnen. Und nun sorgt sie wieder für Reibung. Diesmal beim Übergang zu Mehrkernprozessoren.

Den Ort eines Requests zwangsweise gleichzusetzen mit dem Ort für die Bearbeitung eines Response, ist schlicht eine enge Kopplung. Aber nicht nur für den, der den Request bearbeitet.

Das Problem setzt sich fort beim Aufrufer. Wo ist der Unterschied zwischen

void f() {
  …
  var parts = Split(text);
  …
}

und

void f(Func<string,string[]> Split) {
  …
  var parts = Split(text);
  …
}

Wieder gibt es keinen grundlegenden Unterschied. Die Methode f() benutzt eine Dienstleistung zur Zerteilung von Zeichenketten. Dass die im zweiten Fall als Delegat hineingereicht wird, sozusagen parameter injection, ändert daran nichts. Das ist nur eine Variante von dependency injection, die mal ohne Interface auskommt und nicht auf Klassenebene arbeitet.

In meinem Sinne ist dadurch noch nichts erreicht in puncto Abhängigkeitsreduktion. f() ist in beiden Fällen abhängig:

image

Um f() allein zu testen, muss eine Attrappe her. Und dann testet man eben nicht nur die Funktionsweise der Logik in f(), sondern auch noch, ob Split() korrekt benutzt wird. Das ist aber ein anderer Aspekt, nämlich der der Integration: spielt ureigene f()-Logik korrekt zusammen mit Split()-Logik?

Das klingt subtil, geradezu wie Haarspalterei. Aber je länger ich mich damit beschäftige, desto größer scheinen mir die negativen Auswirkungen der Vernachlässigung dieser Feinheit.

Der Trick ist einfach, dass f() von einer Semantik abhängig ist, nicht nur von einer Form. Die Ursache dafür ist in diesem Fall der Request/Response-Aufruf.

Jetzt dieses kleine Szenario etwas anders:

void f_produce_text(Action<string> Text_produced) {
  …
  Text_produced(text);
}

void f_process_text_parts(string[] parts) {
  …
}

Sehen Sie den Unterschied jetzt? f_produce_text() ist unabhängig von einer Semantik des Delegaten. Es wird immer noch eine Methode injiziert, aber die tut nichts für den Aufrufer. Sie nimmt nur etwas entgegen und transportiert es weiter. Das macht die “Nichtbeachtung” aus.

Beide Methoden können nun ohne Attrappen getestet werden. Beide Methoden tun jetzt auch nur eines: sie implementieren Logik, d.h. sie stellen Operationen dar.

Natürlich muss dann irgendwo alles zusammenkommen. Das geschieht bei der Integration:

void f(Func<string, Action<string[]>> Split) {
  f_produce_text(t =>
    Split(t ,
      f_process_text_parts));
}

Die Integration ist nun abhängig von konkreter Semantik. Sie stellt selbst Semantik her und braucht dafür Sub-Semantiken. Aber sie enthält keine Logik. Deshalb ist der Test einer Integration einfach. Der kann sich nämlich darauf konzentrieren festzustellen, ob die Subsemantiken korrekt “verdrahtet” sind. Das ist alles. Im Falle von f() ist dafür nur ein Test nötig.

Dass eine Integration abhängig ist von weiteren Integrationen bzw. Operationen, ist unvermeidbar. Aber das ist nicht mehr so schlimm, weil in ihr eben keine Logik steckt. Deshalb ist es ja auch möglich, Integrationen wie oben gezeigt in einer trivialen DSL zu notieren.

Die f()-Integration würde damit notiert so aussehen:

f
f_produce_text, split
split, f_process_text_parts

Also: Ein Test von Integrationen ist einfach, auch wenn dafür Attrappen nötig sind/sein sollten, weil nur wenige Tests gebraucht werden. Meist reicht einer. Und der kann auch noch auf recht hoher Abstraktionsebene angesiedelt sein.

Und ein Test von Operationen ist einfach, weil die Operationen keine funktionalen Abhängigkeiten haben, d.h. sie kennen keine Semantik von Konsumenten ihrer Daten. Attrappen sind nicht für ihre Tests nötig.

Fazit

Ich kann nicht mehr anders denken als im Sinne von IOSP und PoMO. Alles andere kommt mir zäh und umständlich und im Ergebnis wenig flexibel vor. Aber das ist einfach meine Erfahrung nach 3 Jahren konstanter Übung.

Ganz praktisch drückt sich das übrigens darin aus, dass ich keine Mock-Frameworks mehr benutze. Wo ich früher noch mit Rhino Mocks o.ä. rumgehüsert habe, da kann ich mich jetzt entspannen. Falls mal eine Attrappe nötig ist, schreibe ich die in ein paar Zeilen selbst. So bin auch ich weniger abhängig – von Tools.

Dass ich Sie nun überzeugt habe, das Lager der Mehrschichtigkeit bzw. der “Abhängigkeitsdenke” zu verlassen, glaube ich nicht. Aber ich habe Ihnen hoffentlich einen Impuls gegeben, Ihre OO- und Architekturwelt ein bisschen anders zu sehen. Das Übliche ist nicht alles. Es geht auch anders. Und nicht nur die Funktionale Programmierung hat etwas zu bieten.

Vielleicht, ganz vielleicht versuchen Sie ja aber einmal – wenn die Kollegen Sie nicht sehen –, die beiden Prinzipien IOSP und PoMO an einem Beispiel wie dem obigen selbst anzuwenden. Machen Sie Ihr persönliches Coding Dojo mit einer Application Kata. Das Wortindex-Szenario könnten Sie wiederholen oder erweitern (Stoppwortliste, Zeilennummern der Worte je Dokument, Dokumentenindizierung als Service in der Cloud). Oder Sie nehmen sich ein anderes Szenario vor. Bei den Clean Code Advisors finden Sie weitere Application Katas.

Falls Sie Fragen haben, melden Sie sich einfach per Email – oder diskutieren Sie mit in der Google Group oder XING-Gruppe zum Thema Flow-Design/Event-Based Components.