Real World Softwaredesign, Teil 1: Tell, don’t ask!

In dieser Artikelserie möchte ich Ihnen die Prinzipien des Softwaredesign anhand von Beispielen aus der realen Welt näher bringen. Eine Einleitung zum Thema könne Sie hier nachlesen.

In der heutigen Folge geht es um das Prinzip: Tell, don’t ask!

Nachfolgend betrachten wir dazu das Beispiel einer Zimmerreservierung in einem Hotel. Der Anrufer führt dabei keinen langen Dialog mit der Hotelrezeption sondern legt nach jeder Frage auf und denkt über die nächsten Schritte nach bevor er erneut anruft. Dieser etwas langsame und unentschlossene Typ von Anrufer entspricht etwas mehr einer Softwareanwendung welche auch jederzeit in der Ausführung unterbrochen und später fortgesetzt wird. Besonders in einer Multithreading-Anwendung ist dies ein wichtiger Aspekt.

 
Anrufer 1, Herr Müller

Hotelrezeption: Guten Tag. Was kann ich für Sie tun.

Müller: Hallo. Ich möchte gern nachfragen wie viele Ihrer Einzelzimmer morgen belegt sind.

Hotelrezeption: 8

Müller legt auf und überlegt: Ich kenne dieses Hotel. Sie haben 10 Einzelzimmer. Wenn nur 8 belegt sind dann ist noch Platz und ich werde buchen.

Müller ruft erneut an:

Hotelrezeption: Guten Tag. Was kann ich für Sie tun.

Müller: Ich möchte gern für morgen ein Einzelzimmer reservieren.

Hotelrezeption: Es tut mir Leid, aber für morgen sind bereits alle Zimmer vergeben.

Was ist hier geschehen? Herr Müller hat eine Annahme über das Hotel getroffen: Es hat 10 Einzelzimmer. Diese Annahme stimmt aber leider nicht mehr. Bei dem letzten Umbau des Hotels wurden Einzelzimmer zu Doppelzimmern zusammengelegt. Das Hotel verfügt nun nur noch über 8 Einzelzimmer.

Es wurden daher folgende Fehler begangen:

  • Die Hotelrezeption hat Informationen nach aussen gegeben die eigentlich für Aussenstehende nicht relevant sind: 8 Zimmer sind belegt
  • Herr Müller hat Annahmen über den internen Zustand des Hotels getroffen die er eigentlich nicht Wissen kann: Kapazität ist 10 Einzelzimmer

 
Anrufer 2, Herr Meier

Hotelrezeption: Guten Tag. Was kann ich für Sie tun.

Meier: Hallo. Ich möchte gern nachfragen ob morgen ein Einzelzimmer frei ist.

Hotelrezeption: Ja, für morgen haben wir ein Zimmer frei.

Meier legt auf und überlegt: Das Hotel gefällt mir eigentlich sehr gut. Ich werde buchen.

Meier ruft erneut an:

Hotelrezeption: Guten Tag. Was kann ich für Sie tun.

Meier: Ich möchte gern für morgen ein Einzelzimmer reservieren.

Hotelrezeption: Es tut mir Leid, aber für morgen sind bereits alle Zimmer vergeben.

Was ist hier in diesem Fall geschehen? Herr Meier hat sich zuerst die Information eingeholt ob ein Zimmer frei ist. Danach wollte er das Zimmer buchen. In der Zwischenzeit hat aber bereits Frau Schuster angerufen und das letzte Zimmer gebucht. Somit waren alle Zimmer belegt.

Es wurden daher folgende Fehler begangen:

  • Herr Meier hat abhängig vom internen Zustand des Hotels eine Entscheidung getroffen.
  • Die daraus folgende Aktion basierte auf der Kenntnis des internen Zustands, der sich aber bis zum Zeitpunkt der Aktion bereits geändert hatte.

 
Anrufer 3, Frau Schmidt

Hotelrezeption: Guten Tag. Was kann ich für Sie tun.

Schmidt: Hallo. Ich möchte gern für morgen ein Einzelzimmer buchen.

Hotelrezeption: Leider ist für morgen kein Zimmer frei.

Dieser Fall ist relativ einfach und klar. Frau Schmidt hat ihr Anliegen geäussert und direkt die Antwort erhalten. Sie kann nun ohne Umschweife ihre Zimmersuche fortsetzen und beim nächsten Hotel anrufen.

 
Zusammenfassung des Real World Beispiels

Frau Schmidt hat am effizientesten das Ziel erreicht. Sie hat ihr Anliegen direkt kommuniziert und ist dadurch ohne Umwege zu einem Ergebnis gekommen (Reservierung erfolgreich oder Hotel voll). Herr Meier und Her Müller sind Umwege gegangen. Sie haben interne Informationen des Hotels abgefragt und teilweise mit eigenen Annahmen vermischt (Hotel hat 10 Einzelzimmer). Dieses Vorgehen hat aber keinerlei Zusatznutzen gebracht, sondern konnte sogar falsche Erwartungshaltungen bezüglich der anschliessenden Zimmerreservierung hervorrufen.

