100 Thesen bzgl. Softwarearchitektur in Enterprise Anwendungen

(nicht alle Thesen gehören wirklich zur Architektur)

Projektstart

Die ersten Wochen im Projekt

Die ersten Wochen im Projekt sind die wichtigsten. Jeder Fehler, der hier gemacht wird, und jede falsche Entscheidung kostet später viel. In den ersten Wochen werden Weichen gestellt, wird die Architektur bestimmt. Aufgrund dessen sinkt oder steigt der Aufwand im späteren Projektverlauf um Faktoren. Jede gute Entscheidung und jede gute Investition in dieser Phase bringt im Verlauf des Projekts zigfache Rendite.

Es hängt viel vom Team ab

Beschäftige für die Basisarchitektur die besten Leute. Die Kosten lohnen sich garantiert. Wichtig sind Erfahrungen in einem ähnlichen Projekt. Ein eingespieltes Team leistet mehr.

Prototyping und Blueprints

Implementiere einen Durchstich, einen Prototyp für einen schwierigen Usecase. Feile an diesem Blueprint in mehreren Iterationen bis dieser wirklich "perfekt" ist. Verwende diesen Prototyp als Blueprint für alle anderen.

Gehe nicht zu schnell in die Breite

Die Gefahr besteht immer, dass man für ein großes Projekt viele Leute bereitstellt. Und alle legen dann zum Projektstart los, entwickeln nach unterschiedlichen Konzepten, entwickeln uneinheitlichen Code und sehr unterschiedliche Qualität. Die Folgekosten sind enorm. Richtigerweise müssen alle Projektbeteiligten in die Projektkonzepte und Blue Prints (die zu diesem Zeitpunkt eine angemessene Reife haben müssen) sorgfältig eingearbeitet werden. Die langfristige Effizienz und Ersparnis ist enorm.

KIS - Keep it simple

Das Risiko ist groß, dass verschiedene Leute unterschiedliche Techniken und Tricks einbringen wollen. Oft ist das nötig. Aber bevor man irgendeine Komplexität ins Projekt bringt, sollte man sich ganz sicher sein, dass dies nötig ist und dass man das selbe Ergebnis nicht mit einer einfacheren Lösung erzielen kann.

Vermeide Gigantomanie

Man muss ich immer die größte Toolsuite verwenden, die gigantischste Datenbank, die größte Anzahl an Subsystemen. Das führt schnell ins Chaos und verhindert effizientes Arbeiten.

Verwende geeignete Tools

Jeder hat seine Vorlieben. Jeder beherrscht verschiedene Tools unterschiedlich gut. Der Nachteil ist die fehlende Austauschbarkeit der Tools und die fehlende gegenseitige Unterstützung der Mitarbeiter. Achte daher auf eine einheitliche Toollandschaft.

Es braucht immer einen Kopf

Es muss immer eine(r) den Hut aufhaben, für die Basisarchitektur, für Module, für das DB Design,.. Diese(r) muss den Überblick haben und auf den Bereich sehen, Reviews durchführen,... Gemeinsame Codeownership ist oft schwierig, oft geht der Besitzerstolz verloren. Eine(r) muss das Konzept kennen und das Sagen haben.

Obsta principiis

Sorge von Anfang an dafür, dass der Code keine Warnings aufweist, dass auf Internationalisierung geachtet wird, dass keine Exceptions (z.B. beim Hochlauf des Applikationservers auftreten), dass alles sauber bleibt. Das Projekt sauber zu halten ist möglich, einen Sauhaufen aufzuräumen ist sehr schwierig.

Basisarchitektur

JavaEE ist eine geeignete Architektur für Enterprise Applications

Auf den ersten Blick erscheint JavaEE für ein Projekt vielleicht etwas "over engeneered". Benötigt man eine Serverplattform, Skalierung, Ausfallssicherheit, Transaktionssteuerung,.. so sollte man auf keinen Fall eine eigene Lösung stricken.

Verwende JavaEE, so wie es gedacht ist

Versuche nicht etwas an der Plattform vorbei zu optimieren, z.B. eigene Threads zu öffnen, auf Files zuzugreifen. Halte Dich an die Vorgaben, was mit größter Wahrscheinlichkeit zu übersichtlichem und stabilem Code führt.

Verwende einen Top Down Ansatz. Also vom Groben zum Feinen

