使用while循环可以避免在java中深度嵌套if语句



嗨,我写了一个类似的小函数

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循环永远不会运行不止一次?

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枚举状态机"。我有一种感觉,这就是你真正想要做的。

你认为这是干净的吗

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

IMHO您的第二个代码片段是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();
}

但IMHO,这完全是一个反模式。事实上,最好的做法是为每个if、else、for都需要大括号,同时可以轻松插入这样的日志语句,而不会有破坏代码的风险。

代码就像你的第一个片段:

if (a == null) return;

是危险的查看各种错误,如Apples SSL灾难如果有人添加doLog时没有注意到缺少括号,那么函数将始终返回null。苹果SSL漏洞(或者它被心碎了?)本质上是

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

看看这个bug有多微妙?幸运的是,如果是关于无法访问的代码,Java编译器会警告你——否则它不一定会警告你。。。通过总是使用括号和格式良好的代码,可以很容易地避免此类错误格式化代码以避免错误,而不是用于美学

使用返回代码也是可以接受的。只是不要将成功设为默认值(再次查看heartleed)。

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"的一个经典用例,另一个好模式。

最新更新