Hallo zusammen,
es gibt verschiedene Kriterien, um Code zu beurteilen. Zum Beispiel:
• Einhaltung der Styleguides der jeweiligen Sprache, des Projektes, der Firma.
• Ansprechende Formatierung
• Effizienz, sprich bestes O() erreichen. Auch Speichereffizienz.
• Mit der Programmiersprache/API arbeiten, statt gegen sie. D.h. in Javaland objektorientiert, in Haskell funktional. Gerne mal die standard library durchforsten, was es da alles schon gibt und wie es einem helfen kann.
• Design pattern nutzen, weil es dadurch ein gemeinsames Vokabular von häufig genutzten Fällen gibt. Seien es die klassischen design pattern wie MVC, Observer, Singleton, etc., seien es funktionale Konstrukte wie map, reduce, filter, etc.
Mich beschäftigt etwas, was irgendwo dazwischen liegt, wofür ich aber nicht so wirklich ein Vokabular finde. Nicht Microästhetik wie Formatierung sondern mehr eine Ästhetik, die den Quellcode verständlicher macht. Die Absicht hinter dem konkreten Ablauf verständlich macht und gleichzeitig verwirklicht.
Ok, das klingt vielleicht zu abstrakt, deswegen ein Beispiel. Ich bin vor ein paar Tagen über diese Quellcodeschnipsel gestolpert. Dort werden zwei Programmiervarianten für ein Problem verglichen. Zum einen eine recht funktionale Variante, zum anderen eine imperative Variante. Das Problem, kurz erklärt, ist eigentlich ein billiges, sehr übliches, man versucht herauszufinden, ob es in einem bestimmten Abschnitt zwei Knoten gibt, die in einem Wert unterschiedlich sind. D.h. die Knoten aus dem Abschnitt aggregieren, die Werte finden, bei zwei unterschiedlichen Werten dann einen Wahrheitswert zurückliefern. Der funktionale Stil macht genau das, auf sehr abstrakte Art. Wenn man es gewöhnt sich, ist es recht verständlich. Der imperative Stil konzentriert sich mehr auf die einzelnen Schritte und erlangt dabei Vorteile wie short-circuiting.
Ich mochte beide Varianten für den Problemfall nicht und bastelte schnell aus Spaß an der Freud' eine eigene Variante. Die Gedanken dahinter:
• Anonyme Lambdas sind praktisch und ein Grundpfeiler jeglichen funktionalen Stils. Dennoch stellen sie Stolpersteine im Lesen dar. Um die Absicht des Ablaufs zu verdeutlichen, ist es recht praktisch statt reinen Codes einen sprechenden Namen, sprich eine Abstraktion zu verwenden. Ideal wäre eine Macro, selbst wenn es nur einmal genutzt wird, aber Macros sind abseits von Lisp-Dialekten leider Mangelware. Ich finde es in Ordnung, dann eine Funktion zu opfern, auch wenn man sie nicht benötigt, und auf die Kapabilitäten moderner Compiler/Interpreter zu hoffen. Deswegen das Auslagern in kleinere, verdaubarere Abschnitte.
• Imperativer Stil hat den Vorteil, bei frühen Bedingungserfüllungen short-circuiting zu betreiben, ohne groß unnötigerweise Daten zu erzeugen. Das wollte ich beibehalten, das erklärt die if-Klausel, die schon wirksam werden kann, wenn man schon die Anzahl der Knoten weiss, ohne die Einzelwerte der Knoten zu überprüfen. Auch bin ich zunehmend skeptischer gegenüber mehreren Ausstiegspunkten aus einer Funktion. Meine Lösung ist es, anfangs einen Resultatwert mit einem Default zu definieren, für Sonderfälle frühe Ausstiegspunkte beizubehalten, aber letztendlich den Default-Ausstiegspunkt immer ans Ende zu setzen.
• Die else-Klausel hat den Default-Ablauf, was sonst nicht ganz so toll ist, aber in diesem Fall aus anderen Gründen pattern matching/guards aus Haskell und Verwandten simulieren sollte. Die Umwandlung von einer collection von Knoten zu einer collection von Werten halte ich im Gegensatz zur imperativen Variante für besser, um zu verdeutlichen, dass man dann auf einer anderen Ebene operiert. Und der Verzicht aufs anonyme Lambda mit Einsatz eines sprechenderen Namens macht das lesbarer. (Nebenbei: Besser wäre natürlich eine lazy list, oder eines Generators wie Pythons imap(), auch schon vorher. Aber die Infrastruktur für so etwas liefert JS derzeit nicht mit und Nachrüsten ist oft zuviel Aufwand.)
Der konkrete Ablauf ist aber mehr der Logik des imperativen Stils angelehnt. Die Logik lässt sich in einem Satz ausdrücken: „Wenn in der Liste von Werten sich ein Wert vom ersten Wert unterscheidet, dann ist die gesuchte Bedingung erfüllt.“ Nun mixt sich das im imperativen Stil mit zuviel Zeug für Iteration und Aufruf zum konkreten Erlangen von Werten. Für eine Lesbarkeit ist etwas Abstraktion besser. Das bekannte funktionale Konstrukt some (woanders: any) macht im wesentlichen das gleiche wie die imperative Schleife, drückt die Absicht besser aus. Und jede Implementierung von some, die nicht von vollkommen Merkbefreiten programmiert wurde, wird short-circuiting sein, so dass der Effizienz-Vorteil des imperativen Stils erhalten bleibt. Und ich mag die Variablen-Benennung firstpick und nextpick, weil das aufzeigt, was eigentlich verglichen werden soll, ähnlich, wie bei Suchen oft die Namenskombination needle und haystack verwendet wird.
Wenn ich das so aufschreibe, klingt das, als stecke da hinter ein paar Zeilen massive Überlegungen. Dabei ist das mehr das Resultate von Jahren von Lesen und Schreiben von Code und ist mehr intuitiv vorhanden. Ähnlich wie der eigene Stil des Schreibens von Text oftmals aus dem Bauch kommt. Natürlich ist so etwas sowohl im Schreiben als auch im Programmieren ein sehr individueller, sehr subjektiver Stil. Dennoch finde ich, es gibt Kriterien. Oder zumindest Dinge, über die man sich bei Quellcode unterhalten kann. Nur fehlt es oft an Vokabular für diese Ästhetik, deswegen wurde das oben auch so lang. Was der Mensch macht, kann er auch irgendwo anhand möglicher Kriterien beurteilen, eine Schönheit ohne Namen gibt es letztendlich so nicht.
Und das ist letztendlich der Grund für dieses Posting: Ich interessiere mich in letzter Zeit für eine Ästhetik/Lesbarkeit/Intuitive Klarheit von Code. Nicht auf einer Ebene von Microsyntax, auch nicht auf der Ebene von Objekthierarchien, sondern irgendwo dazwischen, bei den Algorithmen. Wie man es schafft, seine abstrakte Vorstellung möglichst gut, tlw. deklarativ zu verdeutlichen. Variablenbenennung. Wann man etwas in Makros oder Funktionen auslagern sollte, nicht wegen DRY, sondern wegen Klarheit. (Siehe z.B. Douglas Crockfords Implementierung eines Top-Down-Parsers und wie er Funktionen wie symbol(), infix(), etc. benutzt. Nicht unbedingt nötig, aber es sorgt für sehr viel mehr Klarheit.) Ich suche also nach schönen Code, Diskussion darüber und den Kriterien dafür.
Dummerweise scheint's dazu wenig Diskussion zu geben, weder im Web, noch in Büchern. Oder ich finde es nur nicht. O'Reillys Beautiful Code geht teilweise in so eine Richtung, ist aber nicht unbedingt das gleiche. Und Ihr, die ihr dieses lange Posting überstanden habt, in dem ich versuche zu erklären, wonach ich eigentlich suche, wisst Ihr da vielleicht etwas interessantes? Bücher, Artikel, Postings, Diskussionen? Ich bin derzeit sehr interessiert und recht polygam in Hinblick auf Programmiersprachen und -paradigma.
Gruß,
Tim