views:

72

answers:

3

I have some code that consists of a lot (several hundreds of LOC) of uggly conditionals i.e.

SomeClass someClass = null;

if("foo".equals(fooBar)) {
    // do something possibly involving more if-else statments
    // and possibly modify the someClass variable among others...
} else if("bar".equals(fooBar)) {
    // Same as above but with some slight variations
} else if("baz".equals(fooBar)) {
    // and yet again as above
} 
    //... lots of more else ifs
} else  {
    // and if nothing matches it is probably an error... 
    // so there is some error handling here
}

// Some code that acts on someClass
GenerateOutput(someClass);

Now I had the idea of refactoring this kind of code something along the lines of:

abstract class CheckPerform<S,T,Q> {
    private CheckPerform<T> next;
    CheckPerform(CheckPerform<T> next) {
        this.next = next;
    }

    protected abstract T perform(S arg);
    protected abstract boolean check(Q toCheck);

    public T checkPerform(S arg, Q toCheck) {
        if(check(toCheck)) {
            return perform(arg);
        } 

        // Check if this CheckPerform is the last in the chain...
        return next == null ? null : next.checkPerform();
    }
}

And for each if statment generate a subclass of CheckPerform e.g.

class CheckPerformFoo extends CheckPerform<SomeInput, SomeClass, String> {
    CheckPerformFoo(CheckPerform<SomeInput, SomeClass, String> next) {
        super(next);
    }

    protected boolean check(String toCheck) {
        // same check as in the if-statment with "foo" above"
        returs "foo".equals(toCheck);
    }

    protected SomeClass perform(SomeInput arg) {
        // Perform same actions (as in the "foo" if-statment) 
        // and return a SomeClass instance (that is in the 
        // same state as in the "foo" if-statment) 
    }
}

I could then inject the diffrent CheckPerforms into eachother so that the same order of checks are made and the corresponding actions taken. And in the original class I would only need to inject one CheckPerform object. Is this a valid approach to this type of problem? The number of classes in my project is likely to explode, but atleast I will get more modular and testable code. Should I do this some other way?

Since these if-else-if-...-else-if-else statments are what I would call a recurring theme of the code base I would like to do this refactoring as automagically as possible. So what tools could I use to automate this?

a) Some customizable refactoring feature hidden somewhere in an IDE that I have missed (either in Eclipse or IDEA preferably) b) Some external tool that can parse Java code and give me fine grained control of transformations c) Should I hack it myself using Scala? d) Should I manually go over each class and do the refactoring using the features I am familiar with in my IDE?

Ideally the output of the refactoring should also include some basic test code template that I can run (preferably also test cases for the original code that can be run on both new and old as a kind of regression test... but that I leave for later).

Thanks for any input and suggestions!

+1  A: 

To me the easiest approach would involve a Map<String, Action>, i.e. mapping various strings to specific actions to perform. This way the lookup would be simpler and more performant than the manual comparison in your CheckPerform* classes, getting rid of much duplicated code.

The actions can be implemented similar to your design, as subclasses of a common interface, but it may be easier and more compact to use an enum with overridden method(s). You may see an example of this in an earlier answer of mine.

Unfortunately I don't know of any automatic refactoring which could help you much in this. Earlier when I did somewhat similar refactorings, I wrote unit tests and did the refactoring step-by-step, manually, using automated support at the level of Move Method et al. Of course since the unit tests were pretty similar to each other in their structure, I could reuse part of the code there.

Update

@Sebastien pointed out in his comment, that I missed the possible sub-ifs within the bigger if blocks. One can indeed use a hierarchy of maps to resolve this. However, if the hierarchy starts to be really complex with a lot of duplicated functionality, a further improvement might be to implement a DSL, to move the whole mapping out of code into a config file or DB. In its simplest form it might look something like

foo -> com.foo.bar.SomeClass.someMethod
    biz -> com.foo.bar.SomeOtherClass.someOtherMethod
    baz -> com.foo.bar.YetAnotherClass.someMethod
bar -> com.foo.bar.SomeOtherClass.someMethod
    biz -> com.foo.bar.DifferentClass.aMethod
    baz -> com.foo.bar.AndAnotherClass.anotherMethod

where the indented lines configure the sub-conditions for each bigger case.

Péter Török
Peter your solution seems nice but since on the big "if blocks" we test de value of fooBar, i guess the "sub-if blocks" don't test it and there may be other variables tested here... so you prolly need many maps :)
Sebastien Lorber
@Sebastien, good point, see my update.
Péter Török
Going along the lines of using a data structure to map Strings to Actions, wouldn't a tree take care of the "sub-if blocks"?
Herminator
@Herm, very good point! Indeed, the above configuration basically models a tree. I don't know any Java tree implementation which could handle this out of the box, but there might be something available from Apache Commons. It can also be fairly easily implemented by hand.
Péter Török
Emil H
@Emil, **IF** that is the case, maps don't suffice indeed. However, there is no indication of this in the OP and I prefer the simplest solution that could possibly work :-)
Péter Török
+1  A: 

To answer the more general part of your question, I don't know of any tools for automatic refactoring beyond basic IDE support, but if you want to know what to look for to refactor have a look at the Refactoring catalog. The specific of your question are covered by replace conditional with Polymorphism and replace conditional with Visitor.

GaryF
+1  A: 

What you have described is the Chain of Responsibility Pattern and this sounds like it could be a good choice for your refactor. There could be some downsides to this.

  • Readability Because you are going to be injecting the the order of the CheckPerformers using spring or some such, this means that it is difficult to see what the code will actually do at first clance.
  • Maintainence If someone after you wants to add a new condition, as well as adding a whole new class they also have to edit some spring config. Choosing the correct place to add there new CheckPerformer could be difficult and error prone.
  • Many Classes Depending on how many conditions you have and how much repeated code within those conditions you could end up with a lot of new classes. Even though the long list of if else its very pretty, the logic it in one place, which again aids readability.
mR_fr0g