2005-May-11 Falsches falsch aussehen lassen

From The Joel on Software Translation Project

Jump to: navigation, search

Joel on Software - Making Wrong Code Look Wrong
Deutsche Übersetzung

Das Original
http://www.joelonsoftware.com/articles/Wrong.html
Joel Spolsky, 11. Mai 2005
All contents Copyright © 1999-2006 by Joel Spolsky. All Rights Reserved.
Diese Übersetzung
http://www.bitloeffel.de/DOC/joelonsoftware/Wrong_de.html
Hans-Werner.Heinzen, Dezember 2006

Contents

Falsches falsch aussehen lassen

Es ist lange her, es war September 1983, als ich meinen ersten echten Job antrat. Das war bei Oranim, einer großen Brotfabrik in Israel, wo damals jede Nacht so um die 100.000 Brotlaibe gebacken wurden. In sechs gigantischen Öfen, ungefähr so groß wie ... naja, Flugzeugträger.

Als ich den Backsaal zum ersten Mal betrat, konnte ich das Durcheinander kaum fassen. Die Ofenwände gilbten, Maschinen waren rostig, und überall war Öl oder Schmiere zu sehen.

"Sieht das hier immer so aus?" fragte ich.

"Wie? Was redest du da?" fragte der Chef zurück. "Wir sind gerade erst mit Putzen fertig geworden. So sauber wie jetzt war's seit Wochen nicht mehr."

Oh je!

Mehrere Monate lang putzte ich nun jeden Morgen den Backsaal, bevor ich endlich begriff, was er gemeint hatte. In einer Bäckerei heißt sauber, dass kein Teig an den Maschinen klebt. Sauber heißt, kein gammelnder Teig im Mülleimer und kein Teig auf dem Boden.

Sauber heißt aber nicht, dass die Öfen schön weiß lackiert sind. Einen Ofen lackiert man einmal in zehn Jahren, nicht täglich. Sauber heißt auch nicht: keine Schmiere. Vielmehr gab es eine Reihe von Maschinen, die regelmäßig geölt oder gefettet werden mussten. Ein dünner Fettfilm ist dort eher ein Zeichen dafür, dass die Maschine erst kürzlich gereinigt worden ist.

Sauberkeit in einer Bäckerei muss also als eigenes Konzept verstanden werden. Für einen Außenstehenden ist es gar nicht möglich, einfach so hereinzuspazieren und schon beurteilen zu können, ob es hier sauber ist oder nicht. Ein Außenstehender würde nicht auf die Idee gekommen, im Inneren eines Teigformers nachzusehen, ob saubergekratzt worden ist. Ein Außenstehender würde zwanghaft reklamieren, dass der Ofen ganz verfärbte Kacheln habe; die riesigen Kacheln sprangen einem nämlich sofort ins Auge. Für einen Bäcker aber gibt es wohl nichts Unwichtigeres, als das sich verfärbendes Äußere seines Ofens. Das Brot wird ja trotzdem schmecken.

Nach zwei Monaten in der Bäckerei hatte ich gelernt, Sauberkeit zu "sehen".

Mit Programmcode ist das nicht anders.

Wenn du als Programmierer am Anfang stehst, oder wenn du dich in den Kode einer für dich neuen Programmiersprache einzulesen versuchst, erscheint dir alles gleich unverständlich. Bevor du allein die Sprache verstanden hast, wirst du noch nicht einmal offensichtliche Syntaxfehler erkennen.

In der ersten Lernphase erkennst du schon das, was wir Kodierstil nennen. Du bemerkst zum Beispiel Kodestücke, die nicht dem Einrückungsstandard entsprechen, und du bemerkst ungewöhnliche Groß-/Kleinschreibung.

An dem Punkt stellst du typischerweise fest: "Mist aber auch! Wir brauchen hier dringend einheitliche Kodierkonventionen". Dann verbringst du den nächsten Tag damit, für dein Team solche niederzuschreiben, die nächste Woche, um über die einzig richtige Art Klammern zu setzen zu streiten, und die nächsten drei Wochen damit, alten Kode dem neuen Einzig-Richtigen-Klammersetzstil anzupassen, bis endlich der Chef dich zusammenstaucht, weil du Zeit verschwendest für etwas, das nie Geld einbringen wird, und daraufhin entscheidest du, dass es ja auch keine schlechte Lösung ist, Kode erst dann anzupassen, wenn man ihn sowieso aus anderen Gründen schon in den Händen hat. Etwa die Hälfte des Kodes folgt jetzt dem Einzig-Richtigen-Klammersetzstil, und ... ziemlich bald ist der auch schon wieder vergessen, und dann kannst du dich einer neuen irrelevanten (im Sinne von Geld machen) Idee widmen wie zum Beispiel ... eine String-Klasse durch eine andere zu ersetzen.