Die erfolgreichste Herangehensweise war somit zu sagen was man möchte („Tell“) statt zuvor zu fragen welchen Zustand das Hotel hat („don’t ask“).

 
Beispiel aus der Softwareentwicklung

Jetzt lassen Sie uns annehmen, dass Herr Müller, Herr Meier und Frau Schmidt gerade mit ihrer Ausbildung als Softwareentwickler fertig und auf der Suche nach einer Anstellung sind. Sie haben sich alle bei der gleichen Firma um einen Job beworben und müssen nun im Rahmen des Einstellungstests eine Programmieraufgabe bewältigen.

Die Programmieraufgabe lautet wie folgt:

Es soll eine Stack Klasse entworfen werden welche eine Kapazität von 10 Elementen hat. Innerhalb einer Konsolenanwendung soll ein Element in den Stack eingefügt und anschliessend ausgelesen werden. Es ist dabei nur vorgegeben das der Stack eine Push und eine Pop Funktion enthalten soll. Push gibt einen bool Wert zurück welcher angibt ob das Einfügen erfolgreich war. Pop gibt das oberste Element des Stack zurück oder Null wenn der Stack leer ist.

Die nachfolgenden Codebeispiele zeigen eine verkürzte Version der jeweiligen Lösungen der drei Bewerber. Bitte beachten Sie, dass hier bewusst auf den Quellcode der Stack Klasse verzichtet wurde und nur das Interface dieser Klasse angegeben ist. In der anschliessenden Main Funktion fehlt daher auch die Instanziierung der stack Variablen.

 
Entwickler 1, Herr Müller

    1 class Program

    2 {

    3     interface IStack

    4     {

    5         bool Push(object element);

    6         object Pop();

    7

    8         int Count { get; }

    9     }

   10

   11     static void Main(string[] args)

   12     {

   13         IStack stack;

   14         object element;

   15

   16         //add element

   17         if (stack.Count < 10)

   18         {

   19             if(stack.Push(„abc“) == false)

   20             {

   21                 Console.WriteLine(„Stack is full“);

   22             }

   23         }

   24         else

   25         {

   26             Console.WriteLine(„Stack is full“);

   27         }

   28

   29         //get element

   30         if (stack.Count > 0)

   31         {

   32             element = stack.Pop();

   33             if (element == null)

   34             {

   35                 Console.WriteLine(„Stack is empty“);

   36             }

   37         }

   38         else

   39         {

   40             Console.WriteLine(„Stack is empty“);

   41         }

   42     }

   43 }

 
Herr Müller hat die Stack Klasse implementiert und diese zusätzlich mit einem Property erweitert um die Anzahl der enthaltenen Elemente abzufragen. In der Anwendung welche den Stack verwendet kann man deutlich erkennen welche negativen Auswirkungen diese Vorgehensweise hat.

  • Der Stack gibt Informationen über seinen inneren Zustand nach aussen weiter. Dies verstösst klar gegen das objektorientierte Konzept das interne Informationen verborgen sein sollen.
  • Herr Müller hat die internen Informationen des Stacks genutzt um ausserhalb der Stack Klasse Annahmen über deren Zustand zu treffen. Stellen Sie sich vor die Stack Klasse wird erweitert und kann nun 100 Elemente aufnehmen. Die Prüfung ob weniger als 10 Elemente im Stack sind wird aber ausserhalb in Anwendung durchgeführt und wird nun zu einem unerwünschten Verhalten führen.
  • Die Fehlerbehandlung muss jeweils doppelt durchgeführt werden. Dies ist daran zu erkennen, dass die Console.WriteLine Befehle doppelt vorhanden sind.
  • In einer Multithreading Anwendung kann sich der Zustand des Stack zwischen Abfrage des Count Properties und den anschliessenden Aktionsfunktionen (Push, Pop) ändern wodurch der Nutzen des Count Properties in Frage gestellt werden muss.

 
Entwickler 2, Herr Meier

    1 class Program

    2 {

    3     interface IStack

    4     {

    5         bool Push(object element);

    6         object Pop();

    7

    8         bool IsFull();

    9         bool IsEmpty();

   10     }

   11

   12     static void Main(string[] args)

   13     {

   14         IStack stack;

   15         object element;

   16

   17         //add element

   18         if (stack.IsFull() == false)

   19         {

   20             if (stack.Push(„abc“) == false)

   21             {

   22                 Console.WriteLine(„Stack is full“);

   23             }

   24         }

   25         else

   26         {

   27             Console.WriteLine(„Stack is full“);

   28         }

   29

   30         //get element

   31         if (stack.IsEmpty() == false)

   32         {

   33             element = stack.Pop();

   34             if (element == null)

   35             {

   36                 Console.WriteLine(„Stack is empty“);

   37             }

   38         }

   39         else

   40         {

   41             Console.WriteLine(„Stack is empty“);

   42         }

   43     }

   44 }

 
Herr Meier hat neben den geforderten Push und Pop Funktionen den Stack um weitere Funktionen ergänzt. Mit diesen Funktionen kann er abfragen ob der Stack leer oder voll ist. Diese Lösung ist besser als die Variante von Herrn Müller, da nun keine internen Informationen wie die Anzahl der Elemente nach aussen gegeben werden. Die Auswertung ob der Stack voll ist bleibt dem Stack Objekt vorbehalten. Trotzdem hat auch diese Lösung ein paar Nachteile:

  • Die Fehlerbehandlung muss jeweils doppelt durchgeführt werden. Dies ist daran zu erkennen, dass die Console.WriteLine Befehle doppelt vorhanden sind.
  • In einer Multithreading Anwendung kann sich der Zustand des Stack zwischen den Abfragefunktionen (IsFull, IsEmpty) und den Aktionsfunktionen (Push, Pop) ändern wodurch der Nutzen der Abfragefunktionen in Frage gestellt werden muss.

 
Entwickler 3, Frau Schmidt

    1 class Program

    2 {

    3     interface IStack

    4     {

    5         bool Push(object element);

    6         object Pop();

    7     }

    8

    9     static void Main(string[] args)

   10     {

   11         IStack stack;

   12         object element;

   13

   14         //add element

   15         if (stack.Push(„abc“) == false)

   16         {

   17             Console.WriteLine(„Stack is full“);

   18         }

   19

   20         //get element

   21         element = stack.Pop();

   22         if (element == null)

   23         {

   24             Console.WriteLine(„Stack is empty“);

   25         }

   26     }

   27 }

 
Die von Frau Schmidt entwickelte Applikation sieht auf den ersten Blick wesentlich aufgeräumter, einfacher und auf das wesentliche reduziert aus. Frau Schmidt hat in der Stack Klasse nur die Funktionen Push und Pop implementiert. In der anschliessenden Anwendung werden auch nur diese beiden Funktionen benötigt. Die zuvor festgestellten Nachteile der Lösungen von Herrn Müller und Herrn Meier sind hier nicht erkennbar.

 
Zusammenfassung des Programmierbeispiels

Frau Schmidt hat die effizienteste Lösung abgeliefert. Sie hat ausschliesslich Funktionen implementiert welche eine Aktion auslösen. In der Anwendung brauchte sie nur diese Funktionen aufrufen und deren Resultat auswerten. Herr Müller und Herr Meier sind einen indirekten Weg gegangen und haben zuvor den aktuellen Zustand des Stack Objektes ausgewertet. Herr Müller hat dabei zusätzlich die Auswertungslogik aus der Stack Klasse herausgezogen und in der Hauptanwendung realisiert.

Die erfolgreichste Herangehensweise war somit die Aktionsfunktionen direkt aufzurufen („Tell“) statt zuvor zu fragen welchen Zustand das Objekt hat („don’t ask“).

Versetzen Sie sich jetzt in die Position des Personalchefs der diesen fiktiven Einstellungstest durchgeführt hat. Welchen der drei Bewerber würden Sie einstellen?

 
Tell, don’t ask!

Aus den gewonnenen Erkenntnissen des Real World Beispiels und des Programmierbeispiels lassen sich die Inhalte des Softwareprinzip „Tell, don’t ask!“ relativ leicht ableiten.

  • Klassen sollten so designt werden, dass man ihnen mitteilt was sie tun sollen ohne vorher nachfragen zu müssen ob man dies tun darf.
  • Klassen sollten ihren internen Zustand nicht nach aussen geben da sonst das Risiko besteht das der interne Zustand ausserhalb der Klasse ausgewertet und als Entscheidungsgrundlage genutzt wird. Im Umkehrschluss behält eine Klasse ihr Entscheidungshoheit wenn sie ihren internen Zustand nicht veröffentlicht.
  • Der Verwender einer Klasse sollte sich keine Gedanken darüber machen müssen wie die Klasse ihre Arbeit verrichtet. Bietet die Klasse keinerlei Abfragefunktionen dann wird dem Verwender von vornherein die Möglichkeit genommen sich Gedanken zur Funktionsweise der Klasse zu machen.

Das Designprinzip „Tell, dont ask!“ ist ein Prinzip des objektorientierten Designs. In der prozeduralen Entwicklung wurden im Programmfluss Informationen ausgewertet und Entscheidungen getroffen. In der objektorientierten Entwicklung sollten im Programmfluss aber nur Anweisungen an Objekte gegeben werden. Die Informationsauswertung und Entscheidungsfindung bleibt in den Objekten verborgen.

 
Fazit

In der objektorientierten Entwicklung werden Anweisungen an Objekte geben. Die Objekte verwalten ihren internen Zustand selbst. Der Aufrufer der Anweisungen kennt diesen internen Zustand nicht. Ihm ist es nur wichtig dass seine Anweisungen ausgeführt werden bzw. dass er darüber informiert wird wenn eine Ausführung nicht möglich oder nicht erfolgreich war.

Das Prinzip „Tell, don’t ask!“ lässt sich in einem einfachen Aufruf an alle Entwickler zusammenfassen: Weg mit den Gettern und Abfragefunktionen!

Werbung
Dieser Beitrag wurde unter .NET, C#, Designprinzip, Softwaredesign abgelegt und mit , verschlagwortet. Setze ein Lesezeichen auf den Permalink.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit deinem WordPress.com-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s