为什么空的 else-if 语句风格不好,我应该如何重写它?

Hen*_*rek 73 java if-statement

自动对我的代码进行评分的程序正在为不执行任何代码的 else-if 对接我的“样式点”。它说它可能会导致错误,但我认为它不会。

我不确定如何更改它以使其仍然有效但不违反规则。为什么要做这种糟糕的形式?我认为我写的任何其他方式都会让读者更难理解。应该怎么写呢?

if (! seesWater(LEFT))
{
    turn(LEFT);
}
else if (! seesWater(AHEAD));
else if (! seesWater(RIGHT))
{
    turn(RIGHT);
}
else
{
    turn180();
}
Run Code Online (Sandbox Code Playgroud)

else-if 在那里但什么都不做的原因是因为我希望代码执行的优先级:

if (! seesWater(AHEAD)),那么我根本不希望其余条件运行,因为它们无关紧要。

J.B*_*kus 140

谁说这是“糟糕的风格”?

要问的相关问题是,这比替代方案更清楚吗?在你的具体情况下,我会说它是。代码清楚地表达了 4 个选项之间的选择,其中一个是“什么都不做”。

我要做的唯一改变是用一对空的大括号替换那个相当微不足道的分号,可能用注释来表明这不是错误。

if (! seesWater(LEFT)) {
    turn(LEFT);
}
else if (! seesWater(AHEAD)) {
    // nothing required
}
else if (! seesWater(RIGHT)) {
    turn(RIGHT);
}
else {
    turn180();
}
Run Code Online (Sandbox Code Playgroud)

这并不是支持“空条款”作为一种普遍可接受的风格;只是认为案件应该根据案情进行辩论,而不是根据某些必须遵守的规则。这是培养良好品味的问题,品味的判断是针对人类的,而不是无意识的自动机。

  • 谁说风格不好?Checkstyle确实如此。 (20认同)
  • 如果 Checkstyle 统治世界,我就不能写我的名字,因为它包含空格。;-) @J.Backus:我同意你的观点。可读/易于理解的代码比检查样式错误更有用。我相信,编译器会优化掉不必要的语句。 (17认同)
  • 这是双重否定,如果前面没有水,就什么也不做。整个前提是前面有水,应该首先检查,这个逻辑是有缺陷的,因为它可能永远左转。 (7认同)
  • +1:添加对“不执行任何操作”情况的检查可能很有用。它可以帮助获得我想要的程序流程,并且还可以向下一个开发人员表明我明确考虑过这种情况,而不仅仅是忘记它。包含诸如“//不执行任何操作”之类的注释可以清楚地表明这不是一个被遗忘的实现。 (3认同)
  • 这是正确的答案。有四种不同的情况,它们应该以一致且易于阅读的风格编写。仅仅因为一种情况不需要采取任何行动,并不意味着应该以不同的方式处理。或者您可以回到结构化编程的原则:想象一下您想要记录所做的决定。您需要四个日志记录语句,正如所写的,代码有四个放置日志记录的好位置。 (2认同)
  • 我同意 Checkstyle 的观点。这是代码味道。这不是 4 个选项之间的选择,而是 3 个选项之间的选择。对于每个单独的“if”,您可以说有两个选项,并添加一个无用的空“else”块。 (2认同)
  • *谁说它是“糟糕的风格”?*,通过在其中添加评论,您无意识地承认它*是*糟糕的风格。始终添加注释以明确您的空块是有意的而不是错误。 (2认同)

use*_*460 45

如果这是您想要的逻辑,那么您的代码就没有问题。在代码样式方面,我同意其他用户的意见。在我看来,这样的事情会更清楚:

if (! seesWater(LEFT))
{
    turn(LEFT);
}
else if (! seesWater(AHEAD))
{
    //Do nothing
}
else if (! seesWater(RIGHT))
{
    turn(RIGHT);
}
else
{
    turn180();
}
Run Code Online (Sandbox Code Playgroud)

但是通过优先左转而不是向前移动(通过什么都不做),运动可能会以循环结束:

在此处输入图片说明

如果你想让运动“什么都不做”,但避免像这样进入水域:

在此处输入图片说明

您可能希望将逻辑更改为如下所示:

if (! seesWater(AHEAD))
{
    //Do nothing. Keep moving
}
else if (! seesWater(LEFT))
{
    turn(LEFT);
}
else if (! seesWater(RIGHT))
{
    turn(RIGHT);
}
else
{
    turn180();
}
Run Code Online (Sandbox Code Playgroud)

  • 最后一个代码块不会产生您在第二张图片中显示的输出。在最初的左转之后,代码将继续笔直(到图片的左边缘),没有逻辑让它右转(在图片中面朝上) (25认同)
  • 将 `turn180()` 更改为 `turn(AROUND)` 并使用 `if(seesWater(AHEAD))turn(!seesWater(LEFT)? LEFT: !seesWater(RIGHT)? RIGHT: AROUND);` (5认同)
  • @musefan 这是不可能说的。也许算法在确定下一步行动时总是将航向视为向上。该算法不包含标题,因此分析它是不合理的,IMO (3认同)

Swe*_*per 26

您可以反转! seesWater(AHEAD)条件。然后您可以将其else子句中的代码移动到其if子句中:

if (! seesWater(LEFT))
{
    turn(LEFT);
}
else if (seesWater(AHEAD)) 
{
    if (! seesWater(RIGHT))
    {
        turn(RIGHT);
    }
    else
    {
        turn180();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我发现原始代码结构比这更清晰。 (47认同)
  • @user2357112supportsMonica也许你觉得不清楚的原因是因为,正如user3437460在他们的回答中指出的那样,逻辑本身有点奇怪,但这个问题与逻辑的正确性无关,所以我没有在我的文章中解决这个问题。回答。我在答案中描述的技术“反转 `! seesWater(AHEAD)` 条件,然后将 else 子句中的代码移至 if 子句”也可以应用于 user3437460 答案的最后一个代码片段。如果这样做,是否会比原始代码更清晰? (7认同)
  • 这是正确的结构。`seesWater(AHEAD)` 是重要条件。我们不关心`!seesWater(AHEAD)`。 (7认同)

Ste*_*eve 13

我认为您可以决定根本不更改代码的逻辑。我认为没有理由避免空ifelse if块,如果这会导致您的代码最易读且逻辑最易理解。您通常可以重写您的代码,使其不包含空代码块,但仍然可以理解。但如果没有,我说这很好。

不过有一件事……我不喜欢使用 a;来表示空代码块的风格。这真的很容易错过,通常不会看到,所以它会让读者感到困惑。我建议用;一组空的花括号替换。您还可以在空卷曲中添加注释,以明确表示该代码块为空,即// In this case, we don't want to do anything.


rta*_*aft 13

对于这个特定的代码示例,我不认为逻辑是准确的,并且不直观。如果前方有水,您似乎想转弯……但是在那里左转会让人感到困惑,并且可能是一个错误。最终,我将空洞的逻辑视为双重否定,“如果前方没有水,则什么都不做”,并且双重否定也令人不悦。

if (seesWater(AHEAD))
{
    if (! seesWater(LEFT))
    {
        turn(LEFT);
    }
    else if (! seesWater(RIGHT))
    {
        turn(RIGHT);
    }
    else
    {
        turn180();
    }
}
Run Code Online (Sandbox Code Playgroud)

这是最有意义的,因为附加逻辑基于前方是否有水。如果前方有水,如果您想将动作移动到另一个功能,它也会更容易。

  • @FlorianF 编码风格本身并不是目的。良好的编码风格使错误显而易见,正确的逻辑看起来也很好。这个答案的逻辑可以简化为[这个陈述](/sf/ask/4481375321/ i-rewrite-it#comment113248364_64020089) 突然间,发生了什么就很清楚了,而且这是应该发生的事情。 (6认同)
  • @FlorianF 我知道,操作的代码在原地打转。 (4认同)
  • 问题是关于风格。 (3认同)
  • @rtaft 它可能是一个迷宫求解算法... https://en.wikipedia.org/wiki/Maze_solving_algorithm (2认同)

use*_*366 13

我发现if(!condition)语句中的否定逻辑难以阅读且令人费解,与 if-else-else-else 结合使用时更是如此。我更喜欢正逻辑seesLand()而不是!seesWater.

您可以修改 turn() 函数,这样turn(AHEAD)它什么都不turn(BACK)做,而是进行 180 度转弯,而不需要为此单独使用一个函数。

然后你可以把它改写成。

List<Direction> directions = new ArrayList(LEFT, FORWARD, RIGHT, BACK);
for (Direction d: directions) {
    if(seesLand(d) {
        turn(d);
        return;
}
    
Run Code Online (Sandbox Code Playgroud)

一个好处是,这更加概括。您可以简单地通过定义另一个数组来创建不同的移动策略,您可以在其中根据需要对方向进行排序,并将它们输入到此函数中以检查并按所选顺序转动。

  • 这可能是个好主意,但执行得很差。不能像这样初始化 ArrayList。缺少一个括号,您的代码可能会转动 4 次,而不是在 OP 的情况下只转动一次。 (2认同)

Fra*_*ins 10

你用这样的东西做的是把它变成一种说话的方法,最好把它分开。

选项1:

private turnTowardsGround(){
  if(! seesWater(AHEAD){
    return;
  }
  if (! seesWater(LEFT))
  {
     turn(LEFT);
     return;
  }
  if (! seesWater(RIGHT))
  {
    turn(RIGHT);
    return;
  }
  turn180();
}
Run Code Online (Sandbox Code Playgroud)

选项 2:

private determineMoveDirection(){
  if(! seesWater(AHEAD){
    return AHEAD;
  }
  if (! seesWater(LEFT))
  {
     return LEFT
  }
  if (! seesWater(RIGHT))
  {
    return RIGHT
  }
  return BACK;
}
Run Code Online (Sandbox Code Playgroud)

然后使用选项 2,如:

Direction directionToMove = determineMoveDirection();
turn(directionToMove);
Run Code Online (Sandbox Code Playgroud)

为什么?因为方法名称使代码的意图清晰。并且早期返回退出允许快速阅读,我们不需要阅读整个 if-else 代码以确保一旦一个案例适用就不会发生其他事情。另外,在选项 2 的情况下,您有明确的关注点分离(找出移动到的位置与实际转向的位置),这对模块化非常有用,并且在这种情况下还允许在一个地方记录您要去的最终决定转,例如。

此外,最好不要取消检查,但会有一个“seesLand”或“seesAccessibleFieldType”方法。解析非否定语句在心理上更容易/更快,并且它使您更清楚您实际在寻找什么(即地块,冰块,......)。


Dav*_*258 8

为什么空 if 子句是糟糕的风格?因为它是一种“退出” if 块的非明显方式。它跳过后续的 else-if,更重要的是跳过最后的 else,它在多条件块中最终作为“默认”操作。

对我来说,这里有两个主要问题:

  1. if 语句顺序很重要,但不清楚为什么选择这个顺序
  2. else 语句似乎具有逻辑,它被跳过do_nothing而不是全部

我在查看这样的代码时会问的第一个问题是为什么 do_nothing 选项(! seesWater(AHEAD))不是我们检查的第一件事?是什么让! seesWater(LEFT)我们检查的第一件事是什么?我希望看到关于为什么这个顺序很重要的评论,因为 if 似乎并不明显是排他的(可以在多个方向看到水)。

我要问的第二个问题是在什么情况下我们希望最终出现在包罗万象的else声明中。目前turn180()是“默认”,但如果由于某种原因环顾四周失败,我觉得回到你来的方式并不是很默认的行为。


小智 7

我来自 C,和 MISRA 一起生活,但对我来说,带分号的“if”是不好的风格,无论是否有“else”。原因是人们通常不会在那里放分号,除非它是一个错字。考虑一下:

if (! seesWater(AHEAD));
{
  stayDry();
}
Run Code Online (Sandbox Code Playgroud)

在这里,阅读代码的人会认为,如果您没有看到前方有水,您只会使用stayDry()——您必须仔细观察才能看到它实际上会被无条件地调用。更好的是,如果您不想在这种情况下做任何事情,则使用大括号和注释:

if (! seesWater(AHEAD))
{
  /* don't need to do anything here */
}
Run Code Online (Sandbox Code Playgroud)

事实上,应该始终将大括号放在“if”、“else”等的主体周围,即使子句是单个 turn() 调用,也应该使用大括号!我有时会发现像这样的代码

if (! seesWater(AHEAD))
  stayDry();
Run Code Online (Sandbox Code Playgroud)

那么稍后会有人加入并添加其他内容:

if (! seesWater(AHEAD))
  stayDry();
  doSomethingElse();
Run Code Online (Sandbox Code Playgroud)

当然,无论您是否看到水,其他东西都会完成,这不是第二个编码员的意图。您可能认为没有人会犯如此明显的错误,但他们确实会犯。


Kev*_*one 5

样式检查器报告样式不佳的原因是实施样式检查器的人认为它是糟糕的样式。

你必须问他们为什么,他们的回答可能有价值,也可能没有。

任何需要人类智能的自动检查器都应该谨慎对待。有时它们会产生有用的输出,有时则不会。如果这样的检查员正在评估一个人的工作,那是非常不幸的,如果该评估有后果,那将是非常不幸的。