当前位置: 首页 > 面试题库 >

如何拆分复杂条件并保持短路评估?

胡墨竹
2023-03-14
问题内容

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

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

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

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");
        }
    }
}

对答案的回应:

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

  • 换行并添加评论
  • 照原样
  • 提取方法
  • 早期回报
  • 如果是嵌套/拆分

换行并添加注释:

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

保持原样:

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

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)));
}

我不建议保留原样。

提取方法:

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

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

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

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

早期回报:

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

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;
}

我意识到大多数人都讨厌标签和标签,这种风格可能太罕见了,以至于难以理解。

嵌套/拆分是否:

它允许引入有意义的名称以及优化的评估。缺点是条件语句的复杂树可能随之而来

摊牌

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

嵌套/拆分if,有意义的名称 非常冗长,有意义的名称并没有真正帮助提高此处的可读性

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;
}

嵌套/拆分if,注释 不那么冗长,但仍然难以阅读且易于创建错误

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;
}

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

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;
    }
}

提取的方法 在您的类中导致荒谬的方法,参数传递很烦人

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也许闭包将使此过程变得更容易,只要他们对此表示同意(

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();
}

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

感谢您的输入!

编辑:事后。为这样的复杂逻辑创建特定的类可能会更好:

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);
    }
}

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


问题答案:

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

public static void main1(String[] args) {

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

诚然,它与我的(不受欢迎的)支撑样式(上面未显示)一起使用时效果更好,但是即使在上面的情况下,如果将右括号和右括号放在自己的行上,它也可以正常工作(因此,它们不会在结尾处丢失)最后条件)。

我有时甚至评论个别的地方:

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");
    }
}

如果您真的想把事情讲清楚,可以使用ifdouble来完成:

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");
            }
        }
    }
}

…并且任何优化的编译器都会崩溃。对我来说,它可能太冗长了。



 类似资料:
  • 问题内容: 这是我在处理Django项目时出现的一个问题。关于表单验证。 在Django中,当您提交表单时,可以调用相应的表单对象以触发验证并返回布尔值。因此,通常在视图函数中有类似的代码: 不仅可以验证表单数据,还可以向表单对象添加错误消息,这些错误消息随后可以显示给用户。 在一页上,我同时使用两种形式,并且还希望仅当两种形式均包含有效数据时才保存数据。这意味着我必须在执行代码以保存数据之前在两

  • 什么是 Nutz.Dao 中的复杂SQL条件 对于 Nutz.Dao 来说,它本质上就是将你的 Java 对象转化成 SQL,然后交给 JDBC 去执行。 而 SQL 中,当执行数据删除和查询操作时,最常用的就是 WHERE 关键字。 WHERE 关键字后面的就是所谓的复杂查询条件 Nutz.Dao 将如何如何使用这个条件 Dao 接口的 clear 方法和 query 方法的第二个参数,就是为了

  • 问题内容: 我可以在MySQL 5.5的子句中使用任何短路逻辑运算符(特别是短路和短路)吗?如果没有,那还有什么选择? 在这个小提琴中可以找到关于我的问题的抽象观点以及对我为什么需要它的解释: http://sqlfiddle.com/#!2/97fd1/3 实际上,我们正在数百个国家/地区的数以千计的城市中的数百万家书店中查找数百万本图书,这就是为什么我们无法接受我们发送的每个查询都收到不必要信

  • 问题内容: 大家好:)我有一个评分栏,它可以正常工作,但是当用户离开页面时,该评分不会保存。您如何保存用户评分? 这是代码 任何帮助,将不胜感激。 问题答案: 您可以为此使用 SharedPreferences 。 返回应用程序之前,请保存评级,然后从中检索评级并在评级栏中进行设置。 保存值 您可以 检索 类似 的值 其中 的myKey 是 键名 ,通过它可以识别值.. 编辑 像这样更改代码 而当

  • 问题内容: 从这个问题出发,一个关于使用COALESCE简化复杂逻辑树的简洁答案。我考虑了短路问题。 例如,在大多数语言的函数中,对参数进行全面评估,然后将其传递给函数。在C中: 这似乎不是SQL Server中“功能”的限制: 如果它正在评估分母= 0的第二种情况,那么我会期望看到类似以下的错误: 我发现了一些提到 相关的甲骨文。并使用SQL Server 进行了一些测试。当您包含用户定义的函数

  • 问题内容: 如何在JSX中选择性地包含元素?这是一个使用横幅的示例,如果传递了横幅,该横幅应位于组件中。我要避免的是必须在if语句中复制HTML标签。 问题答案: 只需将横幅标为未定义即可,它就不会包含在内。