„Schon traurig, dass von mir als Informatiker manchmal als einziger Lösungsvorschlag ‚Stecker ziehen und wieder reinstecken‘ kommt. — Und noch trauriger ist es, dass das auch noch hilft.“
Manchmal passieren Dinge, die uns unerklärlich, ja unglaublich erscheinen. In den letzten paar Tagen, durfte ich an ein paar WLANs rumbasteln und dabei sind diverse solcher Dinge passiert. Und das hat mich dann dazu gebracht, obiges zu meinem Cousin zu sagen. Das will ich mal zum Anlass nehmen und etwas darüber sagen, wie diese unerklärlichen Dinge geschehen.
Aber was ist denn nun passiert?
- Der neue WLAN-Router tut nicht so, wie er soll. Zuerst kommt er ins Internet, aber das WLAN geht nicht. Dann geht WLAN aber kein Internet mehr. Nach ein paar mal neu Booten und Stecker ziehen und wieder reinstecken, tut es wieder.
- Ein anderer WLAN-Router (gleiches Modell) hat laut LED eine Verbindung mit dem Internet. Das Kabel steckt aber nicht im WAN-Port. Netzstecker ziehen und wieder reinstecken und es geht.
- Der selbe WLAN-Router ist per Ping zu erreichen, zeigt bei Eingabe der IP in den Browser keine Konfigurationsoberfläche (Timeout). Ein Reset behebt das Problem.
- Bei selben Gerät ein Admin-Passwort eingerichtet (händisch in beide Edit-Felder eingetragen und gespeichert). Danach nimmt er das Passwort beim Anmelden aber nicht mehr an. Nach einem Reset nochmal versucht (diesmal langsam eingetippt). Selbes Problem. Dann Dummy-Passwort „abc“ zum Probieren. Lustigerweise funktioniert das jetzt. Dann das Wunsch-Passwort nicht eingetippt, sondern per Copy’n’Paste jeweils eingefügt. Wieder „Benutzername oder Kennwort falsch“.
- Einen WLAN-AccessPoint als Repeater konfiguriert. Datendurchsatz ca. 40kbps (ja 40kbps bei 802.11n und ner Entfernung von 20cm). Neu Starten hilft diesmal nicht. Zum Testen als AP-Client konfiguriert: selbes Problem. Zum Testen mit anderem AccessPoint verbunden: Klappt problemlos. Wieder zurück zum eigentlich gewünschten AccessPoint: Wieder ewig lahm. Neu Starten hilft immer noch nicht. Einen Tag später nochmal versucht: Klappt auf Anhieb.
- Bei Verwendung des neues Repeaters friert der Rechner ein (Maus und Tastatur reagieren nicht mehr). Der Rechner war per Ethernet-Kabel mit dem Repeater verbunden. Selbst nach Abziehen des Kabels (also der Rechner ist mit dem Repeater nicht mehr verbunden. Beide stehen nur nebeneinander) besteht das Problem weiterhin. Ein bisschen WLAN lässt den Rechner einfrieren.
- Auch ein DSL-Router war mal per Ping zu erreichen und per Browser nicht. Wieder behob ein Steckerziehen das Problem.
Aber wie kann das alles sein? Und was hat das mit Software-Entwicklung zu tun? Ich wage mich mal an eine Erklärung des Unerklärlichen.
Software zu schreiben, die einfach tut, was sie soll, ist leicht. Aber Software zu schreiben, die auch noch dann funktioniert, wenn der User einen Fehler gemacht hat oder sonstige unerwartete Ereignisse passieren, ist schwer. Deshalb werden wir als Entwickler darauf getrimmt, Randfälle zu bedenken.
In meinem diesjährigen Vortrag auf den Delphi-Tagen (Enbugging) hab ich Design by Contract als Denkweise vorgestellt: Man betrachtet Methoden als Verträge. Wenn der Caller seinen Teil der Vereinbarung einhält (Precondition), garantiert der Calee, also die Methode, ihren Teil (Nachbedingung). Darüber hinaus gibt es noch Invarianten, die immer gelten müssen [1].
Es gibt Programmiersprachen (z.B. Eiffel), die DBC von Haus aus unterstützen. Zumindest aber gibt es in den allermeisten Sprachen ein Assert-Statement, mit dem man auch viel in die Richtung machen kann. Viel wichtiger als die konkreten Sprachfeatures finde ich allerdings die zugrundeliegende Denkweise. Durch diese ist man nämlich gezwungen, die ganzen Randfälle und Fehlermöglichkeiten zu bedenken. Die Sprachfeatures helfen dann dabei, diese Denkweise auch wirklich anzuwenden.
Das klingt alles sehr akademisch, ist aber außerordentlich praxisrelevant. Wenn man diese Denkweise nämlich nicht anwendet, bzw. die Sonderfälle nicht bedenkt, kommt es zu den oben beschriebenen Merkwürdigkeiten.
Stecker ziehen und wieder reinstecken bzw. neu starten hilft deshalb, weil das System sich dann wieder in einem validen Zustand befindet. Ganz deutlich sieht man das an dem Beispiel mit dem Router, dessen LED behauptet, er wäre mit dem Internet verbunden, obwohl das WAN-Kabel gar nicht angeschlossen war. Das sind inkonsistente Zustände. Die LED behauptet was anderes als das Kabel. Und intern gibt es wahrscheinlich noch ein paar Variablen, von denen die einen behaupten, Internet wäre da und die anderen behaupten das Gegenteil. Das Neustarten behebt das Problem, aber nicht die Ursache. Normalerweise hätte das System gar nicht erst in diesen Zustand kommen sollen.
Es gibt also irgend eine Kombination von Eingaben (Tastendrücke, Signale auf den Leitungen, was weiß ich), die zu diesem invaliden Zustand führt. Das kann etwas ganz Verrücktes sein, wie dreimal Taste 1 drücken, dann Taste 4 gedrückt halten und dabei das Kabel reinstecken. Das für sich genommen erscheint ziemlich unwahrscheinlich, aber meist gibt es viele unterschiedliche solcher Möglichkeiten und Murphys Gesetz zufolge wird einer dieser Fälle auch eintreten.
Jetzt mag man geneigt sein, zu behaupten, dass kein Mensch solche abstrusen Fälle bedenken kann. Und natürlich wird man das auch kaum tun. Das Prinzip, das hier mal wieder hilft, ist das altbekannte Divide and Conquer: Man bedenkt die Problemfälle nicht für das ganze System, sondern getrennt für jedes Modul bzw. jede Methode. Und damit wären wir wieder bei DBC. Diese Denkweise schützt uns davor, all diese überaus nervigen Merkwürdigkeiten in unserer Software zu vermeiden oder zumindest stark zu reduzieren [2].
Es gibt auch Verfahren, die wirklich 100%ig sicher stellen, dass gewisse Zustände nie erreicht werden. Ganze Heerscharen von Informatikern an den Unis beschäftigen sich mit Verfahren zur formalen Verifikation. So etwas ist allerdings sehr aufwändig und skaliert meist nicht auf Code realer Größe. Es gibt hier ein paar Ausnahmen und manches davon wird bei sicherheitskritischen Systemen, bei Autos, Flugzeugen und ähnlichem auch tatsächlich gemacht. Für das typische Informationssystem ist das aber eher weniger geeignet. Hier finde ich robuste Programmierung durch die oben beschriebene Denkweise wichtiger und sinnvoller.
Schauen wir uns jetzt noch kurz an, was ich als konkrete Ursache der obigen Probleme vermute:
- Beim Problem „Ping möglich, HTTP aber nicht“ gab es wohl einen Absturz des internen Webservers. Ausgelöst vermutlich durch irgendwelche nicht bedachten Sonderfälle.
- Das mit dem nicht akzeptierten Passwort war ein Problem mit der Passwortlänge. Das Passwort war einfach zu lang. Und statt eine Fehlermeldung auszugeben, wurde das Passwort beim Einspeichern einfach nach 9(?) Zeichen abgeschnitten. Ich hatte sowas schon fast vermutet und deshalb testweise das kopierte Passwort beim Einloggen schrittweise gekürzt. Und irgendwann hats eben geklappt. Dass da wohl jemand vergessen hatte, die Vorbedingung „Passwortlänge < 9 Chars" zu prüfen, brauche ich wohl nicht erst zu erwähnen.
- Das mit dem atemberaubend schlechtem WLAN-Durchsatz kann ich mir immer noch nicht erklären.
- Und bei dem Fall mit dem Einfrieren des Rechners bei WLAN in der Nähe, war wohl der angeschlossene (aber nicht verwendete) WLAN-Stick bzw. dessen Treiber verantwortlich. Der hat irgendwelche Signale gesehen (802.11n statt 802.11g [3]), die er so gar nicht einordnen konnte. Dann hat er nen Fehler produziert, der das System einfrieren lies. Vielleicht nen Deadlock im Treiber.
[1] Es gibt verschiedene Arten von Invarianten, bei denen das „immer“ jeweils andere Stellen meint. Für das, was ich hier sagen will, spielen solche Feinheiten aber keine Rolle.
[2] Menschen sind nicht perfekt und damit sind Fehler unausweichlich.
[3] Wobei das WLAN eigentlich im Gemischtbetrieb läuft. Merkwürdig.