Samstag, 11. Juni 2011

Vertical separation

Die vertikale Trennung schreibt die Definition von Variablen und Methoden nahe bei ihrer Verwendung vor. Verbunden mit der vertikalen Trennung ist die Kohäsion von Methoden. Eine kohäsive Methode ist klein und erfüllt genau eine Aufgabe. Das Band zwischen der vertikalen Trennung und kohäsiven Methoden wird durch die Lesefolge gebunden. Ausgehend von einer öffentlichen Methode werden die privaten Methoden, welche in der öffentlichen Methode verwendet werden, unterhalb der öffentlichen Methode im Quellcode geschrieben. Der vertikale Abstand zwischen den kohäsiven Methoden soll gering sein.

Die vertikale Trennung wird oft mit dem Lesen einer Tageszeitung gleichgesetzt. In einer Tageszeitung werden bewusst hervorgehobene Überschriften (Headlines) genutzt, denen ein Artikel folgt. Das Single Level of Abstraction (SLA) Prinzip gibt vor, dass in Klassen und Methoden möglichst nur ein Abstraktionsniveau verwendet werden soll. Teil dieses Prinzips ist, dass vorzugsweise mit lokalen Variablen in Methoden anstelle von Attributen einer Klasse gearbeiten wird. Die Nutzung lokaler Variablen vermeidet Seiteneffekte und beugt Problemen, die durch Nebenläufigkeit entstehen können vor. Quellcode, der dem SLA Prinzip folgt, ist gut zu lesen und zu verstehen. Der Leser erkennt durch die saubere Anordnung des Quellcodes sehr viel leichter essentielle Abschnitte ohne sich dabei in Details zu verfangen. Die vertikale Trennung und Bildung kleiner kohäsiver Methoden ist eine essentielle Praktik bei der Refaktorisierung durch Anwendung der Pfadfinderregel.

Die vertikale Trennung erhöht aber nicht nur die Lesbarkeit von Quellcode, sondern ist partiell nötig, um Kompilierprobleme zu Gunsten eines klaren Methodendesigns zu beseitigen.

Beispiel „Wildcard Capture“:

public static void reverse(List<?> list);
public static <T> void reverse(List<T> list);

Die Methodensignatur der “reverse” Methode kann auf Basis eines Wildcards oder mit einem Typparameter beschrieben werden. Die Wildcard Methodensignatur ist kürzer und lesbarer, bringt aber die Problematik mit sich, dass die übergebene Liste per Definition nicht mit neuen Elementen erweitert werden darf. Ein Kompilierfehler zeigt dieses Problem an. Das Problem ist durch eine zusätzliche Methode, die der vertikalen Trennung genügt, lösbar.

Quellcode einer einfachen ListHelper-Klasse:
 
public class ListHelper {

    public static void reverse(final List<?> list) {
       
        assertNotNull(list);       
        assertNotEmpty(list);
       
        reverseOperation(list);
    }
   
    private static void assertNotNull(final List<?> list) {
       
        if(null == list) {
           
            throw new NullPointerException("List argument is null!");
        }
    }

    private static void assertNotEmpty(final List<?> list) {
       
        if(list.isEmpty()) {
           
            throw new IllegalArgumentException("List contains no elements!");
        }
    }
   
    private static <T> void reverseOperation(final List<T> list) {
       
        final List<T> tmpList = new ArrayList<T>(list);
        final int maxPositions = list.size() - 1;
        int readPosition = 0;
       
        for(int writePosition = 0; writePosition <= maxPositions; writePosition++) {
           
            readPosition = maxPositions - writePosition;
            list.set(writePosition, tmpList.get((readPosition)));
        }
    }
}

In der Methode "reverseOperation" hat der Typparameter den Wildcard erfasst (captured).

Testfälle der ListHelper-Klasse:

@RunWith(value=Parameterized.class)
public class TestListHelper {
   
    private final List<String> original;
    private final List<String> reversed;
   
    public TestListHelper(final String[] original,
                                   final String[] reversed) {
       
        this.original = Arrays.asList(original);
        this.reversed = Arrays.asList(reversed);
    }
   
    @Parameters
    public static Collection<String[][]> getTestParameters() {
       
        return Arrays.asList(new String[][][]{
               
                {{"one", "two", "three"},{"three", "two", "one"}},
                {{"1", "2", "3"},{"3", "2", "1"}}});
    }
   
    @Test
    public void testReverse() {
                    
        ListHelper.reverse(original);       
        assertEquals(reversed, original);
    }
}

public class TestListHelperExceptions {

    @Test(expected=NullPointerException.class)
    public void testReverseWithNullPointerException() {
                    
        ListHelper.reverse(null);
    }
   
    @Test(expected=IllegalArgumentException.class)
    public void testReverseWithIllegalArgumentException() {
                    
        ListHelper.reverse(new ArrayList<String>());
    }   
}


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