Entwerfe das System zunächst in groben Blöcken und verfeinere diese. Dokumentiere dies in derselben Weise und präsentiere dies dem Team genau so.

Verwende Standards, bleibe austauschbar

Verwende keine Application Server oder Datenbank Spezifika, wenn es nicht unbedingt nötig ist. So hat man die Gewähr, den Application Server leicht austauschen zu können, wenn ein anderer in einer neuen leistungsfähigeren Version herauskommt. Ebenso hat man dann z.B. die Option für die Entwicklung MySQL zu verwenden, für den Betrieb Oracle.

Verwende nicht zu viele verschiedene Konzepte und Bibliotheken

Verwende primär die Bibliotheken, die der Applikation Server mitliefert und selbst verwendet. Darüber hinaus verwende projektweit die gleichen Bibliotheken, also nicht z.B. drei verschiedene SAX Parser. Vermische nicht zu viele Technologien.

Verwende einheitliche Konzepte

Nicht einmal Konfiguration durch xml, dann wieder über Annotations,...

Programmiere, wenn immer möglich stateless. States machen Probleme

States, die man nicht hat, können nicht zwischen Client und Server auseinanderlaufen. States, die man nicht hat, können nicht verloren gehen.

Lege großes Gewicht auf die Fehlerbehandlung

Sehr häufig lässt sich beobachten, dass zunächst der Gutfall programmiert wird und der Fehlerfall dann später, wenn noch Zeit ist. Man geht ja davon aus, dass keine/kaum Fehler auftreten. Das Problem an dieser Vorgehensweise ist, dass meistens später keine Zeit mehr ist und dass Fehler aber sehr wohl auftreten. Selbst wenn man später versucht die Fehlerbehandlung zu verbessern, so muss man sich erst wieder in den Code einarbeiten und alle Fehlerszenarien erfassen. Daher sollte man die Fehlerbehandlung zum frühst möglichen Zeitpunkt implementieren, ggf. sogar vor dem Gutfall Code.

Lege großen Wert auf gutes Logging/Tracing (siehe eigenes Kapitel)

Oft werden zuerst mal Features / User Stories implementiert. Infrastrukturthemen werden dann später nachgeschoben. Das hat fatale Auswirkungen.

Verwende Polymorphismus, vermeide ifs, vermeide instanceOf

Sehr lange und viele IF Tiraden sind oft ein Anzeichen von schlechtem objektorientiertem Design. In vielen Fällen kann man diese durch vernünftige Klassenhierarchien und eine konsequente Anwendung von Polymorphismus vermeiden. Ebenso sollten Aufrufe von instanceOf unnötig sein.

Vermeide zu tiefe / starke Verschachtelungen

Es ist immer ein Dilemma: starke Modularisierung, kleine Klassen versus tiefe Verschachtelung. Hier muss man Vor- und Nachteile abwägen und ein gutes Augemaß beweisen.

Investiere in Frameworks

Der Auftraggeber will immer Features / User Stories sehen. Bzgl. Budget für Frameworks muss man daher meist schwer kämpfen, da sie oft Features zugeordnet werden und dadurch das Feature sehr teuer wird. Investitionen in Frameworks zahlen sich mittelfristig aber sehr stark aus.

Divide et impera

Eines der Grundprinzipien des objektorientierten Designs. Teile die Software gemäß unterschiedlichen Belangen, die sie leisten soll, in Module auf.

Vermeide, wann immer das möglich ist, static Methoden und static Variablen

Eigentlich klar, dass dadurch die Objektorientierung untergraben wird. Stellt implizit globale Variablen zur Verfügung. Impliziert aber auch, dass man sehr vorsichtig mit Singletons umgehen sollte.

Vorsicht mit Singletons

Ein Singleton stellt immer ein globales Objekt, globale Variablen und Methoden zur Verfügung, was problematisch sein kann. Man muss immer auf Threading achten.

Achte auf die richtige Sichtbarkeit

Oft wird mit private, protected, package und public visibility sorglos umgegangen. Das erschwert die Wartbarkeit des Codes.

Vermeide eine unnötige Anzahl von Executables

Mit Maven ist es in Mode gekommen, sehr viele Projekte zu erstellen, aus denen ein WAR, JAR oder EAR entsteht. Man verhindert dadurch zwar den direkten Zugriff auf fremde Packages, handelt sich dadurch einen riesigen Overhead ein. Besser ist ein gut strukturierte Packagestruktur und wenige Executables. Nur wirklich getrennte Teams sollten eigene Executables haben.

