46

I'm trying to follow Uncle Bob's clean code suggestions and specifically to keep methods short.

I find myself unable to shorten this logic though:

if (checkCondition()) {addAlert(1);}
else if (checkCondition2()) {addAlert(2);}
else if (checkCondition3()) {addAlert(3);}
else if (checkCondition4()) {addAlert(4);}

I cannot remove the elses and thus separate the whole thing into smaller bits, cause the "else" in the "else if" helps performance - evaluating those conditions is expensive and if I can avoid evaluating the conditions below, cause one of the first ones is true, I want to avoid them.

Even semantically speaking, evaluating the next condition if the previous was met does not make sense from the business point of view.


edit: This question was identified as a possible duplicate of Elegant ways to handle if(if else) else.

I believe this is a different question (you can see that also by comparing answers of those questions).

Ev0oD
  • 559
  • 1
  • 4
  • 8
  • 2
    Possible duplicate of [Elegant ways to handle if(if else) else](https://softwareengineering.stackexchange.com/questions/122485/elegant-ways-to-handle-ifif-else-else) – gnat Jan 12 '18 at 08:36
  • 3
    @gnat I disagree. The proposed duplicate disucces a nested if, while this one discusses else if. – Belle Jan 12 '18 at 12:12
  • per my reading duplicate sufficiently addresses these matters. Another question, [Redundant ElseIf-Else Blocks](https://softwareengineering.stackexchange.com/questions/266949/redundant-elseif-else-blocks) might be an even closer match but not by a large margin – gnat Jan 12 '18 at 12:18
  • 1
    Have you determined that the language you're using will actually evaluate all four expressions if the first one is true (as you've described it, most won't), and has the program been profiled to see if that code is the source of your perceived performance problem? – Blrfl Jan 12 '18 at 12:18
  • 47
    What is actually *wrong* or *unclear* about this code in its context? I can't see how it can possibly be shortened or simplified! The code which evaluates the conditions already appears well-factored, as is the method that is called as a result of the decision. You only have to look at some of the answers below, that merely complicate the code! – Steve Jan 12 '18 at 12:41
  • 38
    There's nothing wrong with this code. It's very readable and easy to follow. Anything you do to shrink it further is going to add indirection and make it harder to understand. – 17 of 26 Jan 12 '18 at 13:01
  • 1
    Language dependent, wouldn't a "switch" method be the best way to perform this task? I know PHP and Javascript support this, and when I have a similar situation I always go down the switch route. – mickburkejnr Jan 12 '18 at 13:28
  • 1
    I think that code is OK – paparazzo Jan 12 '18 at 13:30
  • @mickburkejnr That's not how switch cases work. You would have to have a single expression and a case for each value of the expression, in this case you need a case for each expression. – Krupip Jan 12 '18 at 14:12
  • 21
    Your code is fine. Put your remaining energy into something more productive than trying to shorten it further. – Robert Harvey Jan 12 '18 at 16:10
  • 6
    If it's really just 4 conditions, this is fine. If it's really something like 12 or 50 then you probably want to refactor at higher level than this one method. – JimmyJames Jan 12 '18 at 16:16
  • One detail - "add" in "addAlert" kind of gives the impression that you have a set of alerts and you add another one. Unless that is the case you may want to use a different verb, e.g. showAlert. – JanErikGunnar Jan 12 '18 at 18:43
  • 9
    Leave your code exactly as it is. Listen to what your parents always told you: Don't trust any uncles offering sweets to children on the street. @Harvey Funny enough, the various attempts at "improving" the code all made it a lot larger, more complicated, and less readable. – gnasher729 Jan 12 '18 at 20:21
  • I also find the code fine, moreover it does things the right way round: test for errors and handle them one by one. I used to all to often see code that really hurt readability by separating tests from handling because they tested for OK which forced the error handling to after the happy scenario in reverse order: `if OK1 then if OK2 then if OK3 then … do-happy-stuff … else handle3 end-if else handle2 end-if else handle1 end-if` – PJTraill Jan 12 '18 at 21:16
  • 1
    get your code working first. then plan to go back and refactor it, with the understanding that you probably won't have time. eventually you'll realize good enough is good enough, and you can be far more productive by not worrying about small things like this. ;) – user428517 Jan 12 '18 at 22:53
  • 1
    Stop thinking in terms of following other people's coding principles. Start thinking in terms of solving problems. If someone's code principles are of any value at all, it's because the principles solve or prevent certain problems. Knowing the problems they are intended to solve helps you better understand when they apply and when they don't, and it makes finding ways of adhering to them more obvious. – jpmc26 Jan 12 '18 at 23:43
  • If the goal is to make the code shorter by sacrificing everything else, there's a special Q&A site in this network for this: https://codegolf.stackexchange.com/ – Display Name Jan 13 '18 at 21:40
  • @gnat - I believe this is not a duplicated question - check my edit. To everyone else - Thank you for so many interesting answers and opinions - when I get back to work I'll check which answer works the best with my code. – Ev0oD Jan 14 '18 at 15:54
  • @JanErikGunnar - adding to a set, so "add" it is. Nevertheless, good eye. – Ev0oD Jan 14 '18 at 15:57
  • 1
    I'm curious why you think Uncle Bob would want you to shorten this block of code further. – David K Jan 15 '18 at 04:19
  • @gnat - This would be a duplicate if that other question had asked "How do I refactor `if (! checkCondition1()) { if (! checkCondition2()) { ... } else { addAlert(2); } } else { addAlert(1); } `. The answer to that question would be to invert the tests and rewrite the code as expressed in this question. But that's not what the supposed duplicate asks. This is a very different question. – David Hammen Jan 15 '18 at 09:10
  • @DavidK - He states to have method bodies of 4 to max 6 LOC. More conditions would cause a longer code, but the method contained a few more initialization steps before this specific code. Plus I simplified it here. It was spanning some 12-15 lines. – Ev0oD Jan 22 '18 at 11:15
  • A related question (since this is fundamentally a question of length of a function): https://softwareengineering.stackexchange.com/questions/133404/what-is-the-ideal-length-of-a-method-for-you. Note the Uncle Bob quote: "Functions should hardly ever be 20 lines long." The function in the question has relatively complex requirements: four conditions to be checked, each can set an alert, but only the first alert actually should be set. I would count it as 8 LOC if formatted according to typical coding standards. How often do you write functions like this, anyway? – David K Jan 22 '18 at 12:53
  • 1
    `I cannot remove the elses`. You can. State Machine design pattern. `evaluating those conditions is expensive`, and State Machine will help with that, too: you won't have to check even for the first expensive condition every time, you'll be in the state to do the thing, and will just do the thing. And then when conditions actually change, instead of changing the fields in the class(es), you tweak the state of your machine respectively, and so you only do the expensive part with conditions once, instead of doing it every time you call that outer function. – KulaGGin Feb 20 '23 at 12:36
  • "How do I edit a chain of if-else if statements to adhere to Uncle Bob's Clean Code principles?" The answer to this question you can find in Uncle Bob's Clean Code book. It's a rule: "G23: Prefer Polymorphism to If/Else or Switch/Case": "First, most people use switch statements because it’s the obvious brute force solution, not because it’s the right solution for the situation. So this heuristic is here to remind us to consider polymorphism before using a switch. Second, the cases where functions are more volatile than types are relatively rare. So every switch statement should be suspect" – KulaGGin Feb 20 '23 at 12:52

13 Answers13

82

Ideally I think you should extract your logic for getting the alert code/number into its own method. So your existing code is reduced all the way down to

{
    addAlert(GetConditionCode());
}

and you have GetConditionCode() encapsulate the logic for checking conditions. Maybe also better to use an Enum than a magic number.

private AlertCode GetConditionCode() {
    if (CheckCondition1()) return AlertCode.OnFire;
    if (CheckCondition2()) return AlertCode.PlagueOfBees;
    if (CheckCondition3()) return AlertCode.Godzilla;
    if (CheckCondition4()) return AlertCode.ZombieSharkNado;
    return AlertCode.None;
}
Steven Eccles
  • 745
  • 4
  • 8
  • 2
    If possible to encapsulate like you describe (I suspect it might not be, I think OP is leaving out variables for simplicity), it doesn't change the code, which in itself is fine, but adds code ergonomics and a bit of readability +1 – Krupip Jan 12 '18 at 14:15
  • 17
    With those alert codes, I thank code only one can be returned at a time – Josh Part Jan 12 '18 at 17:47
  • 12
    This seems also a perfect match for use of a switch statement - if that is available in the language of OP. – Frank Hopkins Jan 12 '18 at 19:24
  • 4
    It is probably only a good idea to extract getting the error code to a new method if that can be written so as to be useful in multiple situations without having to be given a bunch of information about the particular situation. Actually there is a trade-off and a break-even point when it is worth it. But often enough you will see that the sequence of validations is specific to the job in hand, and it is better to keep them together with that job. In such cases inventing a new type to tell another part of the code what needs to be done is undesirable ballast. – PJTraill Jan 12 '18 at 21:24
  • @JoshPart At least until it turns out that's a bug and all four are possible. – Pharap Jan 12 '18 at 22:41
  • 6
    One issue with this reimplementation is that it makes the function `addAlert` need to check for the bogus alert condition `AlertCode.None`. – David Hammen Jan 15 '18 at 08:55
  • 1
    @Darkwing Since there are different conditions, the switch would have to be something like `swith(true){case CheckCondition1():(...)break; case CheckCondition2():break;}` which, to me, looks like an unholy abomination. But to each their own. It works perfectly, so you are not incorrect, but I disagree about the _perfect match_ tag... – xDaizu Jan 15 '18 at 16:21
  • Have you tried to measure the cyclomatic complexity of this code? If not then perhaps you could think of this as premature optimization! – Hosam Aly Jan 16 '18 at 06:06
  • @xDaizu You are totally right - especially with the abomination part about your suggested way to use switch, I'd *virtually*^^ beat up a colleague who did something like that. A switch with your solution would come after the enum is determined if you need to do different things based on the error code. Otherwise it doesn't make sense. I was a bit too quick with my original remark. – Frank Hopkins Jan 20 '18 at 16:09
70

The important measurement is complexity of the code, not absolute size. Assuming that the different conditions are really just single function calls, just like the actions are not more complex than what you've shown, I'd say there's nothing wrong with the code. It is already as simple as it can be.

Any attempt to further "simplify" will indeed complicate things.

Of course, you can replace the else keyword with a return as others have suggested, but that's just a matter of style, not a change in complexity whatsoever.


Aside:

My general advice would be, never to get religious about any rule for clean code: Most of the coding advice you see on the internet is good if its applied in a fitting context, but radically applying that same advice everywhere may win you an entry in the IOCCC. The trick is always to strike a balance that allows human beings to easily reason about your code.

Use too big methods, and you are screwed. Use too small functions, and you are screwed. Avoid ternary expressions, and you are screwed. Use ternary expressions everywhere, and you are screwed. Realize that there are places that call for one-line functions, and places that call for 50-line functions (yes, they exist!). Realize that there are places that call for if() statements, and that there are places that call for the ?: operator. Use the full arsenal that's at your disposal, and try to always use the most fitting tool you can find. And remember, don't get religious even about this advice as well.

  • 2
    I'd argue that replacing `else if` with an inner `return` followed by a simple `if` (removing the `else`) might make the code *more difficult to read*. When the code says `else if`, I immediately know that the code in the next block will only ever execute if the previous one didn't. No muss, no fuss. If it's a plain `if` then it might execute or it might not, *irrespective* of whether the previous one executed. Now I'll have to expend some amount of mental effort to parse the previous block to note that it ends with a `return`. I'd rather spend that mental effort on parsing the business logic. – user Jan 14 '18 at 20:20
  • 1
    I know, it's a small thing, but at least to me, `else if` forms one semantic unit. (It's not necessarily a single unit to the compiler, but that's okay.) `...; return; } if (...` doesn't; let alone if it's spread out on multiple lines. That's something I'll actually have to look at to see what it's doing, instead of being able to take it in directly by just seeing the keyword pair `else if`. – user Jan 14 '18 at 20:21
  • @MichaelKjörling Full Ack.I would myself prefer the `else if` construct, especially since its chained form is such a well-known pattern. However, code of the form `if(...) return ...;` is also a well-known pattern, so I would not fully condemn that. I see this really as a minor issue, though: The control flow logic is the same in both cases, and a single closer look at a `if(...) { ...; return; }` ladder will tell me that it's indeed equivalent to an `else if` ladder. I see the structure of a single term, I infer its meaning, I realize that it's repeated everywhere, and I know what's up. – cmaster - reinstate monica Jan 14 '18 at 22:57
  • Coming from JavaScript/node.js, some would use the "belt and suspenders" code of using **both** `else if` **and** `return`. e.g. `else if(...) { return alert();}` – user949300 Jan 15 '18 at 06:43
  • 1
    "And remember, don't get religious even about this advice as well." +1 – Jesus is Lord Jan 15 '18 at 13:47
  • @MichaelKjörling For a short statement I can follow your argument, though I wouldn't make it myself. But the longer a method gets - the more if-else-if constructs it contains, the more a clear exit point where I know I can ignore the rest of the method if I get there outweighs the possible benefit of the if-else. This also helps keeping the depth of the if -else statements low, which makes it way more readable. – Frank Hopkins Jan 20 '18 at 16:15
  • @KulaGGin Please try to avoid general chat in answer comments if possible. Comments should be used to ask for clarification on the answer or direct feedback to improve it. All other conversations should be held in our chat. – maple_shaft Feb 20 '23 at 18:26
22

It's controversial whether this is 'better' than the plain if..else for any given case. But if you want to try something else this is a common way of doing it.

Put your conditions in objects and put those objects in a list

foreach(var condition in Conditions.OrderBy(i=>i.OrderToRunIn))
{
    if(condition.EvaluatesToTrue())
    {
        addAlert(condition.Alert);
        break;
    }
}

If multiple actions are required on condition you can do some crazy recursion

void RunConditionalAction(ConditionalActionSet conditions)
{
    foreach(var condition in conditions.OrderBy(i=>i.OrderToRunIn))
    {
        if(condition.EvaluatesToTrue())
        {
            RunConditionalAction(condition);
            break;
        }
    }
}

Obviously yes. This only works if you have a pattern to your logic. If you try to make a super generic recursive conditional action then the setup for the object will be as complicated as the original if statement. You will be inventing your own new language/framework.

But your example does have a pattern

A common use case for this pattern would be validation. Instead of :

bool IsValid()
{
    if(condition1 == false)
    {
        throw new ValidationException("condition1 is wrong!");
    }
    elseif(condition2 == false)
    {
    ....

}

Becomes

[MustHaveCondition1]
[MustHaveCondition2]
public myObject()
{
    [MustMatchRegExCondition("xyz")]
    public string myProperty {get;set;}
    public bool IsValid()
    {
        conditions = getConditionsFromReflection()
        //loop through conditions
    }
}
Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
Ewan
  • 70,664
  • 5
  • 76
  • 161
  • It should be mentioned that this only works because the condition and the action to be performed are strongly related. If an additional action were required for one particular case, consider leaving this code intact and adding an if below for the special case in order to run this added action. – Neil Jan 12 '18 at 09:31
  • edited to reflect your concern – Ewan Jan 12 '18 at 11:54
  • 27
    This only moves the `if...else` ladder into the construction of the `Conditions` list. The net gain is negative, as the construction of `Conditions` will take just as much code as the OP code, but the added indirection comes with a cost in readability. I'd definitely prefer a clean coded ladder. – cmaster - reinstate monica Jan 12 '18 at 12:06
  • 3
    @cmaster yes I think i did say exactly that "then the setup for the object will be as complicated as the original if statement£ – Ewan Jan 12 '18 at 12:23
  • 7
    This is less readable than the original. In order to figure out what condition is actually getting checked, you need to go digging in some other area of the code. It adds an unnecessary level of indirection that makes the code harder to understand. – 17 of 26 Jan 12 '18 at 13:02
  • 1
    @17of26 maybe, but the OP is asking about a specific way of writing their code. Lets not judge it – Ewan Jan 12 '18 at 13:10
  • @Ewan except that I think the whole point of his question is that he's looking for a way to make the code better. – 17 of 26 Jan 12 '18 at 13:51
  • 1
    maybe read the question again "I'm trying to follow Uncle Bob's clean code suggestions **and specifically to keep methods short.**" – Ewan Jan 12 '18 at 13:52
  • @Ewan Uncle Bob would never support making it short via making it less readable, and there are methods he provides for you to refactor, this is not one of them, you understood the question wrong. – Krupip Jan 12 '18 at 14:17
  • 8
    Converting an if .. else if .. else .. chain to a table of predicates and actions makes sense, but only for much larger examples. The table adds some complexity and indirection, so you need enough entries to amortize this conceptional overhead. So, for 4 predicate/action pairs, keep the simple original code, But if you had 100, definitely go with the table. The crossover point is somewhere in between. @cmaster, the table can be statically initialized, so the incremental overhead for adding a predicate/action pair is one line that merely names them: hard to do better. – Stephen C. Steel Jan 12 '18 at 14:31
  • 1
    @snb readability is a personal judgement call, what uncle bob thinks requires divine intervention. the OP wants shorter code – Ewan Jan 12 '18 at 14:42
  • @StephenC.Steel If you statically initialize the table with one line for each condition/action pair, how is that different from writing the `if...else` ladder? The ladder is well recognized code syntax, the table adds a level of indirection. If you are not able to somehow simplify the conditions/actions themselves, or to reduce their number by generating them programatically (exploiting redundancies between the different conditions/actions), I fail to see any gain in the table over the ladder. The table would just add complexity via indirection, *no matter how long the table/ladder is*. – cmaster - reinstate monica Jan 12 '18 at 15:04
  • Unless the list of conditions is dynamic, or can be encoded in a data-centric way, I would really rather favor hard-coded if-else. – Matthieu M. Jan 12 '18 at 15:35
  • 2
    Readability is NOT personal. It is a duty to the programming public. It is subjective. Which is exactly why it's important to come to places like this and listen to what the programming public has to say about it. Personally I find this example incomplete. Show me how `conditions` is constructed... ARG! Not annotation-attributes! Why god? Ow my eyes! – candied_orange Jan 12 '18 at 15:56
  • 2
    @CandiedOrange At some point someone at Microsoft decided that annotations were amazing and spent millions making us put them everywhere. So at least one person liked them at one point!! – Ewan Jan 12 '18 at 16:07
  • 1
    @Ewan I agree, what is or is not readable is somewhat subjective. However, each of your example is longer than the original code (and less readable IMO) and as such probably isn't best solution for OP – William Perron Jan 12 '18 at 21:03
  • @william yes, but it is O(1) rather than O(n) loc as it were – Ewan Jan 12 '18 at 21:06
  • @Ewan this might be just me, but sacrificing readability simply to "improve" the big O notation of an algorithm simply isn't worth it. I put the wourd "improve" in air quotes hear because, again IMO, O(n) that can run a maximum of 4 times is perfectly acceptable. – William Perron Jan 12 '18 at 21:11
  • 1
    @williamperron My view on questions is that we have to take the example as an example. Im assuming that the OP isnt happy with his current code and wants suggestions. Just saying 'no you are fine' dosent help anyone. Assume that the number of else if statements is more than you are comfortable with. what do you do? – Ewan Jan 13 '18 at 00:15
  • 2
    The OP isn't happy with his code because he's misguided and is under the incorrect impression that shorter is always better. The best answer to his question is 'stop it, that code is fine as-is and trying to make it shorter makes it less readable'. – 17 of 26 Jan 13 '18 at 15:08
  • If the only objective is to make it shorter this is the wrong site. Take it to [codegolf](https://codegolf.stackexchange.com/). We care more about readability here. – candied_orange Jan 13 '18 at 17:49
  • 1
    Also, I don't see how any of this is a big O improvement. – candied_orange Jan 13 '18 at 17:50
  • 1
    the loc is static in regard to the number of conditions. where as the if block grows in proportion. – Ewan Jan 13 '18 at 19:38
  • loc? you mean Lines Of Code? – candied_orange Jan 15 '18 at 06:06
7

Consider using return; after one condition has succeeded, it saves you all the elses. You might even be able to return addAlert(1) directly if that method has a return value.

Kilian Foth
  • 107,706
  • 45
  • 295
  • 310
  • 3
    Of course, this assumes that nothing else happens after the chain of `if`s... That might be a reasonable assumption, and then again it might not be. – user Jan 14 '18 at 20:23
5

I've seen constructions like this considered cleaner sometimes:

switch(true) {
    case cond1(): 
        statement1; break;
    case cond2():
        statement2; break;
    case cond3():
        statement3; break;
    // .. etc
}

Ternary with right spacing can also be a neat alternative:

cond1() ? statement1 :
cond2() ? statement2 :
cond3() ? statement3 : (null);

I guess you could also try to create an array with pair containing condition and function and iterate over it until first condition is met - which as I see would be equal to Ewan's first answer.

zworek
  • 75
  • 3
1

As a variant of @Ewan's answer you could create a chain (instead of a "flat list") of conditions like this:

abstract class Condition {
  private static final  Condition LAST = new Condition(){
     public void alertOrPropagate(DisplayInterface display){
        // do nothing;
     }
  }
  private Condition next = Last;

  public Condition setNext(Condition next){
    this.next = next;
    return this; // fluent API
  }

  public void alertOrPropagate(DisplayInterface display){
     if(isConditionMeet()){
         display.alert(getMessage());
     } else {
       next.alertOrPropagate(display);
     }
  }
  protected abstract boolean isConditionMeet();
  protected abstract String getMessage();  
}

This way you can apply your conditions in a defined order and the infrastructure (the abstract class shown) skips the remaining checks after the first has been meet.

This is where it is superior over the "flat list" approach where you have to implement the "skipping" in the loop that applies the conditions.

You simply set up the condition chain:

Condition c1 = new Condition1().setNext(
  new Condition2().setNext(
   new Condition3()
 )
);

And start evaluation with a simple call:

c1.alertOrPropagate(display);
Timothy Truckle
  • 2,336
  • 9
  • 12
  • 1
    Yes that's called the [Chain-of-responsibility Pattern](https://en.m.wikipedia.org/wiki/Chain-of-responsibility_pattern) – Max Jan 12 '18 at 16:55
  • 5
    I won't pretend to speak for anyone else, but while the code in the question is immediately readable and obvious in its behavior, I would *not* consider this to be immediately obvious as to what it does. – user Jan 14 '18 at 20:25
0

First of all, the original code isn't terrible IMO. It's pretty understandable and there's nothing inherently bad in it.

Then if you dislike it, building up on @Ewan's idea to use a list but removing his somewhat unnatural foreach break pattern:

public class conditions
{
    private List<Condition> cList;
    private int position;

    public Condition Head
    {
        get { return cList[position];}
    }

    public bool Next()
    {
        return (position++ < cList.Count);
    }
}


while not conditions.head.check() {
  conditions.next()
}
conditions.head.alert()

Now adapt this in your language of choice, make each element of the list an object, a tuple, whatever, and you're good.

EDIT: looks like it isn't as clear I thought, so let me explain further. conditions is an ordered list of some sort; head is the current element being investigated - at the beginning it is the first element of the list, and each time next() is called it becomes the following one; check() and alert() are the checkConditionX() and addAlert(X) from the OP.

Ewan
  • 70,664
  • 5
  • 76
  • 161
Nico
  • 125
  • 2
0

Supposing all functions are implemented in the same component, you could make the functions retain some state in order to get rid of the multiple branches in the flow.

EG: checkCondition1() would become evaluateCondition1(), on which it would check if previous condition were met; if so, then it caches some value to be retrieved by getConditionNumber().

checkCondition2() would become evaluateCondition2(), on which it would check if the previous conditions were met. If previous condition was not met, then it checks for condition scenario 2, caching a value to be retrieved by getConditionNumber(). And so on.

clearConditions();
evaluateCondition1();
evaluateCondition2();
evaluateCondition3();
evaluateCondition4();
if (anyCondition()) { addAlert(getConditionNumber()); }

EDIT:

Here's how the check for expensive conditions would need to be implemented in order to this approach work.

bool evaluateCondition34() {
    if (!anyCondition() && A && B && C) {
        conditionNumber = 5693;
        return true;
    }
    return false;
}

...

bool evaluateCondition76() {
    if (!anyCondition() && !B && C && D) {
        conditionNumber = 7658;
        return true;
    }
    return false;
}

Therefore, if you have too many expensive checks to be performed, and things in this code remains private, this approach helps maintaining it, enabling to change the order of the checks if necessary.

clearConditions();
evaluateCondition10();
evaluateCondition9();
evaluateCondition8();
evaluateCondition7();
...
evaluateCondition34();
...
evaluateCondition76();

if (anyCondition()) { addAlert(getConditionNumber()); }

This answer just provides some alternative suggestion from the other answers, and probably will not be better than the original code if we consider only 4 lines of code. Although, this is not a terrible approach (and neither makes maintenance more difficult like others have said) given the scenario I mentioned (too many checks, only main function exposed as public, all functions are implementation details of the same class).

Emerson Cardoso
  • 2,050
  • 7
  • 14
  • I don't like this suggestion - it hides the testing logic inside multiple functions. This can make the code hard to maintain if, for example, you needed to change the order and do #3 before #2. – Lawrence Jan 13 '18 at 17:14
  • No. You can check if some previous condition was evaluated if `anyCondition() != false`. – Emerson Cardoso Jan 13 '18 at 19:27
  • 1
    Ok, I see what you're getting at. However, if (say) conditions 2 and 3 both evaluate to `true`, the OP doesn't want condition 3 evaluated. – Lawrence Jan 14 '18 at 08:46
  • What I meant is that you can check `anyCondition() != false` within functions `evaluateConditionXX()`. This is possible to implement. If the approach of using internal state is not desired I understand, but the argument that this does not work is not valid. – Emerson Cardoso Jan 14 '18 at 22:44
  • 1
    Yes, my objection is that it unhelpfully hides the testing logic, not that it can't work. In your answer (paragraph 3), the check for meeting condition 1 is placed inside eval...2(). But if he switches conditions 1 and 2 at the top level (due to changes in customer requirements, etc), you'd have to go into eval...2() to remove the check for condition 1, plus go into eval...1() to add a check for condition 2. This can be made to work, but it can easily lead to problems with maintenance. – Lawrence Jan 15 '18 at 01:53
  • If I found this code in my current project, I would rewrite it so that it looked like the code in the original question, and then remind everyone in the development group to avoid obscure side effects like these. If your function names were honest, they would be `evaluationCondition1AndSaveResult`, `evaluationCondition2AndSaveResultIfCondition1IsFalse`, and so forth. – David K Jan 15 '18 at 04:16
  • @DavidK - I understand that. I see no problem at all with the original code; this is just some alternative considering other suggestions. I edited my answer to try to demonstrate that under given circumstances (too many checks to be performed, most functions are private) this is really not a terrible approach. If we consider just few checks or lines of code, the original code is better than most of the suggestions presented, not just this one. Anyways, thanks for the feedbacks! – Emerson Cardoso Jan 15 '18 at 10:43
  • My main objection to the code is that it is far less well self-documented than the original code. You have a block of code that appears to say (in plain English) that it evaluates a fixed list of conditions, but actually it may only evaluate some of them. One has to dig into all the subroutines (or read a lot of documentation) to learn the truth. You can partially address that objection by renaming all your functions to things like `evaluateCondition10AndSaveResultIfNoOtherConditionWasTrue()`, but the result is still more complicated than the original. – David K Jan 15 '18 at 12:20
  • Also, let's assume that _any_ version of this logic (in the question or in any of the answers) uses only class-private functions and variables, so we're not trying to make things better for _users_ of the class; we're trying to make things easier and less error-prone when a new member of the development team has to make a change to the class implementation, so that person has to understand what the class-private functions actually do. – David K Jan 15 '18 at 12:39
  • @DavidK - I get it about the naming of the functions. I could try to edit my answer and provide some better names, but this was meant only as an example; I recommend renaming those functions with something more appropriate (but not with `SaveResult` -- IMO this seems to save stuff in DB). Although, regarding the maintenance: this is very simple to understand, really. One does not have to understand _a lot_ of subroutines or read _a lot_ of documentation to be able to maintain a code in this approach. – Emerson Cardoso Jan 15 '18 at 13:28
0

The question lacks some specifics. If the conditions are:

  • subject to change or
  • repeated in other parts of the application or system or
  • modified in certain cases (such as different builds, testing, deployments)

or if the contents in addAlert is more complicated, then a possibly better solution in say c# would be:

//in some central spot
IEnumerable<Tuple<Func<bool>, int>> Conditions = new ... {
  Tuple.Create(CheckCondition1, 1),
  Tuple.Create(CheckCondition2, 2),
  ...
}

//at the original place
var matchingCondition = Conditions.Where(c=>c.Item1()).FirstOrDefault();
if(matchingCondition != null) 
  addAlert(matchingCondition.Item2)

Tuples are not so beautiful in c# < 8, but chosen for convienence.

The pros with this method, even if none of the options above apply, is that the structure is statically typed. You can't accidentally screw up by, say, missing an else.

NiklasJ
  • 675
  • 4
  • 6
0

The best way to reduce Cyclomatic complexity in cases where you have a lot of if->then statements is to use a dictionary or list (language dependent) to store the key value (if statement value or some value of) and then a value/function result.

For example, instead of (C#):

if (i > 10) { return "Two"; }
else if (i > 8) { return "Four" }
else if (i > 4) { return "Eight" }
return "Ten";  //etc etc say anything after 3 or 4 values

I can simply

var results = new Dictionary<int, string>
{
  { 10, "Two" },
  { 8, "Four"},
  { 4, "Eight"},
  { 0, "Ten"},
}

foreach(var key in results.Keys)
{
  if (i > results[key]) return results.Values[key];
}

If you're using more modern languages you can store more logic then simply values as well (c#). This is really just inline functions, but you can also just point to other functions as well if the logic is to narly to put in-line.

var results = new Dictionary<Func<int, bool>, Func<int, string>>
{
  { (i) => return i > 10; ,
    (i) => return i.ToString() },
  // etc
};

foreach(var key in results.Keys)
{ 
  if (key(i)) return results.Values[key](i);
}
Erik Philips
  • 290
  • 1
  • 10
0

I'm trying to follow Uncle Bob's clean code suggestions and specifically to keep methods short.

I find myself unable to shorten this logic though:

if (checkCondition()) {addAlert(1);}
else if (checkCondition2()) {addAlert(2);}
else if (checkCondition3()) {addAlert(3);}
else if (checkCondition4()) {addAlert(4);}

Your code is already too short, but the logic itself shouldn't be changed. At first glance it looks like you are repeating yourself with four calls to checkCondition(), and it's only apparent that each one is different after rereading the code carefully. You should add proper formatting and function names, e.g.:

if (is_an_apple()) {
  addAlert(1);
}
else if (is_a_banana()) {
  addAlert(2);
}
else if (is_a_cat()) {
  addAlert(3);
}
else if (is_a_dog()) {
  addAlert(4);
}

Your code should be readable above all else. Having read several of Uncle Bob's books, I believe that is the message he is consistently trying to get across.

CJ Dennis
  • 659
  • 5
  • 15
  • Yes, and his answer to this is "prefer polymorphism to if/else or switch statements"(G26). This kind of if/else statements is an abomination and a source of bugs as soon as you start putting these conditions in different places for different things. He made very solid cases against this type of code style in his talks that are on YT, in his books Clean Code and Clean Architecture, and many of his videos on CleanCoders.org. – KulaGGin Feb 20 '23 at 13:30
0

Any more than two "else" clauses forces the reader of the code to go thru the whole chain to find the one of interest. Use a method such as: void AlertUponCondition(Condition condition) { switch (condition) { case Condition.Con1: ... break; case Condition.Con2: ... break; etc... } Where "Condition" is a proper enum. If needed, return a bool or value. Call it like this: AlertOnCondition(GetCondition());

It really can't get any simpler, AND it's faster than the if-else chain once you exceed a few cases.

Jimmy T
  • 9
  • 1
0

I cannot speak for your particular situation because the code is not specific, but...

code like that is often a smell for a lacking OO model. You really have four type of things, each associated with its own alerter type, but rather than recognizing these entities and create a class instance for each, you treat them as one thing and try to make up for it later, at a time you really need to know what you are dealing with in order to proceed.

Polymorphism may have suited you better.

Be suspicious of code with long methods containing long or complex if-then constructs. You often want a class tree there with some virtual methods.

Martin Maat
  • 18,218
  • 3
  • 30
  • 57