Mit zunehmender Kodiererfahrung aber wirst du Anderes zu erkennen lernen. Dinge, die vollkommen korrekt sein können, auch nach den Kodierkonventionen, die dich aber trotzdem beunruhigen.

Ein Beispiel (in C):

     char* dest, scr;

Das ist gültiger C-Kode, und das mag auch euren Kodierkonventionen entsprechen, und es mag sogar genau das sein, was beabsichtigt war. Aber mit etwas Erfahrung in C wird dir klar sein, dass damit dest als char-Zeiger deklariert wird, src dagegen nur als char. Und selbst, wenn genau das beabsichtigt war, ist es wahrscheinlich nicht das, was du haben willst. Mit dem Kode stimmt etwas nicht.

Noch etwas subtiler:

     if (i != 0)
         ball(i);

Hier ist der Kode hundertprozentig korrekt; er genügt auch den meisten Kodierkonventionen. Eigentlich ist gar nichts verkehrt, nur dass die einzelne Anweisung nach der Bedingung nicht in geschweiften Klammern steht, das nagt an dir, weil sich jetzt nämlich die Frage aufdrängt: Öha, was ist, wenn jemand eine Zeile wie folgt einfügt:

     if (i != 0)
         fuss(i);
         ball(i);

... und dabei die geschweiften Klammern vergisst. Und schwupps, ball(i) fällt aus der Bedingung heraus! Von nun an verursacht dir der Anblick von Kodeblöcken außerhalb geschweifter Klammern eine Ahnung von Unreinlichkeit und du fühlst dich nicht wohl dabei.

Gut. Bis hierher habe ich jetzt drei Lernstufen des Programmierens beschrieben:

  1. Du kannst sauber von unsauber nicht unterscheiden.
  2. Du hast eine leise Ahnung davon, was Sauberkeit sein könnte, vor allem, was Kodierkonventionen betrifft.
  3. Du fängst an, unter der Oberfläche Anzeichen von Unsauberkeit zu entdecken, und das genügt dir, Kode in die Hand zu nehmen, um ihn zu reparieren.

Darüberhinaus gibt es noch eine weitere Stufe, und von der will ich jetzt sprechen.

  1. Du baust deinen Kode bewusst so, dass er, zusammen mit deinem Riecher für Unsauberkeiten, dazu neigt, zu einem korrekten zu werden.

Und das ist dann die wahre Kunst: robusten Kode dadurch erzeugen, dass er Konventionen folgt, die du nur dafür erfunden hast, um Fehler am Bildschirm auffallen zu lassen.

Ich möchte dir im Folgenden zunächst ein kleines Beispiel vorführen. Im Anschluß daran will ich eine Grundregel für das Erfinden von Kode-Robustheits-Konventionen nennen. Und am Ende wird das zur Verteidigung einer Variante der Ungarischen Notation führen - wenn auch nicht jener Variante, bei der es dir die Fußnägel nach oben dreht - sowie zu einer Kritik an Exceptions in einem bestimmten Umfeld - auch wenn du eher selten in einem solchen Umfeld arbeiten wirst.

Wenn du aber absolut überzeugt davon bist, dass die Ungarische Notation Teufelszeug ist, und Exceptions das Beste sind seit Erfindung der weißen Schokolade, wenn du partout keine andere Meinung hören willst, dann schau halt rüber zu Rory und guck dir stattdessen seine excellenten Comix an - wahrscheinlich wirst du hier eh' nichts verpassen. Um es klar zu sagen, ich werde jetzt echte Kodebeispiele bringen, die dich wohl einschlafen lassen, bevor du überhaupt eine Chance hast, wütend zu werden. Genau! Das Beste wird sein, dich einzulullen bis du ganz schläfrig bist und dich nicht mehr wehrst ... und dann werde ich dir Ungarisch=gut und Exceptions=böse unterjubeln.

Ein Beispiel

Gut. Also hier das Beispiel. Nehmen wir mal an, du baust gerade eine x-beliebige Web-Anwendung - das scheint ja das zu sein, was die Jugend heute antörnt.

Nun gibt es dort eine undichte Stelle die man Cross Site Scripting nennt, auch bekannt als XSS. Ich will mir die Einzelheiten sparen; wichtig zu wissen ist nur, dass man in einer Web-Anwendung Zeichenketten, die vom Anwender über ein Formular eingetippt werden, nicht ungeprüft zurückgeben darf.