Klassen haben 50 bis 300 Zeilen

Dies ist eine einfache Faustregel. Hat man zu kleine Klassen, so verliert man sich in der Menge der Klassen, hat man zu große Klassen, so werden sie zu komplex, unübersichtlich und schwierig zu lesen.

Sei sparsam mit Threads (und der Anzahl Beans im Pool)

Es ist ein verbreiteter Glaube, dass die Applikation um so schneller wird, je mehr Threads sie benutzt. Aber jeder Thread benötigt viele Resourcen und der Aufwand für den Scheduler der Laufzeitumgebung höht sich ebenfalls. Zusätzlich wird die Applikation schwieriger zu debuggen. Ich empfehle Threadpools mit maximal 20 Threads bzw. Bean Pool Größen von 20.

Sei vorsichtig mit Lazy Loading

Auf den ersten Blick ist Lazy Loading sehr schick. Man lädt aus der DB nur was man momentan benötigt. Der Nachteil ist aber, dass man im Code nie sicher sein kann, ob das Objekt, auf das man zugreift im Augenblick schon ausreichend geladen ist.

Lege keine Logik in die Datenbank

Das ist ein großer Glaubenskrieg. Die Datenbank ist schnell - keine Frage. ABER: die Datenbank ist meist auch der Bottleneck. Man kann einen großen Cluster von Application Servern haben, in der Regel hat man nur eine DB. Logik in der DB ist schwerer zu debuggen, Fehler schwerer zu finden, der Ablauf schwerer nachzuvollziehen. Daher lege ich nur einfache Logik in die Datenbank, z.B. einen Trigger zur Historisierung von Datensätzen.

Vermeide unnötiges Umkopieren

Eine Entkopplung der Schichten ist sicher sinnvoll. Dazu müssen teilweise auch unterschiedliche Value Objects eingeführt werden, die ineinander umkopiert werden müssen. Jedoch sind nicht immer unterschiedliche VOs nötig. Wäge den Nutzen ab!

Schichten

Fast immer ist eine Einteilung in Schichten sinnvoll

Nur ganz triviale Projekte sollten auf eine Mehrschichtarchitektur verzichten. Man sollte immer im Hinterkopf behalten, dass die Applikation und damit ihre Komplexität wächst. Später wird man vielleicht froh sein, Schichten eingeführt zu haben.

Jede Schicht darf nur die jeweils darunter liegende(n) Schicht(en) rufen

Dies verhindert zyklische Abhängigkeiten. Ich sehe nicht, dass man sklavisch nur die exakt darunter liegende Schicht rufen darf, das führt dazu, dass in einfachen Fällen Schichten Aufrufe nur an die darunterliegenden Schichten weiterleiten ohne selbst etwas zu tun. Dies würde nur das Debugging erschweren und hätte keinen Mehrwert.

Es gibt aber auch Schichten/Packages - z.B. das Model, die Value Objects -, die von allen anderen verwendet werden.

Sollte klar sein.

Application Exceptions sind checked Exceptions, System Exceptions unchecked

Diese einfache Einteilung empfielt sich im J2EE Umfeld. Denn die (unchecked) System Exceptions werden von der Applikation nicht gefangen, sondern vom Applikation Server. Sie werden dort geloggt und führen zu einem HTTP Status 500. (EJBs werden abgeräumt und müssen neue intitalisiert werden, Transaktionen werden zurückgerollt,...) Diese stellen unerwartete Ereignisse dar. Umgekehrt stellen Applikation Exceptions erwartete Ausnahmen da, die von der Applikation selbst gefangen werden.

Declare or handle Exceptions

Ist ein absolut sinnvolles Java Prinzip, das man z.B. auf .NET ebenso hätte anwenden sollen. Bedeutet, dass man keine Exceptions fängt, die man nicht sinnvoll behandeln kann.

Vermeide Drückeberger

Wie oben bemerkt. Klassen und Schichten sind kein Selbstzweck. Klassen, die nichts anderes machen als zu delegieren, sollten im Allgemeinen vermieden werden.

Standard Schichtenarchitektur

GUI oder Service Schicht - Business - Infrastruktur

