Just-in-Time Requirements

Projekte und Produkte brauchen Konzepte, soweit werden auch extreme Agilisten nicht wiedersprechen. Die hohe Kunst ist dabei Essential Requirements und Designs so früh wie möglich benennen und klar kommunizieren zu können, andererseits aber Details offen zu lassen und später zu vervollständigen.

In diesem Artikel erhält dieses Vorgehen einen schönen Namen: Just-in-Time Requirements.

Command Binding für WPF vereinfachen

Das View-Models für WPF ein sehr mächtiges Architekturpattern sind, ist anerkannt.

Einfach nur nervig und redundant in der Notation sind dagegen Commands, wenn man sie nach dem Standardansatz mit RelayCommands implementiert.

Für die Umsetzung von umfangreichen Commands für neue Features für Quality Spy hatte ich genug davon.

Methoden als Command deklarieren

So einfach muss es sein: im Fokus steht die Domänenlogik (hier Selektions und UI-Logik) und nicht Infrastrukturcode für ICommands:

public abstract class TestPlanNode<T> : ViewModelBase<T>
{
  ...

  [ExposeAsCommand]
  public void CancelInPlaceEditing()
  { 
     this.InPlaceEditingActive = false;

     if (GetTestPlan().newNode == this)
     {
        var prevNode = this.GetPreviousNode() ?? this.GetParent();

        // cancel new node -> means remove it
        this.Remove();

        if (prevNode != null)
        {
           prevNode.IsFocused = true;
        }
     }
  } 
...
}

Wichtig ist das Attribut ExposeAsCommand. Mit Hilfe dieses Attributs kann man über die “magische” Eigenschaft Commands auf die Methoden als Commands zugreifen:

 <TextBox.InputBindings> 
...
 <KeyBinding Key="Escape" Command="{Binding Commands.CancelInPlaceEditing}"/>
...
 </TextBox.InputBindings>

Commands ist vom Typ CommandSet und wird in einer View-Model-Basisklasse bereitgestellt. Die Implementierung von CommandSet stammt von Mebyon Kernow und nutzt ICustomTypeDescriptor von .NET, um der Reflection-API das Vorhandensein von Properties (den Commands) “vorzugaukeln”. Diesem CommandSet kann man einfach RelayCommands übergeben (bei Mebyon Kernow im Original DelegateCommand benannt). Aber er hat das nicht zuende gedacht, denn folgt man seinem Blog-Beispiel musste man die Delegaten schwach typisiert im CommandSet hinzufügen. Daher habe ich eine Helper-Klasse implementiert, welche die Methoden im View-Model nach dem Attribut ExposeAsCommand scannt und dafür Delegaten bereitstellt. Die Initialisierung sieht im View-Model wie folgt aus:

public abstract class ViewModelBase : INotifyPropertyChanged 
{
   private CommandMap commands;

   public CommandMap Commands
   {
      get
     {
        if (this.commands == null)
        {
            this.commands = CommandMapMethodBinder.CreateCommandMap(this);
         }

         return this.commands;
    }
 }

Kostet das Performance? Ja klar, es ist Reflection nötig, die Klasse nach Methoden mit den Attributen zu scannen und es muss mit Reflection eine Delegat auf die Instanz-Methode erstellt werden. Um die Performance zu verbessern, wird der Scan nur einmalig ausgeführt und die Erstellung des Delegaten für den Command wird bis zur ersten Verwendung verzögert:

internal static class CommandMapMethodBinder
{
 private static Dictionary<Type, MethodInfo[]> methodsExposedAsCommandsCache = new Dictionary<Type, MethodInfo[]>();

 internal static CommandMap CreateCommandMap(object viewModel)
 {
   // get the methods - use caching since this method is slow
   MethodInfo[] methods;

   if (methodsExposedAsCommandsCache.ContainsKey(viewModel.GetType()))
   {
     methods = methodsExposedAsCommandsCache[viewModel.GetType()];
   }
   else
   {
     methods = viewModel.GetType().GetMethods().Where(m => m.GetCustomAttributes(typeof(ExposeAsCommandAttribute), false).Length > 0).ToArray();

     methodsExposedAsCommandsCache.Add(viewModel.GetType(), methods);
   }

   CommandMap commands = new CommandMap();

   foreach (var methodInfo in methods)
   {
     // since the creation of the delegate is potentially heavy, we defer it until the first usage 
     DeferredActionCreator lazy = new DeferredActionCreator();
     lazy.targetMethod = methodInfo;
     lazy.targetObject = viewModel;

      commands.AddCommand(methodInfo.Name, (x) => lazy.Execute());
 }

    return commands;
 }

