Samstag, 5. Juli 2008

Dieser Blog ist tot. Ich blogge weiter auf dem «Agile Trail».

Groovy und JavaScript und die Kunst, geschweifte Klammern zu setzen

Vor ein paar Jahren bin ich in ein Projekt gekommen, in dem darüber abgestimmt werden sollte, wie die Klammern in Java zu setzen seien. Es gab zwei Alternativen: den K&R-Stil und den Allman-Stil. Das Ergebnis damals war deutlich knapp. Nach einigen Wochen Gemurre programmierte aber jeder so, wie abgestimmt wurde. Es blieb der Eindruck zurück, dass sehr viel heiße Luft erzeugt und viel produktive Arbeitszeit sprichwörtlich verbrannt worden ist.

Nach hitzigen Diskussion vor der Abstimmung (vielen war "absolut klar", was denn die einzig wahre Art geschweifte Klammern zu setzen sein müsse) wurde mir damals klar, dass es eine Frage von Geschmack und Ästethik sein müsse und nicht objektiv beantwortbar sein könne, welcher Stil besser wäre als der andere. Es gab damals einfach kein rationales Argument für den einen oder den anderen Stil. Die Argumente beruhten auf Spekulation oder subjektivem Empfinden der Beteiligten.

Die Diskussion und die Entscheidung wären vermutlich anders verlaufen, wenn wir damals in Groovy oder JavaScript programmiert hätten. Dort gibt es jeweils ein Beispiel dafür, dass der Allman-Stil nicht immer durchgängig angewendet werden kann. Da der Klammersetzungs-Stil aber gerade eine einheitliche Formatierung des Quelltextes ermöglichen soll, würde eine inkonsistente Anwendung des Stils - mal soll er eingehalten werden, mal nicht - das Aus für diesen Stil bedeuten. Schon rein praktisch stelle ich es mir sehr kompliziert vor: IDEs bieten eine automatische Quelltext-Formatierung, die bei den folgenden Beispielen entsprechende Fehler einbauen würden. Keine schöne Vorstellung.

Zu den Beispielen: Das JavaScript-Beispiel steht in Douglas Crockfords "JavaScript: The Good Parts", das Groovy-Beispiel war dann nicht schwer davon abzuleiten. Hier das von mir adaptierte Beispiel in JavaScript im K&R-Stil:

var assertEquals = function(expected, actual) {
if(expected === actual) return;
alert('AssertionError! Expected: <' + expected + '>, but was <' + actual + '>');
}

var foo = function() {
return {
key: 'value'
};
}

assertEquals('value', foo().key);


Die erste Methode assertEquals ist ein Ersatz für JavaScripts Assertions-Losigkeit. assertEquals hier ist das, was man von JUnits assertEquals als Java-Entwickler kennt. Die Methode vergleicht einen erwarteten mit einem tatsächlichen Wert: Sind sie gleich, passiert nichts (ergo: Alles okay), andernfalls wird eine entsprechende Fehlermeldung als modaler Dialog ausgegeben. Dies ist noch nicht das eigentliche Beispiel, erleichtert aber die Lesbarkeit im Folgenden.

Die Methode foo gibt ein Objekt zurück. In JavaScript können Objekte literal durch geschweifte Klammern dargestellt werden. Das Objekt hat eine Instanzvariable key mit dem Wert value.

Die letzte Zeile ist die Zusicherung: Wenn foo aufgerufen wird, dann kann ich auf die Variable key zugreifen und erhalte den Wert value. So weit, so alles gut.

Nun formatiere ich die Methode foo nach dem Allman-Stil:
var foo = function()
{
return
{
key: 'value'
};
}


Es gibt eine Fehlermeldung in der Zeile mit dem Aufruf der Zusicherung: foo() has no properties, foo() hat keine Variable key. Warum?

JavaScript kennt das Konzept des Semicolon Insertion: Fehlt am Ende einer Zeile ein Semikolon, dann wird es dort von JavaScript automatisch hinzugefügt. Das ist vielfach praktisch, aber ungünstig beim Allman-Stil und dem return-Statement. Was da nun eigentlich steht, ist dies:
var foo = function()
{
return;
{
key: 'value'
};
}


JavaScript kümmert sich nicht um das, was nach dem return-Statement kommt, es wird ignoriert, ohne Fehlermeldung. Wenn wir foo() ausgeben, dann sehen wir den wahren Rückgabewert: undefined. Klar, dass undefined keine Variablen kennt.

In Groovy sieht's ähnlich aus. Dieses Beispiel ist im K&R-Stil formatiert:
def foo() {
return { ->
return 42
}
}

assert 42 == foo().call()


Die Methode foo() gibt eine Closure zurück, die, wenn aufgerufen, 42 zurückgibt. In Groovy ist das return-Statement optional: Die letzte Anweisung einer Methode wird automatisch als return-Statement benutzt. Viele Entwickler schreiben aber gerne noch das return-Keyword hin, um explizit zu machen, dass hier etwas zurückgegeben wird.

Die Zusicherung (im Gegensatz zu JavaScript hat Groovy die schon eingebaut) erwartet 42, wenn die Closure, die foo() zurück gibt, ausgeführt wird. So weit, so alles gut.

Nun formatiere ich die Methode foo nach dem Allman-Stil:
def foo()
{
return
{ ->
return 42
}
}


Es gibt eine Fehlermeldung in der Zeile mit dem Aufruf der Zusicherung: java.lang.NullPointerException: Cannot invoke method call() on null object, auf foo() kann kein .call() aufgerufen werden. Warum?

Auch in Groovy darf man die Semikolons weglassen. Und auch in Groovy wird alles nach dem ersten return-Statement ignoriert. Folglich liefert foo() den Wert null zurück, was die NullPointerException erklärt; die folgende Closure wird ohne Fehlermeldung ignoriert.

Fazit: Ich möchte hier keinen Religionskrieg um den Klammersetzungs-Stil ausrufen oder Öl ins Feuer derer gießen, die sich da bekriegen. Ich fand es nur interessant, dass der Klammersetzungs-Stil in einigen Sprachen Konsequenzen außerhalb von subjektiv empfundener Ästethik oder Benutzungsfreundlichkeit haben kann. Ich sehe diese Beispiele nicht als Argument gegen Semicolon Insertion oder (optionalen und) mehrfachen return-Statements von Groovy und JavaScript; die Vorteile dieser Eigenschaften der beiden Sprachen überwiegen die Nachteile meiner Ansicht nach deutlich.

Meine Lehre, die ich aus den Beispielen ziehe: In Groovy oder JavaScript würde ich Abstand vom Allman-Stil nehmen, weil er nicht konsistent durchgehalten werden kann.

Übrigens I: Python- und Haskell-Entwickler können wirklich froh sein, dass sie durch derartige Religionskriege nicht durch müssen; Python und Haskell ordnen Blöcke durch tiefere Einrückung unter. Eingebaute Schönheit - meinem subjektiven Empfinden nach, versteht sich ;-)

Übrigens II: Bei den Sourcen von werkannwann.de und bei denen vom Grails-Buch formatieren wir Groovy und JavaScript nach K&R mit zwei Ausnahmen:
  1. Geschweifte Klammern einzeiliger Closures oder Methoden schreiben wir mit einem Leerzeichen Abstand zwischen den geschweiften Klammern und dem Inhalt in die gleiche Zeile wie den Closure-/Methoden-Inhalt. Beispiel:
    [1, 2, 3].each{ println it }
    Im oberen Beispiel würde foo() so aussehen:
    def foo() { return { return 42 } }

    Wobei wir das return-Keyword immer dort weglassen, wo es möglich ist. Dann wird daraus:
    def foo() { { 42 } }
  2. if-, do-, while- und for-Statements schreiben wir ohne geschweifte Klammern, wenn es sich beim folgenden Block um einen Einzeiler handelt. Wenn diese Statements einen einzeiligen Block haben, dann schreiben wir ihn in die gleiche Zeile direkt nach dem Statement. Beispiel:
    if(guardCondition) return
Von beiden Ausnahmen machen wir recht häufig Gebrauch, da sowohl Groovy als auch JavaScript einen wesentlich kompakteren Code erzeugt als beispielsweise Java, und wir so auf relativ viele Einzeiler kommen.

blog comments powered by Disqus