Zum Beispiel stellt ein Web-Formular die Frage "Wie heißt du?" und hat dafür ein Eingabefeld. Das Absenden des Formulars führt zur nächsten Bildschirm, der sagt: "Hallo Elmer!", wenn denn der Benutzer Elmer heißt ... nun, das ist ein Sicherheitsloch. Und zwar deshalb, weil der Benutzer anstatt "Elmer" irgendeinen wilden HTML- oder auch Javascript-Kode eintippen könnte, und dieser Kode könnte garstige Sachen machen, und das würde dann so aussehen, als hättest du es getan; beispielsweise könnte der Kode von dir abgelegte Cookies auslesen und weiterleiten an Dr. Übels Üble Web-Seiten.

Lass uns das mal in Pseudocode hinschreiben. Nehmen wir an, dass

     s = FragenNach("name")

die Eingabe (eine POST-Variable) aus dem HTML-Formular liest. Wenn du nur einmal

     Schreiben "Hallo " & FragenNach("name")

kodierst, ist dein Web-Server bereits durch XSS-Angriffe verwundbar; mehr braucht es nicht.

Stattdessen musst du die Zeichenketten umkodieren, bevor du sie wieder als HTML ausgibst. Umkodieren heißt, " ersetzen durch ", > ersetzen durch >, und so weiter. Damit ist

     Schreiben "Hallo " & Umkodieren(FragenNach("name"))

absolut sicher.

Anders formuliert: Alle Zeichenketten, die vom Benutzer stammen, sind potentiell gefährlich. Keine potentiell gefährliche Zeichenkette darf ohne Umkodieren wieder ausgegeben werden.

Versuchen wir nun, eine Kodierkonvention zu erfinden, die sicherstellen soll, dass ein Fehler, wie oben beschrieben, auch sofort falsch aussieht. Wenn falscher Kode wenigstens auch falsch aussieht, dann gibt es eine faire Chance, ihn beim Schreiben oder Gegenlesen auch zu entdecken.

Lösung - 1. Versuch

Eine Lösung ist, jede Zeichenkette sofort dann Umzukodieren, wenn sie vom Benutzer geschickt wird:

     s = Umkodieren(FragenNach("name"))

Unsere Regel besagt also, dass immer, wenn du ein nacktes FragenNach() siehst, das falsch sein muss.

Du trainierst also deine Augen darauf, alleinstehende FragenNach() zu entdecken, weil die ja die Regel verletzen würden.

Das funktioniert soweit, als das Befolgen der Regel vor XSS-Problemen wirkungsvoll schützt, hat aber eine andere Schwäche. Beispielsweise könnte es ja sein, dass du die Benutzereingabe irgendwo in einer Datenbank festhalten willst, das aber vielleicht in HTML-Kodierung sinnlos ist, weil es nicht für HTML-Seiten bestimmt ist, sondern z.B. für eine Kreditkartenanwendung, die mit HTML nichts anfangen kann. Die meisten Web-Anwendungen werden deshalb unter der Annahme entwickelt, dass Zeichenketten intern nicht umkodiert werden, sondern erst im letzten Moment, bevor sie einer HTML-Seite hinzugefügt werden. Diese Vorgehensweise ist wahrscheinlich die bessere.

Wir müssen immer auch Sachen in unsicherer Form eine Zeitlang mit uns führen können.

Ok, noch ein Versuch.

Lösung - 2. Versuch

Wie wär's, wenn wir eine Regel aufstellten, die besagt, dass jede Zeichenkette in dem Moment umkodiert werden muss, in dem sie ausgegeben wird?

     s = FragenNach("name")
      ...
     // sehr viel später
     Schreiben Umkodieren(s)

Wenn du jetzt irgendwo ein nacktes Schreiben... ohne Umkodieren() siehst, weißt du, dass etwas fehlt.

Nun ja, so funktioniert's aber auch noch nicht, ... denn manchmal hast du noch irgendwelche HTML-Bruchstücke in deinem Kode, die du nicht umkodieren darfst:

     If Modus = Zeilenvorschub Dann Präfix = "<br>"
      ...
     // sehr viel später
     Schreiben Präfix

Das wiederum sieht falsch aus nach unserer Kodierregel, die ja besagt, dass jede Zeichenkette vor Ausgabe umkodiert werden muss, also so:

     Schreiben Umkodieren(Präfix)

Dann aber wird das "<br>", das ja in HTML eine neue Zeile beginnen sollte, als "&lt;br&gt" kodiert, und erscheint dann in der Anzeige als <br>. So ist's also auch nicht richtig.

Also: manchmal darf man Zeichenketten beim Lesen nicht umkodieren, manchmal beim Schreiben nicht, und weder der erste noch der zweite Vorschlag funktioniert hier. Und ganz ohne Regel riskieren wir dies hier:

     s = FragenNach("name")
     
      ... Seiten später
     name = s

      ... Seiten später
     // speichern in einer Spalte "name" in einer Datenbank
     recordset("name") = name

      ... Tage später
     einName = recordset("name")

      ... Seiten oder Monate später
     Schreiben einName

