How to split up complex conditions and keep short circuit evaluation?

前端 未结 11 1817
清歌不尽
清歌不尽 2020-12-17 16:55

Sometimes conditions can become quite complex, so for readability I usually split them up and give each component a meaningful name. This defeats short-circuit evaluation ho

相关标签:
11条回答
  • 2020-12-17 17:40

    I find linebreaks and whitespace do a pretty good job, actually:

    public static void main1(String[] args) {
    
        if (args != null
            && args.length == 2
            && !args[0].equals(args[1])
            ) {
                System.out.println("Args are ok");
        }
    }
    

    Admittedly it works better with my (unpopular) bracing style (not shown above), but even with the above it works fine if you put the closing paren and opening brace on their own line (so they're not lost at the end of the last condition).

    I sometimes even comment the individual bits:

    public static void main1(String[] args) {
    
        if (args != null                // Must have args
            && args.length == 2         // Two of them, to be precise
            && !args[0].equals(args[1]) // And they can't be the same
            ) {
                System.out.println("Args are ok");
        }
    }
    

    If you really want to call things out, multiple ifs will do it:

    public static void main1(String[] args) {
    
        if (args != null) {
            if (args.length == 2) {
                if (!args[0].equals(args[1])) {
                    System.out.println("Args are ok");
                }
            }
        }
    }
    

    ...and any optimising compiler will collapse that. For me, it's probably a bit too verbose, though.

    0 讨论(0)
  • 2020-12-17 17:43

    This question is from 2009 but in the future (Java 8) we will be able to use Lambda expressions that may be for this context just like boolean expressions but you can use it so they are only evaluated when needed.

    public static void main2(String[] args) {
    
        Callable<Boolean> argsNotNull = () -> args != null;
        Callable<Boolean> argsLengthOk = () -> args.length == 2;
        Callable<Boolean> argsAreNotEqual = () -> !args[0].equals(args[1]);
    
        if (argsNotNull.call() && argsLengthOk.call() && argsAreNotEqual.call()) {
            System.out.println("Args are ok");
        }
    }
    

    You can do the same with java 5/6 but it is less efficient and much more ulgy to write with anonymous classes.

    0 讨论(0)
  • 2020-12-17 17:44

    Your first solution won't work in many cases, including the example you give above. If args is null, then

    boolean argsNotNull = args != null;
    // argsNotNull==false, okay
    boolean argsLengthOk = args.length == 2;
    // blam! null pointer exception
    

    One advantage of short-circuiting is that it saves on runtime. Another advantage is that it allows you to have early tests that check for conditions that would result in later tests throwing exceptions.

    Personally, when the tests are individually simple and all that's making it complex is that there are many of them, I'd vote for the simple "add some line breaks and comments" solution. This is easier to read than creating a bunch of additional functions.

    The only time I break things into subroutines is when an individual test is complex. If you need to go out and read a database or perform a big computation, then sure, roll that into a subroutine so the top level code is an easy-to-read

    if (salesType=='A' && isValidCustomerForSalesTypeA(customerid))
    etc
    

    Edit: How I'd break up the more complex example you gave.

    When I really get conditions that are this complex, I try to break them up into nested IFs to make them more readable. Like ... and excuse me if the following isn't really equivalent to your example, I didn't want to study the parentheses too closely just for an example like this (and of course the parentheses are what makes it difficult to read):

    if (position < min)
    {
      return (items.get(0).equals(target));
    }
    else if (position >= max)
    {
      return (items.get(items.size() - 1).equals(target));
    }
    else // position >=min && < max
    {
      if (position % 2 == 0)
      {
        return items.get(position).equals(target);
      }
      else // position % 2 == 1
      {
         return position > min && items.get(position - 1).equals(target);
      }
    }
    

    That seems as readable to me as it's likely to get. In this example, the "top level" condition is clearly the relationship of position to min and max, so breaking that out really helps to clarify things, in my opinion.

    Actually, the above is probably more efficient than cramming it all on one line, because the else's allow you to reduce the number of comparisons.

    Personally I struggle with when it's a good idea to put a complex condition into a single line. But even if you understand it, the odds are that the next person coming along won't. Even some fairly straightforward things, like

    return s==null ? -1 : s.length();

    I sometimes tell myself, yeah, I get it, but others won't, maybe better to write

      if (s==null)
        return -1;
      else
        return s.length();
    
    0 讨论(0)
  • 2020-12-17 17:50

    If your goal is readability, why not simply break the lines and add comments?

        if (args != null                // args not null
            && args.length == 2         // args length is OK
            && !args[0].equals(args[1]) // args are not equal
        ) {
    
            System.out.println("Args are ok");
        }
    
    0 讨论(0)
  • 2020-12-17 17:51

    Some good solutions already, but here's my variation. I usually do the null check as the topmost guard clause in all (relevant) methods. If I do it in this case, it leaves only the length and equality check in the subsequent if, which could already be considered a sufficient reduction in complexity and/or improvement in readability?

        public static void main1(String[] args) {
    
            if (args == null) return;
    
            if (args.length == 2 && !args[0].equals(args[1])) {
                System.out.println("Args are ok");
            }
        }
    
    0 讨论(0)
提交回复
热议问题