Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to code these conditional statements in more elegant & scalable manner

In my software, I need to decide the version of a feature based on 2 parameters. Eg.

Render version 1 -> if (param1 && param2) == true;
Render version 2 -> if (!param1 && !param2) == true;
Render version 3 -> if only param1 == true;
Render version 4 -> if only param2 == true;

So, to meet this requirement, I wrote a code which looks like this -

if(param1 && param2) //both are true {
    version = 1;
}
else if(!param1 && !param2) //both are false {
    version = 2;
}
else if(!param2) //Means param1 is true {
    version = 3;
}
else { //Means param2 is true
    version = 4;
}

There are definitely multiple ways to code this but I finalised this approach after trying out different approaches because this is the most readable code I could come up with.
But this piece of code is definitely not scalable because -

  1. Let say tomorrow we want to introduce new param called param3. Then the no. of checks will increase because of multiple possible combinations.
  2. For this software, I am pretty much sure that we will have to accommodate new parameters in future.

Can there be any scalable & readable way to code these requirements?

like image 461
Ryan Gusto Avatar asked Feb 16 '19 13:02

Ryan Gusto


3 Answers

EDIT: For a scalable solution define the versions for each parameter combination through a Map:

Map<List<Boolean>, Integer> paramsToVersion = Map.of(
        List.of(true, true), 1,
        List.of(false, false), 2,
        List.of(true, false), 3,
        List.of(false, true), 4);

Now finding the right version is a simple map lookup:

    version = paramsToVersion.get(List.of(param1, param2));

The way I initialized the map works since Java 9. In older Java versions it’s a little more wordy, but probably still worth doing. Even in Java 9 you need to use Map.ofEntries if you have 4 or more parameters (for 16 combinations), which is a little more wordy too.

Original answer: My taste would be for nested if/else statements and only testing each parameter once:

    if (param1) {
        if (param2) {
            version = 1;
        } else {
            version = 3;
        }
    } else {
        if (param2) {
            version = 4;
        } else {
            version = 2;
        }
    }

But it scales poorly to many parameters.

like image 140
Ole V.V. Avatar answered Nov 16 '22 18:11

Ole V.V.


If you have to enumerate all the possible combinations of Booleans, it's often simplest to convert them into a number:

//                            param1:   F  T  F  T
//                            param2;   F  F  T  T
static final int[] VERSIONS = new int[]{2, 3, 4, 1};

...
version = VERSIONS[(param1 ? 1:0) + (param2 ? 2:0)];
like image 5
Matt Timmermans Avatar answered Nov 16 '22 19:11

Matt Timmermans


I doubt that there is a way that would be more compact, readable and scalable at the same time.

You express the conditions as minimized expressions, which are compact and may have meaning (in particular, the irrelevant variables don't clutter them). But there is no systematism that you could exploit.

A quite systematic alternative could be truth tables, i.e. the explicit expansion of all combinations and the associated truth value (or version number), which can be very efficient in terms of running-time. But these have a size exponential in the number of variables and are not especially readable.

I am afraid there is no free lunch. Your current solution is excellent.


If you are after efficiency (i.e. avoiding the need to evaluate all expressions sequentially), then you can think of the truth table approach, but in the following way:

  • declare an array of version numbers, with 2^n entries;

  • use the code just like you wrote to initialize all table entries; to achieve that, enumerate all integers in [0, 2^n) and use their binary representation;

  • now for a query, form an integer index from the n input booleans and lookup the array.

Using the answer by Olevv, the table would be [2, 4, 3, 1]. A lookup would be like (false, true) => T[01b] = 4.

What matters is that the original set of expressions is still there in the code, for human reading. You can use it in an initialization function that will fill the array at run-time, and you can also use it to hard-code the table (and leave the code in comments; even better, leave the code that generates the hard-coded table).

like image 4
Yves Daoust Avatar answered Nov 16 '22 17:11

Yves Daoust