Mein Bug, dein Bug

Historisch wertvolles TerminalIm Jahr 2002 entwickelten wir Software für eine Messapparatur, die unter anderem mit einer historisch wertvollen Datenbank über eine VT3270-Terminalemulation kommunizieren musste. Der Datenaustausch geschah über kleine Textdateien, die ausgelesen oder geschrieben wurden. Das Format der Textdateien war strikt spezifiziert und auf ein Minimum reduziert. Einige Messwerte mussten im “wissenschaftlichen Format” übergeben werden:
1.32E-01 ist gleichwertig zu 0.132

Wichtig an der Spezifikation war, dass das Format nach dem Exponententrenner “E” immer ein Vorzeichen haben musste, d.h. es wurde für den Wert 132 nicht 1.32E02, sondern 1.32E+02 erwartet.

Dummerweise hatte das damals verwendete Java 1.4.0 genau an dieser Stelle einen Bug:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4691683
Das Format +0.00000E00 erzeugt Wertrepräsentationen wie “+1.32E+02”, aber auch solche Kuriositäten wie “+1.32E-+01”.

Interessant ist dabei, dass das Vorzeichen des Formats im Exponenten wiederholt wird. Manuelles Entfernen der überflüssigen “+”-Zeichen brachte damals das gewünschte Ergebnis in die Dateien.

Dummerweise war das nicht der einzige Bug in Hinsicht auf das wissenschaftliche Formatieren in Suns DecimalFormat, so dass mit der Version Java 1.5 das Format “+0.00000E00” Wertrepräsentationen wie “+1.32E02”, aber auch Kuriositäten wie “+-1.32E02” erzeugt.

Mit der Änderung der Java-Version kam also ein vor Jahren durch Workaround behobener Fehler zurück, da jetzt (unter anderem) der Workaround zum Fehler wurde.

Was habe ich aus dieser Episode gelernt? Liefere möglichst kein spezifiziertes Format aus, das nicht mit Unit Tests vollständig abgesichert wurde. Und vor allem: Sichere jeden Bugreport, der nach außen geht, mit einem entsprechenden Test ab. Denn wenn der Bug nicht mehr da ist, wird auch der Workaround hinfällig und im schlimmsten Fall sogar selbst zum Bug.

Lethal risks of bugfree software

Bei der routinemäßigen Kontrolle unserer Continous Integration Server Hardware fanden wir heute eine tote Spinne auf dem Mainboard. Das ist sehr bedauerlich für das arme Tier, wir glauben aber zu wissen, woran sie gestorben ist: Verhungert wegen zu wenigen Bugs.

Eventuell hätte sie in einen Entwicklerrechner ziehen sollen.

It’s not a bug, it’s coolness

In alten Eclipse-Versionen wurden Anwendungen meistens über den “Run-Button” gestartet. Ein Klick und die vorher gestartete Anwendung wurde erneut gestartet. Das war an sich ganz praktisch, aber für “Code->Run Test->Run Application”-Zyklen leider unpassend.

Also erfanden das Eclipse-Team eine neue Funktionalität für den alten Knopf: Context launching. Leider wurde vergessen, dies dem Benutzer mitzuteilen. Der Knopf sieht aus wie früher, verhält sich (meistens) wie früher und hat auch sonst keine neuen Features. Nur manchmal, heimlich, wechselt der Kontext und man startet völlig unvorhergesehene Dinge.

Zuerst mal ein paar gute Nachrichten zu diesem Feature:

Damit hören die guten Nachrichten aber auch schon auf und die Probleme fangen an. Eine kleine Sammlung an Bugs zum Thema findet sich in den Links.

