How to avoid if/else statements

Posted: July 24th, 2011 | Author: | No Comments »

When I’m programming I always try to follow good practices and dedicate some time to review my own code. Mainly, I do this practice for two reasons: to improve as programmer, and to make easier the life of the person will deal with my code in the future. Techniques like code review help to the organization (and its developers) to get better the quality of its applications; one day I heard that if somebody knows that his code will be read by another one, the motivation to improve the code grows. I really agree with this assertion.

Conditional statements

One way to improve our code is cleaning the parts that seem hard to cope when the requirements are changeable. The nested conditional statements usually are one of these parts of code that should be cleaned.

Conditional statements are essential in any programming language and there isn’t any problem to use them, but sometimes when the code has many nested conditionals can lead to have a code very dirty and with many lines.

if ("value1".equals(value)) {
  doSomething();
} else if ("value2".equals(value)) {
  doSomethingButDifferent();
} else if ("value3".equals(value)) }
  doWhatYouWant();
} else {
  doNothing();
}

The code above is typical; performing an action depending on a value of a variable. In this moment, you can think to avoid this if/else by using of switch/case (Java 7 already supports with string) but it would be to pass the problem back and forth. Even using an enum with the possibles values is not enough, although the code will be cleaner.

Next, I will propose some techniques to refactor your conditional statements:

Avoid instanceof clause

Instanceof is a Java keyword that it should be used occasionally. Code like this shows the abuse of instanceof:

if (obj instanceof Technical) {
  doSomethingWithTechnical();
} else if (obj instanceof Chief) {
  doSomethingWithChief();
} else if (obj instanceof Secretary) }
  doSomethingWithSecretary();
}

// ...

void doSomethingWithTechnical() { ... }
void doSomethingWithChief() { ... }
void doSomethingWithSecretary() { ... }

As you can see, this code will grow when we need more methods for more kinds of employees (for each new employee this pattern needs an if/else statements and its concrete doSomething() implementation). This code can be fixed using OOP benefits, in this case the inheritance and polymorphism help us to avoid this if/else statements. We just have to have an abstract superclass with the method signature we want to call for each subtype.

abstract class Employee {
  abstract void doSomething();
}

class Technical extends Employee {
  void doSomething() {
    // put here the specific code for technical employee
  }
}

class Chief extends Employee {
  void doSomething() {
    // put here the specific code for chief employee
  }
}

class Secretary extends Employee {
  void doSomething() {
    // put here the specific code for secretary employee
  }
}

// Use of superclass type instead of if/else and instanceof
Employee obj;
// ...
obj.doSomething();

Moreover, there is a design pattern named Visitor that correctly applied can be useful when you have different ways for doing something for each type.

Take advantage of the enums

Sometimes the conditional statements are involve in a more complex scenarios than the instanceof example. An example could be the showed in a introduction of this post: performing a action depending of value of a string. As I said, the fact of replacing these statements for switch/case or enums as a container of constants doesn’t mitigate the effect we want to correct. The solution would be to use enums that joins the execution of an action with the implementation of the criteria that says whether action must be executed or not. This technique is really useful when the condition is not a simple “equals”.

    public enum AlgorithmExecutor {

        FIRST {
            boolean match(String condition) {
                // Put here the implementation for this algorithm will be triggered
            }

            Result execute() {
                // Put here the implementation of this first algorithm
            }
        },
        SECOND {
            boolean match(String condition) {
                // Put here the implementation for this algorithm will be triggered
            }

            Result execute() {
                // Put here the implementation of this second algorithm
            }
        },
        DEFAULT {
            boolean match(String condition) {
                return true;
            }
            Result execute() {
                // Put here the implementation of this default algorithm
            }
        };

        public static AlgorithmExecutor getAlgorithmFromCondition(String condition) {
            for(AlgorithmType c : values()) {
                if(c.match(condition)) {
                    return c;
                }
            }

            return DEFAULT;
        }

        abstract boolean match(String condition);

        abstract Result execute();
    }

In this code, you are forcing for each literal inside of enum that implements the methods match and execute. The first one returns a boolean that indicates if the string given in the parameter matches with certain condition. Note that in this method you can use non-trivial comparisons. In addition, the parameter could be encapsulated in an interface that allowed another kinds of objects, not only strings. The second method is for the algorithm implementation.

Finally, the code for calling the component would be:

Result res = AlgorithmExecutor.getAlgorithmFromCondition(condition).execute();

Use reflection

A particular case of the using of refactoring to clean if/else statements is when the conditional body returns an object, and each body returns a different implementation of an interface. It’s a common idiom used in a factory class. An example of this type could be:

public static DocumentType createDocument(String contentType)
  throws UnsupportedDocumentTypeException {

  if (contentType.equals("application/pdf")) {
    return new PDFDocument();
  } else if(contentType.equals("application/msword")) {
    return new WordDocument();
  } else if(contentType.equals("image/jpeg")) {
    return new JPEGDocument();
  }
  throw new UnsupportedDocumentTypeException();
}

In this case, you can use the enums one more time linking the value of each type with the class you have to return. Then with reflection it’s easy to create a new instance of this class and return it. An example is describe below:

    enum ContentType {

        PDF("application/pdf", PDFDocument.class),
        WORD("application/msword", WordDocument.class),
        JPEG("image/jpeg", JPEGDocument.class);

        private String value;

        private Class clazz;

        ContentType(String value, Class clazz) {
            this.value = value;
            this.clazz = clazz;
        }

        public static DocumentType getDocumentFromMimetype(String value)
                throws UnsupportedDocumentTypeException, InstantiationException, IllegalAccessException {
            for(ContentType c : values()) {
                if(c.value.equals(value)) {
                    return (DocumentType) c.clazz.newInstance();
                }
            }
            throw new UnsupportedDocumentTypeException();
        }
    }

This example looks pretty good (at least, for me :) ) because the association between the mimetype and its DocumentType implementation is focused on a enum without using conditional blocks. The client of this component just has to do the following calling:

DocumentType type = ContentType.getDocumentTypeFromMimetype(contentType);

Your own criteria over the design patterns

Although I think the explained patterns in this post help to clean your code, you should to be aware when the application of these techniques are fitting in your design. Maybe, when you have a conditional block with two or three if/else statements, the effort involved may be excessive in relation to the final design gain.


Tags: , , , ,