0

Prompted by Sonar, I am looking to reduce the complexity of a function (one that some might call arrow code). I am loosely familiar with some of the principles of reducing arrow code complexity (flattening, eager returns, guard statements instead of affirmative condition checks), but I doubt that this particular code is satisfied by any of those.

The function takes three parameters and returns a string. If the first parameter does not meet the criteria for if/else-if outer block, the function returns a default string (the initial string assignment). For the sake of brevity, I think we can safely assume the function takes only 2 parameters.

Pseudocode

String getMyString(String parameter1, String parameter2) {
    String myString = "STRING_A.Y"
    if (parameter1 == optionA) {
        if (parameter2 == optionX) {
            myString = "STRING_A.X"
        } else {
            myString = "STRING_A.Y"
        }
    } else if (parameter1 == optionB) {
        if (parameter2 == optionX) {
            myString = "STRING_B.X"
        } else {
            myString = "STRING_B.Y"
        }
    }
    return myString
}  

In the case of three parameters, we are looking at 8 possible outputs. The only way I see to "reduce" complexity would be to turn this into a switch statement, but I personally find that the code would be less readable (particularly when more parameters are introduced). I don't see a meaningful way to separate this code into smaller functions. I don't see a way to add guard statements that might reduce the degree of nesting here. Is a switch statement the best alternative?

j.k
  • 111
  • 3
  • 3
    The problem with pseudocode is, that it leads to pseudo-solutions. If this was the real thing, I'd tell you to determine the two parts of the string separately and catenate them afterwards. – mtj Nov 11 '21 at 13:44
  • Unfortunately, the string is not composed in a way that would allow that. I'm also returning constants in the actual method – j.k Nov 11 '21 at 13:55
  • 4
    The answer will depend on the language you are using as each language will have different ways of solving this. – David Arno Nov 11 '21 at 13:59
  • Does this answer your question? [How to tackle a 'branched' arrow head anti-pattern?](https://softwareengineering.stackexchange.com/questions/205803/how-to-tackle-a-branched-arrow-head-anti-pattern) – gnat Nov 11 '21 at 15:42
  • I'm not sure now in hindsight if anything I said makes sense. I should probably refrain from using this site while completely drunk. :-D I just realized there are still five cases in your example. `A.X,A.Y, B.X,B.Y, O`. Ah well, analyzing cyclomatic complexity is hardly my forte. I tend to not be that zoomed in to care about these things quite as much (more concerned with integration than units at my zoomed-out level). But there is an obvious duplication of logic in the inner branches which sticks out to me and implies more things to test and could go wrong at a glance level... –  Nov 11 '21 at 18:16
  • ... I think I'm looking at something related to psychology that doesn't have to do with control flows and possible branches. I want to talk about "unique branches" instead. If we graph it then I think two control flows that lead to the same code, provided it is not duplicated, can fan in. –  Nov 11 '21 at 18:18
  • So if you have a suffix function for B, that introduces two unique branches (X and Y), and a combined prefix+suffix function has three unique branches (A, B, other). But the A and B branches in that case can rely on something already tested. So it's more like you have three unique branches of code to test, two of which depend on a function that is already tested. You don't have to test A.X and A.Y separately, only A, because X and Y suffix branches are already tested. So there are two "unique branches" you are reducing with such code, even though there are the same overall number of branches. –  Nov 11 '21 at 18:32
  • ... I think where I tripped with the numbers is that it is not reducing 7 to 5, but 5 to 3. The total number of branches is the same with the introduction of a suffix function to eliminate the code duplication, but the total number of 'unique branches" is reduced by two. Anyway, it doesn't take a thorough complexity analysis to see that reducing logical redundancy leads to fewer possible cases to test. –  Nov 11 '21 at 18:33

3 Answers3

6

According to your own comment, you do not build the result based on parameters but you rather mapped specific parameter combinations to some constant. So why not use some ParameterTuple=>Constant map?

Add some default in case there is no match. Actual implementation is language specific, but more parameter would only result in additional map entries, without further complexity.

Matthias
  • 84
  • 4
4

If you are attempting to optimize for readability and not optimizing for cleverness or performance or brevity, this is the way I would write it.

String getMyString(String parameter1, String parameter2) {
    if (parameter1 == optionA && parameter2 == optionX) return "STRING_A.X";
    if (parameter1 == optionA && parameter2 != optionX) return "STRING_A.Y";
    if (parameter1 == optionB && parameter2 == optionX) return "STRING_B.X";
    if (parameter1 == optionB && parameter2 != optionX) return "STRING_B.Y";
    return "STRING_A.Y";
}

This eliminates any confusion and many of the common logic bugs. The conditions are all mutually exclusive and free of sequential dependency; they can be put in any order, and new ones can be added in between, or conditions removed, with little risk of impacting the other conditions. Also, the default value is clearly expressed as a catch-all at the end.

John Wu
  • 26,032
  • 10
  • 63
  • 84
-2

This solves the problem in three lines, and looks more readable to me as well.

String part1 = (parameter1 == optionA ? "String_A" : "String_B";
String part2 = (parameter2 == optionX ? ".X" : ".Y";
return part1 + part2;
gnasher729
  • 42,090
  • 4
  • 59
  • 119