46
Gute Zeilen, schlechte Zeilen Regeln für wartbare Programme Dirk Weil, GEDOPLAN GmbH

Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Embed Size (px)

DESCRIPTION

GEDOPLAN-Vortrag zum Thema Qualität von (Java-)Software: Clean Code, Code-Analyse, Architektur, Qualitätssicherung. (www.gedoplan.de)

Citation preview

Page 1: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Gute Zeilen, schlechte Zeilen

Regeln für wartbare Programme

Dirk Weil, GEDOPLAN GmbH

Page 2: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Dirk Weil

GEDOPLAN GmbH, Bielefeld

Java EE seit 1998

Konzeption und

Realisierung

Vorträge

Seminare

Veröffentlichungen

Gute Zeilen, schlechte Zeilen 2

Page 3: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

GUTE

SCHLECHTE

ZEILEN

ZEILEN

Gute Zeilen, schlechte Zeilen 3

Page 4: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Gibt es guten und schlechten Code?

Software muss funktional korrekt sein

Software muss effizient bearbeitbar sein

Ist (fast) nie fertig

Wird (meist) im Team entwickelt

Teams ändern sich über die Zeit

unterschiedliche Berufserfahrung, Programmierstile, …

Software muss verständlich sein

Gute Zeilen, schlechte Zeilen 4

Page 5: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Richtlinien

… helfen

bei der Einarbeitung in fremde Software

bei der (Weiter-) Entwicklung

Low Level: Namen, Formatierung …

Grundlegendes Klassendesign

Code-Komplexität

Anwendungsstruktur

Gute Zeilen, schlechte Zeilen 5

Page 6: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Statische Code-Analyse

Matching des Codes gegen Regelsätze

Einfache (Text-)Pattern … strukturelle Pattern

Checkstyle

Gute Zeilen, schlechte Zeilen 6

Page 7: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Dokumentation

Javadoc für API

Klassen, Interfaces,

Methoden, Variablen

public, protected

Kontrolle bspw. per Checkstyle

Javadoc Comments

Prüft per Default auch private

scope = protected

Getter/Setter-Doku meist überflüssig

allowMissingPropertyJavadoc = true

Gute Zeilen, schlechte Zeilen 7

Page 8: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Dokumentation

API-Dokumentation

veröffentlichen

Source-Jars erzeugen,

z. B. mit Maven

ermöglicht Unter-

stützung durch

die IDE

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <inherited>true</inherited> <executions> <execution> <id>attach-sources</id> <phase>verify</phase> <goals> <goal>jar-no-fork</goal> </goals> </execution> </executions> </plugin>

Gute Zeilen, schlechte Zeilen 8

Page 9: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Dokumentation

Erklärungsbedürftige Codesequenzen

Trivialdokumentation ist überflüssig

