【问题标题】:Use while loop to avoid deeply nested if statements in java使用 while 循环避免 java 中的深层嵌套 if 语句
【发布时间】:2015-04-14 04:09:23
【问题描述】:

你好,我写了一个小函数,比如

public void foo(MyClassA paraA) {
    if (paraA == null) return;
    MyClassB paraB = doSomeStuff(paraA);
    if (paraB == null) return;
    MyClassC paraC = doMoreStuff(paraB);
    if (paraC == null) return;
    ....
}

上面的失败很快并且很好读(即返回空值的意图很明确)。但是现在我不想简单地返回,而是想做一些错误记录,所以我改为

public void foo(MyClassA paraA) {
    if (paraA == null) {doLog(); return;}
    MyClassB paraB = doSomeStuff(paraA);
    if (paraB == null) {doLog(); return;}
    MyClassC paraC = doMoreStuff(paraB);
    if (paraC == null) {doLog(); return;}
    ....
}

上面的内容也很简洁易读,但我必须重复 doLog() 几次。所以我又改成

public void foo(MyClassA paraA) {
    if (paraA != null) {
        MyClassB paraB = doSomeStuff(paraA);
        if (paraB != null) {
            MyClassC paraC = doMoreStuff(paraB);
            if (paraC != null) {
                ....
                return;
            }
        }
    }
    doLog();
}

上面只调用了一次 doLog(),但我以一些嵌套很深的 if 语句结束,这些语句非常丑陋且难以阅读。那么如何保持与以前一样的清洁度并且只使用一次 doLog() 呢?请注意,不允许为 foo() 返回其他内容而不是 void。而且我还读到使用 try/catch 来反对 null 检查是一种反模式。

如果我想尝试,我想写一些类似的东西

public void foo(MyClassA paraA) {
    while(true) {
        if (paraA == null) break;
        MyClassB paraB = doSomeStuff(paraA);
        if (paraB == null) break;
        MyClassC paraC = doMoreStuff(paraB);
        if (paraC == null) break;
        ....
        return;
    }
    doLog();
}

以上满足了我的所有需求(快速失败、干净、没有嵌套 if),但是这里使用 while 循环是一种反模式,因为这里的 while 循环永远不会运行多次?

【问题讨论】:

  • doLog 是做什么的?你能把它改成带参数吗?
  • @shree.pat18 还没想那么多,为什么?也许您可以就带参数或不带参数提出您的建议,谢谢。
  • 如果您可以在每次变量之一为空时附加到一个字符串,然后在最后将结果字符串传递给doLog,您就不必重复调用它.
  • @shree.pat18 是的,可以这样做,但与我的问题无关。您在谈论哪种模式只调用一次 doLog ?如果您指的是嵌套如果,我反对。如果您指的是我的 while 循环版本,我的问题是询问使用 while 循环是否是反模式。
  • 因为退货;它不会到达 dolog() 方法希望你没问题。

标签: java loops if-statement while-loop nested-if


【解决方案1】:

Java 有一个漂亮的标记 break 结构,可能会对您有所帮助。

public void foo(MyClassA paraA) {
    block: {
        if (paraA == null) { break block; }
        MyClassB paraB = doSomeStuff(paraA);
        if (paraB == null) { break block; }
        MyClassC paraC = doMoreStuff(paraB);
        if (paraC == null) { break block; }
        ...
        return;
    }

    doLog();
}

如果你更好地使用多态性,你可以这样做:

public void foo(MyInterface para) {
    while (para != null) {
        para = para.doStuff();
    }
    doLog();
}

如果您绝对不能使用这样的多态性,请使用调度程序。

但我以前见过这个,它看起来像一个状态机。搜索“java enum state machine”。我有一种感觉,这正是您真正想要做的事情。

【讨论】:

  • 我不知道你可以在不进入 for 或 while 循环的情况下使用 break!这很有趣,让我试试这个。如果可行,我很可能会选择你的答案。
  • JLS #14.15
  • @user1589188 谢谢,但请考虑多态方法,即使您最终没有使用它。这是一个很好的模式。
  • 是的,试过了,效果很好。剩下的唯一问题是,我觉得这个标签有点像 goto。这不是另一种反模式吗?无法进行多态 doStuff(),因为我需要在不同阶段传递一些参数。
  • 这与单个函数的多次返回并没有什么不同......这有点像 goto,但 goto 的滥用比多次返回、中断和继续更糟糕。肯定有人不喜欢多次退货,但我想说这实际上更多的是个人喜好。
【解决方案2】:

你觉得这样干净吗

public void foo(MyClassA paraA) {

    MyClassB paraB = paraA != null?doSomeStuff(paraA):null;
    MyClassC paraC = paraB != null?doMoreStuff(paraB):null;

     if (paraC != null) {
         ....

     }

     doLog();
}

【讨论】:

  • 更好的是,将空检查放入doMoreStuff
  • 这很干净,但不会很快失败。一旦你知道 paraA 为空,你应该直接去 doLog(),所以你的不是最优的,你必须在到达 doLog() 之前评估更多的条件检查
  • 空值检查是最快的。将其留给热点使用 jmp 优化它们。
【解决方案3】:

恕我直言,你的第二个代码 sn-p 是 ypu 应该做的。

不要试图让你的代码简短。这是一种反模式。

if (a==null) {
  log("Failed in step a");
  return;
}
B b = a.doSomething();

阅读和理解速度非常快。压缩此代码不会保存任何内容。零。纳达。将其留给 Hotspot VM,并专注于使代码易于理解。 “if null then log return”是一个经典的、很好理解和接受的模式

尝试使用 lambda 反模式使代码“可读”已变得很流行,如下所示:

B b = ifNullLog(a, () -> a.doSomething())

在哪里

T ifNullLog(Object guard, Function<T> func) {
  if (guard == null) { doLog(); return null; }
  return func.run();
}

但是恕我直言,这完全是反模式。事实上,最好的做法是为每个 if、else、for 和 while 加上 require 大括号,这样可以方便地插入这样的日志语句,而不会有破坏代码的风险。

类似于你的第一个 sn-p 的代码:

if (a == null) return;

很危险。 查看 Apples SSL 灾难等各种错误 如果有人在没有注意到缺少括号的情况下添加 doLog,则该函数将始终返回 null。苹果的 SSL 漏洞(或者说是让人心血来潮?)本质上是一个

if (a==null)
  return;
  return;
B b = a.doSomething();

看看这个错误有多微妙?幸运的是,您的 Java 编译器会警告您是否涉及无法访问的代码 - 否则它不一定会警告您……通过始终使用括号和格式良好的代码,可以轻松避免此类错误。 格式化代码以避免错误,不适用于美学

使用返回码也是可以接受的。只是不要默认成功(再次参见 heartbleed)。

Code c = execute(a);
if (c != Code.SUCCESS) {
  doLog(c);
  return;
}

在哪里

Code execute(A a) {
  if (a == null) { return Code.FAILED_A_NULL; }
  B b = a.doSomething();
  if (b == null) { return Code.FAILED_B_NULL; }
  ...
  return Code.SUCCESS;
}

“return”的经典用例,另一个很好的模式。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-30
    • 2023-03-30
    • 1970-01-01
    • 1970-01-01
    • 2015-12-28
    • 2017-02-06
    • 1970-01-01
    相关资源
    最近更新 更多