Kurzbeschreibung
Softwarequalität ist keine Spracheigenschaft. In jeder noch so guten Programmiersprache kann man schlechte Programme schreiben – sogar in Java. Herr Seekamp, Senior Consultant bei der GEDOPLAN GmbH, macht in diesem Vortrag anhand von Fallbeispielen aus seinen Projekten deutlich, was verständlichen und wartbaren Code ausmacht, welche Regeln man dafür beherzigen sollte und welche Analysewerkzeuge dabei unterstützen können.
Inhalt
Regeln für guten Java-Code
Statische Code-Analyse
Refactoring
Werkzeuge zur Sicherung der Qualität
Java Code Quality: Gute Software braucht guten Code - Regeln für verständliche und wartbare Java-Programme
1. Java Code Quality
Regeln für gute Java-Programme
Treffpunkt Semicolon, 22.02.2011, GFU Cyrus AG
Jens Seekamp, GEDOPLAN GmbH
2. Java Code Quality – ein ganzheitlicher Ansatz
Agenda
Software-Qualität
Regel 1: System-Dekomposition und Build-Prozess
Regel 2: Schichten-Architektur
Regel 3: Modell-getriebene Entwicklung
Regel 4: inkrementelle Entwicklung
Regel 5: Richtlinien und statische Code-Analyse
Regel 6: integrierter, automatisierter Test
Regel 7: Refactoring und Regressionstest
2
4. Regel 1: System-Dekomposition und Build-Prozess
System-Dekomposition
Zerlegung des Software-Systems in Komponenten
eine Komponente kapselt einen Funktionsbereich (Schnittstelle
vs. Rumpf)
eine Komponente basiert oftmals auf einer Implementierungs-
Technologie (z. B. EJB)
eine Komponente ist i. d. R. ausführbar
4
6. Architektur einer „großen“ Java-EE-Anwendung
Projekt BDE:
Betriebsdatenerfassung für
Fertigungsindustrie
6
7. Regel 1: System-Dekomposition und Build-Prozess
Build-Prozess
„Bauen“ des Software-Systems aus seinen kompilierten
Komponenten
dabei wird je Komponente ein Build-Artefakt erstellt
Werkzeuge für den Build-Prozess
Build-Prozess wird vollständig mit Maven 3.x durchgeführt
Java-Compiler der IDE ist (theoretisch) überflüssig
Continuous Integration wird mit Hudson realisiert (z. B. Nightly
Build)
7
9. Regel 2: Schichten-Architektur
Zerlegung in 3-Schichten-Architektur
Präsentations-Schicht (GUI)
Fachlogik-Schicht (Geschäftsfälle, Dienste, Fachklassen)
Datenhaltungs-Schicht (Speicherung der Objekte, RDBMS)
zusätzlich oftmals „übergreifende“ Schichten
fachliches Klassenmodell (Entitäten)
Basis-Dienste und –Klassen (z. B. Ausnahmen, Meldungen)
Schichten-Zerlegung ist möglich
auf Ebene des Software-Systems
auf Ebene der Komponenten
9
11. Sicherstellung der Schichten-Architektur
häufige Verletzungen der Schichten-Architektur
„Überspringen“ einer Schicht (Präsentation Datenhaltung)
„umgekehrte“ benutzt-Beziehung (Datenhaltung Fachlogik)
falsche Zuordnung von Implementierung
Realisierung von fachlicher Logik in Dialogklassen
erkennbar in Benutzung von Fachklassen anstelle von Dienst-Schnittstelle
Sicherstellung durch Spezifikation von
erlaubten benutzt-Beziehungen
nicht erlaubten benutzt-Beziehungen
11
13. Regel 3: Modell-getriebene Entwicklung
aus einem „Modell“ generierter Java-Code ist
strukturiert
einheitlich
korrekt und stabil
Code-Generierung steigert außerdem
die Effizienz der Software-Entwicklung
die Wartbarkeit des Software-Systems
13
14. „klassische“ Modell-getriebene Entwicklung
Generierung @Entity
public class Land
des fachlichen Klassenmodells {
@Id
(Entitäten) private String isoCode;
private String name;
als POJO-Klassen mit JPA- public Land()
Annotationen {}
public String getIsoCode()
aus UML-Klassenmodell (z. B. { return this.isoCode; }
Enterprise Architect) public void setIsoCode(String code)
{ this.isoCode = code; }
public String getName()
{ return this.name; }
public void setName(String name)
{ this.name = name; }
}
14
15. Generierung von XML-Zugriffsklassen (1)
Generierung
für die Umwandlung Java-Objekte XML-Dokumente
(Marshalling / Unmarshalling)
als POJO-Klassen mit JAXB-Annotationen
aus XML-Schema (z. B. XMLSPY)
mit dem JAXB-Schema-Compiler (Java Architecture for XML
Binding)
15
16. Generierung von XML-Zugriffsklassen (2)
XJC
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Satzart_Ctp", propOrder =
{"verfahren", "zeitraumAb", "zeitraumBis"})
public class Satzart implements Serializable
{
@XmlElement(name = "Verfahren", required = true)
private String verfahren;
public String getVerfahren()
{ return verfahren; }
public void setVerfahren(String daten)
{ verfahren = daten; }
16
17. Generierung eines Formel-Parser
Generierung
eines Parser für arithmetisch-logische Formeln
aus einer kontextfreien Grammatik
mit dem Parser-Generator JavaCC (Java Compiler Compiler)
TOKEN: {
| < PLUS: "+" > 12 * x + 5 ...
| < MINUS: "-" >
}
void Addition():
{ Token t = null;
StringBuilder sb = new StringBuilder();
} +
{ Multiplikation()
( ( t = <PLUS> Multiplikation() ) { sb.append(t.image); }
| ( t = <MINUS> Multiplikation() ) { sb.append(t.image); } * 5
)*
{ jjtThis.jjtSetValue(sb.toString()); }
} 12 x
17
18. Regel 4: inkrementelle Entwicklung - Randbedingungen
package de.aoksystems.pragsys.service.pruefkern;
import de.aoksystems.pragsys.bo.statistik.Statistik;
import de.aoksystems.pragsys.bo.pruefung.Pruefergebnis;
@Stateless
public class PruefServiceBean implements PruefService
{
/**
* Diese Methode prüft eine Statistik, die an das Prüfsystem
* übergeben wurde, und liefert das Prüfergebnis zurück.
*/
public Pruefergebnis pruefeStatistik(Statistik s)
{...}
}
18
19. Regel 4: inkrementelle Entwicklung - Tipps
Implementierung: konkret beginnen und schrittweise verfeinern
erst „in die Tiefe“, später „in die Breite“ implementieren (Prototyping,
depth-first)
möglichst frühe Rückkopplung
gleichzeitige Erstellung von Unit-Tests
Review durch Projekt-Kollegen
Demonstration für Benutzer
Grundsätze beachten (vgl. http://www.clean-code-developer.de)
immer objektorientiert und „sauber“
möglichst einfach (KISS), redundanzfrei (DRY), ...
„Software ist (fast) nie fertig.“ (evolutionäre Entwicklung, TODOs)
19
20. Regel 5: Richtlinien und statische Code-Analyse
ein Team von SW-Entwicklern ist heterogen (Berufs-/Projekterfahrung,
Programmierstil)
für einheitlichen, lesbaren, kommentierten usw. Java-Code sind
Entwicklungs-Richtlinien unabdingbar
Richtlinien-Katalog zusammengefasst im Entwickler-Handbuch
Beschreibung der Richtlinie (ggf. mit Motivation, Zielsetzung)
Positiv- und ggf. Negativ-Beispiele (Do‘s and Dont‘s)
Umfang des Programm-Code und Anzahl der Richtlinien erfordern
automatisierte Überwachung
Werkzeuge für die statische Code-Analyse
z. B. Checkstyle, FindBugs, SonarJ
20
21. Regel 5: Richtlinien-Katalog (Beispiele)
Standard-Konventionen für Java der Firma Sun
deutsche Bezeichner für Klassen, Attribute, Methoden etc.
verwenden
Konstanten bestehen nur aus Großbuchstaben, Ziffern und dem
Unterstrich "_"
anstatt null ein Array der Länge 0 zurück geben
falls eine Exception geworfen wird, muss sie protokolliert
werden
mehrmals benötigte Programmlogik wird in eine separate Methode
bzw. Klasse ausgelagert
Reflection darf nicht verwendet werden
21
22. Regel 5: Werkzeuge für statische Code-Analyse
Idealfall: für jede Richtlinie gibt es eine aktivierte Analyse-Regel
und umgekehrt
für „kleine“ Projekte sollte ein Code-Analyse-Werkzeug reichen
für „große“ Projekte und Vollständigkeit müssen ggf. mehrere Code-
Analyse-Werkzeuge parallel eingesetzt werden
erhöhter Konfigurationsaufwand
Problem der Mehrfach-Meldung von Verletzungen
Standardisierung / Wiederverwendung des Richtlinien-Kataloges und
der Werkzeug-Konfiguration (über Projekt- und Abteilungsgrenzen)
Werkzeuge machen Reviews durch Software-Architekten oder
erfahrene Entwickler nicht überflüssig
22
24. Regel 6: integrierter, automatisierter Test
Software-Test hat zwei Zielsetzungen
im Java-Code möglichst viele Fehler aufdecken
Korrektheit der Anwendung demonstrieren
Test ist integraler Bestandteil der Software-Entwicklung, und nicht
nur nachgelagert (vgl. testgetriebene Entwicklung, test-first)
Test dient zum Nachweis der dynamischen, funktionalen Korrektheit
des Java-Code (dies ist mit statischer Code-Analyse nicht möglich)
Fokus liegt auf der Realisierung von automatisierten Tests
dafür Einsatz von Java-Test-Frameworks und –Werkzeugen
24
25. Regel 6: Test-Konzeption für Java-(EE)-Anwendungen
Schritt 1: Entwicklertest für wichtige Klassen und Methoden
„Standard“-Framework JUnit 4.x
Schritt 2: Realisierung einer Testdaten-Verwaltung
Nutzung dedizierter Test-Datenbank(en)
explizite Testdaten-Erzeugung mittels Java (DBUnit, XMLUnit)
Schritt 3: Integrationstest der Service-Schicht
per JUnit-Testclient gegen den Application-Server (remote)
mittels z. B. OpenEJB innerhalb der IDE (embedded)
Schritt 4: „automatisierter Abnahmetest“ der GUI-Clients
Werkzeug abhängig von GUI-Technologie und –Bibliothek
z. B. QF-Test (alle), Selenium (Web), Abbot (Swing)
Schritt 5: Test nicht-funktionaler Anforderungen (Performanz, Last)
25
26. Regel 6: Bibliothek der automatisierten Testfälle
Zusammenfassung aller automatisierten Testfälle aus Schritt 1 bis 5
zu einer Test-Bibliothek
als Test-Suites gemäß JUnit
(JUnit-Integration aller Werkzeuge /
Frameworks vorausgesetzt)
hierarchische Strukturierung
der Test-Suites
gesamte Test-Bibliothek auf
Entwicklungsrechner lokal
ausführbar
26
27. Regel 7: Refactoring und Regressionstest
Fehler und Qualitätsmängel des Java-Code werden laufend
festgestellt durch
werkzeuggestützte, automatisierte Tests
werkzeuggestützte, statische Code-Analyse
direkte Notwendigkeit für Fehlerbehebung und Mängelbeseitigung
bedingt oftmals Refactoring, d. h. weitergehende, strukturelle
„Umbau-Arbeiten“
nach einem Refactoring
ist der Java-Code fehlerbereinigt und / oder qualitativ besser
ist die Gesamt-Funktionalität unverändert
27
28. Beispiel: Redundanz-Beseitigung und Kapselung
Aufdecken von redundantem Java-Code mit
Checkstyle-Modul „Strict Duplicate Code“
Auslagern der Code-Redundanz in eine separate
Klasse
dadurch gleichzeitig Kapselung der Verwendung
des JAXB-Framework
28
29. Regel 7: Regressionstest im Rahmen der CI
Wahrung der funktionalen Korrektheit nach Refactorings wird durch
den Regressionstest sichergestellt
Regressionstest ist der Test des gesamten Java-Code auf Basis der
Test-Bibliothek
Zusammenfassung aller automatisierten, qualitätssichernden
Maßnahmen in der Continuous Integration (Hudson):
Integrations-Build (Subversion, Maven)
Generierung der Programm-Dokumentation (JavaDoc)
statische Code-Analyse (Checkstyle, ...)
Regressionstest (JUnit, ...) auf Basis der Test-Bibliothek
Messung der Test-Überdeckung (Cobertura)
Deployment auf QS-Umgebung (für manuelle Tests)
29