Lose Kopplung

Klassen und Module sollten nicht zu stark aneinandergekoppelt sein und nicht zu stark von einander abhängen.

Komposition ist der Ableitung meist vorzuziehen

Allgemein anerkanntes Prinzip, um Wiederverwendung besser zu ermöglichen.

Einfachheit und Klarheit und Sicherheit

Das Big Picture, also die globalen Zusammenhänge, müssen dokumentiert, jederzeit aktuell und jedem Entwickler immer klar und gegenwärtig sein.

Zu häufig verzettelt sich die Dokumentation in Details und verhindert für neue Mitarbeiter den Einstieg.

Zusammenhängende Information gehört an EINE Stelle

Informationen sollten nicht über verschiedene Klassen, Files verstreut werden. Beispiel: wenn ein Attribut ein Eingangs- und Ausgabeattribut ist, so sollte die Validierung nicht in der Eingangsklasse und dann nochmal in der Ausgangsklasse gemacht werden. Ähnlich ist es z.B. mit SQL Datenbankzugriff. Es sollte die Entity, die Tabellen- und Spaltennamen an einem Platz sein (am besten im Java File, nicht wie früher noch einmal getrennt in einem xml File)

Object ist der void Pointer des 21. Jahrhunderts

Verwende möglichst stark typisierte Objekte und NICHT nur Object (um später dann mit instanceOf darauf zuzugreifen). Das erleichtert die Lesbarkeit und ist sicherer.

Alles gehört an den richtigen Platz. Beispiel @Table sagt, wie die Tabelle der Entity heisst

Es gibt/gabe einen Hype zusätzlich zum Java Code vieles in XML Files abzulegen. Z.B. die Spring Konfiguration, aber auch Deployment Descriptoren,.. um zum Deployzeitpunkt noch Veränderungen vornehmen zu können. Aber mal ehrlich: welcher Deployer will kurzfristig das Transaktionsverhalten einer Bean verändern? Wer verändert in letzte Minute das Datenbankschema oder die Tabellennamen? Das sind theoretische Fälle. Daher sind in der Regel viele Dinge im Code am besten aufgehoben.

Lesbarkeit hat Vorrang vor Eleganz. Bsp.: zu kompliziertes Template

Oft steht man vor dem Dilemma Code/Redundanzen einzusparen, aber dadurch den Code sehr kompliziert zu machen. Lesbarkeit/Wartbarkeit hat Vorrang.

Keine Tricks -> KIS. Nicht das letzte % herausholen

Keep is simple. Vermeide Tricks, versuche zuerst den geraden Weg und versuche nicht die letzten Tricks aus einem Framework herauszuholen. Die Kollegen kennen und verstehen diese Trick vielleicht nicht und dann entstehen Probleme und Fehler.

Magic nur, wenn sie 100%ig robust ist, z.B. Aspekte debuggen ist kompliziert

Beispiel aspektorientierte Programmierung ist ein mächtiges Werkzeug. Aber man sollte dieses nicht überstrapazieren, denn wenn man den Code nicht geschrieben hat ist nicht leicht nachzuvollziehen, dass hier "versteckt" etwas passiert. Es ist schlecht zu debuggen. Selbst Datenbank Trigger können für neue Mitarbeiter verwirrend wirken. Muss gut dokumentiert und robust sein. Sonst lohnt es sich nicht.

Verwende Pattern

Klar, warum das Rad immer neu erfinden. Es lohnt sich auf jeden Fall immer wieder zu stöbern, ob es nicht eine Musterlösung für ein ähnliches Problem gibt, wie man es gerade bearbeitet. Der Einsatz von Mustern erleichtert auch die Kommunikation im Team.

Verwende Pattern nicht um jeden Preis, sondern mit Augenmaß

Umgekehrt sollte man das Projekt nicht um jeden Preis mit Pattern zupflastern. Pattern sind kontraproduktiv, wenn sie nicht auf das Problem passen. Auf keinen Fall sollte man einen sportlichen Ehrgeiz darauf verwenden, alle bekannten/möglichst viele Pattern einzusetzen.

Sei vorsichtig mit Singletons

Singletons sind manchmal sehr in Mode und werden z.B. von Spring auch standardmäßig eingesetzt. Leider kann man dadurch in Threading Probleme laufen, oder eben Bottlenecks erzeugen. Ein Singleton ist ein bisschen wie eine globale Variable (oder er enhält zumindestens welche). Daher Vorsicht.

