7

Long story short, I've inherited a Java piece of code made of methods like this one:

@Override
public Action decide() {

    if (equalz(in.a, "LOC")) {//10
        if(( //20
                equalz(tmp.b, "BA")
                && notEquals(in.c,"U")
                && equalz(in.d,"Y")
            )||(
                equalz(tmp.b, "HV")
                && notEquals(in.c,"U")
                && equalz(in.d,"Y")
                && varEqualsOneOf(in.e,"FLG","FLR","RRG"))
        ) {
            if(equalz(tmp.b, "BA")) {//30
                if(varEqualsOneOf(in.e,"RRG","NL")//40
                    && lessThan(in.f,in.g)) {
                    return Action.AC015;
                } else {
                    if(varEqualsOneOf(in.e,"FLG","FLR")) {//50
                        return Action.AC015;
                    } else {
                        return Action.AC000;
                    }
                }
            } else {
                if (equalz(in.e,"RRG")) {//60
                    return Action.AC014;
                } else {
                    return Action.AC010;
                }
            }
        } else {
            if(equalsOrMissing(in.c,"U")    
                && varEqualsOneOf(in.e,"FLG","FLR")) {//70
                return Action.AC011;
            } else {
                return Action.AC000;
            }
        }
    } else {
        if(varEqualsOneOf(in.a,"MNC","BAN","LCI","CTV","LEA","INS")) {//80
            if (//90
                equalz(in.h,"A")
                || equalz(in.i,"Y")
            ) {
                return Action.AC000;
            } else {
                if(notEquals(in.h,"U")) {//100
                    if(greaterThan(in.j,in.k)) {//110
                        return Action.AC000;
                    } else {
                        if(varEqualsOneOf(in.e,"FLG","FLR")) {//120
                            if(notEquals(in.l,"U")) {//130
                                return Action.AC004;
                            } else {
                                if(oneOfVarsEqual(in.m,in.n,"Y")) {//140
                                    return Action.AC012;
                                } else {
                                    return Action.AC002;
                                }
                            }
                        } else {
                            if(//150
                                (
                                    equalz(in.e,"RRG")
                                    && varEqualsOneOf(in.a,"MNC","BAN","LCI","CTV","LEA")   
                                )
                                ||
                                (
                                    varEqualsOneOf(in.e,"RRG","NL")
                                    && equalz(in.a,"INS")
                                )
                            ) {
                                if (notEquals(in.l,"U")) {//160
                                    if (notEquals(in.o,"U")) {//170
                                        return Action.AC005;
                                    } else {
                                        return Action.AC018;
                                    }
                                } else {
                                    bp(false);
                                    if (equalz(in.n,"Y")) {//180
                                        return Action.AC012;
                                    } else {
                                        return Action.AC002;
                                    }
                                }
                            } else {
                                if (equalz(in.p,in.q)) {//190
                                    if (notEquals(in.o,"U")) {//200
                                        return Action.AC006;
                                    } else {
                                        return Action.AC008;
                                    }
                                } else {
                                    return Action.AC000;
                                }
                            }
                        }
                    }
                } else {
                    bp(false);
                    if (notEquals(in.l,"U")//210
                        && varEqualsOneOf(in.e,"FLG","FLR")) {
                        return Action.AC004;
                    } else {
                        return Action.AC000;
                    }
                }
            }
        } else {
            return Action.AC000;
        }
    }
}

where a series of conditions are checked to return the correct action to be performed. I don't find this solution very elegant, and furthermore I need a way to quickly tell the path that led to a certain output (logging it to the DB).

I'm thinking about a way to refactor the code and represent the condition tree in a compact fashion, so that it could be a bit easier to read, maintain and log.

I've thought about a bidimensional matrix having a row for every condition made up of 4 elements

  • the condition id
  • condition id (or return value) if current condition is true
  • condition id (or return value) if current condition is false
  • the condition itself

so, at the end i'd have this matrix

Object[][]matrix=
    {
            {10,20,70,  
                equalz(in.a, "LOC")},

            {20,30,80,  
                (equalz(tmp.b, "BA")
                && notEquals(in.c,"U")
                && equalz(in.d,"Y")
                )||(
                equalz(tmp.b, "HV")
                && notEquals(in.c,"U")
                && equalz(in.d,"Y")
                && varEqualsOneOf(in.e,"FLG","FLR","RRG"))},

            {30,40,70,  
                equalz(tmp.b, "BA")},

            {40,Action.AC015,50,    
                varEqualsOneOf(in.e,"RRG","NL")
                && lessThan(in.f,in.g)},

            {50,Action.AC015,Action.AC000,  
                varEqualsOneOf(in.e,"FLG","FLR")},

            {60,Action.AC014,Action.AC010,  
                equalz(in.e,"RRG")},

            {70,Action.AC011,Action.AC000,  
                equalsOrMissing(in.c,"U")   
                && varEqualsOneOf(in.e,"FLG","FLR")},

            {80,90,Action.AC000,    
                varEqualsOneOf(in.a,"MNC","BAN","LCI","CTV","LEA","INS")},

            {90,Action.AC000,100,   
                equalz(in.h,"A")|| equalz(in.i,"Y")},

            {100,110,210,   
                notEquals(in.h,"U")},

            {110,Action.AC000,120,  
                greaterThan(in.j,in.k)},

            {120,130,150,   
                varEqualsOneOf(in.e,"FLG","FLR")},

            {130,Action.AC004,140,  
                notEquals(in.l,"U")},

            {140,Action.AC012,Action.AC002, 
                oneOfVarsEqual(in.m,in.n,"Y")},

            {150,160,190,   
                    (equalz(in.e,"RRG")
                    && varEqualsOneOf(in.a,"MNC","BAN","LCI","CTV","LEA")   
                    )||(
                    varEqualsOneOf(in.e,"RRG","NL")
                    && equalz(in.a,"INS"))},

            {160,170,180,   
                    notEquals(in.l,"U")},

            {170,Action.AC005,Action.AC018, 
                    notEquals(in.o,"U")},

            {180,Action.AC012,Action.AC002, 
                    equalz(in.n,"Y")},

            {190,200,Action.AC000,  
                    equalz(in.p,in.q)},

            {200,Action.AC006,Action.AC008, 
                    notEquals(in.o,"U")},

            {210,Action.AC004,Action.AC000, 
                    notEquals(in.l,"U")
                    && varEqualsOneOf(in.e,"FLG","FLR")}
    }
;

driven by a method that jumps between conditions an logs the whole execution representing the path with the conditions' ID.

At the beginning I thought this could have been a good idea, but I'm not that sure now that it's written down.

How would you handle this problem? Is my solution totally worthless? Is there any other better way to achieve the goal?

  • 4
    This question probably belongs on Code Review. – David Arno Aug 14 '17 at 13:23
  • 2
    Uhm, if that's the case I'll move the question there. I thought this could be the right place given that I'm not really interested in the code, but in higher level ideas to handle this somehow "generic" problem. – Luigi Cortese Aug 14 '17 at 13:27
  • As painful as it will be, don't forget to write tests before refactoring. 2. What does bp(false) do? There are a couple of those in the code. 3. Whatever solution you choose, please use more meaningful names than these
  • – Vincent Savard Aug 14 '17 at 13:38
  • 1
    @VincentSavard 1) I have a very big set of input-ouput previously calculated to use as test after the refactoring 2) bp(boolean x) was an attempt to build a path to be logged, so that after every evaluation, a bp(true) or bp(false) would be invoked. I forgot to remove some of them 3) original names are more meaningful than that =) – Luigi Cortese Aug 14 '17 at 13:46
  • 1
    Also beware the insidious if {} if{} combination. They are completely different blocks without any else condition joining them. I've run into too much code that had two if blocks formatted as if they were an else statement. – Berin Loritsch Aug 14 '17 at 14:10
  • 1
  • Btw: I see not one call to an object's method. – Martin Schröder Aug 15 '17 at 05:39
  • Is any of this data in a database? Can it be to maybe sort it out? There is no way anyone could get out of rewriting this whole thing. – johnny Aug 15 '17 at 19:19
  • To clarify my response, which of the "original names are more meaningful"? I see 3 huge areas for improvement: the comments (What does "20", "30" mean?), the String constants, and the Action.ACnnns. Are all of them "meaningful" in the original code? – user949300 Aug 15 '17 at 22:31