Concepts

Clean Code: Hide your Switch Statements!

I’m in the middle of reading “Clean Code” by Robert (Uncle Bob) Martin. This book is like a rite of passage for developers, it’s a light and enjoyable read but presents some excellent ideas for how to make code more readable – and for someone with very little experience it really demonstrates how one should write long-lasting code. I thought I’d summarise some of the concepts I come across that strike me as particularly interesting.

Disclaimer: The ideas are not mine, I’m simply drilling further/making a note of things I find interesting/particularly helpful.

Writing switch/case statements, or if/else statements is unavoidable in code. You need your system to behave differently based on the value of a certain field, and that’s fine. However, this should be done in one place, and reused multiple times rather than being put in several different places. This means that if something changes, we only have to modify the code in one place – which is much more preferable than digging around to find all instances of the logic so we know what to change.

I’ll present an example to show what I mean. Obviously, it’s centred around the world of magical mayhem 🙂

In Hogwarts, your house is like your family. You live with them, go to classes with them, support them in the inter-house Quidditch tournament, and win and lose points with them. Therefore, there’s a lot of behaviour of a student that is specific to the house that they’re in – which immediately makes one think that there’s a danger of duplication, as we will need to keep evaluating a student’s house and guide the behaviour on that basis.

We first want to create a timetable for a particular student. Since we assign students to a particular class based on their house, this will need to take that information into account:

enum House {
    GRYFFINDOR,
    HUFFLEPUFF,
    RAVENCLAW,
    SLYTHERIN
}

class TimeTableGenerator throws InvalidHouseException {
    TimeTable generateFor(Student student) {
        switch(student.house()) {
          case GRYFFINDOR:
	        return timeTableGeneratedForGryffindor();
          case HUFFLEPUFF:
	        return timeTableGeneratedForHufflepuff();
          case RAVENCLAW:
	        return timeTableGeneratedForRavenclaw();
          case SLYTHERIN:
	        return timeTableGeneratedForSlytherin();
          default:
            throw new InvalidHouseException();
	}
    }

    //implementations of timetable generation for each house
}

However, there are other things associated with a student’s house, in fact we have seen one in a previous blog post with the PointsAdder:

public class Points {
    private int housePoints;
    private String house;

    public void addPoints(int points, House house) throws InvalidHouseException {
        if (GRYFFINDOR.equals(house)) {
             addPointsTheGryffindorWay(points);
        } else if (SLYTHERIN.equals(house)) {
             addPointsTheSlytherinWay(points);
        } else if (RAVENCLAW.equals(house)) {
            addPointsTheRavenclawWay(points);
        } else if (HUFFLEPUFF.equals(house)) {
            addPointsTheHufflepuffWay(points);
        } else {
            throw new InvalidHouseException();
        }
    }

    //implementations of points adding for each house
}

This if/else block looks extremely familiar…

In the (albeit very unlikely) event that Hogwarts decides to add another house in memory of the late and great Albus Dumbledore, we have our work cut out for us. We have to find new dorms, a new portrait to guard the dorm, a new ghost to represent the house AND we’ll need to extend the Great Hall to fit another table… The last thing we want to have to do on top of all of this is worry about modifying our code in multiple places! The above shows we’ve already got two places to change, and who knows how many others are lurking out there :(.

Consider the following alternative, where we have a StudentRecord containing basic information like student name and which house they belong to:

abstract class Student {

    enum House {
        GRYFFINDOR,
        HUFFLEPUFF,
        RAVENCLAW,
        SLYTHERIN
    }
    private final StudentRecord studentRecord;

    public Student (StudentRecord studentRecord) {
        this.studentRecord = studentRecord;
    }

    public abstract TimeTable generateTimeTable();
    public abstract void addPoints(int points);
    //other methods specific to students
}

public interface StudentFactory {
   public Student createStudent(StudentRecord studentRecord) throws InvalidHouseException;
}

public class StudentGenerator implements StudentFactory {
   public Student createStudent(StudentRecord studentRecord) throws InvalidHouseException {
        if (GRYFFINDOR.equals(studentRecord.house){
                return new GryffindorStudent(studentRecord);
        }
        if (HUFFLEPUFF.equals(studentRecord.house){
                return new HufflepuffStudent(studentRecord);
        }            
        if (RAVENCLAW.equals(studentRecord.house){
                return new RavenclawStudent(studentRecord);
        }            
        if (SLYTHERIN.equals(studentRecord.house){
                return new SlytherinStudent(studentRecord);
        }
        throw new InvalidHouseException();
    }
}

You’ll notice we’ve also removed the switch statement and if/else block, and moved to a series of “if” statements instead – this way we know for certain that the code will only fall into this block if the condition is fulfilled, there’s no change of it accidentally executing something we didn’t intend. Switch statements are generally considered a “code smell” (i.e. could suggest something is wrong).

Each type of Student (GryffindorStudent etc) will extend the abstract Student class and implement the methods in their own way. When we want to add the new Dumbledore house, all we need to do is:
– add a new if statement in StudentGenerator which returns a DumbledoreStudent
– create a new DumbledoreStudent class as an extension of the base Student class, and implement the methods accordingly.

Simples!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s