Mittwoch, 6. Mai 2009

JAX 09 - 10 typische Fehler in Enterprise-Java-Anwendungen

Ein weiterer JAX-Talk diesmal von Eberhardt Wolff am 4. Tag. Seinen Vortrag findet Ihr auf seinem Blog. Der Vortrag selbst war inhaltlich gut (wenn auch nicht überraschend), aber vom Stil und den Folien gruselig. Ich hatte das Gefühl er ginge davon aus, das man auf den ersten Blick in einem Code-Schnipsel erkennt, welche Sünde er nun gerade ansprechen will.

Die 10 "üblichen" Fehler aus seiner Sicht sind:

#1 - Transaktionen so zu bauen, dass sie durch Exceptions verlassen werden können, ohne dass sie geschlossen werden. Seine Lösung ist die Verwendung von deklarativen annotierten Transaktionen oder die Verwendung von Transaktions-Templates. Dabei darf dann nur eine Transaktion in der Methode vorhanden sein.

#2 - Exception Design mit checked Exceptions. D.h. jede Exception muss behandelt werden, auch wenn das sinnvoll nicht möglich ist. Man sollte hier möglichst keine checked Exceptions verwenden und es vermeinden Exceptions zu wrappen und einfach weiterzuwerfen. Setze einen Aspect für das Logging ein, um die Informationen zu erhalten und im Code nur die Exceptions behandeln, mit denen man auch sinnvoll umgehen kann. Alle anderen Exceptions sollten in einem generischen Handler alle Runtime-Exceptions fangen. Eine weitere Variante ist über AOP alle Exceptions zu fangen und zu loggen.

#3 - falsches Exeption-Handling, z.B. kein Handling (leerer Catch-Block) oder Ausgabe des Stacktraces (ohne Logging). #2 führt dazu, das dieses Problem hier auftritt und damit Fehler sehr schwer zu finden sind, weil das Verhalten der Anwendung wirr wird. Die Lösung ist die gleiche wie zuvor: immer loggen, überlegen, ob die Exception sinnvoll bearbeitet oder ignoriert werden kann und auf jeden Fall generisch an den Außengrenzen des Systems abfangen.

#4 - Zirkuläre Paketabhängigkeiten, wodurch der Code wie klebrige Spaghetti verwoben ist und zusammenhängt. Eine sinnvolle Zerlegung des Codes ist somit nicht mehr möglich. Spaghetti muss im Vorfeld (!) durch saubere Architekturen vermieden werden. Zusätzlich sollte man regelmäßiges Refactoring einplanen. Allerdings kann man durch Refactoring nur schwer eine schlechte Architektur aufräumen.

#5 - Adaptor Layer, die in jeder Methode integriert werden müssen, z.B. Traceingausgaben bei Ein- und Austritt aus Methoden. Genauso Security-Checks und Null-Checks. Die Lösung hier ist natürlich AOP.

#6 - Keine DAO's zu verwenden - erschwert das Testen, da immer eine Datenbank verfügbar sein muss. Führt zu ungewollten SQL-Exceptions, u.s.w.

#7 - Keine oder schlechte Tests zu machen, wodurch die Code Qualität gering ist und man schwerlich feststellen kann, ob man durch eine Änderung andere Funktionalitäten verändert hat. Zudem immer positiv UND negativ Unit-Tests vorsehen. Mocks vereinfachen das Durchführen der Tests.

#8 - SQL-Statements schreiben und diese nicht manuell zu konkatenieren. Die Gefahr von SQL-Injektion muss an jeder Stelle geprüft werden. Es ist besser hier entsprechende Mapper oder Wrapper zu verwenden und Zeichen über Positivlisten (WhiteList für Character) zu filtern. Und Prepared Statements verwenden.

#9 - Man sollte sich Gedanken über das Mengengerüst und die Art der Anfragen machen und dieses auch frühzeitig testen. Je eher je besser. Wenn die Performanz Probleme später auftreten hat man keine Zeit für eine vernünftige Lösungssuche mehr. D.h. auch schon einzelne Module frühzeitig auf Performanz testen.

#10 - Nicht Threadsafe zu entwickeln, weil Fehler, die mit multiplen Threads und memory leaks verbunden sind während der Unit-Test nie auftreten werden und nur durch Code-Reviews und Analyse von Threaddumps zu finden sind. Man sollte lokale Variablen bevorzugt nutzen, Synchronisieren und WeakHashMaps einsetzen um Memoryleaks zu vermeiden.

Details und Codebeispiele findet Ihr in seinen Folien.