Aesthetic Beauty: Switch vs If

Introductory


As developers, we are faced with code every day, and the more that we like, we see, write, the more enthusiastic we get, the more productive and effective we become. What can I say, we are just proud of our code. But one dilemma haunts me: when 2 developers look at the same code, they can experience completely opposite feelings. And what if these feelings, emotions, inspired by its aesthetic beauty, do not coincide with the emotions of most of the professionals around you? In general, the story is about why the language of the switch construction may not be liked so much that you prefer if. Who is interested in this holivarny position welcome under cat.

Some nuances


We will discuss below the comparison of the switch statement and its specific Java implementations with the if statements in the form of opponents of this construction. Everyone knows that switch operator has an ace in its sleeve — performance (with rare exceptions), and this point will not be considered - since there is nothing to cover it with. But still, what is wrong?

1. Verbosity and bulkyness


Among the reasons that push me away from using the switch statement are the verbosity and cumbersomeness of the design itself, just take a look at switch, case, break and default, and omit the brackets that there will probably be return and throw between them. Do not think that I urge you not to use java service words or to avoid a large number of them in the code, no, I just think that simple things should not be verbose. Here is a small example of what I'm talking about:

    public List getTypes1(Channel channel) {
        switch (channel) {
            case INTERNET:
                return Arrays.asList("SMS", "MAC");
            case HOME_PHONE:
            case DEVICE_PHONE:
                return Arrays.asList("CHECK_LIST");
            default:
                throw new IllegalArgumentException("Неподдерживаемый канал связи " + channel);
        }
    }
 

option with if

    public List getTypes2(Channel channel) {
        if (INTERNET == channel) {
            return Arrays.asList("SMS", "MAC");
        }
        if (HOME_PHONE == channel || DEVICE_PHONE == channel) {
            return Arrays.asList("CHECK_LIST");
        }
        throw new IllegalArgumentException("Неподдерживаемый канал связи " + channel);
    }

Total for me: switch / case / default VS if.

If you ask who likes the code more, the majority will prefer the first option, while for me, it is verbose. We are not talking about code refactoring here, we all know that one could use a constant like EnumMap or IdentityHashMap to search for a list by the channel key or even remove the necessary data in the Channel itself, although this is a debatable solution. But back.

2. Indentation


It is possible in academic examples to use the swicth operator - this is the only part of the code, but the code that you have to deal with is more complex, overgrown with checks, hacks, and somewhere just subject matter complexity. And personally, every indentation in such a place is annoying. We turn to the “living” example (removed the excess, but the essence remains).

     public static List getReference1(Request request, DataSource ds) {
         switch (request.getType()) {
             case "enum_ref":
                 return EnumRefLoader.getReference(ds);
             case "client_ref":
             case "any_client_ref":
                 switch (request.getName()) {
                     case "client_types":
                         return ClientRefLoader.getClientTypes(ds);
                     case "mailboxes":
                         return MailboxesRefLoader.getMailboxes(ds);
                     default:
                         return ClientRefLoader.getClientReference(request.getName(), ds);
                 }
             case "simple_ref":
                 return ReferenceLoader.getReference(request, ds);
         }
         throw new IllegalStateException("Неподдерживаемый тип справочника: " + request.getType());
     }

option with if

    public static List getReference2(Request request, DataSource ds) {
        if ("enum_ref".equals(request.getType())) {
            return EnumRefLoader.getReference(ds);
        }
        if ("simple_ref".equals(request.getType())) {
            return ReferenceLoader.getReference(request, ds);
        }
        boolean selectByName = "client_ref".equals(request.getType()) || "any_client_ref".equals(request.getType());
        if (!selectByName) {
            throw new IllegalStateException("Неподдерживаемый тип справочника: " + request.getType());
        }
        if ("client_types".equals(request.getName())) {
            return ClientRefLoader.getClientTypes(ds);
        }
        if ("mailboxes".equals(request.getName())) {
            return MailboxesRefLoader.getMailboxes(ds);
        }
        return ClientRefLoader.getClientReference(request.getName(), ds);
    }

Total for me: 5 indentation VS 2.

But again, who likes what option? Most will prefer getReference1.
Separately, it is worth noting that the number of indentation still depends on the chosen style of code formatting.

3. Check for null


If the switch statement is used with strings or enums, the selection parameter must be checked for null. Back to the getTypes examples.

    // switch: проверка на null нужна
    public List getTypes1(Channel channel) {
        // Проверка на null
        if (channel == null) {
            throw new IllegalArgumentException("Канал связи должен быть задан");
        }
        switch (channel) {
            case INTERNET:
                return Arrays.asList("SMS", "MAC");
            case HOME_PHONE:
            case DEVICE_PHONE:
                return Arrays.asList("CHECK_LIST");
            default:
                throw new IllegalArgumentException("Неподдерживаемый канал связи " + channel);
        }
    }
    // if: можно обойтись без проверки на null
    public List getTypes2(Channel channel) {
        if (INTERNET == channel) {
            return Arrays.asList("SMS", "MAC");
        }
        if (HOME_PHONE == channel || DEVICE_PHONE == channel) {
            return Arrays.asList("CHECK_LIST");
        }
        throw new IllegalArgumentException("Неподдерживаемый канал связи " + channel);
    }

