How to get rid of these ugly if statements

I’ve been “hit” again by “if driven development” code as I tend to call it. With large “if per method rating”.
It also has been full of copy and paste of the same if statements from method to method, sometimes adding/removing one or two or just slightly modifying it. I know it’s very easy to write and it’s so natural to transform requirements to something like this, but it’s also such a pain to understand, maintain or extend it. It’s also not reusable (especially if you need to modify it slightly on each use and don’t want method with quadrillions of parameters and many more ifs to handle parametrization).

So how could you make it little bit better? Best is to solve it with some polymorphic invocations, so Java (but it will work in any object oriented language) does all guessing what should be called when for us. Let’s check it on example similar to code I’ve had to cope with today. It was just validation of some bean:

Our example bean will look like:

    public class User {
        public enum GENDER {MAN, WOMAN};
        private String name;
        private int age;
        private GENDER gender;
        private Collection<String> hatesToWatch;
        private String location;
        
        ... Accessors ...
    }

Let’s imagine our user is a woman and is trying to access some adult only material that women usually try to access (don’t ask me, I’m a man ;-)). For our example we also demand so that content is in category she likes and not in category she hates and that it’s at least 15 degrees Celsius outside. So our method that is preparing this content will execute only if:

  1. User is a woman
  2. User is at least 18 years old
  3. User likes adult only for women content
  4. There is at least 15 degrees Celcius in user location

So the easiest and most natural would be to do:

    public String generateAdultForWomanContent(User user) {
        String category = "ADULT_FOR_WOMEN";
        if (user.getGender() != GENDER.WOMAN) {
            return "You're not a woman. No access";
        }

        if (user.getAge() < 18) {
            return "You're too young. No access";
        }

        if (user.getHatesToWatch().contains(category)) {
            return "You hate this stuff. No access";
        }

        String location = user.getLocation();
        double temp = 0;

        try {
            weather.AirTemperatureService service = new weather.AirTemperatureService();
            weather.AirTemperaturePortType port = service.getAirTemperature();
            Parameters params = new Parameters();
            params.setStationId(location);
            AirTemperatureMeasurements result = port.getAirTemperature(params);
            temp = result.getData().getItem().get(0).getAT();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        if (temp < 15) {
            return "It's too cold. No access";
        }

        return "CONTENT";
    }

But wait. Now we're expanding our services. Now our site is also serving some nice warming up images of tropics. But only if user likes it and there is cold outside. Or, if it's hot, some pics of cold broccoli juice but only for women or beef juice with ice for men or sweets'n'chocolate cocktail only for children…

You get the point? You would be messing with all these if statements. Copying them over and over and modifying slightly. So what could you do? Implement some validators for example:

Let's declare validator interface and few implementations:

    public interface Validator {

        public boolean validate(User user);

        public String getInvalidMessage();
    }

    public class AdultValidator implements Validator {

        private static final int ADULT_AGE = 18;

        public boolean validate(User user) {
            return user.getAge() >= ADULT_AGE;
        }

        public String getInvalidMessage() {
            return "You're not an adult. No access.";
        }
    }

    public class LikesValidator implements Validator {

        private final String category;

        public LikesValidator(String category) {
            this.category = category;
        }

        public boolean validate(User user) {
            return !user.getHatesToWatch().contains(category);
        }

        public String getInvalidMessage() {
            return "You don't like this stuff. No access.";
        }
    }

    public class GenderValidator implements Validator {

        private final GENDER gender;

        public GenderValidator(GENDER gender) {
            this.gender = gender;
        }

        public boolean validate(User user) {
            return gender == user.getGender();
        }

        public String getInvalidMessage() {
            return "You're not a " + gender.toString() + ". No access.";
        }

    }

    public class TemperatureValidator implements Validator {

        private final int temp;
        private final boolean abowe;

        public TemperatureValidator(int temp, boolean abowe) {
            this.temp = temp;
            this.abowe = abowe;
        }

        public boolean validate(User user) {
            double currentTemp = temp;
            String location = user.getLocation();
            try {
                weather.AirTemperatureService service = new weather.AirTemperatureService();
                weather.AirTemperaturePortType port = service.getAirTemperature();
                Parameters params = new Parameters();
                params.setStationId(location);
                AirTemperatureMeasurements result = port.getAirTemperature(params);
                currentTemp = result.getData().getItem().get(0).getAT();
            } catch (Exception ex) {
                ex.printStackTrace();
            }

            if (abowe) {
                return currentTemp > temp;
            } else {
                return currentTemp < temp;
            }
        }

        public String getInvalidMessage() {
            return "Temperature is " + (abowe ? "abowe" : " below") + temp + ". No access.";
        }
    }

… and some composite validator to use more than one at once (it also implements Validator interface so you can combine it with others and use composite in place of single validator):

    public class Validators implements Validator {

        private final Validator[] validators;
        private String invalidMessage = "";

        public Validators(Validator... validators) {
            this.validators = validators;
        }

        public boolean validate(User user) {
            for (Validator validator : validators) {
                boolean result = validator.validate(user);
                if (!result) {
                    invalidMessage = validator.getInvalidMessage();
                    return false;
                }
            }

            return true;
        }

        public String getInvalidMessage() {
            return invalidMessage;
        }
    }

Now let's use them instead of these if statements:

    public String generateAdultForWomanContentRight(User user) {
        String category = "ADULT_FOR_WOMEN";
        Validators validator = new Validators(
                new GenderValidator(GENDER.WOMAN),
                new AdultValidator(),
                new LikesValidator(category), 
                new TemperatureValidator(15, true));

        if(!validator.validate(user)) {
            return validator.getInvalidMessage();
        }

        return "CONTENT";
    }

    public String generateHotBeefJuice(User user) {
        String category = "JUICE_FOR_MEN";

        Validators validator = new Validators(
                new GenderValidator(GENDER.MAN),
                new AdultValidator(),
                new LikesValidator(category),
                new TemperatureValidator(10, false));

        if(!validator.validate(user)) {
            return validator.getInvalidMessage();
        }

        return "CONTENT";
    }

… and how do you like it now? Doesn't it look much better? I think this way you can easily make your and maintainers of you code life easier for the future. It's some overhead on the beginning to create the validators, but afterwards, especially if you need to validate this bean in many places, it will make your code simpler and cleaner.

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

%d bloggers like this: