Das Google Web Toolkit (GWT) und dessen API für Smart Client (SmartGWT) haben in den letzten Jahren eine grosse Anhängerschaft gewonnen. Sie bieten dem Entwickler ein umfassendes Framework mit einer grossen Auswahl von fertigen GUI-Komponenten wie Formulare, Tabs, Buttons, Listen, etc. das eine effiziente und zeitsparende Programmierung von Rich Internet Applications (RIA) erlaubt. Im Gegensatz zu den meisten vergleichbaren Frameworks wird dabei in Java entwickelt, das daraufhin in Javascript und HTML cross-compiled wird. Dies ermöglicht das Erstellen von hochgradig interaktiven und komplexen Web-Applikationen, die ohne jedes zusätzliche Plug-in im Browser laufen.
Wie beim Einstieg in jede Technologie gibt es aber auch bei SmartGWT eine Anzahl von klassischen Anfängerfehlern (a.k.a „Pitfalls“), die einem Entwickler am Anfang das Leben schwer machen können und im ungünstigsten Fall für erheblichen Verlust von Zeit (und Nerven) sorgen. Dieser Blog-Eintrag soll ein paar der häufigsten Pitfalls aufzeigen, um damit zukünftigen SmartGWT-Einsteigern das Leben zu erleichtern.
Development Mode vs. Web Mode
Der sogenannte „Development Mode“ erlaubt dem Entwickler die Applikation nach Codeänderungen auf die Schnelle zu starten (und zu Debuggen) ohne das die ganze Applikation dafür neu deployed werden muss. Dabei wird die Applikation über eine separate „Development Console“ gestartet, die auch etwaige Fehlermeldung mit Stacktrace anzeigt, die im Browser nicht zu sehen sind.
Das Verhalten der Applikation in „Development Mode“ sollte dabei identisch zum Verhalten als im normalen „Web mode“ sein. Dies ist aber besonders bezüglich fehlerhaftem Code nicht immer der Fall. Während im „Development Mode“ gewisse Fehler wie Nullpointer Exceptions in nativem Javascript Code grosszügig verkraftet und lediglich eine Fehlermeldung in der „Development Console“ anzeigt, führen selbige im „Web Mode“ oft zum Blockieren der ganzen Anwendung ohne das dem Benutzer die Ursache mitgeteilt wird. Die Gefahr besteht deshalb hauptsächlich darin, dass ein Entwickler beim Programmieren eines Feature bei sich lokal immer nur im „Development Mode“ testet, und es anschliessend commitet, wodurch der umbemerkte Fehler daraufhin in der Test- oder Integrations-Umgebung gelangt, wo die Applikation natürlich im normalen „Web Mode“ läuft, und im schlimmsten Fall die ganze Applikation blockieren kann. Da der Benutzer dabei aber keine spezifische Fehlermeldung erhält, kann die Reproduktion und Behebung solcher Bugs sehr schwierig sein.
Lösung:
Um dies zu verhindern, empfiehlt es sich immer vor dem Commiten von Code dessen Funktionalität auch noch einmal im „Web Mode“ zu testen. Weiter hat es sich bewährt beim Entry Point der Applikation ein try-catch block zu platzieren, der alle bis dahin nicht abgefangene Exceptions behandelt und sie zumindest dem Benutzer in einer Form präsentiert, die einen brauchbaren Bug-Report ermöglichen.
Overlay Types und Vererbung
Javascript Overlay Types bieten die Möglichkeit Java Klassen um native Javascript Objekte zu wrappen. Dies erlaubt nebst einer einfache Handhabung einen minimalen Overhead für die Verarbeitung häufig verwendete Objekte und resultiert dadurch in einer sehr hohe Performanz. Overlay Types sollten jedoch mit Vorsicht eingesetzt werden, denn neben den eben genannten Vorteilen, besitzen sie auch einige Nachteile. Eine vorschnelle Entscheidung, ganze Klassen-Hierarchien für zentrale Objekte der Applikation als Overlay Types umzusetzen, kann im späteren Verlauf der Entwicklung zu Problemen führen, wenn deren Einschränkungen zu unzähligen (und schwer wartbaren) Workarounds zwingen. Hier deswegen eine kleines Code-Snippet, das einige der bedeutendsten Einschränkungen von Overlay Types aufzeigt. Auf den ersten Blick scheint es (abgesehen von der speziellen native Javascript- Methode „fooNative“ dem korrekten Java-Syntax zu folgen:
public class myOverlay extends JavaScriptObject {
Integer i;
public final native String fooNative()
/*-{ return this.first_name; }-*/;
public void foo() {
i = 1;
}
}
Keine Instanziierung mit „new“ Operator möglich
Der Versuch, ein Overlay Type mit dem „new“ Operator zu instantiieren, scheitert mit der Fehlermeldung:
[ERROR] ‘new’ cannot be used to create instances of JavaScriptObject subclasses; instances must originate in JavaScript
Workaround:
Verwenden der statischen Methode zur Erzeugung eines „JavaScriptObject“s:
myOverlay test = (myOverlay) JavaScriptObject.createObject();
Keine Public Konstruktoren erlaubt
Aber auch dann meckert der Compiler weiterhin, da der default Konstruktor automatisch public ist:
[ERROR] Constructors must be ‘protected’ in subclasses of JavaScriptObject
Workaround:
Manuell einen protected Konstruktor definieren.
Instanz-Variablen in Java Klasse sind nicht erlaubt.
Overlay Types erlauben keine Instanz-Variablen. Die nächste Fehlermeldung bezieht sich daher auf die dritte Zeile:
[ERROR] Instance fields cannot be used in subclasses of JavaScriptObject
Workaround:
Nur möglich für primitive Typen oder Subklassen von Overlay Type, durch Setzen als Javascript-Variablen in nativen Methoden.
Einschränkungen bezüglich Vererbung
Overlay Types erlauben keine nicht-finale Methoden, ausser in finalen Klassen. Die Methode „foo“ führt daher zu folgender Fehlermeldung:
[ERROR] Instance methods must be ‘final’ in non-final subclasses of JavaScriptObject
Workaround:
Die Klasse oder die Methode „finale“ machen. Da dadurch eine Subklasse die Methoden ihrer Superklasse nicht mehr überschreiben kann, geht damit natürlich einer der Vorteile der Verwendung von Vererbungs-Hierarchien verloren. Auch die Alternative, dass verschiedene Overlay Type Subklassen ein gemeinsames Interface implementieren, erlaubt GWT nicht. Als weitere Einschränkung liefert der Vergleichsoperator „instanceof“ beim Vergleich zweier Overlay Type Objekte fälschlicherweise immer „true“ zurück, unabhängig von ihrer realen Vererbungs-Beziehung.
Fazit
Overlay Types sollten nur für Performance-kritische Objekte verwendet werden, die in keiner komplexen Vererbungshierarchien zueinander stehen sollen. Für alle anderen Anwendungszwecke ist der Entwickler mit POJOs besser bedient, und erspart sich einen Menge von hässlichen Workarounds.
Nicht eindeutige Ids von Komponenten
Ein kleiner aber doch klassischer Fehler ist das manuelle Vergeben von nicht-eindeutigen Komponenten-Ids. Die meisten GWT Komponenten erlauben dem Entwickler manuell die verwendete Id der Javascript Repräsentation einer Komponente zu definieren. Verwendet man versehentlich die selbe Id für zwei verschiedene Komponenten, wird dies zur Kompilierzeit zwar nicht bemerkt, führt aber zur Laufzeit zu Kollisionen im DOM-Tree. Dies kann dann zu scheinbar nicht-deterministischem Verhalten der Applikation führen, was das Auffinden dieser Fehlerursache sehr erschwert.
Lösung:
Wenn möglich, manuelles Vergeben von Ids unterlassen, GWT generiert dann automatisch eine eindeutige Id für die Komponente.
Vermischen von GWT-Core und SmartGWT Komponenten
Eine weit verbreitete Fehlerursache ist auch das unbedachte Vermischen von GWT-Core und SmartGWT Komponenten. Dies führt auch wiederum zu keinen Fehler zur Kompilierzeit, kann aber genau wie die nicht-eindeutigen Komponenten-Ids zu schwer zu isolierenden Fehlverhalten zur Laufzeit führen. Oftmals äussert sich dies in Form von HTML-Layout und Z-Ordering Problemen und führt dazu dass andere, eigentlich völlig unbeteiligte GUI Komponenten plötzlich nicht mehr auf User-Interaktion reagieren.
Lösung:
Ausser in Ausnahmefällen ist das direkte Verwenden von GWT-Core Komponenten in einer SmartGWT-Applikation gar nicht nötig, da für praktisch alle GWT-Core Komponenten ein entsprechender SmartGWT-Wrapper existiert.
Referenzen
Offizielle GWT Homepage
http://code.google.com/intl/de-DE/webtoolkit/overview.html
Offizielle SmartGWT Homepage
http://code.google.com/p/smartgwt/
SmartGWT showcase
http://www.smartclient.com/smartgwt/showcase/
SmartClient Forum
http://forums.smartclient.com/forumdisplay.php?f=14