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


73

自动对我的代码评分的程序会将我的“样式点”停靠在别的位置(如果不执行任何代码)。它说可能会导致错误,但我认为不会。

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

if (! seesWater(LEFT))
{
    turn(LEFT);
}
else if (! seesWater(AHEAD));
else if (! seesWater(RIGHT))
{
    turn(RIGHT);
}
else
{
    turn180();
}

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

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


4
假定此代码在方法中(如果不是,则应在其中),您应该使用终止该方法,return而不是将其保留为空白。
美联社

27
我不同意在此使用退货。在这里,我们不讨论完整的功能或方法。谁说这之后没有更多代码了?
史蒂夫

34
就个人而言,我觉得之后的分号(! seesWater(AHEAD))有点难以发现。部分原因是因为))));外观相似,部分原因是立即if使用分号来跟从子句有点不寻常。也许{ }可能更适合这种情况。
Panzercrisis 20/09/23

15
将分号放在同一行通常是一个错字。许多样式检查器都会对此发出警告,您需要使用一个空块{}或将分号放在下一行以使其静音。
Barmar

5
@Panzercrisis:不仅很难发现,而且很难理解作者在发现后期望代码执行的操作,因为恕我直言,大多数人都不会这样做。
jamesqf

Answers:


140

谁说这是“坏风格”?

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

我唯一要做的更改是用一对空括号替换那个相当微不足道的分号,可能会加上一条注释,以表明这不是错误。

if (! seesWater(LEFT)) {
    turn(LEFT);
}
else if (! seesWater(AHEAD)) {
    // nothing required
}
else if (! seesWater(RIGHT)) {
    turn(RIGHT);
}
else {
    turn180();
}

这不是在认可“空子句”为普遍接受的样式。只是应该根据案情辩论案件,而不是根据某些必须遵守的规则进行辩论。这是培养良好品味的问题,品味的判断是针对人类的,而不是无意识的自动机。


20
谁说这是不好的风格?Checkstyle可以。
波希米亚

17
如果Checkstyle统治世界,那么我将无法写出我的名字,因为它包含空格。;-) @ J.Backus:我同意你的看法。可读/易懂的代码比checkstyle错误更有用。我相信编译器会优化掉不必要的语句。
Mirko

7
这是一个双重否定,如果前面没有水,那就什么也不做。整个前提是前面有水,应该首先检查,并且这种逻辑是有缺陷的,因为它可能永远消失。
rtaft

3
+1:添加对“不执行任何操作”情况的检查很有用。它可以帮助获得所需的程序流程,还可以向下一个开发人员显示我明确考虑过这种情况,而不仅仅是忘记了这种情况。包括“ //什么都不做”之类的注释可以清楚地表明这并不是一个被遗忘的实现。
卡尔·莱斯

2
我同意Checkstyle。这是代码的味道。这不是4个选项之间的选择,而是3个选项之间的选择。对于每个孤独者,if您可以说有2个选项并添加一个无用的空else块。
xehpuk

44

如果这是您想要的逻辑,则您的代码没有错。在代码样式方面,我同意其他用户的看法。我认为这样的事情会更清楚:

if (! seesWater(LEFT))
{
    turn(LEFT);
}
else if (! seesWater(AHEAD))
{
    //Do nothing
}
else if (! seesWater(RIGHT))
{
    turn(RIGHT);
}
else
{
    turn180();
}

但是,通过优先选择左转而不是前进(不执行任何操作),该运动可能会转圈:

在此处输入图片说明

如果您想让运动“什么也不做”,但避免进入这样的水域:

在此处输入图片说明

您可能需要将逻辑更改为以下内容:

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