Das Feature wird bleiben – und damit eine meiner Meinung nach mittlere Usability-Katastrophe.

  • Alle Veteranen des “Run-Button” müssen umlernen oder die Funktion deaktivieren (anstatt es bei geeigneter Coolness bewusst zu aktivieren). Ok, das ist nur ein temporäres Problem der nächsten Monate
  • Der Kontext, und damit die “Intelligenz” im Hintergrund der Entscheidung, bleibt unsichtbar. Keine Farbe, kein Sinnbild, kein Ton. Ich bin wahrlich geübt im Umgang mit interrelationsreichen Computerprogrammen und Geräten, aber diesen Kontext habe ich nicht intuitiv erfasst.
  • Der Knopf bietet keinerlei Hinweis darauf, dass er jetzt etwas anderes tun wird als gerade eben noch. Er bietet auch keinen Hinweis darauf, was er jetzt tun wird. Schon eine leichte Umfärbung bei einem Kontextwechsel wäre zumindest ein Anfang.
  • Der Knopf funktioniert entweder mit Kontext oder ohne (im sogenannten Last-launch-Modus). Eine Art “Modus-Umschaltung” durch gedrückte Shift-Taste oder ähnlich gibt es nicht. Hier hilft nur der Weg durch die globalen Einstellungen oder das Bewusstmachen des Kontextes.

Fazit: Intention des Eclipse-Teams war die Erhöhung der Coolness der IDE. Erreicht wurde meiner Meinung nach eine Verhöhnung versierter Anwender: “Ihr habt es nicht mehr im Griff!”. Echte Coolness braucht den Überraschungsmoment nicht, um zu wirken.


Nachtrag/Ergänzung: In einem Kommentar zum verlinkten Blog-Eintrag des Eclipse-Teams wird davon geredet, dass das Feature sehr praktisch ist, wenn Eclipse im Tutor-Betrieb vor Studenten eingesetzt wird. Ich hoffe, trotz der unbestrittenen Wichtigkeit der Lehre, dass dies nicht der anvisierte bzw. hauptsächliche Einsatzzweck für Eclipse sein soll.

Flattening the namespace again

In sehr frühen Zeiten der Programmierung mussten Namen von Variablen oder Funktionen mit Bedacht gewählt werden, da sie alle global sichtbar waren. Dann wurden Namensräume, d.h. Namespaces erfunden und später über Objekte und für Java auch Packages weiter verfeinert.

Das Package-System von Java, auch schonmal als “Übel Nummer Eins” bezeichnet, hielt selbst gleichnamige Klassen, beispielsweise java.awt.List und java.util.List, zuverlässig auseinander.

Mit dem Aufkommen von modernen IDEs und deren Fähigkeit zur automatischen Namensergänzung (Code Completion, Content Assist, etc.) werden die Namensräume allerdings wieder klein. Dies merkt man besonders dann, wenn man eine gutgemeinte Bibliothek integriert, die beispielsweise eine eigene Klasse File mitbringt.

Die Lösung wird sein, heuristische Verfahren für die Code Completion einzusetzen, mit Prefixen (JList, etc.) zu arbeiten oder sich wieder deutlich mehr Gedanken über die Namensgebung zu machen. Wir haben das Problem nicht gelöst, sondern nur auf eine neue Ebene gehoben.

Hibernate Pitfalls

Nehmen wir an wir hätten eine Klasse Activity, die u.a. ein Attribut Duration hat, das die Dauer in Sekunden als Quantity Second angibt. Wenn man nun die Mapping Datei folgendermassen gestaltet:

<hibernate-mapping default-lazy="false">
<class name="Activity" table="activity">
...
<property name="duration" column="duration" type="double" not-null="true"/>
</class>
</hibernate-mapping>

scheint alles ok zu sein. Hibernate findet sein Attribut und seine Setter und Getter. Also los! Das grosse Stirnrunzeln kommt dann beim Starten des Programms (bzw beim Speichern der Activities in die Datenbank):

java.sql.SQLException: Attempt to insert null into a non-nullable column:
column: DURATION table: ACTIVITY in statement [...]