 class DeferredActionCreator
 {
    internal MethodInfo targetMethod;
    internal object targetObject;

    private Action action;

    public void Execute()
    {
      if (this.action == null)
      {
         this.action = (Action)Delegate.CreateDelegate(typeof(Action), targetObject, targetMethod);
      }

      this.action();
    }
  }
}

Und schon hat man WPF mächtig aufgebohrt.

Download vollständiger Quellcode

WPF vs. ASP.NET MVC

developing a WPF app can be quite pleasant and productive, despite WPF’s many flaws, while writing an ASP.NET MVC app can be quite painful and tedious, despite MVC’s many virtues.

Gefunden bei Paul Stovell – Six Years of WPF.

Honest Loser Award

Die große Masse der Amis hat vielleicht einen Schaden. Aber manche haben uns einfach was voraus.

Einen Honest Loser Award kann ich mir in keiner deutschen Firma vorstellen, um schlechte Leistungen zu “würdigen”, wo doch bei uns schon die goldene Himbeere immer per Post zugestellt werden muss.

Make me think!

“Don’t make me think!” ist heutzutage ein weit verbreiteter Slogan im Usability-Bereich.

Nutzer wollen über ein User Interface nicht nachdenken. Beispiele wie der 300 Million $ Button zeigen, dass sie dies auch nicht tun. Die Aufmerksamkeitsspanne ist sehr klein. Bereitschaft zum Denken ist wenig bis gar nicht vorhanden.

Als Entwickler habe ich ein paar Jahre gebraucht, um dies zu akzeptieren. Wenn man es aber einmal getan hat, kann man recht fanatische Einstellungen dazu entwickeln (Don’t make me think! Don’t make me think! Don’t make me think! Uff-ta-Uff-ta-ta).

Aber: wenn man sich dieses Phänomen mal genauer anschaut, kommt es eigentlich aus dem Web-Bereich mit absoluten Gelegenheitsnutzern, wenn nicht gar Einmalnutzern.

Man kann es auch herumdrehen:  Software darf nicht zu einfach sein. Make me think!

Man sollte dabei prinzipiell seine Nutzer im Kopf haben. Wie häufig benutzen diese die Software und mit welcher Motivation? Wenn die Motivation gering ist, dann liegt mit Don’t make me think! richtig. Ansonsten gilt es die Motivation weiter zu befeuern. Das geht natürlich nicht, wenn alles total einfach ist.

Vor gut 2 Jahren habe ich einen Beitrag zu Software Gameplay geschrieben. Da ging es darum, wie ein gut gemachtes Spiel Aufgaben stellt, welche bewältigt werden müssen. Sind gute Spiele einfach? Sicher nicht, denn dann wären sie langweilig.

Eine Software ist dann gut gemacht, wenn man als Anfänger kleine Erfolge erzielen kann und man als Fortgeschrittener immer neue “Herausforderungen” entdeckt. Eine unübersichtliche UI mit komischen Buttons gehört aber sicher nicht zu den Herausforderungen, die ein Anwender gerne löst.

Aber komplexe Modelle. Schöne komplizierte Methoden, die fragwürdige Ergebnisse liefern, die man als Mensch kaum noch nachvollziehen kann. Das beeindruckt und macht Spaß!

Theoretisch sollten Produkte unwirtschaftlich sein, wenn sie keinen Mehrwert ggü. den einfachen Pendants bieten, aber komplizierter zu bedienen sind.  Aber scheinbar ist auch dies nicht so. Bestes Beispiel dafür ist SAP. In einem bestimmten Teilbereich habe ich selbst Expertenwissen (kurz den Ekel überwinden…ah es geht wieder), was man braucht um die einfachsten trivialsten Aufgaben zu erledigen, z.B. Übersetzen und “Transportieren” von Standardtextbausteinen, die in UIs und Programmbausteinen benutzt werden. Dieses Wissen findet sich nirgendwo in gängigen Büchern oder gar über Google. NEIN! Das muss man sich von anderen Experten und geheimen SAP-Dossiers zusammenklauben. Das sichert einerseits die Jobs von SAP-Beratern, aber es macht auch zufrieden dieses komplizierte Biest zu beherrschen. Kleine Randnotiz: Vielleicht sind ja deshalb Unternehmen die SAP im Einsatz haben so erfolgreich. Wer das Ding am Laufen hält, der hat’s drauf und ist auch anderswo gut.

Aber zurück zum Text. Selbst vor Konsumenten-Produkten macht gewünschte Kompliziertheit keinen Halt. OK, ist keine Software mehr, aber in diesem Vortrag von Dan Ariely geht es auch kurz darum, dass amerikanische Hausfrauen einen Kuchenteig abgelehnt haben, der zu “einfach” zu backen war.

Also, Don’t make me think ist schön und gut, aber wieso nicht mal genau das Gegenteil probieren?

Spaß für Kreativität und Ernstheit für Exaktheit?

Hoffentlich bin ich nicht der einzige Entwickler dem Bugfixing keinen Spaß macht. Wenn man Pyschologen glauben will, könnte das sogar hilfreich sein:

  • Bei guter Laune können wir besser über den Tellerrand schauen und neue Ideen entwickeln
  • Gute Laune ist jedoch schlecht für die Konzentration!

Tja und Konzentration braucht man beim Bugfixing eben sehr, neue Ideen jedoch selten…

Dazu gibt es hier einen interessanten Artikel.

Der wahrscheinlich dümmste Login-Screen der Welt

Wow, so einen dummen Login-Screen habe ich auch noch nicht gesehen:

Wenn ich in den Login-Dialog etwas eingebe, erstelle ich ein Projekt!?

War Software früher so öfters? Wie haben das die Computernutzer früher nur ausgehalten!!??

Tutorials zu System Composer, Break-It-Down und Quality Spy

Auf der Seite http://purple-box.blogspot.de/ finden sich nun einige Tutorials für System Composer, Break-It-Down und Quality Spy:

  • Wie kann kann man Systemideen mit System Composer visualisieren
  • Wie kann man große Systeme hierarchisch zusammensetzen
  • Wie kann man Fortschritt tracken
  • Wie kann man Wireframing Tools wie Balsamiq Mockups integrieren
  • Wie kann man System Composer, Break-It-Down und Quality Spy miteinander kombinieren

Die 3 Tools werden zusammen in der Suite Purple Box installiert. Natürlich gibt es dafür ein ein schönes Icon:

Das ist übrigens der neue Name für “DonkeyTools”. Ich habe ein eingesehen, dass der Name etwas albern war ;)

Auch die einzelnen Tools haben natürlich ihren Namen geändert:

System Composer -> früher “DonkeySpecs”, ganz früher “RichSpec-Editor”

Break-It-Down -> früher “DonkeyPlanner”

Quality Spy -> hieß bei der Veröffentlichung noch “TestRun”

Diese Namen gefallen mir aber so gut, dass das jetzt hoffentlich ein paar Jahre bestehen bleiben.

WPF makes the hard trivial and trivial hard

WPF makes the hard trivial and trivial hard

Gefunden bei Richard Mitchell.

Selbst-optimierende ORMs – Give me the free lunch!

Einst wurde O/R Mapping als Vietnamkrieg der Informatik bezeichnet. Es mag sein, dass dieser Vergleich bei näherer Betrachtung hinkt, er verdeutlicht jedoch auf jeden Fall, dass O/R Mapping ein heftig umstrittenes Feld ist:

„Although it may seem trite to say it, Object/Relational Mapping is the Vietnam of Computer Science. It represents a quagmire which starts well, gets more complicated as time passes, and before long entraps its users in a commitment that has no clear demarcation point, no clear win conditions, and no clear exit strategy.“

Ich möchte gar nicht so sehr auf den Vietnamkrieg-Vergleich als solches eingehen. Es sollte nur zeigen, was es für ein heißes Eisen war und immer noch ist. Vereinfachend kann das Pro und Kontra auf das Abwägen von Performance und Produktivität reduziert werden. Mangelnde Performance ist ein häufig geäußerter Kritikpunkt an O/R Mapping. ORMs sind nicht per se inperformant, aber ein Tuning erfordert Fingerspitzengefühl und Expertenkenntnisse. Die Mächtigkeit von objektorientiertem Design zu ermöglichen, ist dagegen das große Plus des O/R Mappings.

Es wird oft geäußert, dass zwischen dem Objektparadigma und dem relationalen Paradigma ein Impedience Mismatch existiert. Tabellen sind zwar nicht eins zu eins auf Klassen abbildbar, aber aus meiner Sicht ist dies keine große Kluft, sondern nur ein kleiner Spalt, der mittels O/R Mapping ja ausreichend überbrückt wird. Das viel größere Problem ist aus meiner Sicht ein ganz anderes. Es hat auch gar nichts mit Datenbanken oder dem relationalen Paradigma zu tun, sondern ist ganz einfach ein Grundproblem der Objektorientierung:

Objektorientierung und Verteilung harmonieren nicht!

Dies ist ein anerkanntes „Naturgesetz“, das von namenhaften Autoren beschrieben wurde:

  • Patterns of Enterprise Application Architecture: Fowler’s erstes Gesetz des verteilten Objektorientierten Designs: “Don’t distribute your objects.“ – Dieses Gesetz wurde zwar nicht direkt für O/R Mapping postuliert, jedoch hat Lazy Loading das gleiche Performance-Verhalten wie “verteilte Objekte”
  • The Law of Leaky Abstractions – Joel Spolsky beschreibt unter anderem, dass die Abstrahierung von Hardware wie z.B. Netzwerken unerwartete Auswirkungen haben kann

Und auch CORBA ist gescheitert, weil das Unmögliche eben doch nicht immer möglich ist.

Warum Verteilung den O/R Mappern so viele Schwierigkeiten bereitet, lässt sich in ein paar Zeilen Pseudocode verdeutlichen:

<table>
  @foreach(var customer in database.Customers)
  {
    <tr>
      <td>@customer.Name</td>
      <td>
        @foreach(var phoneNumber in customer.PhoneNumbers.Where(p => p.IsGermanMobileNumberPattern())
        {
          <div>@phoneNumber.Value</div>
        }
      </td>
    <tr>
   }
</table>

In diesem primitiven Code sind viele typische Fallstricke des O/R Mappings versteckt.

Zunächst sieht man zwei verschachtelte foreach-Anweisungen. Ein primitives O/R Mapping wird für jeden Kunden SQL-Commands an die Datenbank schicken, um die Telefonnummern zu laden. Dieses Phänomen ist als Cripple Loading bekannt. Gute O/R Mapper erlauben (mittlerweile) situatives und selektives Eager Loading, um dieses Problem zu umgehen, d.h. beim Laden der Daten können auf eine produktive Art und Weise Beziehungen angegeben werden, die mitgeladen werden sollen.

Ein weiteres oft beschriebenes Problem ist das unnötige Laden von Fat-Content. Ein O/R Mapper lädt standardmäßig immer alle Felder eines Objektes. Es gibt jedoch sehr komplexe Objekte mit hunderten von Feldern wie z.B. einen Personalstammsatz. Vor allem wenn einzelne Felder Langtexte, Bilder oder Ähnliches enthalten, können Performance-Einbußen entstehen.

Aber das Problem ist hier noch nicht zu Ende. Das selektive Eager Loading ist ein manuelles Performance-Tuning. Was ist, wenn jedoch außer einfachen „Daten-Eigenschaften“ Methoden aufgerufen werden, wie z.B. die Prüfung „IsGermanMobileNumberPattern“ im Pseudocode-Beispiel?  Hier kann kaum Optimierung stattfinden, denn durch Information Hiding und Vererbung weiß man per Definition der Objektorientierung nicht, was sich hinter dem Aufruf einer Methode versteckt. Wird da ein Webservice aufgerufen oder nur eine Regex-Prüfung ausgeführt?

Das relationale Paradigma funktioniert perfekt mit verteilten Umgebungen

Relationale Datenbanksysteme funktionieren dagegen schon seit Urzeiten perfekt als verteilte Systeme mit Datenbankservern und mit Clients. Aber sie bieten eben keine Funktionalität wie Information Hiding oder Substituierbarkeit.

Passend zum obigen Beispiel könnte eine SQL-Abfrage sehr performant die Daten zurückliefern:

SELECT Id, Name FROM Customer;

SELECT PhoneNumber.CustomerId, PhoneNumber.Value FROM Customer
JOIN PhoneNumber ON PhoneNumber.CustomerId = Customer.Id;

Aber was ist, wenn nun die Client-Anwendung Code hinzufügt, z.B. um eine bestimmte Icon-Regel auszuführen, die für bestimmte Kundengruppen ein Icon ermittelt?

Dementsprechend muss das SQL geändert werden. Dies kann als wartungsunfreundlich bezeichnet werden, da häufige Änderungen am Datenzugriff nötig sind. Außerdem kann es unter Umständen gar nicht so einfach sein, die zu ladenden Daten festzulegen, wenn ein gutes objektorientiertes Design mit Information Hiding und  Wiederverwendung erstellt wird. Alle potentiellen Konsumenten der Daten müssen analysiert werden.

Daraus kann man schließen, dass es in vielen Anwendungen ökonomisch vernünftig ist, auf Performance-Optimierungen zu verzichten und stattdessen einfacheren, wartbaren und Objekt-orientierten Code zu schreiben.

Können wir nicht beides haben? Give me the Free Lunch!

Nun kommen wir zum eigentlichen Thema dieses Artikels (war die Einleitung vielleicht etwas lang geraten?!). Es wäre wirklich paradiesisch ein System zu haben, welches einfach und wartbar zu programmieren ist, jedoch gleichzeitig eine gute Performance besitzt.

Warum auf den Free Lunch verzichten, bei der Hardware haben wir auch lange Zeit ständig Verbesserungen erfahren, ohne dafür mehr zu bezahlen oder anders programmieren zu müssen.

Nun ist die große Preisfrage, wie dies funktionieren könnte. Die Zielstellung ist einfach. Eine Engine müsste herausfinden, in welcher Situation welche Felder und Beziehungen zu einem Objekt geladen werden sollen.

Ich denke, dass dies prinzipiell auf zwei Arten passieren kann:

  1. statische Codeanalyse
  2. statistisches Lernen

Puh, beides nicht gerade einfach. Ich bin der Meinung, dass Objektdatenbanken (war es evtl. Objectivity DB von Versant?) durchaus bereits in der Vergangenheit einen statistischen Ansatz verfolgt haben.

Ich habe auch den statistischen Ansatz favorisiert, da mich die statische Codeanalyse bei der Programmierung schlicht überfordern würde. Mein kleines Proof of Concept belegt dabei eindeutig: YES, you can have free lunch!

Das System hat folgendes Design: 

Das Grundprinzip soll dabei sein, dass während der Laufzeit aufgezeichnet wird, wann welche Properties zugegriffen werden. Auf diese Kenntnisse wird dann beim Laden von Entities bzw. beim Ausführen von Datenbankqueries zurückgegriffen. Diese Statistik global für jede Entity zu führen wäre recht sinnlos, denn ein Property wird ja in vielen verschiedenen Kontexten verwendet. Um das Proof of Contept nicht zu kompliziert zu gestalten, wird ein DestinationCall eingeführt, welcher typischerweise eine Programm-Funktion darstellt, wie z.B. „Zeige Kundenliste an“, „Bearbeite Kunde“ oder „führe Kreditberechnung durch“. Für jeden DestinationCall werden dann die zugegriffenen Properties aufgezeichnet, daraus werden LoadAdvices generiert. Der QueryExecutor, d.h. eine absolute Kernkomponente eines O/R Mappers, muss dann auf die LoadAdvices zugreifen und damit die Optimierung umsetzen.

Eine Voraussetzung für das Laden ist ein Konzept zum effizienten Umgang mit partiell geladenen Entities. Dies wird durch SparseObjects und ein damit verbundenes Typsystem abgebildet. Diese sind an die aus WPF bekannten Dependency Properties angelehnt. Man muss also auf jeden Fall auf POCOs verzichten und seine Entities auf eine spezielle Art und Weise implementieren.

Das Proof of Concept beinhaltet die API und ein Konsolenprogramm, welches einige Beispiele ausgibt:

Download Beispiel und Source Code

Das Proof of Concept hat derzeit einige Beschränkungen:

  • Es werden lediglich primitive Properties unterstützt, d.h. keine Kind-Objektgraphen / Beziehungen. Das Recording müsste dafür sicher nicht viel verändert werden, jedoch der QueryExecutor und das Typsystem, weswegen ich vorerst darauf verzichtet habe
  • Der DestinationCall-Kontext muss derzeit als Feld gesetzt werden. Es wäre eleganter dies mit PostSharp aspektorientiert zu implementieren und damit Methoden wie z.B. einen EventHandler zu attributieren
  • Der DestinationCall-Kontext müsste eher ein Stack als ein einzelnes Feld sein
  • Die Objekte unterstützen kein Lazy-Loading. Im Falle eines falschen Load-Advices könnten keine Daten nachgeladen werden. Das Lazy-Loading selbst kann intelligenter sein, als das heutiger O/R Mapper. Ein einzelnes Lazy-Loading, könnte zu einem Batch-Loading führen, z.B. falls eine Property in einer foreach-Anweisung zugegriffen wird
  • Die LoadAdvices werden nicht aus einer Datenbank geladen, sondern nur per API oder aus dem Recording der Laufzeit

Wer Lust hat, diese Unzulänglichkeiten zu beheben, kann sich gerne bei mir melden!

Da das Recording einen Overhead für die Laufzeit bedeutet, sollte es für ein reales Programm nicht ständig ausgeführt werden. Nachdem eine Lernphase abgeschlossen ist, sollte das Recording deaktiviert werden. Dann ist der Overhead nur noch sehr gering, denn die Implementierung ist für Inlining optimiert, sodass der Overhead wirklich nur wenige MSIL-Instruktionen beinhaltet. Diese Lernphase muss auch überhaupt nicht beim Endnutzer stattfinden, sondern könnte auch während der Entwicklung oder in einem Testlabor „nebenbei“ stattfinden. Die aufgezeichneten LoadAdvices könnten dann als Teil des Programms (in einer Datenbank) ausgeliefert werden.

Die API nutzen

Die Implementierung ist zwar nur ein Prototyp, aber man kann aber auf jeden Fall damit rumspielen. Dazu muss eine Entitätsklasse definiert werden, welche Metadaten über Typ und Properties definiert. Wem das sehr ähnlich zu den Dependency Properties von WPF erscheint, der hat dies absolut richtig erkannt, lediglich die Initialisierung erfolgt etwas anders, da zusätzlich zum Property auch noch ein EntityType definiert werden muss und EntityType eine Eigenschaft mit den dazugehörigen Properties haben soll:

Natürlich schreit dies gerade zu nach Codegeneration!

Fazit

Es ist wirklich ein Beweis erbracht, dass ein statistischer Ansatz funktionieren könnte, allerdings müsste das System tief in einen O/R Mapper integriert werden und kann nur schwer einem vorhanden modular hinzugefügt werden. Sprich: man müsste seinen eigenen O/R Mapper implementieren oder ein Open-Source-System wie NHibernate forken.

Das Prinzip des DestinationCalls ist sicher nicht das Ende der Weisheit, eine weiterführende Engine, die Kosten für Eager Loading and Cache Misses analysiert und darauf basierend Entscheidungen trifft, wäre natürlich viel ausgeklügelter, aber ich denke, dass selbst das einfache DestinationCall Paradigma bereits in vielen Anwendungsteilen enorme Performance-Verbesserungen erzielen kann. Dadurch, dass der Entwickler den DestinationCall immer manuell benennen muss, z.B. durch das Setzen eines Attributes, ist auch ein Schutz vor fehlgeleiteter Optimierung gegeben.

Aber sicher wäre noch viel Arbeit zu tun, um diese Funktionalität in einen O/R Mapper zu integrieren.