有时条件会变得非常复杂,因此为了便于阅读,我通常会将它们拆分并为每个组件指定一个有意义的名称.然而,这会使短路评估失败,这可能造成问题.我想出了一个包装器的方法,但在我看来它太冗长了.
有人可以为此提出一个简洁的解决方案吗?
请参阅下面的代码,了解我的意思:
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, Listitems, 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示例.这是我的结果:
如果是嵌套/拆分,有意义的名称 非常详细,有意义的名称在这里并没有真正帮助这里的可读性
private boolean targetFound1(String target, Listitems, 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, Listitems, 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, Listitems, 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, Listitems, 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 items, int position, int max) { return (position >= max && items.get(items.size() - 1).equals(target)); } private boolean foundBefore(String target, List items, int position, int min) { return (position < min && items.get(0).equals(target)); } private boolean foundInWindow(String target, List 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 Listitems, 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 Listitems; private final int position; private final int min; private final int max; public IsTargetFoundExpression(String target, List 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 boolean areArgsOk(String[] args) { if(args == null) return false; if(args.length != 2) return false; if(args[0].equals(args[1])) return false; return true; } public static void main2(String[] args) { boolean b = areArgsOk(args); if(b) System.out.println("Args are ok"); }
我觉得linebreaks和whitespace做得很好,实际上:
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"); } }
如果你真的想把事情搞砸了,多个人if
会这样做:
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"); } } } }
......任何优化编译器都会崩溃.对我来说,它可能有点过于冗长.
如果您的目标是可读性,为什么不简单地划分界限并添加评论?
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"); }