如何分解复杂条件并保持短路评估? [英] How to split up complex conditions and keep short circuit evaluation?

查看:46
本文介绍了如何分解复杂条件并保持短路评估?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有时条件可能会变得非常复杂,因此出于可读性考虑,我通常将它们拆分并为每个组件起一个有意义的名称.然而,这使短路评估失败,这可能带来问题.我想出了一种包装方法,但是我认为这太冗长了.

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 however, which can pose a problem. I came up with a wrapper approach, but it is way too verbose in my opinion.

有人可以为此提出一个巧妙的解决方案吗?

Can anybody come up with a neat solution for this?

有关我的意思的示例,请参见下面的代码

See code below for examples of what I mean:

public class BooleanEvaluator {

    // problem: complex boolean expression, hard to read
    public static void main1(String[] args) {

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

    // solution: simplified by splitting up and using meaningful names
    // problem: no short circuit evaluation
    public static void main2(String[] args) {

        boolean argsNotNull = args != null;
        boolean argsLengthOk = args.length == 2;
        boolean argsAreNotEqual = !args[0].equals(args[1]);

        if (argsNotNull && argsLengthOk && argsAreNotEqual) {
            System.out.println("Args are ok");
        }
    }

    // solution: wrappers to delay the evaluation 
    // problem: verbose
    public static void main3(final String[] args) {

        abstract class BooleanExpression {
            abstract boolean eval();
        }

        BooleanExpression argsNotNull = new BooleanExpression() {
            boolean eval() {
                return args != null;
            }
        };

        BooleanExpression argsLengthIsOk = new BooleanExpression() {
            boolean eval() {
                return args.length == 2;
            }
        };

        BooleanExpression argsAreNotEqual = new BooleanExpression() {
            boolean eval() {
                return !args[0].equals(args[1]);
            }
        };

        if (argsNotNull.eval() && argsLengthIsOk.eval() && argsAreNotEqual.eval()) {
            System.out.println("Args are ok");
        }
    }
}

答复答案:

感谢您的所有创意!到目前为止,已提交了以下替代方法:

Thanks for all your ideas! The following alternatives were submitted so far:

  • 中断行并添加评论
  • 保持原状
  • 提取方法
  • 早归
  • 嵌套/如果是

中断行并添加评论:

只需在条件中添加换行符,就会被Eclipse(ctrl + shift + f)中的代码格式化程序撤消.内联注释可以帮助解决此问题,但是每行上留有很少的空间,并且可能会导致包装很丑.在简单的情况下,这可能就足够了.

Just adding linebreaks within a condition gets undone by the code formatter in Eclipse (ctrl+shift+f). Inline comments helps with this, but leaves little space on each line and can result in ugly wrapping. In simple cases this might be enough however.

保留原样:

我给出的示例条件非常简单,因此在这种情况下,您可能不需要解决可读性问题.我在考虑情况要复杂得多的情况,例如:

The example condition I gave is quite simplistic, so you might not need to address readability issues in this case. I was thinking of situations where the condition is much more complex, for example:

private boolean targetFound(String target, List<String> items,
        int position, int min, int max) {

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

我不建议保留原样.

提取方法:

我考虑了提取方法,正如在几个答案中所建议的那样.这样做的缺点是这些方法的粒度通常很低,它们本身可能没有什么意义,因此可能会使您的类混乱,例如:

I considered extracting methods, as was suggested in several answers. The disadvantage of that is that these methods typically have a very low granularity and may not be very meaningful by themselves, so it can clutter your class, for example:

private static boolean lengthOK(String[] args) {
    return args.length == 2;
}

在类级别,这实际上不应该是一个单独的方法.同样,您必须将所有相关参数传递给每个方法.如果仅创建一个单独的类来评估非常复杂的条件,那么IMO可能是一个好的解决方案.

This would not really deserve to be a separate method at class level. Also you have to pass all the relevant arguments to each method. If you create a separate class purely for evaluating a very complex condition then this might be an ok solution IMO.

我试图用BooleanExpression方法实现的是逻辑保持局部.请注意,甚至BooleanExpression的声明都是本地的(我认为以前我从未遇到过本地类声明的用例!).

What I tried to achieve with the BooleanExpression approach is that the logic remains local. Notice that even the declaration of BooleanExpression is local (I don't think I've ever come across a use-case for a local class declaration before!).

早日返回:

即使我不赞成这种惯用语,早期的回报解决方案似乎也足够了.另一种符号:

The early returns solution seems adequate, even though I don't favor the idiom. An alternative notation:

public static boolean areArgsOk(String[] args) {

    check_args: {
        if (args == null) {
            break check_args;
        }
        if (args.length != 2) {
            break check_args;
        }
        if (args[0].equals(args[1])) {
            break check_args;
        }
        return true;
    }
    return false;
}

我意识到大多数人都讨厌标签和标签,这种风格可能太罕见了,以至于不易阅读.

I realize most people hate labels and breaks, and this style might be too uncommon to be considered readable.

是否嵌套/拆分:

它允许引入有意义的名称以及优化的评估.缺点是可能会产生复杂的条件语句树

It allows the introduction of meaningful names in combination with optimized evaluation. A drawback is the complex tree of conditional statements that can ensue

摊牌

因此,要查看我最喜欢哪种方法,我将一些建议的解决方案应用于上面介绍的复杂targetFound示例.这是我的结果:

So to see which approach I utlimately favor, I applied several of the suggested solutions to the complex targetFound example presented above. Here are my results:

嵌套/拆分if,并使用有意义的名称非常冗长,有意义的名称并不能真正改善此处的可读性

nested / split if's, with meaningful names very verbose, meaningful names don't really help the readability here

private boolean targetFound1(String target, List<String> items,
        int position, int min, int max) {

    boolean result;
    boolean inWindow = position >= min && position < max;
    if (inWindow) {

        boolean foundInEvenPosition = position % 2 == 0
                && items.get(position).equals(target);
        if (foundInEvenPosition) {
            result = true;
        } else {
            boolean foundInOddPosition = (position % 2 == 1)
                    && position > min
                    && items.get(position - 1).equals(target);
            result = foundInOddPosition;
        }
    } else {
        boolean beforeWindow = position < min;
        if (beforeWindow) {

            boolean matchesFirstItem = items.get(0).equals(target);
            result = matchesFirstItem;
        } else {

            boolean afterWindow = position >= max;
            if (afterWindow) {

                boolean matchesLastItem = items.get(items.size() - 1)
                        .equals(target);
                result = matchesLastItem;
            } else {
                result = false;
            }
        }
    }
    return result;
}

嵌套/拆分是否带有注释不太冗长,但仍然难以阅读且易于创建错误

nested / split if's, with comments less verbose, but still hard to read and easy to create bugs

private boolean targetFound2(String target, List<String> items,
        int position, int min, int max) {

    boolean result;
    if ((position >= min && position < max)) { // in window

        if ((position % 2 == 0 && items.get(position).equals(target))) {
            // even position
            result = true;
        } else { // odd position
            result = ((position % 2 == 1) && position > min && items.get(
                    position - 1).equals(target));
        }
    } else if ((position < min)) { // before window
        result = items.get(0).equals(target);
    } else if ((position >= max)) { // after window
        result = items.get(items.size() - 1).equals(target);
    } else {
        result = false;
    }
    return result;
}

早期回报甚至更紧凑,但是条件树仍然一样复杂

early returns even more compact, but the conditional tree remains just as complex

private boolean targetFound3(String target, List<String> items,
        int position, int min, int max) {

    if ((position >= min && position < max)) { // in window

        if ((position % 2 == 0 && items.get(position).equals(target))) {
            return true; // even position
        } else {
            return (position % 2 == 1) && position > min && items.get(
                    position - 1).equals(target); // odd position
        }
    } else if ((position < min)) { // before window
        return items.get(0).equals(target);
    } else if ((position >= max)) { // after window
        return items.get(items.size() - 1).equals(target);
    } else {
        return false;
    }
}

提取方法导致你的课堂上没有意义的方法参数传递很烦人

extracted methods results in nonsensical methods in your class the parameter passing is annoying

private boolean targetFound4(String target, List<String> items,
        int position, int min, int max) {

    return (foundInWindow(target, items, position, min, max)
            || foundBefore(target, items, position, min) || foundAfter(
            target, items, position, max));
}

private boolean foundAfter(String target, List<String> items, int position,
        int max) {
    return (position >= max && items.get(items.size() - 1).equals(target));
}

private boolean foundBefore(String target, List<String> items,
        int position, int min) {
    return (position < min && items.get(0).equals(target));
}

private boolean foundInWindow(String target, List<String> items,
        int position, int min, int max) {
    return (position >= min && position < max && ((position % 2 == 0 && items
            .get(position).equals(target)) || (position % 2 == 1)
            && position > min && items.get(position - 1).equals(target)));
}

重新访问

BooleanExpression包装器请注意,方法参数必须声明为final对于这种复杂的情况,详细程度是可以辩护的IMO如果他们同意,关闭可能会使此操作更容易(-

BooleanExpression wrappers revisited note that the method parameters must be declared final for this complex case the verbosity is defendable IMO Maybe closures will make this easier, if they ever agree on that (-

private boolean targetFound5(final String target, final List<String> items,
        final int position, final int min, final int max) {

    abstract class BooleanExpression {
        abstract boolean eval();
    }

    BooleanExpression foundInWindow = new BooleanExpression() {

        boolean eval() {
            return position >= min && position < max
                    && (foundAtEvenPosition() || foundAtOddPosition());
        }

        private boolean foundAtEvenPosition() {
            return position % 2 == 0 && items.get(position).equals(target);
        }

        private boolean foundAtOddPosition() {
            return position % 2 == 1 && position > min
                    && items.get(position - 1).equals(target);
        }
    };

    BooleanExpression foundBefore = new BooleanExpression() {
        boolean eval() {
            return position < min && items.get(0).equals(target);
        }
    };

    BooleanExpression foundAfter = new BooleanExpression() {
        boolean eval() {
            return position >= max
                    && items.get(items.size() - 1).equals(target);
        }
    };

    return foundInWindow.eval() || foundBefore.eval() || foundAfter.eval();
}

我想这真的取决于情况(一如既往).对于非常复杂的条件,包装器方法可能是可以辩护的,尽管这种情况并不常见.

I guess it really depends on the situation (as always). For very complex conditions the wrapper approach might be defendable, although it is uncommon.

感谢您的所有投入!

事后思考.为诸如此类的复杂逻辑创建特定的类可能会更好:

afterthought. It might be even better to create a specific class for complex logic such as this:

import java.util.ArrayList;
import java.util.List;

public class IsTargetFoundExpression {

    private final String target;
    private final List<String> items;
    private final int position;
    private final int min;
    private final int max;

    public IsTargetFoundExpression(String target, List<String> items, int position, int min, int max) {
        this.target = target;
        this.items = new ArrayList(items);
        this.position = position;
        this.min = min;
        this.max = max;
    }

    public boolean evaluate() {
        return foundInWindow() || foundBefore() || foundAfter();
    }

    private boolean foundInWindow() {
        return position >= min && position < max && (foundAtEvenPosition() || foundAtOddPosition());
    }

    private boolean foundAtEvenPosition() {
        return position % 2 == 0 && items.get(position).equals(target);
    }

    private boolean foundAtOddPosition() {
        return position % 2 == 1 && position > min && items.get(position - 1).equals(target);
    }

    private boolean foundBefore() {
        return position < min && items.get(0).equals(target);
    }

    private boolean foundAfter() {
        return position >= max && items.get(items.size() - 1).equals(target);
    }
}

逻辑很复杂,足以保证需要一个单独的类(和单元测试,是的!).它将使使用此逻辑的代码更具可读性,并在其他地方需要此逻辑的情况下促进重用.我认为这是一个不错的课程,因为它确实有一个责任,只有最后一个领域.

The logic is complex enough to warrant a separate class (and unit test, yay!). It will keep the code using this logic more readable and promotes reuse in case this logic is needed elsewhere. I think this is a nice class because it truely has a single responsibility and only final fields.

推荐答案

我发现换行符和空格确实做得很好,

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 可以做到:

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.

这篇关于如何分解复杂条件并保持短路评估?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