Haben wir auch nicht vergessen, die Zeichenkette umzukodieren? Wir können die Überprüfung nicht auf eine einzelne Stelle beschränken - die gibt es nicht. Wenn du einen großen Quelltext hast, wird es zu Detektivarbeit, den Ursprung jeder Zeichenkette aufzuspüren, wenn du sicherstellen willst, dass auch wirklich alles umkodiert wurde, was als HTML rausgeht.

Die richtige Lösung

Lass mich nun eine Lösung vorstellen, die auch funktioniert. Sie hat genau eine Regel:

  • Alle Zeichenketten, die vom Benutzer kommen, werden in Variablen gespeichert, deren Namen mit us, für (Unsafe String) unsichere Zeichenkette, beginnen. Und alle Zeichenketten, die HTML-Kodiert wurden, und alle, die aus einer als sicher bekannten Quelle stammen, werden in Variablen gespeichert, deren Namen mit s, für (Safe String) sichere Zeichenkette, beginnen.

Ich schreibe jetzt den Kode von oben noch einmal hin, wobei ich nur die Variablennamen der neuen Kodierkonvention anpasse.

     us = FragenNach("name")
     
      ... Seiten später
     usName = us

      ... Seiten später
     // speichern in einer Spalte "name" in einer Datenbank
     recordset("usName") = usName

      ... Tage später
     sName = Umkodieren(recordset("usName")

      ... Seiten oder Monate später
     Schreiben sName

Ich möchte, dass du den folgenden Aspekt der neuen Regel erkennst: Wenn du einen Fehler mit einer unsicheren Zeichenkette machst, kannst du es immer in der gleichen Kodezeile sehen, wenn nur die Kodierregel gilt! Also:

     s = FragenNach("name")

ist von vorneherein falsch, weil man sieht, dass das Ergebnis von FragenNach() einer Variablen zugewiesen wird, die mit s beginnt. Das ist gegen die Regel. Das Ergebnis von FragenNach() ist grundsätzlich unsicher und muss deshalb einer Variablen zugewiesen werden, deren Name mit us beginnt.

     us = FragenNach("name")

ist immer OK.

     usName = us

ist immer OK.

     sName = us

ist natürlich falsch.

     s = Umkodieren(us)

ist natürlich richtig.

     Schreiben usName

ist natürlich falsch.

     Schreiben sName

ist OK, wie auch:

     Schreiben Umkodieren(usName)

Jede Kodezeile kann kann für sich alleine überprüft werden und, wenn dann jede einzelne Zeile korrekt ist, dann ist auch der gesamte Kode korrekt.

Mit dieser Kodierregel lernt dein Auge nach und nach, jedes Schreiben usXXX zu sehen und als falsch zu erkennen, und du weißt dann auch sofort, wie das korrigiert werden muss. Klar, am Anfang ist es noch schwer, falschen Kode zu sehen, aber mach das mal 3 Wochen lang, und deine Augen werden darauf trainiert, genau wie der Arbeiter in der Brotfabrik, der lernte einen Blick in die große Backhalle zu werfen und augeblicklich zu murmeln: "Öha ... lang nimme butzt, odä? ... so an Saustall!"

Die o.g. Regel können wir noch erweitern und die Funktionen FragenNach() und Umkodieren() umbenennen - oder verpacken (wrap) -, so dass sie UsFragenNach() und SUmkodieren() heißen, henauso wie bei den Variablen. Der Kode sieht jetzt so aus:

     us = UsFragenNach("name")
      ...
     usName = us
      ...
     recordset("usName") = usName
      ...
     sName = SUmkodieren(recordset("usName")
      ...
     Schreiben sName

Siehst du, was ich getan habe? Jetzt kann man nämlich nach Fehlern suchen, indem man prüft, ob beide Seiten der Zuweisung mit dem gleichen Präfix beginnen.

     us = UsFragenNach("name")   // ok, auf beiden Seiten "us"
     s = UsFragenNach("name")    // falsch
     usName = us                 // ok
     sName = us                  // natürlich falsch
     sName = SUmkodieren(us)     // natürlich richtig

Pass auf! Ich kann sogar noch einen Schritt weiter gehen, indem ich aus Schreiben() SchreibenS() und aus SUmkodieren() SFromUs() mache:

     us = UsFragenNach("name")
      ...
     usName = us
      ...
     recordset("usName") = usName
      ...
     sName = SFromUs(recordset("usName")
      ...
     SchreibenS sName

Falsches falsch aussehen zu lassen, ist prima. Es ist zwar nicht notwendig die beste Lösung für jedes Sicherheitsproblem, und es verhindert auch nicht jeden Fehler, weil du dir vielleicht doch nicht jede Kodezeile einzeln anschaust. Besser als nichts ist es allemal - mir ist es so jedenfalls lieber: mit einer Kodierregel, die Fehler sichtbar werden lässt. Man erreicht dadurch automatisch eine schrittweise Kodeverbesserung, denn jedes Mal, wenn eines Programmiereres Auge die Kodezeilen überfliegt, wird die Korrektheit geprüft und Fehlern vorgebeugt.

Eine allgemeine Regel

Unsere Methode, Falsches falsch aussehen zu lassen, stützt sich darauf, die richtigen Dinge am Bildschirm nahe beieinander zu zeigen. Nehmen wir eine Zeichenkette - wenn ich korrekten Kode haben will, so muss ich bei jedem Auftreten der Variablen wissen, ob der Inhalt sicher ist oder nicht. Diese Information möchte ich nicht suchen müssen, weder will ich am Bildschirm blättern noch mir eine andere Datei ansehen müssen. Ich muss das an Ort und Stelle sehen können, und daraus folgt eine Namenskonvention für die Variablen.

Es gibt viele andere Beispiele für Kodeverbesserung dadurch, dass Dinge zueinander gerückt werden. Die meisten Kodierkonventionen enthalten Regeln wie diese:

  • Halte Funktionen kurz.
  • Deklariere Variablen möglichst nahe am Einsatzort.
  • Baue dir mit Makros keine eigene Programmiersprache
  • Benutze keine GOTOs.
  • Öffne und schließe Klammern auf einer Bildschirmseite.

All diesen Regeln ist gemeinsam, dass sie versuchen, die relevanten Informationen darüber, was eine Kodezeile bewirkt, so dicht wie möglich aneinander zu rücken. Das erhöht die Chance, dass schon deine Augäpfel sehen können, was Sache ist.

Ganz allgemein muss ich zugeben, dass ich Angst habe vor Spracheigenschaften, die etwas verstecken. Schau dir den folgende Kode an:

     i = j * 5;

... und in C weißt du zuverlässig, dass j mit 5 multipliziert wird und das Ergebnis in i gespeichert wird.

Siehst du das gleiche in C++, weißt du erst einmal nichts. Absolut gar nichts! Der einzige Weg ist, herauszufinden, von welchem Typ i und j jeweils sind, etwas, das ganz woanders deklariert sein kann. Es könnte ja sein, dass j von einem Typ ist, der den operator* so überladen hat, dass dort etwas wahnsinnig trickreiches geschieht, wenn du "Multiplikation" anwendest. Und i kann von einem Typ sein, der operator= so überladen hat, dass die beteiligten Typen nicht übereinstimmen, so dass schließlich noch automatische Typkonversionen aufgerufen werden. Um das herauszufinden, genügt es nicht nur den Typ der Variablen zu bestimmen, sondern du musst auch den Implementierungskode dieser Typen untersuchen, und gnade dir Gott, wenn Vererbung beteiligt ist, denn jetzt wirst du die ganze Klassenhierarchie durchforsten müssen, um herauszufinden, wo der Kode tatsächlich steckt, und wenn noch irgendwo Polymorphismus auftritt, dann hast du ein echtes Problem, weil es nicht genügt zu wissen, von welchem Typ i und j sind, sondern du musst wissen, als was für Typen sie in der gegebene Situation auftreten, du hast also nicht-abschätzbar viel Kode zu untersuchen und du kannst nie sicher sein, dass du alles im Blick hast - dem Halteproblem sei Dank(Puh!).

Wenn du also i=j*5 in C++ irgendwo siehst, dann stehst du ganz alleine da, und das, meine ich, beeinträchtigt die Fertigkeit, Probleme allein durch Kodelesen zu entdecken.

Nichts von alledem war beabsichtigt, natürlich nicht. Wenn du, superschlau, operator* überladen kannst, so war das als wasserdichte Abstraktion gedacht. Heureka, j ist vom Typ Unicode-Zeichenkette und eine offensichtlich superschlaue Abstraktion ist, zum Konvertieren von Traditionellen Chinesisch in Standard-Chinesisch die Unicode-Zeichenkette mit einer Ganzzahl zu multiplizieren, richtig?

Das Problem ist natürlich: wasserdichte Abstraktionen sind keine. Darüber habe ich bereits ausführlich in The Law of Leaky Abstractions geschrieben - ich will mich hier nicht wiederholen.

Scott Meyers hat es zu seinem Beruf gemacht zu zeigen, wie Abstraktionen versagen und dir ans Bein pinkeln, zumindest für C++. (Übrigens ist gerade[Mai 2005, A.d.Ü] die dritte Auflage von Scotts Effective C++ erschienen - also besorg' es dir!)

Gut.

Ich schweife zu sehr ab. Besser ist es, die Geschichte bis hierher zusammenzufassen:

Such nach Kodekonventionen, die Falsches falsch aussehen lassen. Bring zusammengehörende Informationen auch sichtbar zusammen in einen Bereich am Bildschirm - dann wirst du bestimmte Probleme deines Kodes sofort sehen und korrigieren können.

Ich bin Ungar

Also nun zur berüchtigten Ungarischen Notation.

Die wurde erfunden von Charles Simonyi, einem Programmierer bei Microsoft. Eines der größeren Projekte, an denen Simonyi für Microsoft aktiv war, war Word; genauer gesagt leitete er das Projekt des weltweit ersten WYSIWYG-Schreibprogramms, in Xerox Parc bekannt als Bravo.

Bei einem WYSIWYG-Schreibprogramms lässt sich das Arbeitsfenster verschieben. Also muss jede Koordinate zum einen relativ zum Fenster und zum anderen relativ zur Dokumentseite interpretiert werden können. Das zu unterscheiden ist lebenswichtig.

Das wird wohl einer der vielen guten Gründen gewesen sein, die Simonyi zu einem Stil veranlassten, der später Ungarische Notation genannt wurde. Er sah aus wie Ungarisch und Simonyi stammte aus Ungarn. Daher der Name. In Simonyis Version der Ungarischen Notation bekommt jeder Variablenname einen Präfix in Kleinbuchstaben; dieser zeigt an, von welcher Art der Inhalt der Variablen ist.

Heißt z.B. die Variable rwCol, dann ist rw der Präfix.

Ich habe mit Absicht von der Art des Inhalts gesprochen. Simonyi nämlich benutzte in seinem Aufsatz dummerweise das Wort Typ ... und Generationen von Programmierern verstanden ihn falsch.

Wer den Aufsatz genau studiert, erkennt, dass Simonyi auf die gleiche Art Namenskonvention abzielt, die ich weiter oben benutzt habe, also da, wo us "unsafe string" und s "safe string" bedeutete. Beide sind von Typ string. Und wenn du den einen dem anderen zuweist, merkt der Compiler nichts und Intellisense hilft dir auch nicht weiter. Aber die zwei sind semantisch verschieden, sie müssen verschieden interpretiert werden, und es braucht eine irgendwie geartete Konversionsfunktion, um eins ins andere zu überführen. Andernfalls gibt's Fehler zur Laufzeit... wenn du Glück hast.

Simonyis ursprüngliches Konzept wurde microsoftintern Apps-Ungarisch (Apps Hungarian) genannt, weil es im Unternehmensbereich "Anwendungsentwicklung" (Application Division), also für Word und Excel, benutzt wurde. Im Quelltext von Excel gibt es haufenweise rw und col, wobei man sofort an Zeilen (rows) und Spalten (columns) denkt. Genau, beides sind Ganzzahlen aber es ist Unsinn, eins dem anderen zuzuweisen. In Word, ist mir gesagt worden, gibt's jede Menge xl und xw, wobei xl bedeutet "X-Koordinate relativ zum Layout" und xw bedeutet "X-Koordinate relativ zum Bildschirmfenster (window)". Beides Ganzzahlen. Nicht austauschbar. In beiden Anwendungen findet man viele cb, was bedeutet "Anzahl Bytes" (count of bytes). Genau. Wieder eine Ganzzahl, aber schon dadurch, dass du den Namen siehst, weißt du schon wesentlich mehr. Es ist ein Bytezähler, die Länge eines Puffers. Und wenn dann ein xl = cb vor die Augen tritt, schrillen bei dir die Alarmglocken, weil das ganz offensichtlich falsch ist, weil es sich zwar um Ganzzahlen handelt es aber trotzdem kompletter Unsinn ist, einen horizontalen Abstand mit dem Inhalt eines Bytezählers zu versorgen.

In Apps-Ungarisch werden Präfixe für Funktionen genau wie für Variablen verwendet. Um ehrlich zu sein, ich habe nie Word-Quelltext gesehen. Aber ich wette hundert zu eins, dass es dort eine Funktion YlFromYw gibt, die vertikale Fensterkoordinaten in vertikale Layoutkoordinaten wandelt. Apps-Ungarisch verlangt nämlich die TypeFromType-Notation anstelle der traditionellen Schreibweise TypeToType, wodurch jeder Funktionsname mit der Variablenart beginnt, die sie zurückgibt - genauso wie ich das weiter oben getan habe, als ich Umkodieren durch SFromUs ersetzt habe. In der Tat würde Apps-Ungarisch das genau so verlangen, es gibt keine andere Wahl. Das ist gut so, weil du dir weniger zu merken brauchst, und weil du nicht rätseln musst, was "Umkodieren" eigentlich meint; du hast etwas Präziseres in der Hand.

Apps-Ungarisch war außerordentlich wertvoll, besonders zu Zeiten der C-Programmierung, als die Typumwandlung der Compiler noch sehr zu wünschen übrig ließ.

Dann machte irgendwer irgendwo einen verhängnisvollen Fehler...

...und das Böse übernahm die Ungarische Notation.

Niemand scheint genau zu wissen, wie und warum das geschah. Es scheint aber so, als ob Handbuchautoren des Windows-Teams versehentlich etwas erfanden, das später als Systems-Ungarisch (Systems Hungarian) bekannt wurde.

Irgendwo las jemand Simonyis Aufsatz, und wo dieser das Wort "Typ" benutzt hatte, dachte jener an Typ, im Sinn von Klasse, im Sinne der Typprüfung, die der Compiler macht. Aber das hatte es gar nicht gemeint! Vielmehr hatte er sorgfältig und genau beschrieben, was er mit "Typ" meinte. Umsonst. Das Unheil passierte.

Apps-Ungarisch hatte sehr nützliche Präfixe wie "ix" für Index eines Arrays, "c" für Zähler (count), "d" für die Differenz von Zahlen (zum Beispiel "dx" für Breite), und so weiter.

Systems-Ungarisch hat weit weniger nützliche Präfixe: "l" für "long", "ul" für "unsigned long" oder "dw" für Doppelwort, was auch nichts anderes ist als ... äh ... "unsigned long". Der Präfix in Systems-Ungarisch sagt dir also nicht mehr als nur den Datentyp der Variablen.

Es war nur ein kleines Missverständnis aber eine komplette Fehldeutung dessen, was Simonyi wollte und tat, was wieder nur zeigt, dass du nicht verstanden wirst, wenn du gewundene akademische Prosa schreibst. Deine Ideen werden fehlgedeutet, und die fehlgedeuteten Ideen werden dann lächerlich gemacht, auch wenn diese gar nicht von dir stammen. In Systems-Ungarisch hat man eine Menge dwXyz, soll heißen "Doppelwort Xyz", aber vergiß es: dass eine Variable ein Doppelwort enthält, verrät dir verdammt wenig, eigentlich nichts Nützliches. Kein Wunder, dass gegen Systems-Ungarisch rebelliert wurde.

Systems-Ungarisch wurde nah und fern verkündet, es ist durchgängiger Standard der Windows-Programmierdokumentation, multipliziert wurde es durch Bücher wie Charles Petzolds Programming Windows, der Bibel für Windows-Programmierung, und es wurde schnell zur dominierenden Form des "Ungarischen", sogar bei Microsoft selbst, wo es nur wenige Programmierer außerhalb der Word- und Excel-Teams gab, die verstanden hatten, welcher Fehler hier gemacht worden war.

Dann kam der Großen Aufstand. Nach und nach bemerkten Programmierer, die von Anfang an die Ungarische Notation nicht verstanden hatten, dass die fasch verstandene Untermenge, die sie benutzten, absolut nervig und ziemlich nutzlos war. Nun, das wiederum stimmt nicht ganz; da gibt es immer noch Eigenschaften, die einen Fehler schneller entdecken lassen. Zumindest weißt du bei Systems-Ungarisch auf der Stelle, von welchem Typ eine Variable ist. Aber es ist lange nicht so nützlich wie Apps-Ungarisch.

Der Große Aufstand erreichte seinen Höhepunkt mit der ersten Auslieferung von .NET. Schließlich gab Microsoft die Parole aus: "Ungarische Notation wird nicht empfohlen". War das eine Freude! Ich glaube, die hatten sich nicht einmal die Mühe gemacht, zu sagen warum. Sie nahmen sich gerade mal das Kapitel Namensrichtlinien vor und schrieben über jeden Artikel "Ungarische Notation nicht mehr benutzen!". Zu dem Zeitpunkt war Ungarische Notation bereits dermaßen unbeliebt, dass niemand mehr zu protestieren wagte. Und alle Welt - außer den Excel- und Word-Teams - war erleichtert, diese lästige Namenskonvention los zu sein. Schließlich gibt es heutzutage leistungsstarke Typprüfungen und außerdem Intellisense, oder nicht?

Nichtsdestotrotz bleibt Apps-Ungarisch von enormem Wert. Es verbessert den sprachlichen Zusammenhalt des Quelltexts, was ihn leichter lesen, schreiben, entwanzen und ändern lässt. Und das Wichtigste: Es lässt Falsches falsch aussehen.

Exceptions

Bevor ich zum Schluss komme - ich hab's versprochen - gibt's nochmal Prügel für die Exceptions. Als ich das das letzte Mal tat, bekam ich eine Menge Ärger. Ich hatte auf der Joel-on-Software-Homepage locker hingeschrieben, ich möge keine Exceptions, weil sie letztendlich versteckte GOTOs seien, was, so räsonierte ich, noch schlimmer sei, als GOTOs, die man sehen kann. Und natürlich ging's mir dann an den Kragen. Der Einzige, der zur meiner Verteidigung eilte, war Raymond Chen, der übrigens der weltbeste Programmierer ist, und das sagt doch was, oder?

Hier also, im Zusammenhang dieses Artikels, die Sache mit den Exceptions. Deine Augen lernen Falsches zu sehen, solange es etwas zu sehen gibt, und das beugt dann Fehlern vor. Willst du mithilfe von Kodeinspektion zu wirklich robusten Kode kommen, brauchst du Kodierregeln, die für sprachlichen Zusammenhalt sorgen. In anderen Worten, je mehr Informationen darüber, was der Kode bewirkt, an Ort und Stelle zu sehen sind, umso besser wirst du deiner Aufgabe gerecht, Fehler zu finden. Schau dir diesen Kode an:

     TuEtwas()
     Aufraeumen()

Sagen dir deine Augen etwa, was falsch ist? Wir räumen jedesmal auf!? Aber die Möglichkeit, dass TuEtwas() eine Exception auslöst, bedeutet, dass Aufraeumen() eventuell gar nicht aufgerufen wird. Gut, das wäre leicht zu lösen, indem man finally oder sowas benutzt, aber das ist nicht entscheidend. Worauf ich hinaus will: Wenn du wissen willst, ob Aufraeumen() wirklich aufgerufen wird, musst du den ganzen Kode-Baum von TuEtwas() durchgehen, ob es vielleicht irgendwo irgendwas gibt, das eine Exception auslösen könnte. Das kann dann auch in Ordnung sein, und es gibt ja auch noch Checked Exceptions, die dir Mühe erleichtern. Der Knackpunkt ist der, dass Exceptions den sprachlichen Zusammenhang auflösen. Du must woanders nachsehen, um die Frage zu beantworten zu können, ob der Kode macht, was er soll, und damit kannst du die dem Sehen innewohnende Fähigkeit, Fehler sehen zu lernen, nicht nutzen. Es gibt ja nichts, was man sehen könnte.

Sicher, für ein kleines Skript, das mir eine Handvoll Daten zusammensucht und einmal am Tag anzeigt, dafür sind Exceptions super. Nichts ist schöner, als einmal vergessen zu dürfen, was alles schief gehen kann, und so packe ich das gesamte Programm in ein großes try/catch, und lasse mir Post schicken, wenn etwas passiert. Exceptions sind prima für "Quick-and-Dirty"-Programmierung, für Skripte, für unkritischen Kode also, von denen kein Leben abhängt. Wenn du aber ein Betriebssystem schreibst, oder Software, die ein Kernkraftwerk oder eine Hochgeschwindigkeitskreissäge für Operationen am offenen Herzen steuert, dann sind Exceptions extrem gefährlich.

Ich weiß, viele werden mich für einen schlechten Programmierer halten, weil ich Exceptions nicht verstehe, weil ich nicht verstehen will, wie sie mir mein Leben angenehmer machen könnten, wenn ich nur mein Herz für sie öffnete. Vergebene Liebesmüh! Wirklich zuverlässigen Kode schreibt man nur mit einfachen Methoden, die die typisch menschliche Schwächen mit berücksichtigen. Das funktioniert nicht mit komplizierten Methoden und ihren versteckten Nebenwirkungen oder ihren lecken Abstraktionen, die einen unfehlbaren Programmierer voraussetzen.

Zum Weiterlesen

Wenn dich Exceptions immer noch euphorisch stimmen, lies den Aufsatz von Raymond Chen Cleaner, more elegant, and harder to recognize. "Es ist außerordentlich schwer, den Unterschied zwischen gutem und schlechtem Exceptions-basierten Kode zu erkennen ... Exceptions sind zu schwierig und ich bin nicht intelligent genug dafür."

In Raymonds Tirade gegen Tod durch Makros A rant against flow control macros geht es um ein anderes Beispiel, wo man vergebens Informationen vor Ort sucht und der Kode deshalb nicht wartbar ist. "Wenn du Kode siehst, der Makros enthält, musst du dich durch Header-Dateien wühlen, um endlich herauszufinden, was sie tun."

Für Hintergründe zur Geschichte der Ungarischen Notation lies zuerst den Original-Simonyi-Artikel "Hungarian Notation". Dough Klunder hat sie mit einem etwas klareren Papier beim Excel-Team eingeführt. Weitere Geschichten über "Ungarisch" und wie es durch Handbuchautoren ruiniert wurde: ein Beitrag von Larry Osterman, ein Kommentar von Scott Ludwig und ein Beitrag von Rick Schaut.

The contents of these pages represent the opinions of one person.
All contents Copyright © 1999-2006 by Joel Spolsky. All Rights Reserved.

Personal tools