如何简化这组if语句?(或者是什么让它感觉如此尴尬?)

And*_*ong 31 java logic refactoring boolean-logic if-statement

我的同事向我展示了这段代码,我们都想知道为什么我们似乎无法删除重复的代码.

private List<Foo> parseResponse(Response<ByteString> response) {
    if (response.status().code() != Status.OK.code() || !response.payload().isPresent()) {
      if (response.status().code() != Status.NOT_FOUND.code() || !response.payload().isPresent()) {
        LOG.error("Cannot fetch recently played, got status code {}", response.status());
      }
      return Lists.newArrayList();
    }
    // ...
    // ...
    // ...
    doSomeLogic();
    // ...
    // ...
    // ...
    return someOtherList;
}
Run Code Online (Sandbox Code Playgroud)

这是一个替代表示,使它不那么罗嗦:

private void f() {
    if (S != 200 || !P) {
        if (S != 404 || !P) {
            Log();
        }
        return;
    }
    // ...
    // ...
    // ...
    doSomeLogic();
    // ...
    // ...
    // ...
    return;
}
Run Code Online (Sandbox Code Playgroud)

是否有更简单的方法来写这个,而不重复!P?如果没有,是否有一些关于情况或条件的独特属性使得无法分解!P

JLR*_*she 29

它看起来像很多代码的一个原因是它非常重复.使用变量来存储重复的部分,这将有助于提高可读性:

private List<Foo> parseResponse(Response<ByteString> response) {
    Status status = response.status();
    int code = status.code();
    boolean payloadAbsent = !response.payload().isPresent();

    if (code != Status.OK.code() || payloadAbsent) {
      if (code != Status.NOT_FOUND.code() || payloadAbsent) {
        LOG.error("Cannot fetch recently played, got status code {}", status);
      }
      return Lists.newArrayList();
    }
    // ...
    // ...
    // ...
    return someOtherList;
}
Run Code Online (Sandbox Code Playgroud)

编辑:正如斯图尔特在评论中指出的下面,如果可能的话,比较response.status()Status.OK直接,然后就可以消除外来通话.code(),并使用import static直接访问状态:

import static Namespace.Namespace.Status;

// ...

private List<Foo> parseResponse(Response<ByteString> response) {
    Status status = response.status();
    boolean payloadAbsent = !response.payload().isPresent();

    if (status != OK || payloadAbsent) {
      if (status!= NOT_FOUND || payloadAbsent) {
        LOG.error("Cannot fetch recently played, got status code {}", status);
      }
      return Lists.newArrayList();
    }
    // ...
    // ...
    // ...
    return someOtherList;
}
Run Code Online (Sandbox Code Playgroud)

关于如何处理payloadAbsent逻辑重复的问题,Zachary提供了一些与我建议的内容兼容的好主意.还有一个选择是保留基本结构,但要更明确地检查有效负载.这将使逻辑更容易遵循,并使您不必||在内部使用if.OTOH,我自己并不热衷于这种方法:

import static Namespace.Namespace.Status;

// ...

private List<Foo> parseResponse(Response<ByteString> response) {
    Status status = response.status();
    boolean failedRequest = status != OK;
    boolean loggableError = failedRequest && status!= NOT_FOUND ||
        !response.payload().isPresent();

    if (loggableError) {
      LOG.error("Cannot fetch recently played, got status code {}", status);
    }
    if (failedRequest || loggableError) {
      return Lists.newArrayList();
    }
    // ...
    // ...
    // ...
    return someOtherList;
}
Run Code Online (Sandbox Code Playgroud)

  • 这似乎没有解决我认为是问题的主要问题:函数的逻辑结构以及!P重复的事实. (9认同)

Zac*_*ary 21

如果您想澄清条件检查并保持相同的逻辑结果,则以下内容可能是合适的.

if (!P) {
    Log();
    return A;
}
if (S != 200) {
    if (S != 404) {
        Log();
    }
    return A;
}
return B;
Run Code Online (Sandbox Code Playgroud)

或者(这是OP首选)

if (S == 404 && P) {
    return A;
}
if (S != 200 || !P) {
    Log();
    return A;
}
return B;
Run Code Online (Sandbox Code Playgroud)

或者(我个人更喜欢这个,如果你不介意开关)

if (P) {
    switch (S) {
        case 200: return B;
        case 404: return A;
    }
}
Log ();
return A;
Run Code Online (Sandbox Code Playgroud)

可以通过删除大括号并将单行主体移动到与if语句相同的行压缩if语句.但是,单行if语句可能会令人困惑,也可能是不正确的不良行为.根据我从评论中收集到的内容,您的偏好将不利于此用途.虽然单行if语句可以压缩逻辑并使代码更清晰,但清晰度和代码意图应该被重视为"经济"代码.需要明确的是:我个人认为有些情况下单行if语句是合适的,但是,由于原始条件很长,在这种情况下我强烈建议不要这样做.

if (S != 200 || !P) {
    if (S != 404 || !P) Log();
    return A;
}
return B;
Run Code Online (Sandbox Code Playgroud)

作为辅助节点:如果Log();语句是嵌套if语句中唯一可到达的分支,则可以使用以下逻辑标识来压缩逻辑(Distributive).

(S != 200 || !P) && (S! = 404 || !P) <=> (S != 200 && S != 404) || !P

编辑重要编辑以重新排列内容并解决评论中提到的问题.

  • 所以,我并不是说字面意思缩短代码.在我工作过(或知道)的大多数地方,单行`if`被禁止,而你的仅限运营商的混音只能在代码高尔夫设置中被接受(:不过你最后的建议也不错!它分开了总体结果的事件和重复"返回"而不是"!P".我喜欢它. (13认同)
  • 我倾向于远离单行,除非条件和线都简洁(即使那时我很想使用单线括号).如果返回语句是第二个if语句后面的唯一代码(因为重复代码将变得繁琐 - 在这种情况下,您总是可以将其转换为单独的方法),最终建议才有用. (4认同)
  • 为您的第一个示例投下了非常混淆的代码.已经很难遵循嵌套的if语句而不包括其中一些,但不包括其他语句.我认为绝大多数编码器(包括我自己)都会将`return A`作为if语句的主体读取,而不是真正检查它并注意到`Log();`.请记住:代码的读取频率高于编写代码!垂直空间并不昂贵; 使用那些括号! (4认同)
  • 由于添加了更多版本,为了明确未来的读者,我首选的是以`if(S == 404 && P)返回A;`开头的版本. (3认同)

Old*_*eon 13

请记住,简洁并不总是最好的解决方案,在大多数情况下,编译器会优化本地命名变量.

我可能会用:

    boolean goodPayload = response.status().code() == Status.OK.code() && response.payload().isPresent();
    if (!goodPayload) {
        // Log it if payload not found error or no payload at all.
        boolean logIt = response.status().code() == Status.NOT_FOUND.code()
                || !response.payload().isPresent();
        if (logIt) {
            LOG.error("Cannot fetch recently played, got status code {}", response.status());
        }
        // Use empty.
        return Lists.newArrayList();
    }
Run Code Online (Sandbox Code Playgroud)

  • 是的,当然,但是因为布尔变量表达了是或否答案,不应该将变量命名为人类如何提出问题,实际动词和所有?我说是!我喜欢你把我当作原教旨主义者的活力,但我非常注重实用主义.当然,一切都很好.;-) (4认同)

Pie*_*ens 13

代码味道对我来说,你是 Response对象,而不是告诉它.问问自己为什么Parse方法在Response对象的外部而不是它的方法(或者更可能是它的超类).是否可以在Response对象构造函数中调用Log()方法而不是其Parse方法?在属性status().code()payload().isPresent()在构造函数中计算的那一刻,是否可以将默认解析对象分配给私有属性,使得只有一个简单(和单个)if ... else ...保留在Parse()中?

如果有人能够使用实现继承来编写面向对象的语言,那么应该查询每个条件语句(和表达式!),以查看它是否被提升到构造函数或方法中(s) )调用构造函数.所有课堂设计的简化都是非常重要的.

  • 无论如何,+1告诉"不要问" (3认同)
  • 是的我也在考虑可能会创建一个新的类,它将响应作为构造函数参数,因此可以包含验证响应的所有逻辑. (2认同)

Vec*_*ohn 10

实际上多余的主要是!P(有效载荷存在).如果你把它写成一个布尔表达式,你有:

(A || !P) && (B || !P)
Run Code Online (Sandbox Code Playgroud)

正如你所观察到的那样,!P似乎在重复,而且它是不必要的.在布尔代数中,您可以将AND处理为类似乘法,而OR类似于加法.因此,您可以像使用简单代数一样将这些括号扩展为:

A && B || A && !P || !P && B || !P && !P
Run Code Online (Sandbox Code Playgroud)

您可以将所有具有!P的ANDed表达式组合在一起:

A && B || (A && !P || !P && B || !P && !P)
Run Code Online (Sandbox Code Playgroud)

由于所有这些术语都有!P,你可以像乘法一样把它拿出来.当你这样做时,你将它替换为true(就像你将1一样,因为任何东西本身都是1次,真实且任何东西本身):

A && B || !P && (A && true || B && true || true && true)
Run Code Online (Sandbox Code Playgroud)

请注意,"true && true"是OR表达式之一,因此整个分组始终为true,您可以简化为:

A && B || !P && true
-> A && B || !P
Run Code Online (Sandbox Code Playgroud)

也许,我在这里用正确的符号和术语生锈了.但这就是它的要点.

回到代码,如果你在if语句中有一些复杂的表达式,就像其他人已经注意到的那样你应该把它们放在一个有意义的变量中,即使你只是要使用它一次并扔掉它.

所以把这些放在一起你会得到:

boolean statusIsGood = response.status().code() != Status.OK.code() 
  && response.status().code() != Status.NOT_FOUND.code();

if (!statusIsGood || !response.payload().isPresent()) {
  log();
}
Run Code Online (Sandbox Code Playgroud)

请注意,即使您将其否定,该变量也会被命名为"statusIsGood".因为带有否定名称的变量确实令人困惑.

请记住,您可以对真正复杂的逻辑进行上述简化,但您不应该总是这样做.你最终会得到技术上正确的表达,但没有人能通过观察它来说明原因.

在这种情况下,我认为简化澄清了意图.


emo*_*t01 5

恕我直言,问题主要是重复和嵌套.其他人建议使用我推荐的clear变量和util函数,但您也可以尝试分离验证的问题.

如果我错了,请纠正我,但似乎您的代码在实际处理响应之前尝试验证,因此这是编写验证的另一种方法:

private List<Foo> parseResponse(Response<ByteString> response)
{
    if (!response.payload.isPresent()) {
        LOG.error("Response payload not present");
        return Lists.newArrayList();
    }
    Status status = response.status();
    if (status != Status.OK || status != Status.NOT_FOUND) {
        LOG.error("Cannot fetch recently played, got status code {}", status);
        return Lists.newArrayList();
    }
    // ...
    // ...
    // ...
    return someOtherList;
}
Run Code Online (Sandbox Code Playgroud)