Freitag, 3. September 2010

Clean with care

Das Refactoring von Quellcode ist ein wesentlicher Bestandteil von TDD. Im TDD Kontext gilt der Satz: "Make it work then make it right!". Ein Refactoring ist damit vorprogrammiert. CCD propagiert die Pfadfinderregel, sodass der Quellcode schrittweise besser wird. Zum Refactoring und der Pfadfinderregel gehört auch das Aufräumen des Quellcodes.

Kommentare, die keinen Informationsgehalt haben oder Lügen und veralteter nicht benutzter Quellcode sind gute Kandidaten für die Anwendung der Pfadfinderregel. Bei einer noch fremden Quellcodebasis und in Quellen, in denen Frameworks eingesetzt werden, ist allerdings vorsichtiges Refactoring ratsam.

Beim Refactoring spielt häufig der Verwender einer Methode eine Rolle. Allzugerne nutzt man für das Finden der Verwender die Möglichkeiten seiner Entwicklungsumgebung. Sofern kein Verwender für eine öffentliche Methode gefunden werden kann, handelt es sich offensichtlich um toten Quellcode und schon fällt man in die Fallgrube. 

Frameworks geben einen einzuhaltenen Vertrag vor, der beim nicht Einhalten zu undefinierten Verhaltensmustern führen kann. Frameworks implementieren häufig generische Funktionalitäten zur allgemeinen Verwendung, die auf den in Java bekannten Reflections basieren. Einen Reflection-Aufruf findet man allerdings nicht mit den Mitteln der Entwicklungsumgebung (Eclipse: Ctrl-Shift-G), sodass das Entfernen einer Methode, die über Reflections gerufen wird in der Folge zu einem Laufzeitfehler führt.

Beispiel eines Methodenaufrufs über Reflections und als regulärer Methodenaufruf

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.junit.Test;

class Reflection {
   
    public void reflectionMethod() {
       
        System.out.println("reflectionMethod() - called");
    }
}

public class ReflectionTest {

    private final Reflection reflection = new Reflection();
   
    @Test
    public void testReflectionMethodCall() throws SecurityException,
                                          NoSuchMethodException, IllegalArgumentException,
                                                                      IllegalAccessException, InvocationTargetException {
       
        final Method method = reflection.getClass().getMethod("reflectionMethod");
        method.invoke(reflection, (Object[]) null);
    }
   
    @Test
    public void testRegularMethodCall() {
       
        reflection.reflectionMethod();
    }
}

Ein gutes Beispiel für den einzuhaltenen Vertrag eines Frameworks ist das JUnit-Plugin. Beim Schreiben von Testfällen legt man zunächst das Test Fixture fest und definiert dabei die Daten für den Testlauf. Die Vererbung von Daten an die Testklasse liegt dabei auf der Hand,. Tatsächlich bringt diese Vorgehensweise aber ein erstaunliches Ergebnis hervor. Das Verhalten des JUnit-Frameworks  ist bei einer Testklasse aufgefallen, die einen Scope-Test definiert.

Auszug aus der Scope-Testklasse

import static org.junit.Assert.assertEquals;
import org.junit.Test;

class ScopingBase {
   
    protected static String output = "A";
   
    static {
 
        output += "B";
    }
   
    {       
        output += "C";
    }

    protected ScopingBase() {
       
        output += "D";
    }   
   
     protected static String getOutput() {
           
            return output;
     }
}

public class Scoping /* extends ScopingBase */ {
   
    @Test
    public void testScopingWithoutInheritance() {
      
        assertEquals("AB", ScopingBase.getOutput());
        assertEquals("ABCD", new ScopingBase().getOutput());      
    }   

    @Test
    public void testScopingWithInheritance() {

        assertEquals("ABCDCD", ScopingBase.getOutput());
        assertEquals("ABCDCDCD", new ScopingBase().getOutput());      
    }
}

Das Ergebnis des Testlaufs ist korrekt, sofern man die Testklasse Scoping nicht von der Klasse ScopingBase ableitet. Nutzt man die Ableitung, fällt das Ergebnis anders aus. Vermutlich wird die Testklasse vom JUnit-Framework per Reflection gerufen. Dies dürfte der Grund sein, warum die Testergebnisse mit und ohne Ableitung unterschiedlich ausfallen.

Once again, clean with care!


Der Rechtshinweis des Java Blog für Clean Code Developer ist bei der Verwendung und Weiterentwicklung des Quellcodes des Blogeintrages zu beachten.