Donnerstag, 9. Mai 2013

Tell don't ask

Over the years data transfer objects which are generally full with getter and setter are used without business logic. Technologies like J2EE lead to see them as pattern and common development practice. Originally designed to carry data over layers (serializable object) and to be part of an object assemble strategy they mutated to a development practice which can now be seen as an anti-pattern. Okay, we know the danger of getter to retrieve state data to manage the state outside the instance of a class. Smart guys are used to immutable objects by using finals and only getter without any setter, but indirect state changing by retrieving a reference with a getter is still possible and dangerous.

As a developer I have never heard: “Remove all getter, we don’t use them”. There is no coding convention for this topic. I still think it is not possible to live without getter, but I know to follow the way of immutable objects is a good way to write stable software. The usage of defensive copies may help to implement real immutable objects. Think about using the clone() method and in case of collections about deep copying objects. Strategies you can trust, even when race conditions occur.

I share my opinion with experienced developers which it is a good design practice to give the domain objects behavior to expose less state. I know that entities needs getter/setter as the entity manager (ORM technology) expects them, but on the other hand it's a rich implementation practice to give entities additionally behavior. The annotation @Transient may help you. Don’t see the domain layer as a stupid data heap by putting all business logic in the service layer. Doing this you will break OOP by starting functional programming.

Even in the past, J2EE already propagated to put business logic in CMP/BMP entities. Until today I do not understand why this had not been emphasized and written down in red bold letters that every developer recognized this fact as an important development practice.There is no doubt that using rich domain objects are in line with OO principles like high cohesion and data hiding.

Following this principle you will not end with a layer of anemic domain objects which are difficult to put into a test strategy. Think about isolation and the power to test rich domain objects which do not allow publishing their state. The design of lean service layer accessing rich domain models which holds the key business logic is an honest achievement which is comfortably implementable with POJOs and the rich set of Java EE annotations.

The evil often begins with some of the Java API classes like the iterator.  The iterator is a vehicle to loop through the collection of state holder. A common practice is to call a getter to get the reference of a collection instance to call the iterator method. How will you test that? Think about isolation and the benefit of louse coupling. The problem occurs when you have developed such a structure and later you write the test case. Tight coupling often makes it impossible to write test cases. Test cases are important. Without test cases no refactoring will happen.

The following example includes a simple division functionality which shows the difference between tell don't ask like software and the opposite. Look at the test class which tests both classes and hopefully gives an impression about the benefits of not using anemic objects.

Anemic division class (not tell don't ask conform):

package org.ccd.tell.dont.ask;

public class AnemicDivision {

    private int divisor;
    private int divident;
   
    public int getDivisor() {
        return divisor;
    }
   
    public void setDivisor(int divisor) {
        this.divisor = divisor;
    }
   
    public int getDivident() {
        return divident;
    }
   
    public void setDivident(int divident) {
        this.divident = divident;
    }
}

Division class (tell don't ask conform):

package org.ccd.tell.dont.ask;

public class Division {

    public int buildQuotient(final int divident, final int divisor) {
       
        assertDivisor(divisor);
       
        return divident / divisor;
    }

    private void assertDivisor(final int divisor) {
       
        if(0 == divisor) {
       
             throw new IllegalArgumentException("Divisor is 0: Leads to divide by zero exception!");
        }
    }
}

Test:

package org.ccd.tell.dont.ask;

import junit.framework.Assert;

import org.junit.Before;
import org.junit.Test;

public class DivisionTest {

    private AnemicDivision anemicDivision;
    private Division division;
    private int quotient;
   
    @Before
    public void before() {
   
        anemicDivision = new AnemicDivision();
        division = new Division();
    }
   
    @Test
    public void testAnemicDivision() {
               
        anemicDivision.setDivident(100);
        anemicDivision.setDivisor(10);
       
        quotient = anemicDivision.getDivident() / anemicDivision.getDivisor();
       
        Assert.assertEquals(10, quotient);
    }
   
    @Test(expected=ArithmeticException.class)
    public void testAnemicDivisionDivideByZero() {
               
        anemicDivision.setDivident(100);
        anemicDivision.setDivisor(0);
       
        try {
       
            quotient = anemicDivision.getDivident() / anemicDivision.getDivisor();
        }
        catch(ArithmeticException arithmeticException) {
                   
            throw arithmeticException;
        }       
    }
   
    @Test
    public void testDivision() {
               
        quotient = division.buildQuotient(100, 10);
       
        Assert.assertEquals(10, quotient);
    }
   
    @Test(expected=IllegalArgumentException.class)
    public void testDivisionDivideByZero() {
               
        quotient = division.buildQuotient(100, 0);
    }
}

Note: There is a difference in semantics between Data Transfer Objects (DTOs) and Value Objects (VOs) therefore, VO should not be an alias name for DTO. Both objects hold data but a VO is identified by its values (enum kind style, no identity but equality) as against to a DTO which can have an identity (unique identifier as attribute).


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