Vermeide Interfaces mit nur einer Implementierung (Adam Bien)

Nicht jede Bean benötigt ein eigenes Interface. Der Code kann dadurch leichter wartbar werden. Die Schnittstelle muss nicht unbedingt ein Interface sein. Es können auch die public Methoden sein.

Sei spezifisch. Ist wie bei dem void Pointer. Verwende z.B niemals throws Exception. Verwende Generics um typsicher zu sein.

Erleichtert Lesbarkeit, Fehlerfreiheit und Wartbarkeit des Codes.

Es gibt keine irrelevanten Errors. Halte das Log sauber, sonst werden echte Fehler verdeckt

Hat man erst mal viele "falsche" Fehler im Log, so kann man im Problemfall nicht mehr erkennen, was das wirkliche Problem ist.

Einfaches muss einfach bleiben

Hört sich selbstverständlich an. Doch leider passiert es oft, dass sich Entwickler bei einfachen Dingen total verkünsteln und eine riesige Komplexität hineinbringen. Das ist ein Warnsignal.

Verwende immer @Override bei überschriebenen Methoden

Die @Override Annotation ist sehr wichtig, wenn diese fehlt, so kann es passieren, dass man in der Basis oder in der abgeleiteten Klasse die Signatur der Funktion verändert und dabei die abgeleitete/Basisklasse vergisst oder übersieht. Die Folge ist, dass die Methode nun nicht mehr überschrieben ist, was nicht beabsichtig war. Exisitert aber die Annotation, so gibt es in einem derartigem Falle einen Compile Error.

Prüfe regelmäßig auf Cyclic Dependencies

Tests

Teste automatisiert. Unittests laufen schneller ab und sind robuster. Integrationstests und automatisierte GUI Tests (z.B. Selenium) ist weniger robust, aber viel aussagekräftiger.,

Tests müssen zu 100 % durchlaufen

Es nützt nichts eine riesige Anzahl von Tests zu haben und 5% davon scheitern. Unter den gescheiterten Tests könnte ein entscheidender dabeisein. Außerdem merkt man nicht, wenn ein Test, der unbedingt gut gehen müsste, plötzlich aufgrund einer Änderung schiefgeht. Daher: alles oder nichts.

Namen sind nicht Schall und Rauch

Sei sehr sorgfältig mit der Benamsung von Klassen, Variablen, .... Dadurch erhöht sich die Lesbarkeit enorm.

Vermeide Typos

Code mit Typos wird wenig sorgfältig und vertrauenserweckend. Kommentare mit Typos ebenso.

Namen sind nicht Schall und Rauch

Effizienz

Programmiere für die Gegenwart und nicht auf Vorrat

Immer wieder werden Klassen/Funktionen geschrieben, die irgendwann später gebraucht werden könnten. Das Problem ist, dass diese Funktionen dann nicht sofort ordendlich getestet werden. Später weiß man dann oft nicht mehr, dass es diese Funktionen schon gibt oder wie sie gemeint waren.

Beseitige erkannte Mängel so früh wie möglich

Häufig scheut man sich Mängel sofort beim Erkennen zu beseitigen, da ja gerade keine Zeit ist. Refaktorings werden hinausgeschoben. Verbesserungen ebenso. Das ist ein Fehler, denn der Aufwand, den man für die Verbesserung investiert, lohnt sich oft sehr bald.

Vermeide zu große Projektabhängigkeiten, verwende Mocks

Bestehen Abhängigkeiten zwischen Projekten, so führen Verzögerungen oder Fehler im einen Projekt zwangsläufig zu Problemen in den abhängigen. Dies kann man durch den Einsatz von Mocks vermeiden. Erst zum Integrationszeitpunkt müssen alle Komponenten fertig sein und funktionieren.

Versuche nicht zu blenden und wenn doch, so kalkulier die Konsequenzen ein

Es ist sehr beliebt dem Kunden/Management/Chef mehr Fortschritt vorzutäuschen als tatsächlich vorhanden ist. Dazu verwendet man Dummyimplementierungen,... Das Problem ist, dass man dann natürlich noch mehr unter Druck steht, da alle erwarten, das Release steht kurz bevor. Es ist geschickter, Verzögerungen rechtzeitig einzugestehen und den Fortschritt realistisch darzustellen.