25
最后的代码块不会产生您在第二张图片中显示的输出。在最初的左转之后,代码将继续笔直(向图片的左边缘),没有逻辑使其右转(在图片中
朝上

3
@musefan这是不可能说的。在确定下一个动作时,算法可能总是将标题设为UP。IMO中未包含标题,因此对其进行分析是不合理的,IMO
Adam

5
更改turn180()turn(AROUND)和使用if(seesWater(AHEAD)) turn(!seesWater(LEFT)? LEFT: !seesWater(RIGHT)? RIGHT: AROUND);
霍尔格·

26

您可以反转! seesWater(AHEAD)条件。然后,您可以将代码从其else子句移至其if子句:

if (! seesWater(LEFT))
{
    turn(LEFT);
}
else if (seesWater(AHEAD)) 
{
    if (! seesWater(RIGHT))
    {
        turn(RIGHT);
    }
    else
    {
        turn180();
    }
}

46
我发现原始代码结构比这更清晰。
user2357112支持莫妮卡

7
@ user2357112supportsMonica也许您发现不清楚的原因是,正如user3437460在他们的答案中指出的那样,逻辑本身有点怪异,但是这个问题不是关于逻辑的正确性,因此我没有在我的文章中解决这个问题。回答。我在答案中描述的技术“反转! seesWater(AHEAD)条件,然后将其else子句中的代码移至其if子句”也可以应用于user3437460答案的最后一个代码段。如果这样做,是否使它比原始代码更清晰?
清扫器

1
@Sweeper我认为这可能是解决迷宫的算法……在这种情况下,它更有意义。en.wikipedia.org/wiki/Maze_solving_algorithm
user3067860

7
这是正确的结构。seesWater(AHEAD)是重要条件。我们不在乎!seesWater(AHEAD)
xehpuk

1
这就是我为了获得积分而最终要做的。但是我个人认为,我最初编写它的方式更加清晰。我希望自动评分系统不会因为此类主观原因而记分。
亨利·科祖雷克20'Sep

13

我认为您可以决定完全不更改代码的逻辑。我认为没有必要避免为空ifelse if阻塞,因为这会导致您的代码最易读且逻辑最易理解。您通常可以重写代码,以不包含空代码块,但仍可以理解。但是,如果没有,我说这很好。

不过有一件事...我不喜欢使用a;表示空代码块的样式。这真的很容易被遗漏,并且通常不会被看到,因此可能会使读者感到困惑。我建议将其替换为;空的花括号。您还可以在空的curl内添加注释,以明确表示该代码块为空,即// In this case, we don't want to do anything


13

对于这个特定的代码示例,我不认为逻辑是准确的,并且不会直观地传递。看起来好像要转弯,如果前方有水...但是那儿左转,那会造成混乱,并可能是错误。最终,我将空逻辑视为双重否定,“如果前方没有积水,那就什么也不做”,双重否定也被反对。

if (seesWater(AHEAD))
{
    if (! seesWater(LEFT))
    {
        turn(LEFT);
    }
    else if (! seesWater(RIGHT))
    {
        turn(RIGHT);
    }
    else
    {
        turn180();
    }
}

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


4
@FlorianF我知道,op的代码旋转成圈。
rtaft

3
问题是关于风格。
Florian F

2
@rtaft可能是迷宫求解算法... en.wikipedia.org/wiki/Maze_solving_algorithm
user3067860

6
@FlorianF编码风格本身并不是目的。良好的编码风格可以使错误变得显而易见,并且正确的逻辑看起来也不错。这个答案的逻辑可以简化为该语句,突然之间,很清楚发生了什么,应该继续进行。
Holger

OP表示要在左,前,右和后之间选择第一个可用选项。您的代码说要向左尝试,然后如果无法直行,请尝试向右并返回。4例之间的对称性不明显。
Florian F

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;
}
    

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


2
这可能是个好主意,但执行效果不佳。您不能像这样初始化ArrayList。有一个缺少的括号,您的代码可能会旋转4次,而不是OP的情况。
埃里克·杜米尼尔

1
我并不是在试图编写语法完美的代码,而是传达一些想法。本可以更清楚地说明这一点。谢谢你的评论。“转四圈”是什么意思?
user985366's

1
如果您运行OP的代码,它turn最多只能调用一次,而我们不知道之后会发生什么。您的代码可以调用turn4次,每个方向一次,而中间没有任何代码。
埃里克·杜米尼尔

1
是的,return在我的示例中,放错了位置,实际上没有任何作用。它本来是要中断执行的,但是由于它位于foreach的内部函数中,所以没有。那是个错误。需要用不同的方式编写它,也许for (Direction d: directions)可以按照我的预期使用较旧的样式。我现在更改了代码以反映最初的意图。
user985366's

1
抱歉,OP没有使用any return,因此您也不应该使用。上面的代码段之后可能还有更多代码。实现你的想法的一种方式是用流filterfindFirstifPresent
埃里克·杜米尼尔

10

对这样的事情,您要做的就是将其转换为语音方法,最好将其拆分。

选项1:

private turnTowardsGround(){
  if(! seesWater(AHEAD){
    return;
  }
  if (! seesWater(LEFT))
  {
     turn(LEFT);
     return;
  }
  if (! seesWater(RIGHT))
  {
    turn(RIGHT);
    return;
  }
  turn180();
}

选项2:

private determineMoveDirection(){
  if(! seesWater(AHEAD){
    return AHEAD;
  }
  if (! seesWater(LEFT))
  {
     return LEFT
  }
  if (! seesWater(RIGHT))
  {
    return RIGHT
  }
  return BACK;
}

然后使用选项2,例如:

Direction directionToMove = determineMoveDirection();
turn(directionToMove);

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

同样,最好不取消检查,但应使用“ seesLand”或“ seesAccessibleFieldType”方法。从心理上讲,解析非否定的语句更容易/更快,并且可以使您更清楚地实际查找(例如,地砖,冰砖等)。


8

为什么Empty-if子句的样式会不好?因为这是“退出” if块的一种非显而易见的方式。它跳过后续的else-if,更重要的是跳过最终的else,在多条件块中最终将其作为“默认”操作。

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

  1. if语句顺序很重要,但不清楚为什么选择该顺序
  2. else语句似乎具有逻辑,被逻辑do_nothing而不是包罗万象

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

我要问的第二个问题是,在什么情况下,我们期望最终得到全包else陈述。目前turn180()是“默认值”,但如果因某种原因环顾四周失败,让我回过头来,感觉不像是默认值-y。


7

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

if (! seesWater(AHEAD));
{
  stayDry();
}

在这里,阅读代码的人会认为,如果您前面看不到水,那么您只会stayDry()-您必须很难看清它实际上将被无条件调用。更好的是,如果您在这种情况下不想执行任何操作,则使用大括号和注释:

if (! seesWater(AHEAD))
{
  /* don't need to do anything here */
}

实际上,应始终将大括号放在“ if”,“ else”等主体的周围,即使子句是单个turn()调用,也有利于使用大括号!我有时会找到类似的代码

if (! seesWater(AHEAD))
  stayDry();

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

if (! seesWater(AHEAD))
  stayDry();
  doSomethingElse();

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


5

样式检查器报告某些样式不良的原因是,实施样式检查器的人认为样式较差。

您必须问他们为什么,他们的答案可能有,也可能没有。

任何需要人的智力的自动检查程序都应谨慎对待。有时它们产生有用的输出,有时却没有。如果这样的检查员正在评估一个人的工作,这是非常不幸的,而如果这种评估有后果,那将是非常不幸的。


2

您对第二个else if语句不做任何操作,应该这样重写

        else if(!seesWater(AHEAD)){return }

这样,程序就可以使用条件运算符来返回布尔语句,希望这对-SG有帮助


10
我不同意在此使用退货。我们在这里不讨论完整的功能或方法。谁说这之后没有更多代码了?
史蒂夫

3
@Steve好吧,将其转换为方法是合理的
Frank Hopkins

4
但是退出代码流程和/或引入新功能与OP问题无关。您基本上是在说:“与其允许一个空的if-else块,不如将整个if / elseif / else序列放入一个函数中,然后从该函数的空if-else块中返回,而不是离开它为空”-我希望您同意这不是该问题最直接的答案。
史蒂夫

-1

好吧,我们真的不知道整个问题的背景。我们不知道这是否是方法中的唯一代码。我不确定为什么要先测试LEFT。我本以为您会先测试AHEAD,因为如果可以的话,我会以相同的方向继续前进。如果继续向左走,您将围成一圈。因此,鉴于缺乏要求,很难给出完整的答案。

但是,我倾向于同意我不喜欢空的if语句。如果您想继续向前走,则需要以某种方式指出。

其他的建议:

  1. 我宁愿使用一个积极的方法名称,例如noWater(...)。然后,if语句将成为正面测试,而不是负面测试。

  2. 为什么要调用的方法有多种形式:turn(LEFT),turn(RIGHT)和turn180()?为什么turn180()不同?我将创建一个可以接受任何方向参数的方法。

使用以上建议,您将获得以下内容:

if (noWater(FORWARD))
    turn(0);
else if (noWater(LEFT))
    turn(270);
else if (noWater(RIGHT))
    turn(90);
else
    turn(180);

使用这样的结构,您不会有一个空的if语句,并且移动变为参数化(并且是显式的),因此可以根据需要具有不同的值。


2
由于一个关键原因,如果此版本具有完全相同的效果,我发现这很难理解。我知道一个空块什么都不做。我应该如何知道go(0)通话的内容?我猜是研究。此外,转turngo是为了可读性错误的方向又迈进了一步。在这里,我只能猜测这go(0)意味着“转0度”,这意味着“什么都不做”。 go(0)可能意味着“进行救生船演习,为该地区的船只冰雹,然后旋转X(0)度”。我没办法知道 原来,毫无疑问我会离开...什么都没有。
史蒂夫

同意将方法名称简化为turn(0)。该参数将更加明显。但是,由于OP甚至没有回复任何建议,因此不值得花时间辩论。
camickr
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.