Montag, 30. August 2010

Some more about writing code

Die Prinzipien und Praktiken für Clean Code Developer sind schon lange bekannt und gut formuliert. Beispiele für "Code Smells" sind nicht nur von Robert C. Martin in seinem Buch Clean Code, sondern auch von Martin Fowler und Kent Beck in ihrer Literatur formuliert worden. Die vielen Beispiele der Literatur beinhalten Code Styles, Refactorings und Best Practices, um "Code Smells" wie den folgenden zu vermeiden:

// a loop construct
do while(l < 1)
  System.out.println("l is " + l);
while(l > 1);

Warum riecht der Quellcode? Sicherlich ist der Kommentar unvorteilhaft, dass es sich um ein Schleifenkonstrukt handelt, ist offensichtlich. Die Formatierung ist schlecht gewählt und die Klammersetzung wurde ausgelassen. Obwohl es sich nur um ein paar Zeilen Quellcode handelt, ist der Programmablauf nicht sofort ersichtlich. Bessere Formatierung und die Klammersetzung würden den Programmablauf des Schleifenkonstrukts sofort ersichtlich werden lassen.

Wesentlich für die Maxime von CCD ist, dass man sich als Clean Code Developer auch als Autor versteht, der sauberen und leicht verständlichen Quellcode für andere Autoren schreibt. Darüber dürfte in der CCD-Community Einigkeit herrschen.

CCD beschäftigt sich mit Prinzipien für höhere Softwarequalität und propagiert Kohäsion, Kapselung und lose Kopplung. Klassen wie die nachfolgende mit geringer Kohäsion stinken, wenn man CCD und objektorientierten Paradigmen folgt.

public class LowCohesion {

    void takeOrders() {}
    void servesFood() {}
    void cleanDesk() {}
   
    void takeOrdersAndServesFoodAndCleanDesk() {
       
        takeOrders();
        servesFood();
        cleanDesk();
    }
}

Ein wichtiger Punkt, der weniger stark betrachtet wird, ist die Korrektheit von Programmen  für eine definierte Systemumgebung. Systemumgebungen wie Java EE Applikationsserver beinhalten sehr viele Verträge, die bei der Erstellung von Java Applikationen einzuhalten sind. Die Spezifikationen des Java Community Processes (JCP) sind voll von Einzelheiten über Verträge, die zwingend bei der Applikationsentwicklung einzuhalten sind. JCP-Spezifikationen beschreiben Best Practices und Antipatterns für die Implementierung von Java-Software.

Die Programmiersprache Java beinhaltet selbst wiederum sehr viele Verträge. Man denke an den Equals- und HashCode-Vertrag, der zwingend im Rahmen der Java Collections einzuhalten ist. Die Verträge im Java-Umfeld sind häufig stark zwingend ausgelegt, sodass festgelegte Schritte bei der Programmierung eines Algorithmus ausgeführt werden müssen.Typisches Beispiel ist die Suche in einer Collection mit "BinarySearch", die nur dann ein definiertes Ergebnis liefert, falls die Collection, auf deren Basis gesucht wird, zuvor sortiert worden ist.Die Sortierung einer Datenmenge unterliegt wiederum einem durch die Programmiersprache Java festgelegten Sortierkriterium für die einzelnen Elemente der Datenmenge.

Hinzu kommt die Nebenläufigkeit in Mehrbenutzerumgebungen, die wiederum die Wahl der korrekten Collection und die angemessene Synchronisation von Threads erfordert. Korrekte Programme bekommt man nicht geschenkt, sondern sie basieren auf tiefer Erfahrung in einer konkreten Systemumgebung. Die Vielfalt der Fehlermöglichkeiten reicht bei falsch erstellten nebenläufigen Programmen von keiner Synchronisation bis hin zum Deadlock. Zwischenzustände, die ein Programm sporadisch korrekt erscheinen lassen, sind möglich, äußern sich aber mit zunehmender oder abnehmender Last fehlerhaft. Probleme, die durch die Nebenläufigkeit auftreten, sind durch ihre sporadische Natur häufig schwer zu debuggen.

Java Boardmittel zur Synchronisation von Programmen, wie beispielsweise synchronized, wait, sleep, yield und join sind sicherlich schwierig anzuwenden. Die Schöpfer von Java haben diesen Schwachpunkt erkannt und seit Java 5 die Java Concurrent Utils (java.util.concurrent) als Antwort parat.

Die Java-API selbst hält viele Fallstricke bereit, die nicht alle durch einen Kompilierfehler angezeigt werden. Solche Fehler treten vielmehr zur Laufzeit auf und führen zu Ausnahmen, die vom Java Laufzeitsystem geworfen werden. Die Java-API verhält sich auch nicht immer in der Form, wie man es erwarten würde, sodass das Schreiben von Unit-Tests ratsam ist. Nachfolgend ein paar einfache Beispiele, die zum Nachdenken anregen sollen.

Beispiel - Integer-Datentyp und Vergleichsoperator:

 @Test
public void testCompareSuccessul() {
    
    Integer firstInt = 45;
    Integer secondInt = 45;

    assertEquals(true, firstInt == secondInt);
}

@Test
public void testCompareFailed() {

    Integer firstInt = 145;
    Integer secondInt = 145;

    assertEquals(true, firstInt == secondInt);
}

Beispiel - Inkrementoperator und Returnwert:

private int getIntValue() {
        
  int intValue = 1;
  
  return(intValue++);

@Test
 public void testReturnValueSuccessful() {
                   
     assertEquals(1, getIntValue());
 }

@Test
 public void testReturnValueFailed() {
                   
     assertEquals(2, getIntValue());
 }

Beispiel - Downcast:

class CleanCodeDeveloper {}
class JavaDeveloper extends CleanCodeDeveloper {}


@Test
 public void testDowncastFailed() {
        
    CleanCodeDeveloper cleanCodeDeveloper = new CleanCodeDeveloper();
    JavaDeveloper javaDeveloper = (JavaDeveloper) cleanCodeDeveloper;

}

Beispiel - Arrays:

class CleanCodeDeveloper {}
class JavaDeveloper extends CleanCodeDeveloper {}


@Test
public void testArraysAreNotTypeSafe()  {

    CleanCodeDeveloper[] cleanCodeDeveloper = new JavaDeveloper[1];               
    cleanCodeDeveloper[0] = new CleanCodeDeveloper();                        
}

Die Liste der Laufzeitprobleme könnte noch sehr viel länger werden und veranschaulicht ein paar einfache Verträge der Programmiersprache Java. Sollte deshalb nicht auch ein CCD-Entwickler eine hohe Kohäsivität seiner Programmierskills anstreben, um optimal in einer Programmiersprache und Programmierumgebung agieren zu können?

Ich denke, dass man diese Frage mit "Ja" beantwortet. TDD und Eclipse Plugins wie PMD mildern diese Anforderung zwar ab, weil Unit-Tests ein Sicherheitsnetz bilden und PMD Programmfehler vorausschauend erkennt, aber dennoch halte ich die Fahne für die hohe Kohäsivität von Programmierskills nach oben.