Vermeide Redundanzen (auch in der Doku)

Klarerweise muss man bei Redundanzen im Code jede Fehlerbehebung mehrfach machen, für jedes neue Feature den Code doppelt anpassen. Aber auch in der Doku sollte man keine Redundanzen haben, auch hier ist sonst mehrfache Pflege erforderlich (Fehlerquelle!)

Copy, paste and change ist keine Lösung

Beliebte Lösung um schnell voran zu kommen ist copy/paste. Der kurzfristige Gewinn ist langfristig IMMER ein Verlusst.

Achte auf Build- und Deployzeiten. Sinnvolle Zahl von Executables

Die Build- und Deployzeiten werden oft vernachlässigt mit dem Argument, dass man ja nur einmal bauen und deployen muss. In der Entwicklung sind diese Zeiten aber signifikant. Durch die Aufteilung des Projekts in zu viele Executables steigt die Build Zeit.

Achte auf ein einfaches Deploy, verifiziere Build auf leerem Rechner

Das Deployment darf nicht zu kompliziert und fehlerträchtig sein. Es muss auf einem "leeren" Rechner verifiziert werden.

Chunky statt Chatty

Achte (gerade bei Interprozesskommunikation) darauf, lieber weniger Calls mit mehr Nutzdaten aufzurufen, als viele Calls mit wenig Nutzdaten. Letzteres killt Performance.

Archiktur muss den Anforderungen entsprechen

Die schönste Lehrbucharchitektur hilft nicht, wenn sie nicht zu den Anforderungen passt. Bsp.: MDA

Qualität

Lege höchstes Gewicht auf die Basisarchitektur

Die Basis muss stimmen, an den Randbereichen darf man schlampen, dürfen Neulinge sich austoben.

Hacks nur in Randbereichen

Kommt man ein manchen Punkten nicht auf schöne/solide Lösungen, so kann das in Randbereichen akzeptabel sein. Handelt es sich aber um prinzipielle Dinge, so läuft man in Probleme.

Halte den Code frei von TODOs, FIXMEs

Schon nach kurzer Zeit verliert man den Überblick, was ein echtes TODO ist, oder was nur ein Überbleibsel von früher ist. Bei einem TODO muss immer klar sein, was zu tun ist, wann das gemacht werden kann/muss und von wem.

Halte den Code frei von Warnings.

Nach kurzer Zeit sieht man schon nicht mehr, welche Warnings echte Probleme sind und was "normal" ist. Die Qualität leidet.

Achte auf Performance und Entwicklungsperformance

Oft denkt man, dass Build- und Deployzeiten keine Rolle spielen, denn das wird ja "nur einmal" gemacht. In Wahrheit findet das auf den Entwicklungsrechnern ständig statt. Nimmt man hier lange Zeiten in kauf, so summiert sich das im Projektverlauf sehr stark. Achte darauf, dass zur Entwicklung nicht immer ein kompletter Deployzyklus erforderlich ist, sondern dass man vieles in Modulen entwickeln kann.

Betrachte das Gesamtsystem

Ein Problem ist oft, dass jeder Entwickler/jedes Team seine Komponenten betrachtet. Es reicht nicht, wenn alle Komponenten funktionieren. Man muss immer im Blick haben, ob auch das Zusammenspiel funktioniert. Alle Unittests grün und trotzdem funktionieren einfache Usecases nicht?

Vertraue nicht zu sehr auf Codecoverage

Die Codecoverage bei Unittests zu betrachten ist sinnvoll. Doch es reicht nicht aus. Einfaches Bespiel: In einer Funktion ist ein Nullpointer Check. Um 100 % Codecoverage zu erreichen übergibt man in einem Test dieser Funktion null. Alles funktioniert. In einer anderen Funktion wird der Nullcheck vergessen. Daher wird auch kein Test geschrieben, der null übergibt, denn die Codecoverage ist ja bereits 100%. Mache daher beim Testen nicht die gleichen Denkfehler wie beim Codieren!

Vermeide unnötige (und falsche) Kommentare

