Иногда условия могут стать довольно сложными, поэтому для удобства чтения я обычно разделяю их и даю каждому компоненту значащее имя. Тем не менее, это приводит к потере оценки короткого замыкания, что может создать проблему. Я придумал подход к обертке, но, на мой взгляд, это слишком многословно.Как разбить сложные условия и провести оценку короткого замыкания?
Может ли кто-нибудь придумать опрятное решение для этого?
Смотрите ниже код для примеров того, что я имею в виду:
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)));
}
Я не рекомендовал бы оставить это как есть.
Extract методы:
Я считал извлекая методы, как это было предложено в нескольких ответов. Недостатком этого является то, что эти методы обычно имеют очень низкую степень детализации и может быть не очень значимыми сами по себе, так что он может засорять свой класс, например:
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
Так, чтобы увидеть, какой подход я utlimately пользу, я применил некоторые из предложенных решений в сложном пример 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)));
}
логическое выражение обертки Обратите внимание, что параметры метода должны быть объявлены окончательными для этого сложного случае подробность оборонять 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();
}
Я предполагаю, что это действительно зависит от ситуации (как всегда). Для очень сложных условий подход обертки может быть защищен, хотя он необычен.
Спасибо за все ваши данные!
EDIT: поздняя мысль. Это может быть даже лучше создать специальный класс для сложной логики, таких как это:
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);
}
}
Логика достаточно, чтобы гарантировать отдельный класс комплекса (и модульное тестирование, яй!).Он сохранит код, используя эту логику, более читабельным и будет способствовать повторному использованию, если эта логика понадобится в другом месте. Я думаю, что это хороший класс, потому что он действительно имеет одну ответственность и только конечные поля.
Мне нравится ваше первое решение, однако при правильном присвоении имен переменных оно может и не понадобиться. Второе решение - слишком многословная ИМО. – BacMan