/* * Fahrstrassen innerhalb von Fahrstrassen expandieren, d. h. durch ihre Elemente * ersetzen. Dies geschieht in einer Schleife solange, bis alle Expansionen * erledigt sind oder kein Fortschritt mehr erzielt wird. */ int letzteAnzahlFahrstrassenFahrstrassen = 0; int anzahlFahrstrassenFahrstrassen = 0; while (true) { for (Fahrstrasse fahrstrasse : this.fahrstrassen) …

// Neue Weichenstellung protokollieren this.logger.trace(this + ": setStellung(" + stellung + ")");

Gute Zeilen, schlechte Zeilen 9

Page 10: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Namen

Klassen, Variablen, Methoden entsprechend ihrer Aufgabe benennen

spart umfangreiche Dokumentation

Well-Known Names nicht umdeuten!

Anschauungsbeispiel: "Nothalt-Funktion"

Anlagenstatus ist zu generell

Methode setzt den Status nicht, sondern toggelt

setXyz ist well-known mit anderer Bedeutung

public void setAnlagenstatus() { Anlagenstatus.getInstance().changeAnlagenstatus();

Gute Zeilen, schlechte Zeilen 10

Page 11: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Namen

Keine Präfixnotation für Typen, Sichtbarkeit etc.:

Präfixnamen tendieren zur Unlesbarkeit

this. ist aussagekräftiger (OO) als m_

Unterscheidung Klasse vs. Interface zweitrangig

werden durch IDE-Unterstützung mehr als ersetzt

Instanzvariable Name beginnt mit m_

Variable vom Typ List<Integer> Name beginnt mit lI_

Interface Name beginnt mit I

… …

Gute Zeilen, schlechte Zeilen 11

Page 12: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Namen

CS kann Namenskonventionen prüfen

(Module group Naming Conventions)

IDE-Komfort nutzen!

Quick fix: Namensvorschläge (Variablen, Konstanten ..)

Save actions: Member mit this. qualifizieren

Gute Zeilen, schlechte Zeilen 12

Page 13: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Formatierung

Code sollte im Team einheitlich formatiert sein

Einrückung (Tab/Blanks, wie viele?)

Platzierung von {

Vorteile

Code liest sich leichter

kleinere Change Sets im SCM

Lässt sich mit IDE-Komfort leicht erreichen

Save action: Format code

Überprüfung durch Code Checker eher zweitrangig

Gute Zeilen, schlechte Zeilen 13

Page 14: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

equals, hashCode

Jedes Geschäftsobjekt sollte equals und hashCode definieren

IDEs bieten gute Unterstützung

Achtung bei equals in Basisklassen

public class BadEquals { public boolean equals(Object obj) { … if (getClass() != obj.getClass()) // if (!(obj instanceof BadEquals)) // unsymmetrisch, wenn Subklasse überschreibt // if (obj.getClass() != BadEquals.class) // funktioniert nicht für Subklassen { …

Gute Zeilen, schlechte Zeilen 14

Page 15: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

equals, hashCode

equals definieren hashCode definieren

nicht nur equals(MyType)

Codeanalyse:

CS: Equals and Hashcode, Covariant Equals

FB: Class defines equals and uses Object.hashCode

Gute Zeilen, schlechte Zeilen 15

Page 16: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

switch

Regeln:

default nicht vergessen

kein Fall Through

CS: Missing Switch Default, Fall Through

Gute Zeilen, schlechte Zeilen 16

Page 17: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Protokollierung

keine Protokollausgabe auf stdout, stderr

"Mal schnell 'ne Ausgabe"

Achtung: IDE-Templates!

CS: Regexp… mit passendem Pattern

Gute Zeilen, schlechte Zeilen 17

Page 18: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Exception-Verwendung

Werfen und Fangen von Throwable, Exception,

RuntimeException i. A. fehlerhaft

CS: Illegal Catch, Illegal Throws

Gute Zeilen, schlechte Zeilen 18

Page 19: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

DRY

Keine Copy&Paste-Programmierung

CS: Strict Duplicate Code

(nicht wirklich empfehlenswert)

PMD: Copy Paste Detection

Gute Zeilen, schlechte Zeilen 19

Page 20: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Komplexität

Klassen / Methoden nicht zu lang

Anzahl Methodenparameter nicht zu groß

CS: Maximum Method Length, Maximum Parameters,

Maximum File Length,

Cyclomatic Complexity

(Anwendung im

Team diskutieren!)

Gute Zeilen, schlechte Zeilen 20

Page 21: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Einfach machen

Einfache Lösungen sind gute Lösungen

Vorsicht bei:

Reflection

extrem schlecht lesbar

Refactoring problematisch

hochgradig konfigurierbaren Klassen

schwer nutzbar (bspw. GridBagConstraints)

übermäßigem Einsatz von Typparametern

Gute Zeilen, schlechte Zeilen 21

Page 22: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Einfach machen

Anschauungsbeispiel: Entity Editor

Generischer Editor für Geschäftsobjekte

Steuerung per Annotation @Editable

Remote funktionsfähig ( kein EntityManager)

Hochgradige Nutzung von Reflection

Umfangreiche Konfiguration von Services etc.

Einsatzfall BDE-System

ca. 35 Entities

Gute Zeilen, schlechte Zeilen 22

Page 23: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Klassen sparen lohnt nicht

Anschauungsbeispiel "Wachdienst":

Gebäudekontrolle durch Prüfung aller Räume

Räume sind auf Etagen verteilt

Kontrollierte Räume

werden abgehakt

Gute Zeilen, schlechte Zeilen 23

Page 24: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Klassen sparen lohnt nicht

Anschauungsbeispiel "Wachdienst"

1. Ansatz: Keine Klasse für Etage

Dialogaufbau unnötig kompliziert

(~ Gruppenwechsel)

Kein Platz für zukünftige Erweiterung

um Etagen-Daten

Gute Zeilen, schlechte Zeilen 24

Page 25: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Klassen sparen lohnt nicht

Anschauungsbeispiel "Wachdienst"

Besser: Zusätzliche Ebene für Etagen

Klares Konzept

Real World Klasse

Gute Zeilen, schlechte Zeilen 25

Page 26: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Wohin mit der Logik?

Gute Zeilen, schlechte Zeilen 26

Page 27: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Wohin mit der Logik?

Anschauungsbeispiel "Modellbahnsteuerung":

Reservieren einer Fahrstraße

= Stellen der betroffenen

Weichen und Signale

Fahrstrasse liegt als

Geschäftsobjekt vor

Anforderung als Webservice

Gute Zeilen, schlechte Zeilen 27

Page 28: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Wohin mit der Logik?

Anschauungsbeispiel "Modellbahnsteuerung":

1. Ansatz: Iteration über Fahrstraßenelemente im Webservice

Nachteile:

nicht wieder-

verwendbar, da

im Webservice

"Polymorphie

für Arme"

(instanceof

~ goto der OO)

@POST @Path("/fahrstrasse/{bereich}/{name}/reserviert") public Response setFahrstrassenreservierung(...) { Fahrstrasse fahrstrasse = … for (FahrstrassenElement fe : fahrstrasse.getElemente()) { Fahrwegelement fwe = fe.getFahrwegelement(); if (fwe instanceof Weiche) { Weiche w = (Weiche) fwe; w.setStellung(…); } …

Gute Zeilen, schlechte Zeilen 28

Page 29: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Wohin mit der Logik?

Anschauungsbeispiel "Modellbahnsteuerung":

Besser: Platzierung der Logik in Fahrstrasse,

abstrakte Methode statt expliziter Typabfrage

@Path("{bereich}/{name}/reserviert") @POST public Response setReserviert(…) { Fahrstrasse fahrstrasse = … fahrstrasse.setReserviert(reserviert); }

public void setReserviert(boolean reserviert) { for (FahrstrassenElement element : this.elemente) element.setReserviert(reserviert);

public abstract void setReserviert(boolean reserviert);

Gute Zeilen, schlechte Zeilen 29

Page 30: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Wohin mit der Logik?

Geschäftslogik in Geschäftslogik-Schicht

CDI, EJB, JPA, …

Präsentationslogik bspw. in JSF-Beans

CDI-Models, Managed Beans

Boundary-Code in EJBs, Webservices etc.

Saubere Schichtung

erhöht Wiederverwendbarkeit

macht den Code übersichtlicher

Gute Zeilen, schlechte Zeilen 30

Page 31: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

In Objekten denken

Anschauungsbeispiel: 1:n-Relation (JPA)

Query "Suche Books eines Publishers"

(Warum so kompliziert?):

@Entity public class Publisher { @Id @GeneratedValue Integer id; @OneToMany(mappedBy = "publisher") List<Book> books; …

@Entity public class Book { @Id @GeneratedValue Integer id; @ManyToOne Publisher publisher;

Publisher publisher = …; … em.createQuery("select b from Book b where b.publisher.id=:publisherId", Book.class) .setParameter("publisherId", publisher.getId()) .getResultList();

Gute Zeilen, schlechte Zeilen 31

Page 32: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Packages

Zwei Anti-Beispiele

Gute Zeilen, schlechte Zeilen 32

Page 33: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Anekdoten

String name = new String(); name = "Hugo";

if (val != null && ("" + val.getClass().getName()).equals("java.lang.String"))

Set<String> texte = …; Set<Object> labels = new TreeSet<>(); labels.addAll(texte);

int adr = …; String adrAsString = new Integer(adr).toString();

Offensichtlich komplett falsche Vorstellung

von Objekten und Referenzen darauf

instanceof

Sind die Labels nicht Texte? Warum dann Set<Object>?

Wenn unterschiedliche Typen erlaubt: Set<?>

Integer.toString(adr)

Gute Zeilen, schlechte Zeilen 33

Page 34: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Anekdoten

public void changeRichtung() { if (isRueckwaerts()) setRueckwaerts(false); else setRueckwaerts(true); }

public class Anlagenstatus { private static Anlagenstatus anlagenstatus = null; private Anlagenstatus() { } public static Anlagenstatus getInstance() { if (anlagenstatus == null) anlagenstatus = new Anlagenstatus(); return anlagenstatus; }

setRueckwaerts(!isRueckwaerts())

public static final Anlagenstatus

= new Anlagenstatus()

Noch besser: enum-Singleton

Gute Zeilen, schlechte Zeilen 34

Page 35: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Anekdoten

@POST @Path("artikel/{artNr}/menge") public Response setSpeed(@PathParam("artNr") String adrNr, @FormParam("menge") String menge) { try { int mengeInt = Integer.parseInt(menge); … } catch (NumberFormatException e) {

Auto car1 = ...; Auto car2 = ...; if (car1.getId().getValue().equals(car2.getId().getValue())) { ...

int menge

if (car1.equals(car2))

Gute Zeilen, schlechte Zeilen 35

Page 36: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Anekdoten

if (this.kommPunkt.isTurnText()) { g2.setFont(system.getKommPunktFont()); g2.translate(kommPunktBreite / 2 + 2, 0 * kommPunktBreite / 2 - 1); g2.rotate(Math.toRadians(this.kommPunkt.getDisplayWinkel() - 90), this.kommPunkt.getDisplayX(), this.kommPunkt.getDisplayY()); g2.setColor(system.getColor(system.PROPERTY_KOMMPUNKT_TEXT_COLOR)); g2.drawString(this.kommPunkt.getId().getValue(), this.kommPunkt.getDisplayX() - 3, this.kommPunkt.getDisplayY() + 2); g2.setTransform(this.father.baseTransform); } else { g2.setFont(system.getKommPunktFont()); g2.translate(kommPunktBreite / 2 + 2, 0 * kommPunktBreite / 2 - 1); g2.rotate(Math.toRadians(this.kommPunkt.getDisplayWinkel() - 90), this.kommPunkt.getDisplayX(), this.kommPunkt.getDisplayY()); g2.setColor(system.getColor(system.PROPERTY_KOMMPUNKT_TEXT_COLOR)); g2.drawString(this.kommPunkt.getId().getValue(), this.kommPunkt.getDisplayX() - 24, this.kommPunkt.getDisplayY() + 2); g2.setTransform(this.father.baseTransform); }

Gute Zeilen, schlechte Zeilen 36

Page 37: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Unit Tests

Test der funktionalen Korrektheit

auf Unit-Ebene

einzelne Klasse

isoliert von ihrer Umgebung, ggf. mittels Mock-Objekten

als Integrationstests

während der Entwicklung

als nachträglich Absicherung

oder als Vorgehensweise (Test Driven Development)

als Qualitätssicherungsmaßnahme

automatisiert

regelmäßig

Gute Zeilen, schlechte Zeilen 37

Page 38: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Unit Tests

nicht aufwändiger als Main-Programm zum „Ausprobieren“

public class WaehrungServiceUnitTest

{

private static WaehrungService WAEHRUNG_SERVICE = new WaehrungService();

@Test

public void testUmrechnenUSD()

{

double actual = WAEHRUNG_SERVICE.umrechnen(100.0, "USD");

assertThat("Euro-Betrag", actual, is(83.41));

} public class WaehrungServiceMain

{

private static WaehrungService WAEHRUNG_SERVICE = new WaehrungService();

public static void main(String [] args)

{

BigDecimal actual = WAEHRUNG_SERVICE.umrechnen(100.0, "USD");

System.out.println("100 USD = " + actual + " EUR");

}

Gute Zeilen, schlechte Zeilen 38

Page 39: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Unit Tests

Testwissen wird explizit gemacht

als Einstiegsdokumentation / Tutorial geeignet

automatisch, unbedient ausführbar

Continuous Integration (Hudson, Jenkins, …)

regelmäßige Ausführung aller Tests effizient möglich

dauerhafte Qualitätssicherung

unverzichtbar für Refactoring und Weiterentwicklung

Absicherung gegen „Verschlimmbesserung“

Gute Zeilen, schlechte Zeilen 39

Page 40: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Continuous Integration

Manuelle Ausführung reicht nicht

belastet den Entwicklungsprozess

keine (einheitliche) Veröffentlichung der Ergebnisse

keine (einheitliche) Eskalation bei Fehlern

Bei mir läuft's! Oh, sorry – das habe

ich noch nicht

eingecheckt.

An dem Teil habe ich

nichts gemacht!

Gute Zeilen, schlechte Zeilen 40

Page 41: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Continuous Integration

Voraussetzung: Projekt enthält ausführbare Tests

Junit, TestNG, …

Build selbst ist auch ein Test!

Gute Zeilen, schlechte Zeilen 41

Page 42: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Continuous Integration

Anforderungen an eine Build- und Test-Umgebung

Regelmäßige, automatische Ausführung

zeitgesteuert ("Daily Build")

durch Check-In getriggert

Kompletter Build

Ausführung aller Tests

Qualitätsprüfung (Style, Coverage, …)

Reporting

Benachrichtigung

Gute Zeilen, schlechte Zeilen 42

Page 43: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Continuous Integration

Entw.-

Umgebung

Plan

SCM

Test

Archiv

Feedback

Build

CI-Umgebung

Gute Zeilen, schlechte Zeilen 43

Page 44: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Continuous Integration

Gute Zeilen, schlechte Zeilen 44

Page 45: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

Qualitäts-Schulden

Schlechte Code-Qualität = Kredit

Code-Review + Korrektur = Rückzahlung

Weiterentwicklung trotz Schwächen

= Zinszahlungen

können überwältigend werden!

Nichts tun = "absaufen"

Ad-hoc-Maßnahmen sind

keine Dauerlösung

Gute Zeilen, schlechte Zeilen 45

Page 46: Gute zeilen, schlechte zeilen - Regeln für wartbare Software

More

Seminare zum Thema, z. B.

Java Effective

Java Software Testing

http://javaeeblog.wordpress.com/

http://expertenkreisjava.blogspot.de/

[email protected]

@dirkweil

[email protected] Gute Zeilen, schlechte Zeilen 46