Kommentare sollen kurz und aussagekräftig sein. Das Problem an Kommentaren ist, dass sich oft der Code ändert und die Kommentare nicht nachgezogen werden. Das ist dann kontraproduktiv, denn wenn es erst einmal falsche Kommentare gibt so vertraut man keinem Kommentar mehr und alle Kommentare haben an Wert verloren. Vermeide aber auch unnötige Kommentare
for (int i=0; i < n; i++ ) // über alle n
Assert(i < 0) // debug check
Das nützt niemanden und stört die Lesbarkeit des Codes

Dokumentiere keine getter und setter

Das nützt niemanden und stört die Lesbarkeit des Codes.

Kümmere dich zuerst um das Offensichtliche

Beispiel Codequalität Wenn es viele Warnings im Sourcecode gibt, dann braucht man zunächst keine Sonar Qualitätsüberprüfung machen. Wenn die Startseite sich nicht vernünftig öffnet, braucht man detaillierte Features nicht testen.

Optimiere relevante Usecases, nicht irrelevante

Jeder hat sein Steckenpferd, doch das muss zurückstehen. Die wichtigen Usecases müssen flüssig und stabil durchlaufen.

Performance

Wie oben: Optimiere die relevanten Usecases und überprüfe, ob die Optimierungen auch wirklich relevant sind. Oft werden aus Performancegründen irgendwelche unschönen Tricks gemacht. Messungen könnten zeigen, dass diese oft kaum Wirkung haben. Tricks nur, wenn es etwas bringt. Bsp.: Manchmal wird die DB aus Performancegründen denormalisiert. Nur wenn es wirklich nötig ist!!!

Verdecke keine Fehler

Fehler werden umso teurer, je später sie erkannt werden. Daher sind die klassischen catch (...) und duck dich ein großes Problem. Fehler müssen frühzeitig erkennbar sein.

Fail fast

Wenn ein Fehler auftritt, so soll die Software möglich schnell scheitern und den Fehler melden. Fehler vertuschen und nicht richtig behandeln, so dass die Software später scheitert ist teuer. Es erschwert die Fehlersuche.

Gib nicht null in unimplementierten Methoden zurück