Total for me: extra code.

Even if you are absolutely sure now that null will not come, this does not mean that it will always be so. I analyzed the corporate bugtrack and found confirmation of this statement. In fairness, it is worth noting that the code structure expressed through if is not without this problem, often the constants for comparison are used on the right and not on the left, for example, name.equals ("John"), instead of "John" .equals (name). But in the framework of this article, on this point, I wanted to say that, ceteris paribus, the switch approach is inflated by checking for null, while if the check is not needed. I also add that static code analyzers easily cope with possible null bugs.

4. Motleyness


Very often, with long-term maintenance of the code, the code base is inflated and you can easily find code similar to the following:

public static void doSomeWork(Channel channel, String cond) {
    Logger log = getLogger();
    //...
    switch (channel) {
        //...
        case INTERNET:
            // Со временем появляется такая проверка
            if ("fix-price".equals(cond)) {
                // ...
                log.info("Your tariff");
                return;
            }
            // Еще под выбор?
            // ...
            break;
        //...
    }
    //...
}

Total for me: different style.

There used to be a clean switch, but now switch + if. A mixture of styles occurs, as I call it, part of the 'select' code is expressed through switch, part through if. Of course, no one forbids using if and switch together, if this does not apply to the select / under select operation, as in the above example.

5. Whose break?


When using the switch statement, a cycle may appear in case blocks or one revolution, a switch may appear in a cycle, with its interruptions in the processing. The question is, whose break, gentlemen?

public static List whoseBreak(List states) {
    List results = new ArrayList<>();
    for (String state : states) {
        Result result = process(state);
        switch (result.getCode()) {
            case "OK":
                if (result.hasId()) {
                    results.add(result.getId());
                    // Надуманный случай, но break чей-то?
                    break;
                }
                if (result.getInnerMessage() != null) {
                    results.add(result.getInnerMessage());
                    // Вот это поворот
                    continue;
                }
                // ...
                break;
            case "NOTHING":
                results.add("SKIP");
                break;
            case "ERROR":
                results.add(result.getErroMessage());
                break;
            default:
                throw new IllegalArgumentException("Неверный код: " + result.getCode());
        }
    }
    return results;
}

Total for me: code readability goes down.

To be honest, there were much more complicated code examples.

6. Inappropriateness


In java 7, you can now use the switch statement with strings. When our company switched to java 7 - it was a real Switch-Boom. Maybe for this, or maybe for another reason, but in many projects there are similar blanks:

public String resolveType(String type) {
    switch (type) {
        case "Java 7 же?":
            return "Ага";
        default:
            throw new IllegalArgumentException("Люблю switch, жаль, что нет такого варианта с case " + type);
    }
}

Total for me: inappropriate designs appear.

7. Glamorous switch to the accompaniment of hardcode


A bit of humor doesn't hurt.

public class Hit {
    public static enum Variant {
        ZERO, ONE, TWO, THREE
    }
    public static void switchBanter(Variant variant) {
        int shift = 0;
        ZERO: ONE: while (variant != null) {
            shift++;
            switch (variant) {
                default: {
                    THREE: {
                        System.out.println("default");
                        break THREE;
                    }
                    break;
                }
                case ONE: {
                     TWO: {
                     THREE: for (int index = shift; index <= 4; index++) {
                            System.out.println("one");
                            switch (index) {
                                case 1: continue ONE;
                                case 2: break TWO;
                                case 3: continue THREE;
                                case 4: break ZERO;
                            }
                        }
                    }
                    continue ONE;
                }
                case TWO: {
                     TWO: {
                        System.out.println("two");
                        if (variant == THREE) {
                            continue;
                        }
                        break TWO;
                    }
                    break ZERO;
                }
            }
            variant = null;
        }
    }
    public static void main(String[] args) {
        switchBanter(ONE);
    }
}

No comments.

Conclusion


I do not urge you to refuse the switch statement, in some places it is really good-looking and the noodles from if / if-else / else are still a mess. But its excessive use, wherever it gets, can cause dissatisfaction with other developers. And I am one of them.

I would also like to note that from the point of view of understanding the code, I have no problems with switch / case - the meaning of the written is clear, but from the point of view of perceiving aesthetic beauty - there is.

And finally. Use what you like, omitting the imposed opinions, the main thing is that your code is simple, working, beautiful and reliable. Good luck.

Also popular now: