2

嗨,我写了一个小函数,比如

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 反对空检查是一种反模式。

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

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

4

3 回答 3

3

Java 有一个漂亮的标记 break 结构,可以帮助你,here。

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”。我有一种感觉,这就是你真正想要做的。

于 2015-02-13T07:11:35.330 回答
1

恕我直言,您的第二个代码片段是 ypu 应该做的。

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

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

阅读和理解速度非常快。压缩此代码不会保存任何内容。零。纳达。将其留给 Hotspot VM,并专注于使代码易于理解。“如果为空,则返回日志”是一种经典的、易于理解和接受的模式

尝试使用 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 都使用大括号,以便轻松插入这样的日志语句而不会冒破坏代码的风险。

像您的第一个片段一样的代码:

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

于 2015-02-14T10:49:10.277 回答
1

你觉得这干净吗

public void foo(MyClassA paraA) {

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

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

     }

     doLog();
}
于 2015-02-13T07:08:04.770 回答