/* TODO * / throw new RuntimeException ("Not yet implemented") ist besser als
/* TODO * / return null;
Denn, wenn man vergisst, dass die Methode noch nicht implementiert ist, so erhält man zur Laufzeit im ersten Fall einen aussagekräftigen Stacktrace. Im zweiten Fall kommen vielleicht einige Schichten mit dem null Wert gut zurecht und der Fehler tritt an einer ganz anderen Stelle auf und ist schwer zu finden.

Vermeide Autoboxing

Autoboxing führt häufig zu Nullpointer Exceptions an Stellen, an denen man es nicht erwartet. Daher sollte man Autoboxing als Compiler Warning einstellen.

Sei sparsam mit generiertem Code

Verwendet man Codegenerierung, so besteht die Gefahr, dass zu viel Code generiert wird, weil es ja so einfach geht. Das führt aber zu langen Build- und Laufzeitzeiten. Außerdem wird der Code dadurch nicht übersichtlicher.

Mische nicht generiertem Code und manullen Code

Verwende keine Usersections in generiertem Code. Ist viel zu unübersichtlich. Generiere im generierten Code "Einsprungpunkte", also Methoden, die überschrieben werden können. Leite von deiner generierten Klasse ab und implementiert in der abgeleiteten Klasse. Dadurch besteht eine saubere Trennung zwischen generierten und manuellem Code.

Fehler schnell erkennen und beseitigen können

Ein Maß für die Qualität einer Applikation ist, wie schnell ein aufgetretener Fehler analysiert und beseitigt werden kann. Kann er schnell erkannt werden, so sind die Traces gut. Kann er leicht und sicher behoben werden, so ist das Design und die Architektur gut.

Jeder Fehler wird immer wieder abgeschrieben

Eine Klasse / Methode kann gar nicht zu gut sein. Denn es stellt sich heraus, dass jede Schlampigkeit und jeder Fehler immer wieder abgeschrieben wird und man findet ihn dann immer wieder, selbst wenn er einmal beseitigt ist.

Logging/Tracing

Führe eine Logfacade ein

Üblicherweise loggen alle Entwickler eines Projekts direkt mit Log4J oder java.util.Logging. Das führt dazu, dass jeder in unterschiedlichem Format loggt und mit unterschiedlicher Strategie. Unterbinde diese und führe eine Facade ein, die z.B. Methoden wie logEnterBusinessFacade(..), logApplicationException(..) enthält. Das hat den zusätzlichen Effekt, dass man leicht global Loglevels ändern kann, z.B. alle Schnittstellen Traces statt auf Info auf Debug logggen.

Achte auch auf den Betrieb, er muss die Logs lesen können

Logs, die voller sinnloser Information sind, können vom Betrieb nicht gelesen werden. Logs müssen sprechend und relevant sein. Sie dürfen auf keinen Fall "sinnlose" Errors enthalten.

Log müssen auf statistisch auswertbar sein

Mit Hilfe von Logs muss auch analysiert werden können z.B. welche Funktionalität am häufigsten verwendet/aufgerufen wird.

Vergiss Tester, Service und Betrieb nicht

Entwickler denken beim Erstellen von Logging Code meist nur an sich selbst. Im laufenden Betrieb ist es aber auch wichtig, dem Service Diagnosemöglichkeiten zu geben. Ebenso sind Tester auf die Logs angewiesen.

Monitore immer Errors. Jenkings monitored nur Build Probleme

Im Continious Integration Umfeld ist es üblich, dass der Ergebnis des Builds und der Unittests gemonitored wird. Ebenso sollte man aber Log Errors auf den Integrationssysstemen monitoren. Das bietet eine Chance Fehler frühzeitig zu erkennen, bevor sie in Produktion gehen.

Lege auf Produktion ein Logfile an, das nur Errors sammelt

Dieses Logfile muss regelmäßig gecheckt werden, idealerweise sollte es leer sein. Um die auftretenden Probleme muss man sich mit hoher Prio kümmern.

Traces dürfen nichts ins Serverlog, sondern in ein Applicationlog

Das Serverlog dient nur für Probleme des Applicationservers oder z.B. der Datenbank. Anwendungsspezifische Traces haben hier nichts verloren.

Stacktraces sind oft hinderlich

In Java hat es sich eingebürgert, statt einer Fehlermeldung immer den Stacktrace auszugeben. Und das oft noch über mehrere (EJB-)Schichten hinweg, so dass man im Fehlerfall sehr mühsam den Rootcause herausfindet. Viele Entwickler sind der Meinung, dass es nichts schöneres und aussagekräftigers gibt als einen Stacktrace. Das ist Quatsch. Beispiel: Integer.parse("aaa"); Für einen derartigen Fehler sieht man oft im Log ellenlange Exceptionstacks, die nichts bringen. Versuche spezifische und aussagekräftige Fehlermeldungen ins Log zu schreiben

Fazit

Sei nicht zu schnell zufrieden

Es reicht nicht, wenn der Meilenstein irgendwie geschafft wurde. Prüfe immer kritisch, welche offenen Punkte, Probleme und Verbesserungsmöglichkeiten es noch gibt. Plane Zeit und Budget für Verbesserungen ein, auch wenn das schwer durchsetzbar ist. Qualität zahlt sich aus.

Erfolg ist schwer messbar

Ist ein Projekt abgeschlossen, so ist es meist schwer messbar, ob man das gleiche Projekt mit geringeren Kosten in einer größeren Qualität abliefern hätte können.

Alles multipliziert sich. Sowohl Schlampigkeiten als auch Motivation

Ein motiviertes Team, das in einem produktiven Umfeld arbeitet, hat Spass und arbeitet daher umso besser. Gibt es Blokaden oder Schlampigkeiten, so demotiviert das. Das führt zu weiteren Schlampigkeiten und es geht immer so weiter.

Es kommt auf die Software an

Mögen alle Metriken noch so gut sein, mögen tausende Unittests durchlaufen,funktioniert die Applikation offensichtlich trotzdem nicht, so nützt das alles nichts.

Es kommt nicht nur auf Kosten und Funktionalität an

Der oberflächliche Erfolg ist kurzzeitig. Qualität ist ein Nutzen, der erst langfristig sichtbar wird.

Iterative Verfahren sind sinnvoll

Bei komplexeren Systemen ist es unmöglich beim initialen Entwurf an alles zu denken und zu überblicken, wie sich das System später mal verhalten wird. Daher entwickelt man iterativ.

Halte dich an deine Prozesse

Wenn immer gleich gebaut, getestet, deployed wird, so kann man leichter die Qualität sicherstellen. Im Fehlerfall ist es leichter herauszufinden, wann und wie der Fehler entstanden ist.