Hmmm… also erstmal den Debugger anschmeissen und der sagt einem duration ist nicht null, ist doch alles ok, aber warum meckert dann hibernate?

Die Lösung ist ganz einfach: hibernate kann den Rückgabewert der getter Methode nicht als double interpretieren und nimmt daher null an, anstelle zu melden, dass die getter Methode den falschen Rückgabetyp hat. Sinnvolle Fehlermeldungen könnten manchmal die Fehlersuche wirklich verkürzen…

Trick 17 mit JUnit

vergleich1.png

Nehmen wir an, wir hätten eine Klasse FileProvider, die mittels der Methoden getFile() File-Instanzen zurückgibt, deren Pfad bestimmte Eigenschaften erfüllen muss. Im folgenden Test prüfen wir eine dieser Eigenschaften ab:


public void testFilePath() {
FileProvider fileProvider = new FileProvider();
assertEquals("path/myfile.txt", fileProvider.getFile());
}

Wenn jetzt der verwendete FileProvider im einfachsten Fall wie folgt implementiert ist:


class FileProvider {
public File getFile() {
return new File("path/myfile.txt");
}
}

schlägt der Test trotzdem fehl:

junit.framework.AssertionFailedError: expected:<path/myfile.txt> but was:<path/myfile.txt>

und man verbringt eine harte Zeit, sich klarzumachen, was gerade wirklich passiert ist.

Die Lösung liegt in der verwendeten assertEquals-Methode und der Implementierung der toString()-Methode im File-Typ. Anstatt, wie eigentlich vorgesehen, zwei String-Objekte zu vergleichen, werden mit der obigen assertEquals-Methode zwei Objects, konkret ein String als Soll-Instanz und ein File als Ist-Instanz, miteinander verglichen. Dieser Vergleich geht schon wegen der verschiedenen Instanztypen schief. Die Ausgabe basiert allerdings auf dem Ergebnis, das die jeweilige toString()-Methode zurückgibt. Und da geben beide Instanzen die gleiche Zeichenkette zurück, was zu der obigen, sehr mißverständlichen Fehlermeldung führt.

Ich hätte dazu zwei Verbesserungsvorschläge:

  • Die Fehlermeldung so umbauen, dass sie die verschiedenen Typen der Objekte zeigt, wenn die Gleichheitsprüfung am Typ der Objekte gescheitert ist (das könnte mit der gegenwärtigen Struktur der equals()-Methoden etwas schwieriger werden) und nur dann auf die Darstellung der Instanzwerte (über toString()) zurückgreift, wenn die Prüfung daran scheiterte.
  • Eine zusätzliche Methode definieren, die ebenfalls eine textuelle Objektrepräsentation zurückgibt und, falls sie vorhanden ist, von JUnit anstelle von toString() aufgerufen wird. Also eine toJUnitString() oder so ähnlich. Ist sie nicht definiert, wird weiterhin toString() verwendet. Im meinem Fall hätte dieser Vorschlag also nichts geholfen.

Was wären weitere Möglichkeiten?

Bubble, bubble, Build’s in… Bubbles!

Vor kurzem war unser Code Flow-O-Meter fast ausgetrocknet. Auch Zimmerbrunnen verlangen nämlich regelmäßig Wasser, sogar eher mehr als Zimmerpflanzen. Beim Nachgießen fiel mir auf, dass der Brunnen mittlerweile ziemlich veralgt ist. Also beschloss ich, mit etwas Spülmittel dem Wasser eine frischere Note zu geben.

Den nächsten Check-In machte Luke von einem weit entfernten Büro. Ich hörte das Plätschern des Brunnens und sah dann aus den Augenwinkeln ein großes, weißes Etwas vom Brunnen in die Bücher kippen.

Spülmittel in Zimmerbrunnen hat die Eigenschaft, massiv Schaum zu bilden.

