02 - Entwurf im Überblick
Im Entwurf wird die Umsetzung vorweggenommen. Er stellt die Lösung des Problems dar, ohne “es zu tun”. Er entwickelt nur eine Vorstellung davon, wie die geforderte Leistung durch Software erbracht werden könnte.
Bevor ich dir konkret erkläre, wie ich meine, dass du sehr leichtgewichtig entwerfen solltest und warum genau so in einer bestimmten Weise, möchte ich dir ausführlich darstellen, was ich grundsätzlich damit meine und was das soll.
In Kapitel 01 habe ich schon versucht, den Entwurf als unverbrüchliche Phase jeder Softwareentwicklung herauszuarbeiten. Du kommst aus meiner Sicht sowieso nicht um ihn herum, auch wenn du der Meinung bist, ihn nicht zu brauchen. Doch lass uns noch einen genauere Blick darauf werden, was das ist, der Entwurf.
Den Entwurf abstecken
Mit ein paar Aussagepflöcken stecke ich das Thema Entwurf mal grob ab. Das ist abstrakt, aber keine Sorge, du wirst später noch genügend konkrete Entwürfe sehen.
- Ein Entwurf stellt die Lösung eines Problems dar, d.h. er erfüllt die Anforderungen des Auftraggebers.
- Entwurf ist allerdings nicht die eigentliche Sache, die der Auftraggeber will. Wie auch immer ein Entwurf aussieht, es ist also kein Code, er ist nicht ausführbar. Das widerspräche der Definition von Entwurf.
- Ein Entwurf ist nur eine Beschreibung der eigentlichen Sache, insofern ist seine Lösung nur theoretisch/konzeptionell. Nicht jede Beschreibung der eigentlichen Sache ist jedoch ein Entwurf. Damit eine Beschreibung ein Entwurf ist, muss sie vor der Herstellung der Sache angefertigt worden sein. Eine Beschreibung der eigentlichen Sache nach der Herstellung ist eine Dokumentation.
- Ein Entwurf als Beschreibung dessen, was Code leisten soll, bevor dieser Code geschrieben wird, hat den Zweck, die Codierung deutlich zu erleichtern, wenn nicht gar, sie überhaupt zu ermöglichen.
- Der Preis für die Erleichterung der Codierung darf allerdings nicht zu hoch sein. Es ist ein gutes Verhältnis zwischen Entwurfs- und Codierungsaufwand zu finden, das den Gesamtaufwand der Softwareherstellung reduziert. Indem Entwurf und Codierung zusammenkommen, soll Energie frei werden, die vorher gebunden war in “roundtrips” (aka Debugging, Testsitzungen, Iterationen).
- Um eine Lösung zu sein, die vor der Codierung entwickelt werden kann, diese erleichtert und selbst nicht zu aufwändig ist, muss ein Entwurf auf einer deutlich höheren Abstraktionsebene stattfinden, als die Codierung. Er darf nicht durch Details der Codierung behindert werden; er sollte weniger Komplexität als der spätere Code aufweisen.
- Die höhere Abstraktion darf jedoch nicht dazu führen, dass der Entwurf abhebt. Er muss also gleichzeitig konkret und aussagekräftig sein.
- Leichtgewichtigkeit ist ein Kennzeichen für hilfreiche Entwurfswerkzeuge, denn sonst werden sie nicht benutzt, wenn der Druck, mit der Codierung zu beginnen, groß ist.
- Und schließlich muss ein Entwurf durch die Realität der Codierung informiert sein und sich geradlinig in Code übersetzen lassen. Es braucht also Bezugspunkte im Entwurf für den Code.
Entwurf und Implementation sollen “nicht überlappen” und die Implementation soll den Entwurf “spiegeln”.
- Der Entwurf löst das Problem, nur nicht genauso wie der Code.
- Und der Code lässt erkennen, dass er aus dem Entwurf abgeleitet wurde, d.h. Verbindungen zwischen ihm und den Entwurfsmitteln sind klar.
Mit dem Entwurf erarbeitest du eine Zielvorstellung für den Code, d.h. die lauffähige Lösung.
Hierarchie der Lösungen
Nun gibt es die Sichtweise, dass Code selbst schon ein Entwurf (engl. design) sei.1 Denn der Code, den du in C# oder Java oder JavaScript schreibst, ist ja nicht das, was am Ende ausgeführt wird und tatsächlich das Problem des Kunden löst.
Dein Hochsprachencode wird übersetzt in Maschinencode. Das kann man als eine Form von Produktion ansehen. Und Produktion basiert auf einem Plan, einem Entwurf. Ein Haus wird nach einem Plan hergestellt, einen IKEA-Schrank baust du nach einem Plan auf.
In der materiellen Welt ist die Produktion so sichtbar und aufwändig, dass ein vorheriger Entwurf zwingend ist und auffällt. Bei der Softwareentwicklung ist die Produktion hingegen so unsichtbar und schnell, dass sie nicht auffällt - und man die Codierung für die Produktion halten könnte.
Ich mag mich diesem Verständnis jedoch nicht recht anschließen. Oder wenn ich mich ihm anschließe, dann finde ich die Aussage nicht hilfreich. Ob Code nun ein Entwurf ist oder nicht, ändert nichts an der Tatsache, dass er selbst unabhängig von jeder Kategorie sehr schwer zu schreiben und zu verstehen ist.
Wenn Code eine Form von Design darstellt, dann ist eben dieses Design sorgfältig zu produzieren. Dann muss ein Entwurf sogar vor diesem Design stattfinden.
Bei einer gegebenen manifesten Lösung, sei das Maschinencode oder Hochsprachencode, ist ein Entwurf das, was der Lösung vorhergeht und sie auf einem höheren Abstraktionsniveau vorwegnimmt. Lösungen existieren mithin nicht nur in einer Form, sondern in einer Hierarchie. Lösungen gibt es auf vielen unterschiedlichen Abstraktionsebenen. So kann des einen manifeste Lösung des anderen theoretische sein, also “nur” ihr Entwurf. Aus dieser Perspektive betrachtet sehe ich mindestens folgende Abstraktionsniveaus:
- Maschinencode ist die manifeste Lösung, das Produkt? Dann ist Hochsprachencode der Entwurf.
- Hochsprachencode ist die manifeste Lösung, das Produkt? Dann ist ein Modell dessen Entwurf.
- Ein Modell ist die Lösung, das Produk? Dann ist ein Lösungsansatz dessen Entwurf.
- Ein Lösungsansatz ist die Lösung, das Produkt? Dann ist eine “Produktidee” dessen Entwurf.
Mit Maschinencode bist du wahrscheinlich nicht vertraut. Das macht nichts, denn ich will ja nicht die Umwandlung von Hochsprachencode in ihn behandeln. Auch wenn ich es früher immer gern schreiben wollte, ist dies kein Buch über Compilerbau.
Mit Hochsprachencode bist du vertraut. Den will ich dir deshalb ebenfalls nicht erklären. Dies ist keine Einführung in die Programmierung. Aber die Übersetzung von Entwurf - genauer: Modell - in Hochsprachencode, das ist eine andere Sache, den werde ich dir ausführlich vorstellen.
Entwurf, wie ich ihn hier verstehe, führt zu Hochsprachencode und erfolgt auf zwei Ebenen: zuerst in Form eines informellen Lösungsansatzes für eine “Produktidee”, dann in Form einer formalen Modellierung des Lösungsansatzes.
Die “Produktidee” - also letztlich das, was Anforderungen des Auftraggebers beschreiben - lasse ich ebenfalls aus in diesem Band. Wie du zu den Voraussetzungen für einen Entwurf von Hochsprachencode kommst, ist Thema des dritten Buches der Reihe Programming with Ease.
Nur soviel an dieser Stelle: Auch die Anforderungen beschreiben die Lösung. Allerdings ist dieser Entwurf so abstrakt, so weit von der Codeebene entfernt, dass man “das entwerfende Element” darin im Grunde nicht erkennt. Anforderungen sind mehr ein “Wunschkonzert”. Dennoch, wenn du genau hinschaust, befinden sich Anforderungen in dem von den neun obigen Pflöcken abgesteckten Gebiet. Lediglich Punkt 9, die geradlinige Übersetzbarkeit in Code, erfüllen sie nicht.2
Der Entwurfsprozess - oder sogar die mehreren Entwurfsschritte hinab die Abstraktionshierarchie - kannst du als Schärfung verstehen. Ein zunächst grobes Bild, ein Wunschbild, wird schärfer, detailreicher, klarer mit jeder Phase. Wie dir die 3. Iteration des Hello-World Beispiels in Kapitel 01 gezeigt hat, ist ein Sprung vom Wunsch zum Hochsprachencode nicht möglich. Du musst dich heranarbeiten. Du musst dir erst ein grobes Bild machen, das du nach und nach verfeinerst.
Von der Kunst lernen
In der bildenden Kunst werden Skizzen und Kompositionen angefertigt, bevor der Künstler sich an die Schaffung des eigentlichen Werkes macht. Hier ein Beispiel dafür aus meiner Zeichenmappe aus Jugendjahren:
- Am Anfang stand nur eine Idee: Ich wollte die Endlichkeit des menschlichen Lebens darstellen. Eine Sanduhr war mir dazu gleich vor Augen. “Bild mit Sanduhr und Mensch” war also meine Anforderung.
- Davon ausgehend habe ich zuerst recht pauschale Entwürfe gemacht. Die haben die Komposition geklärt, also die Grobstruktur des Werkes. Anfänglich hatte ich nur die Idee einer Sanduhr in einer Hand. Erst im zweiten Schritt kam die menschliche Gestalt dazu. Die war also noch nicht Bestandteil der ursprünglichen Anforderungen, sondern hat sich ergeben.
- Im dritten Schritt ist die Sanduhr auf die Hand gewandert, die nun auch schon etwas detaillierter ausgearbeitet ist. Auch dieser Schritt war nicht vorherzusehen, sondern ein Ergebnis dessen, dass ich mir die Idee vorher mit den beiden anderen Entwürfen vor Augen geführt hatte.
- Das vierte Bild ist eine Detailstudie. Der Entwurf konzentriert sich auf eine genauere Ausarbeitung nur der Sanduhr mit der menschlichen Figur. Dort habe ich wohl noch Unsicherheit gespürt und wollte mich vergewissern.
- Im finalen Werk spiegeln sich die Entwürfe deutlich - aber es sind auch Abweichungen zu erkennen. Die Handhaltung ist nochmal leicht anders und die Form der Sanduhr hat sich verändert. Wenn ich mich recht erinnere, hatte ich bei der Ausarbeitung gemerkt, dass ich meine linke Hand, die mir Modell stand, besser so mit dem Stundenglas halten konnte. Außerdem hatte das konkrete Glas, das ich hielt, diese Eiform.
Die Entwürfe waren schnell gemacht und abstrakt. Dennoch - oder gerade deshalb - konnte ich mich mit ihnen zügig an die letzte Variante der Lösung heranarbeiten, das Modell. Die Übersetzung in das endgültige Werk hat dann deutlich länger gedauert und musste das Modell nochmal der Realität der Umsetzung anpassen.
Meine Erfahrungen mit der Zeichnenkunst sagen mir: ohne Entwurf geht es nicht. Ein nicht triviales Werk entsteht nicht ohne eine erkleckliche Zahl von Entwürfen, die auf unterschiedlichen Abstraktionsniveaus liegen und sogar unterschiedliche Ausschnitte behandeln.
Entwerfen ist fachgerecht
Das kreative Werk als manifeste Lösung braucht einen iterativen Prozess. In dem wird eine Vorstellung als Skizze externalisiert, um dann in der Betrachtung zurück zu wirken auf die Vorstellung.
Vorstellungen so greifbar wie möglich vor sich zu stellen, um sie von allen Seiten auf ihre Wirkung (Lösungstauglichkeit) zu überprüfen, ist für mich genauso natürlich wie zwingend. Weniger geht nicht in einem kreativen Prozess. Wer versucht, das Kunstwerk lediglich im Kopf kurz anzudenken, um es dann sogleich in seiner finalen Form zu produzieren, wird viel Verschwendung betreiben.3 Beim Zeichnen besteht sie aus Zeit und ist erkennbar am Verbrauch von Papier und Stiften.
Bei der Programmierung besteht die Verschwendung auch aus Zeit, aber erzeugt leider keinen Materialverbrauch. Das macht es so verführerisch, glaube ich, den Entwurf zu überspringen. Verschwendung ist von produktiver Arbeit oberflächlich schwer zu unterscheiden. Erkennbar ist Verschwendung primär an Inkorrektheit und Unordnung und sekundär an Verzögerungen und Frustrationsäußerungen.
Nach 40 Jahren Programmierung bin ich der festen Meinung: Wer auf einen expliziten und auch noch visuellen Entwurf vor der Produktion von Hochsprachencode verzichtet, der handelt fahrlässig und verschwendet das Geld seiner Auftraggebers. Vor dem Codieren zu entwerfen, ist für mich ein Grundbaustein fachgerechter Arbeit als Softwareentwickler. Und ebenso gehört zur fachgerechten Arbeit die test-first Codierung, wie im ersten Band der Reihe ausgeführt.
Entwerfen ist agil
Dass der explizite Entwurf seit Aufkommen der Agilität zunehmend in Verruf geraten ist, ist ein Übelstand, den ich nicht genug bedauern kann. Und wo das im Namen der Agilität geschehen ist, halte ich es für ein grobes Missverständnis der Agilität.
“Working software over comprehensive documentation” im Agilen Manifest ist keine Aufforderung, auf Entwurf zu verzichten. Ausdrücklich ist ja documentation genannt, nicht design. Wie oben definiert, ist Entwurf jedoch keine Dokumentation, wenn auch “lediglich” eine Beschreibung und keine working software.
Und nur, weil es heißt “responding to change over following a plan”, ist das keine Aufforderung jegliches Planen sein zu lassen. Dann dürfte es ja auch kein Spring Planning in Scrum geben. Ein Entwurf ist ein Plan im Sinne einer Gestaltungsidee für einen zukünftigen Zustand der Welt. Er drückt den Glauben aus, “Ja, so wird es wohl funktionieren!” Doch deshalb ist ein Entwurf nicht unumstößlich. Meine Skizzen oben im Vergleich zum finalen Werk beweisen es: Nur, weil das Werk anders ist als die Skizzen, sind die nicht unnötig gewesen. Die Abweichung vom Plan, den Skizzen darstellen, ist selbstverständlich erlaubt, wenn bei der Ausführung neue Erkenntnis auftauchen.4
Ein Entwurf steht einem “deliver working software frequently” aus den 12 Prinzipien des Agilen Manifests ebenfalls nicht im Wege. Im Gegenteil! Durch Entwurf wird der Code korrekter und ordentlicher und also wandelbarer.
Und ein visueller Entwurf, wie ich ihn dir nahelegen werde, ich ein Beförderer des Prinzips “the most efficient and effective method of conveying information to and within a development team is face-to-face conversation.” Wenn du eine Lösungsidee hast und kannst die nicht anders als in Code ausdrücken, dann lässt sie sich nur sehr schwer kommunizieren und diskutieren. Ohne Entwurf reduzierst du die Chance auf face-to-face conversation.
Schließlich: Wie willst du als agiler Programmierer dem Prinzip “Simplicity - the art of maximizing the amount of work not done - is essential” dienen, ohne einen Entwurf? Nur mit einem Entwurf kannst du nämlich überhaupt über Arbeit sprechen, bevor du sie tust. Sobald du an der IDE sitzt und Code tippst, steigerst du den amount of work. Besser, du klärst vorher ein paar Alternativen ab und diskutierst mit deinen Kollegen.
Dafür brauchst du allerdings eine klare und anfassbare Vorstellung von deiner Lösung vor deren Implementation. Zu der kommst du in zwei Schritten:
1. Der Lösungsansatz
In den Entwurf gehst du, wenn du die Anforderungen verstanden hast. Vorher hast du einfach nicht genügend Grundlage, auf der du eine Lösung aufbauen könntest.
Hier ein Beispiel für Anforderungen die das Kriterium auf Kapitel 01 erfüllen: Es liegen Beispiele vor und eine Funktionssignatur ist gegeben.
Du erkennst natürlich sofort, das mit “ordnen” hier gemeint ist “sortieren”: Die Zahlen sollen aufsteigend sortiert werden.
Schon diese Klassifikation der Anforderungen ist ein erster Schritt in Richtung Lösung. Jetzt kannst du nämlich in der Literatur nachschlagen, wie man das macht. Andere haben das Problem schon vor dir gelöst. Du müsstest nicht einmal selbst entwerfen, sondern womöglich nur eine Lösung abschreiben. Oder noch besser: deine Programmiersprache oder Standardbibliothek der Programmiersprache bieten bereits eine fertige Sortierfunktion.
“Ah, das Problem lässt sich durch Sortieriung lösen!” würde ich als erste Erkenntnis im Sinne eines Lösungsansatzes in diesem Fall zählen.
Aber der Übung halber will ich davon absehen, dass es schon Lösungen für das Sortieren gibt. Das Problem ist nämlich gut geeignet, um zu erklären, was ein Lösungsansatz ist.
Du könntest natürlich jetzt jedes weitere Nachdenken in den Wind schlagen und mit classical TDD in die Tasten greifen. Inkrementell könntest du versuchen, direkt eine Lösung zu codieren. Das ist bestimmt möglich. Die Testfälle könnten z.B. wie folgt schrittweise schwieriger werden:
-
[]->[]// Akzeptanztest und natürlicher Startpunkt -
[3]->[3] -
[1,3]->[1,3] -
[3,1]->[1,3] -
[3,1,4]->[1,3,4] -
[3,4,1]->[1,3,4] -
[1,5,2,9,8,4]->[1,2,4,5,8,9]// finaler Akzeptanztest
Die Zunahme des Schwierigkeitsgrades der Tests sieht plausibel aus. Ohne weitere Lösungsidee kannst du dir da jedoch nicht sicher sein. Das ist ein Grund, warum ich zwar sehr für test-first Codierung bin, doch nur mit einem vorherigen Entwurf.
Wenn du einmal versuchst zu vergessen, was du alles schon über Sortieralgorithmen weißt, was würdest du nach Studium der Anforderungen tun? Nein, nicht codieren, sondern im Kopf oder auf einem Stück Papier.
Jetzt ist womöglich die größte Kreativität in der Softwareentwicklung gefragt. Ich halte diese Phase jedenfalls für ihren Kern. Dafür sind Millonen Menschen Softwareentwickelnde geworden! Das ist der Teil, wo du an einem Problem knobeln kannst. Wer knifflige Probleme liebt, der ist im Entwurf bei der (Er)Findung eines Lösungsansatzes genau richtig.
Ich glaube, selbst dieses Problem löst du nicht im Kopf. Du musst deine Vorstellungen vor dir manifestieren. Blatt und Stift reichen dafür aus. Beim Lösungsansatz gelten keine Regeln. Alles ist erlaubt, was dich dem Ergebnis näher bringt. Das ist echte Freistil-Softwareentwicklung. In allen weiteren Phasen musst du irgendwelchen Regeln und Formalismen folgen. Genieße also die Freiheit in diesem Moment!
Wie kannst du das Problem auf einem Blatt Papier angehen? Hier ist mal ein Vorschlag:
Ist das formal? Nein. Verstehst du, was ich damit meine? Wahrscheinlich nicht. Kenne ich jetzt die Lösung? Ja! Und nur das zählt.
Ich habe mit einer Notation mit graphischen Elementen und Konventionen, die ich mir spontan ausgedacht habe, eine Darstellung geschaffen, die es mir erlaubte, meine ganz grobe Lösungsidee aus meinem Kopf aufs Blatt zu bekommen. Du siehst eine Lösung als Skizze.
Es hätte aber auch anders aussehen können. Über die folgende Darstellungsart bin ich im Internet gestolpert, als ich für das Buch recherchiert habe:
Hier wird die zu sortierende Liste als Matrix dargestellt, bei der in der vertikalen die Werte an der jeweiligen Position aufgetragen sind. Dadurch ist die schrittweise Herstellung der gewünschten Ordnung visuell sehr schön nachvollziehbar. Der gelbe Wert in einem Bild i ist im Bild i+1 einfach mit dem bis dahin letzten im blau markierten Listenabschnitt vertauscht worden (rot). So formen die Punkte von Bild zu Bild zunehmend eine aufsteigende Linie.5
Beim Lösungsansatz geht es nur um das Verfahren. Solange das plausibel wird, konkret und erklärbar(er) ist, ist das Ziel dieser Entwurfsphase erreicht.
Wenn du mit einem Problem konfrontiert bist, kann es sein, dass du es sofort lösen kannst. Das ist der Fall bei der vorliegenden Aufgabe. Du kannst natürlich eine Liste von Zahlen sortieren.
Nur, weil du das kannst, kannst du es aber noch lange nicht auch noch erklären. Wie geht das mit dem Sortieren? Wie gehst du vor? Was ist dein Verfahren, deine Herangehensweise, deine Methode, dein Ansatz? In der ersten Phase des Entwurfs findest du nicht nur heraus, ob du “es” kannst oder “irgendwie weißt wie es geht”. Nein, du musst dein Können und Wissen vermitteln können. Erste Herausforderung: Kannst du es dir selbst erklären? Zweite Herausforderung: Kannst du es anderen erklären?
In dieser Phase bist du im Grunde Erfinder. Du brauchst dafür kein wirres Haar, keine Brille und auch kein chaotisches Arbeitszimmer. Du bist Erfinder qua Aufgabe, die lautet: Finde eine Lösung, die du erklären kannst.
Deine Erfindung bezieht sich auf die anliegende funktionale oder nicht-funktionale Aufgabe. Im Beispiel ist es zunächst nur die funktionale, eine Liste überhaupt zu sortieren. Sie besonders effizient zu sortieren, war nicht gefragt.6
Ich kann mir vorstellen, dass diese “Lösungsansatzdenke” für dich noch ein bisschen abstrakt ist. Deshalb ein weiteres Beispiel, bevor es an die nächste Entwurfsphase geht:
Das ist wieder die 3. Iteration des Hello-World Programms. Schon diese Anforderungen umzusetzen war ja schwierig, solange ein Entwurf fehlt, wie ich versucht habe, in Kapitel 01 zu vermitteln. Wie könnte der jetzt also aussehen, um die Umsetzung zu vereinfachen? Oder genauer: Wie könnte sogar zunächst nur ein Lösungsansatz aussehen?
Für mich beginnt der Lösungansatz oft mit einer Sammlung dessen, was gebraucht wird. Welche “Komponenten” sind nötig? Was für Funktionseinheiten sind klar ersichtlich? Welche Subprobleme müssen gelöst werden? Im konkreten Fall gehört für mich dann auch dazu, welchem Ansatz die Persistenz folgen soll.
Auf der linken Seite siehst du ein Gedächtnisstütze. Ich habe die Benutzerschnittstelle skizziert, um mir währenddessen das Problem nochmal zu vergegenwärtigen. Natürlich ist die Benutzerschnittstelle schon Teil der Anforderungsdefinition, die du mit dem Auftraggeber zusammen erarbeitest. Doch zur Fokussierung auf den Entwurf ist es nicht schlecht, die Oberfläche dessen, was nun zu entwerfen ist, zu wiederholen und ggf. in ein anderes Format zu bringen, das dir als Entwickler taugt.
Rechts oben eine Liste der Funktionsbereiche. Aus den Anforderungen und der Vorstellung, wie die Bedienung des Programms sich anfühlen könnte, habe ich abgeleitet, was mindestens getan werden muss innerhalb des Programms. Dafür reicht erstmal eine Spiegelstrichliste ohne weitere Ordnung. Die ist sozusagen ein brain dump dessen, was dir so einfällt. Achte nicht auf Abstraktionsniveaus oder Beziehungen zwischen diesen Funktionsbereichen. Mir ist eingefallen:
- Irgendwie muss der Name erfragt werden. Das ist eine “Kompetenz”, die im Programm ausgebildet werden muss. Die kann z.B. leere Namen abweisen und erneut auffordern, wenn das gewollt sein sollte.
- Irgendwie muss dann auch die Liste der Gäste geführt werden, in der gezählt wird bzw. aus der abgelesen werden kann, wie oft ein Gast (identifiziert über seinen Namen) schon da war.
- Und irgendwie muss der Name inkl. seiner Besuchszahl in eine konkrete Begrüßung überführt werden, die dann angezeigt wird. Jenachdem, wie oft der Gast schon da war, wird hier entschieden, mit welcher Formel er begrüßt wird.
Weniger geht nicht, finde ich. Diese Funktionsbereiche stechen für mich als eigenständig heraus.
Zum Abschluss dann noch eine Idee, wie die Gästeliste über die Laufzeit des Programms hinaus persistent gemacht werden könnte. Mir scheint, dass dafür eine simple Textdatei im CSV-Format ausreicht. Zu jedem Namen wird darin vermerkt, wie oft der Gast schon da war. Kommt der Gast wieder, wird sein Besuchszähler erhöht.
Vielleicht hast du eine ähnliche Idee gehabt für einen Lösungsansatz, vielleicht auch nicht. Wichtig ist nicht, dass er genau so aussieht wie meiner, sonder dass es überhaupt einen gibt. Jeder Lösungsansatz auf einem Blatt Papier (oder in einer iPad-App wie Notability oder GoodNotes) ist besser als keiner. Denn mit jedem aufgezeichneten, d.h. externalisierten, explizierten und visualisierten Lösungsansatz bist erstens du dir selbst klarer über die Lösung geworden und zweitens kannst du jetzt anfangen, den Lösungsansatz mit anderen zu diskutieren. Wenn du mit Kollegen das Problem angehst, dann hast du die Lösung auf einem Medium, das ihr teilt. Darauf könnt ihr beide schauen, daran könnt ihr beide arbeiten.
Aber auch wenn ich sage, dass es für den Lösungsansatz keine spezielle Form gibt, weil du möglichst frei sein sollst, deiner Kreativität Raum zu geben, gibt es Grenzen der Nützlichkeit. Hier ein Bild aus einem Clean Code Workshop. Diesen Lösungsansatz hatte ein Team am Whiteboard zurückgelassen, als es sich an die Codierung gemacht hat. Bei aller Offenheit für persönlichen Stil und individuelle Darstellungen ist mir das dann doch zu wenig.
Also: Beim Lösungsansatz geht eine Menge. Mach dir keinen Kopf, “es richtig zu tun”. Wichtiger ist, dass du es überhaupt versuchst und einen Ausgangspunkt für den nächsten Entwurfsschritt schaffst. Doch achte darauf, dass ein substanzieller Bezug zum Problem zu sehen ist. Nur dann kann sich ein gemeinsames Modell entwickeln, weil alle von demselben Bild ausgehen. Ansonsten hängen nur Worte in der Luft, unsichtbar und flüchtig. Auf die kannst du ungleich schwerer Bezug nehmen. Deren Interpretation geht schnell auseinander.
Ein zu Papier gebrachter, expliziter Lösungsansatz hilft dir auch zu iterieren. Sei nicht mit der ersten Idee zufrieden. Wenn du sie vor dir siehst (oder auch nur versuchst, sie vor dich hinzustellen), kann es sein, dass du erstmalig merkst, dass die Idee doch noch nicht so gut ist. Schwierigkeiten in der Visualisierung im Speziellen oder Erklärung mit Worten im Allgemeinen sind ein gutes Signal für dich, weiter darüber nachzudenken. Und so kann es sein, dass du auf einen neuen Lösungsansatz kommst - den du selbstverständlich ebenfalls zu Papier bringst. Hier ein Beispiel für eine Revision der Persistenzidee für das Hello-World Beispiel:
Die Funktionsbereiche haben sich nicht verändert. Doch das Persistenzformat gefällt mir nicht mehr. Warum die Namen zusammen mit Besuchszählern speichern? Dann muss die Gästeliste immer komplett neu geschrieben werden, wenn es eine Textdatei ist, obwohl sich nur ein Wert verändert hat. Oder ich müsste zu einer echten Datenbank greifen, die einen gezielten Zugriff auf nur einen Datensatz bietet. Oder ich müsste die Namen statt in einer Datei auf viele verteilen, die ich getrennt aktualisieren kann.
Viel einfacher scheint mir jedoch, bei einer Datei zu bleiben, an die Namen allerdigs nur angehängt werden. Jeder Name taucht darin dann so häufig auf, wie der Gast auf einer Party war. Das kann jederzeit gezählt werden.
Diesen Ansatz nenne ich Event Store, weil jeder Besuch ein Ereignis ist, das minimal mit dem Besuchernamen dokumentiert wird. Das ist ein total flexibler Ansatz, der ohne Schema auskommt.7
Wie auch immer der Ansatz aussieht, das Wesentliche ist, überhaupt Klarheit über deine Lösungsidee zu bekommen. Versuche Anforderungen nicht sofort in Code umzusetzen. Versuche nicht einmal, eine Lösung für die Anforderungen sofort mit einem formalen Modell zu beschreiben. Nein, nimm dir die Zeit, die Lösung “im Freistil” zu erarbeiten. Deiner Kreativität sollen dabei keine Grenzen gesetzt werden. Dadurch wird der Lösungsansatz sehr wahrscheinlich auch schon ganz natürlich deklarativ. Betrachte in ihm Verhalten und/oder Daten, wie du magst. Wie es dir taugt, um Klarheit zu bekommen. “Ah, ja, so kann es gehen!” sollst du am Ende ausrufen. Dann bist du bereit für den nächsten Schritt.
Lediglich auf Papier musst8 du deinen Lösungsansatz früher oder später bringen. Das ist ein erster Test, ob er etwas taugt. Denn wenn du ihn nicht mal “im Freistil” auf Papier beschreiben kannst, wie willst du das später im Korsett des Codes schaffen?
2. Das Modell
Der Lösungsansatz ist notwendig, aber nicht hinreichend als Entwurf. Mit ihm hast du zwar eine Lösung erarbeitet, die kannst du nur nicht geradlinig in Code übersetzen. Damit kommen wir zum Modell: Das Modell ist die Lösung in solchermaßen formalisierter Form, dass dir danach die Codierung der Lösung leicht(er) von der Hand geht.
Jede Phase im Softwareentwicklungsprozess, den ich dir im Rahmen von Programming with Ease empfehle, hat einen sehr konkreten, engen Zweck:
- Die Anforderungsanalyse baut bei dir Verständnis für ihr Problem in einer Form auf, die konkret und testbar ist. Das Ergebnis sind Funktionen mit zugehörigen Testfällen.
- Der Entwurf findet eine Lösung für das Problem, das du nach der Analyse verstanden hast. Das Ergebnis ist ein Modell.
- Zunächst erarbeitest du die Lösung in Form eines Lösungsansatzes. Das ist informell, sehr abstrakt, möglichst visuell und “auf Papier”.
- Anschließend konkretisierst und formalisierst du den Lösungsansatz. Das Abstraktionsniveau sinkt etwas, die Lösung wird feiner ausgearbeitet, dennoch ist das resultierende Modell deklarativ und “codefrei”.
- Die Codierung übersetzt die entworfene Lösung in Hochsprachencode. Das Ergebnis sind Produktions- und Testcode.
- In der Codierung schreibst du zuerst einen Test, um dir eine Latte aufzulegen, über die du springen willst. Mit test-first vergisst du nicht, Tests zu schreiben, und du weißt sofort, wann du fertig bist mit dem Produktionscode.
- Der Produktionscode ist die Übersetzung des Modells in eine Form, die dem Kunden am Ende nutzt. Die Funktionen und Beziehungen aus dem Modell übersetzt du in Code. Anschließend füllst du die Funktionen mit Logik an, so dass tatsächlich Verhalten hergestellt wird. Das ist nun die imperative Lösung des Problems, das die Anforderungen aufgeworfen haben.
- Immer wieder refaktorisierst du Produktions- und durchaus auch Testcode, um das, was trotz eines guten Modells und geradliniger Übersetzung unsauber geworden ist, wieder in eine zukunftsfähige Ordnung zu bringen. Das passiert immer mal wieder und ist nicht schlimm. Gelegentlich weichst du auch vom Modell ab, weil du in der Codierung neue Erkenntnisse gewinnst.
Für gegebene Anforderungen ist das mehr oder weniger ein Wasserfall. Du durchschreitest diese Phasen von 1. bis 3.3. in dieser Reihenfolge. Theoretisch jedenfalls, denn praktisch gibt es darin Schleifen bzw. Rückwärtsschritte: Du gehst von der Modellierung zurück zum Lösungsansatz, weil du bei der Konkretisierung bemerkst, dass irgendetwas noch fehlt. Du gehst womöglich vom Lösungsansatz zurück zur Anforderungsanalyse und sprichst mit dem Auftraggeber, weil du bemerkst, dass dir irgendetwas noch unklar ist. Du “drehst dich im Kreis” innerhalb der Codierung während der Übersetzung eines Modells; das gehst du Funktion für Funktion mit 3.1, 3.2 und 3.3 an.
Der Wasserfall ist also entschärft. Keine Sorge, du musst keinen “Agilitätseid” brechen. Außerdem gilt der Wasserfall nur für die anliegenden Anforderungen. Es gibt keine Not, alle Anforderungen erst komplett zu analysieren. Du kannst einen beliebig kleinen Ausschnitt wählen. Manchmal rauschst du den Wasserfall in vier Stunden herunter, manchmal in einem Tag, manchmal in zwei Tagen. Länger sollte es zumindest für Entwurf + Codierung nicht dauern. Ich glaube fest daran, dass Inkremente nicht mehr als 16 Stunden für die Umsetzung brauchen sollten, d.h. z.B. von heute 9:00 Uhr bis morgen 17:00 Uhr.9
Modellarten
Das Modell formalisiert den Lösungsansatz. Es konkretisiert, was der Lösungsansatz mehr oder weniger grob angedacht hat. Was bisher vielleicht nur verschwommen zu sehen war, muss nun geklärt werden. Das betrifft beide Seiten jeder Lösung: das Verhalten und die Daten. Es gibt daher zwei Arten von Modellen:
- Das Verhaltensmodell beschreibt, was getan werden muss, um das Problem zur Laufzeit zu lösen.
- Das Datenmodell beschreibt, mit welchen Daten etwas getan werden muss.
Software weißt insofern eine grundsätzliche Dualität auf. Verhalten und Daten sind deren gegenüberstehende Seiten und ergeben zusammen das Ganze. Ohne Daten kein Verhalten, ohne Verhalten keine Daten.
Allerdings hat einer dieser Aspekte für mich Priorität: das Verhalten. Dafür wird Software gemacht! Eine Problemlösung besteht immer in Verhalten, das Daten transformiert.10
Datenmodelle
Die mainstream Objektorientierung wie auch lange Jahrzehnte sehr begrenzter Hauptspeicher und daraus folgend eine Wichtigkeit von Datenbanksystemen haben aus meiner Sicht viele Softwareteams dazu verleitet, zuerst und vor allem über Datenmodelle nachzudenken. Für diesen Aspekt hat Entwurf eine gewisse Akzeptanz und Sichtbarkeit behalten. Ich nehme an, dass auch du schon z.B. von Entity-Relationship (ER)-Modellen gehört hast:
Diese Darstellung ist “nur” ein Modell, weil sie kein Code ist. Weder siehst du programmiersprachliche Anweisungen, um eine solche Datenstruktur in einer Datenbank herzustellen, noch siehst du Klassen, die sie in-memory darstellen könnten, noch ist überhaupt klar, wie die einzelnen Elemente des Modells implementiert werden.11
Oder hier ein anderes Datenmodell von einer Seite, die UML (Unified Modelling Language) Klassendiagramme vorstellt:
Bei Datenmodellen geht es darum, Datenelemente zu benennen, Daten zusammenzufassen und Zusammenfassungen in Beziehung zu stellen. Diese Daten tun nichts, vielmehr wird mit ihnen etwas getan. Das ist die Aufgabe von Verhalten.
Im weiteren werde ich nicht viel zu konkreten Datenmodellierungsansätzen sagen. Ich entwerfe und benutze Datenmodelle einfach in der einen oder anderen Form. Datenmodellnotationen sind bei aller Unterschiedlichkeit der Darstellungen doch so einfach und naheliegend und weit verbreitet, dass ich mir weitere Ausführungen erspare. Ich bin gewiss, dass du mit der Datenmodellierung keine Schwierigkeiten haben wirst, wenn du erstmal weißt, um welche Daten es bei einer Lösung geht.
Das allerdings ist wiederum innerhalb der Lösungsfindung ein Problem, mit dem wir uns befassen wollen. Zu oft wird nämlich für meinen Geschmack eine vorzeitige Optimierung im Hinblick auf das Datenmodell vorgenommen. Das Datenmodell wird gesetzt und daran muss sich dann das Verhalten anlagern. Für mich steht auf diese Weise jedoch die Entwurfswelt auf dem Kopf!
Verhaltensmodelle
Wie gesagt, Priorität hat für mich das Verhaltensmodell. Im Verhaltensmodell wird beschrieben, was passieren soll. Man könnte sagen: Verhaltensmodelle drehen sich um Verben, Datenmodelle um Substantive. Funktionseinheiten werden benannt, zusammengefasst und Zusammenfassungen in Beziehung gesetzt. Diese Funktionseinheiten tun etwas mit Daten.
Vielleicht fällt dir sogar eine Art Verhaltensmodell ein: lange Zeit war das Flowchart sehr beliebt.
Das sieht konkreter aus als die bisherigen Lösungsansätze, oder? Das kannst du “runterprogrammieren”, oder?
Ich halte das aber für kein nützliches Modell. Es abstrahiert für meinen Geschmack zu wenig von den Mitteln einer Hochsprache. Ein Flowchart stellt einen Kontrollfluss dar wie Hochsprachencode. Es enthält Fallunterscheidungen und vor allem auch Schleifen wie Hochsprachencode. Diese Art der Darstellung von Verhalten bietet schlicht keine Skalierbarkeit. Du kannst damit keine größeren Lösungen beschreiben: das resultierende Diagramm ist dann genauso wenig verständlich wie Hochsprachencode.
Flowcharst stammen aus einer Zeit vor der Strukturierten Programmierung. In ihnen sind beliebige Verzweigungen (lies: Sprünge) erlaubt. Es gibt keine wirklich beschränkende Syntax. Sie sind mithin wenig hilfreich in der Praxis - auch wenn sie hier und da bei sehr begrenzter Funktionalität mal zum Einsatz kommen können.
Für mich gibt es zwei Kategorien von Lösungen: algorithmische und prozessurale. Algorithmische Lösungen bewegen sich sehr nah an den Mitteln der Strukturierten Programmierung von Programmiersprachen. Du bist versucht, sie mit Pseudocode oder Flowcharts zu modellieren. Der Lösungsansatz für das Sortierproblem fällt in diese Kategorie.
Als Beispiel für Pseudocode die obige Lösung für die Behandlung einer nicht funktionierenden Lampe:
1 If lamp is plugged in then
2 if bulb is burned out then
3 replace bulb
4 else
5 repair lamp
6 end if
7 else
8 plug in lamp
9 end if
Den Code verstehst du, auch wenn er zu keiner speziellen Programmiersprache gehört. Er ist eine Verallgemeinerung dessen, was in vielen Sprache an Mitteln vorhanden ist. Deshalb lässt er sich schnell hinschreiben; du musst auf keine syntaktischen/semantischen Feinheiten achten. Hauptsache er liest sich flüssig.12
Algorithmische Lösungen sind am Ende aber die unkritischen. Sie müssen gefunden werden, klar. Doch ihr Umfang ist gewöhnlich vergleichsweise klein. Ich sage mal etwas flapsig: Der Code rein algorithmischer Lösungen passt handschriftlich auf eine DIN A4 Seite. Das ist kein Umfang, der für langfristig hohe Produktivität eine große Hürde darstellt. Solange die Logik für eine algorithmische Lösung fokussiert in einer Funktion steht und keine funktionalen Abhängigkeiten bestehen, wirst du dir ein Verständnis erarbeiten können. Debugging hilft im Zweifelsfall.
Natürlich ist das eine Vereinfachung. Mir gehts hier aber um das big picture. Wenn du auf einen “algorithmischen Kern” in einem Problem gestoßen bist, dann modelliere mit einem Flowchart. Nur vermute ich, dass du zu früh gewiss bist, dass ein Problem schon algorithmisch ist. Du machst es dir damit zu schwer, ein Modell (oder später den Code) zu finden.
Ich glaube, dass die meisten Probleme zuerst und vor allem prozessuale Lösungen brauchen. In denen findest du dann keine Fallunterscheidungen oder Schleifen, sondern lediglich Funktionsaufrufe. Die stehen für Schritte in einem Prozess, der das gewünschte Verhalten erzeugt durch die Transformation von Eingabedaten in Ausgabedaten unter Verwendung von Zustand und Ressourcen.
Algorithmische Modelle sind schon sehr nah am Wie. Prozessuale Modelle hingegen zeigen vor allem das Was. Hier als Beispiel ein UML Sequenzdiagramm aus Wikipedia:
Die durchgezogenen Pfeile stellen “Funktionsaufrufe” dar. Im Bild ruft also der Computer den Server mehrfach auf im Rahmen der Lösung des Problems “check mail”.13
Das ist ein deutlich abstrakteres Verhaltensmodell als ein Flowchart. Hier geht es nicht um einzelne Anweisungen, sondern lediglich um die zu erledigenden Schritte. Wie genau ein Schritt wie sendUnsentEmail sein Teilverhalten erzeugt, ist unterhalb des Radars dieses Modells. Die Logik dafür würdest du in einer test-first Codierung finden. Sie ist ganz bewusst kein Bestandteil des Entwurfs.
Allerdings: Solange du in der Modellierung noch das Gefühl hast, dass so eine Funktionseinheit wie der Block für sendUnsentEmail zu groß ist, um in der Codierung zügig mit Logik gefüllt zu werden, solange solltest du mit dem Modell noch nicht zufrieden sein. Aber dazu später mehr.
Im Verhaltensmodell liegt der Fokus auf dem Was. Deshalb werde ich dir im Weiteren keine Flowcharts oder Pseudocode nahelegen. In beiden steckt für mich zuviel Wie. Aber auch das Sequenzdiagramm werde ich nicht weiter verwenden. Es skaliert ebenfalls nicht, wenn die Prozessschritte zu vielen “Akteuren” angehören (im Beispiel z.B. Computer).
Dennoch geben Sequenzdiagramme einen ersten Eindruck davon, wie ein Verhaltensmodell grundsätzlich aussieht.
Abstraktion
Verhaltensmodelle und Datenmodelle beschreiben die zwei Seiten von Software: Verarbeitung und Material. Das kann in ganz vielfältiger Weise geschehen. Die obigen Modelle sind sollen dafür nur Beispiele sein, um dir das Thema Modellierung etwas fasslicher zu machen.
Wenn du dir einen Eindruck von der Bandbreite an Modellierungsansätze verschaffen willst, dann schau dir z.B. Bücher wie UML Distilled von Martin Fowler14 oder Modellierung: Grundlagen und formale Methoden von Uwe Kastens15 an. Du wirst erstaunt sein, wie vielfältig du Lösungen ohne Code beschreiben kannst; oder manchmal auch nur Lösungsansätze, denn einige Modellierungsmethoden ordne ich eher der vorgelagerten Phase zu.
Egal aber, welchen Ansatz du insbesondere für die im Weiteren fokussierte Verhaltensmodellierung wählen solltest, solltest du eines nicht aus den Augen verlieren: die Umsetzung in Code. Beim Blick auf einen Modellierungs- oder allgemeiner Entwurfsansatz frage ich mich immer: “Und wo sind die Funktionen?” Denn die Funktionen sind die Container für die Logik. Und die Logik ist das, was das Verhalten erzeugt und so schwierig korrekt hinzubekommen ist. Und um sie korrekt zu erschaffen und auch zu erhalten, ist ein test-first Vorgehen bei der Codiernung nötig. Und dafür wiederum sind Funktionen als Ansatzpunkte zwingend.16
Was du als und wie du in der Entwurfsphase die Lösung findest, am Ende musst du sie in einem Modell formalisiert formulieren, das glasklar macht, welche Funktionen mit welchen Verantwortlichkeiten der Code aufweisen muss.
Diese funktionalen Atome, die alle ihren Beitrag leisten zum Gesamtverhalten, dürfen aber natürlich nicht “einfach herumliegen”. Vielmehr müssen sie in Beziehung gesetzt werden, um ein Zusammenspiel zu erreichen. Im Sequenzdiagramm oben ist das der Fall:
- Funktionen wie
sendUnsentEmailoderdeleteOldEmailsind Verhaltensatome. - Pfeile zwischen den “Akteuren”
ComputerundServersetzen Funktionen in Beziehung, hier:checkEmailmit z.B.sendUnsentEmail; erstere ruft letztere auf.
Und die “Akteure” selbst setzen Funktionen ebenfalls in Beziehung. Sie fassen sie zusammen, hier: in Server sind sendUnsentEmail und deleteOldEmail vereint.
Modelle als konkrete, formalisierte Lösungen und Ausgangspunkte für deinen Code müssen damit mindestens Folgendes leisten:
- Atomisieren: Die noch zu findende Logik mit Funktionen, d.h. Verhaltensatomen repräsentieren.
- Komponieren: Funktionen mit ihren Teilverhalten zu größerem Verhalten zusammenfassen. Aus Verschiedenem wird etwas Neues.
- Aggregieren: Funktionen thematisch zusammenfassen. Aus Ähnlichem wird etwas Größeres.
Dass Modelle außerdem auch noch die Daten, die die Funktionen verarbeiten, beschreiben müssen, ist selbstverständlich. Wie gesagt, das halte ich jedoch für ein vergleichsweise kleines Problem und sekundär. Wenn du von der mainstream Objektorientierung geprägt sein solltest, mag dir das merkwürdig erscheinen, doch versuche einmal deine Skepsis für die folgenden Seiten auf Urlaub zu schicken.
Atomisieren, komponieren und aggregieren sind für mich Abstraktionsleistungen. Für Details, Einzelteile, Feinheiten werden Begriffe gefunden, mit denen es sich leichter umgehen lässt. Und das kann dann sogar auf beliebig vielen Ebenen stattfinden.
Das Ergebnis ist ein Abstraktionsbaum mit beliebiger Tiefe für Komposite und Aggregate.
Ohne einen solchen Baum in zwei Dimensionen - Komposition und Aggregation - bekommen wir wachsende Lösungen einfach nicht in den Griff, glaube ich. Er existiert am Ende de facto im Code – fragt sich nur, wie es zu ihm gekommen ist. War das “Zufall”, “hat es sich ergeben”? Oder hast du ihn bewusst entworfen? Ich plädiere für Letzteres.
Plane deine Abstraktionen. Plane sie vor allem nicht allein, sondern gemeinsam mit deinen Entwicklerkollegen. Strebe nicht nur nach collective code ownership, wie es einmal im eXtreme Programming heißt. Ich meine, es muss auch ein collective design ownership geben. Ihr müsst alle zusammen hinter den Abstraktionen stehen, die die Lösung repräsentieren und formen.
Die soziale Dimension eines Entwurfs ist nicht zu verachten. Er ist ein Werkzeug für’s Denken wie für’s Kommunizieren. Deshalb ist es auch nicht so wichtig, dass ein Entwurf “für sich selbst stehen kann”. Lege deinen Lösungsansatz oder auch dein Modell nicht einfach jemandem zur Weiterverarbeitung stumm vor. Beide sind bei allem Detailreichtum “nur” Gesprächsanlässe. Entwürfe müssen für die Weiterverarbeitung mit Erklärungen übergeben werden. Am besten geschieht das im Dialog, zur Not schriftlich oder per Video. Je mehr Interaktionsmöglichkeit zwischen dem Empfänger deines Entwurfs und dir, desto besser. Denn der Empfänger wird Fragen haben. Er muss Fragen haben, weil du nie alles, was dir zu einer Lösung im Kopf herumgeht, vollständig in einem Entwurf festhalten kannst.
Zusammenfassung
Im Entwurf findest du zuerst eine Lösung und formalisierst sie dann abstrakt. Für mich gilt dabei: Behavior first, data second. Was das bedeutet, wirst du in den folgenden Kapiteln sehen.
Während du in der Lösungsfindung noch sehr frei bist, was den visuellen Ausdruck angeht - und visuell sollte er sein! -, engt die das Modell jedoch ganz bewusst sein. Seine Abstraktionen sollten so gestaltet sein, dass du sie leicht in Codestrukturen übersetzten kannst.
Es gibt eine Vielzahl an Modellierungswerkzeugen. Manche machen es dir schwerer, andere leichter, diese Forderungen zu erfüllen. In den folgenden Kapiteln stelle ich dir den Ansatz vor, von dem ich meine, dass er für dich der erste sein sollte, durch dessen Brille du auf eine Lösung schaust, um sie zu formalisieren. Nicht der einzige, aber der erste, dein Default. Er ist breit einsetzbar und leichtgewichtig, wie ich dir hoffentlich vermitteln kann. Andere Ansätze hab gerne auch in deinem Entwurfsköcher – doch gerade um deine Lösungen in groben Strichen zu skizzieren für “das Ausmalen” in der Codierung, halte ich das Flow-Design, wie ich es nenne, für ideal.
Übungsaufgaben
Aufgabe - Lösungsansatz finden
Die SARS-CoV-2 Pandemie 2020 hat vielleicht das Interesse für Statistik in der Bevölkerung nicht erhöht, doch zumindest haben jetzt mehr Menschen von Begriffen wie Sensitivität und Spezifizität gehört und dass ein positives Testergebnis auf SARS-CoV-2 Infektion weder notwendig eine Erkrankung bedeutet, noch zwingend korrekt ist. Aus diesem Anlass folgende Aufgabe, der so genannte Bedingte Wahrscheinlichkeiten zugrundeliegen. Das ist Mathematik, die nicht jedem jenseits der 4. Klasse Spaß gemacht hat, doch es ist keine höhere Mathematik und lässt sich mit ein bisschen googlen zu dem Begriff gut erkunden; mehr als Grundrechenarten sind nicht nötig. Dass du dich mal mit Bedingten Wahrscheinlichkeiten auseinandersetzt, ist ein Gewinn fürs Leben. Da bin ich gewiss.
Entwickle bitte nur einen Lösungsansatz für folgende Anforderungen.
Weder Modell, noch Codierung sind nötig. Mach dir also nicht zu viel Arbeit. Überlege, was hier wirklich “entwurfswürdig” nur in Bezug auf einen Lösungsansatz ist. Konzentriere deinen Lösungsansatz darauf. Sei so visuell wie möglich. Anhand deines Lösungsansatzes solltest du die Lösung jemand anderem leicht erklären können.