Как разделить сложные условия и сохранить оценку короткого замыкания?

Иногда условия могут стать довольно сложными, поэтому для удобства чтения я обычно разделяю их и даю каждому компоненту осмысленное имя. Однако это отрицательно сказывается на оценке короткого замыкания, что может создать проблему. Я придумал подход обертки, но, на мой взгляд, он слишком многословен.

Кто-нибудь может придумать аккуратное решение для этого?

Посмотрите код ниже для примеров того, что я имею в виду:

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

Я понимаю, что большинство людей ненавидят ярлыки и разрывы, и этот стиль может быть слишком необычным, чтобы считаться читаемым.

Вложено / разделено, если:

Это позволяет вводить значимые имена в сочетании с оптимизированной оценкой. Недостатком является сложное дерево условных операторов, которое может

Showdown

Таким образом, чтобы увидеть, какой подход я полностью одобряю, я применил несколько предложенных решений к сложному примеру targetFound, представленному выше. Вот мои результаты:

вложенный / разделенный, если со значимыми именами очень многословные, значимые имена не очень помогают читабельности здесь

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

вложенный / разделенный, если с комментариями менее многословный, но все еще трудно читаемый и легко создавать ошибки

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 упаковщики пересмотрены обратите внимание, что параметры метода должны быть объявлены окончательными для этого сложного случая, многословие защищаемо. 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);
    }
}

Логика достаточно сложна, чтобы гарантировать отдельный класс (и юнит-тест, ура!). Это сделает код, использующий эту логику, более читабельным и способствует повторному использованию в случае, если эта логика нужна где-то еще. Я думаю, что это хороший класс, потому что он действительно несет единственную ответственность и только заключительные поля.

Ответы на вопрос(11)

Ваш ответ на вопрос