Das Algenproblem ist nach wie vor ungelöst, aber die Bücher sind wieder trocken. Nur das Brunnenschild hat bleibende Schäden davongetragen. Leider hatte ich keinen Photoapparat zur Hand, sonst hätte ich im Lachen noch ein paar Bilder schießen können.

Der Eintragstitel ist übrigens eine Abwandlung eines Artikels auf Pragmatic Automation.

First Test First

Vor kurzem wurde in der Schneide ein “Test First” Workshop abgehalten. Wir arbeiteten zusammen mit einem ehemaligen Schneide-Mitarbeiter, der sein Wissen direkt vom geistigen Vater des “Test First”-Ansatzes beziehen kann, an einem kleinen aber nicht trivialen Projekt. Trotz einer unsäglichen Zeit früh am Morgen war die Stimmung gut und der Lernwille groß. Natürlich sind wir in keinster Weise fertig geworden, aber erste Eindrücke sind gewonnen.

Was ist Test First?
Im wesentlichen ist Test First eine konkrete Handlungsanweisung für Programmierer, die ihre Software mit einer ordentlichen Testabdeckung versehen wollen und, das ist für mich die beste Erkenntnis gewesen, deren Software vor allem “good enough” (und nicht bedeutend mehr) programmiert und getestet sein soll.

Die Handlungsanweisung kann in sieben Schritte unterteilt werden:

  1. Schreibe einen Testfall
  2. Schreibe Code, so dass der Testfall kompiliert
  3. Lasse den Test laufen, der Testfall wird fehlschlagen
  4. Schreibe Code, damit der Testfall nicht mehr fehlschlägt
  5. Lasse alle Tests laufen, es sollte kein Testfall fehlschlagen
  6. Refaktorisiere
  7. Mache weiter bei 1.

Natürlich ist dieses Vorgehen nur ergänzt von anderen Zyklen wirklich sinnvoll. Man sollte den richtigen Zeitpunkt für ein Einchecken des Codes, für das Beheben eines Issues und auch für Orientierungen innerhalb der Projektstruktur nicht außer acht lassen. Für den Umgang direkt mit dem Code ist das Vorgehen aber geeignet – vor allem, wenn man noch nicht ganz genau weiß, wie der Code aussehen soll.
Im Workshop sind wir nach einer Standardklasse mit entsprechenden Standardtests relativ schnell zu “interessanteren” Problemen gekommen: Wie testet man parallele Ereignisse? wie testet man Beobachter-Strukturen? welche Bestandteile eines Tests sollte man mit Mock-Objekten realisieren, wo sollte die reale Implementierung zum Einsatz kommen?
Wichtigste Erkenntnis: In der Praxis müssen auch die Tests vor allem “gut genug” sein. Gerade Threads bzw. parallele Abläufe mit asynchronen Ereignissen sind auch im Testcode nur schwierig abzubilden und sollten durch geeignetes Implementierungs-Design so weit wie möglich vermieden werden.

Durch die Vielzahl an Problemstellen beim Testen fokussiert man fast von selbst den Blick auf den Produktivcode ausschließlich vom zu testenden Aspekt her. Die Implementierung und das Design des Produktivcode werden “testabhängig”. Das ist zum einen sehr positiv, ermöglicht es doch gute Testbarkeit. Auf der anderen Seite entsteht unterhalb einer bestimmten Code-Ebene (auf der dann die Integrationstests ansetzen müssen) eine Art “maximaler Freiheitsgrad” für die Verwendung des Codes. Welchen Effekt diese Eigenschaft auf die Stabilität des Projekts hat, kann ich noch nicht beurteilen.

Das Motto, das ich aus diesem ersten Workshop mitgenommen habe: “Schreibe keinen Produktivcode ohne Motivation. Erkläre deine Motivation am besten in einem Test.”

Die Erkenntnis, die ich aus diesem ersten Workshop mitgenommen habe: “Ich will mehr darüber erfahren – in der Praxis, nicht in der Theorie”

Ein großes Dankeschön an Matthias für diesen wundervoll anstrengenden Test First Morgen.

Extreme Feedback Device: Die ONOZ! Lampe

If you are interested in the english version of this article, check out https://schneide.wordpress.com/2008/10/27/extreme-feedback-device-xfd-the-onoz-lamp/

Wenn zwei gute Ideen irgendwo auf der Welt zusammentreffen, dann entsteht unter Umständen eine weitere, noch bessere Idee. Vor einiger Zeit passierte genau das in der Schneide.

Die erste Idee:
Es begann mit einem Blogeintrag von Alberto Savioa, der am 1. April 2004 u.a. die Idee der zwei Lava-Lampen als Anzeige für den Status eines Projekts formulierte. Einer der Gründer der Schneide war an diesem Tag leider zu beschäftigt, um die Idee gleich aufzugreifen. Das tat Mike Clark mit seinem Buch “Pragmatic Project Automation” und veröffentlichte gleich auch noch eine Bauanleitung für die Lava Lampen.

Die zweite Idee:
Die zweite gute Idee erreichte uns in Form eines kleinen Bildchens, das sich hervorragend für Signaturen oder hämische Kommentare eignet:
onoz-omg.gif(Im Original offenbar von Jonn Wood).
Wir fanden, dass unser Verhalten nach dem zerbrochenen Bau eines Projekts sehr treffend abgebildet war und übernahmen das Bild in unser Kulturgut und den Begriff “ONOZ” in unseren Sprachschatz.

Das Zusammentreffen:
Wir entschieden uns frühzeitig, die Lava-Lampen auch bei uns auszuprobieren. Aber erst mit dem Bild fanden wir dann die für uns passende Realisierung: Wir verzichteten auf die grüne Lampe (“alles O.K.”) und verwendeten statt einer Lava-Lampe eine normale Schreibtischlampe mit genug Helligkeit. Für den Wegfall der grünen Lampe haben wir vier gute Gründe:

  • Es spart Strom
  • Wir brauchen keinen zeitgesteuerten Ausschalter
  • Wir haben farbenblinde Mitarbeiter
  • Die Anzeige ist nicht mehr redundant

Die ONOZ! Lampe:
Mit geringen Investitionen haben wir ein System ausgebaut, das zentral den Zustand aller Projekte der Schneide überwacht, indem jeder Bauprozess sein Ergebnis an einen Serverprozess schickt. Geht ein Bau schief, sendet er ein X10-Signal an die Lampe und alarmiert uns dadurch unübersehbar. Erst wenn alle Projekte wieder baufähig sind, geht die Lampe servergesteuert aus.
onozlamp.jpg
Die Lampe ist bei uns so zentral plaziert, dass sie keiner übersehen kann. Im Normalfall ist es einfach nur eine Lampe. Im Fehlerfall allerdings ist es ein glühendes Infernal unseres Scheiterns. Beinahe sind wir auch bei der Namensgebung gescheitert: Die Lampe hieß zuerst “ONOEZ! Lamp”, was scheinbar die einzige nicht gängige Schreibweise des Ausrufs ist.

Die Folgen:
Die Lampe funktioniert großartig. Allein ihre Präsenz wirkt beruhigend, solange sie aus ist (was glücklicherweise die überwiegende Zeit der Fall ist). Sobald sie angeht, bindet sie alle Aufmerksamkeit für einen Moment auf sich und macht jedem klar, dass es ein unaufschiebbares Problem gibt. Ein bisschen wirkt sie wie die Reißleine im Toyota Produktionssystem, bei dem die ganze Fabrik angehalten wird, sobald ein Problem festgestellt wird. Mit dem Unterschied, dass bei uns ein unbestechlicher, immer aufmerksamer Mitarbeiter – unser Continous Integration System – die Reißleine bedient.


Mehr über unsere Extreme Feedback Devices: