Switch语句中使用返回值 -- 代码正确性

117

假设我有一段C语言的代码,大致结构如下:

switch (something)
{
    case 0:
      return "blah";
      break;

    case 1:
    case 4:
      return "foo";
      break;

    case 2:
    case 3:
      return "bar";
      break;

    default:
      return "foobar";
      break;
}

显然,break并不是代码正确运行所必需的,但是如果我不将它们放在那里,就好像是一个不好的实践。

你觉得呢?删除它们是否可以?还是为了增加“正确性”而保留它们?

18个回答

179

移除break语句。这些语句并非必需,有些编译器可能会发出"无法到达的代码"警告。


2
同样适用于其他无条件控制跳转语句,如 continuegoto - 在使用 break 的位置使用它们是惯用语。 - caf

44

我会采取完全不同的方法。不要在方法/函数中间使用RETURN,而是只需将返回值放入本地变量中并在最后发送。

个人而言,我发现以下代码更易读:

String result = "";

switch (something) {
case 0:
  result = "blah";
  break;
case 1:
  result = "foo";
  break;
}

return result;

28
“一条出路”(One Exit) 的理念在 C 语言中十分有意义,因为你需要确保正确清理事物。但是,在 C++ 中你可能会被异常在任何时候从函数中抛出,因此,“一条出路”的理念在 C++ 中并不实用。 - Billy ONeal
34
理论上这是个好主意,但它通常需要额外的控制块,可能会影响可读性。 - mikerobi
10
我这样做的主要原因是,在较长的函数中,当浏览代码时很容易错过退出向量。然而,当退出始终在结尾时,更容易快速理解正在发生的事情。 - NotMe
7
好的,那么,在代码中间使用返回语句让我想起了GOTO滥用的情况。 - NotMe
7
@Chris: 在较长的函数中,我完全同意 - 但通常这意味着存在更大的问题,因此需要重构。在许多情况下,这只是一个返回 switch 的微不足道的函数,并且非常清楚应该发生什么,因此不要浪费精力跟踪额外的变量。 - Stephen
显示剩余8条评论

8

我会将它们删除。在我的理解中,像这样的死代码应该被视为错误,因为它会让你反复思考并问自己:“我怎么可能执行那一行代码?”


7

移除它们。在 case 语句中返回是惯用语,否则会产生“不可达代码”的噪音。


7

个人而言,我会删除回车并保留换行符。我会使用switch语句将一个值分配给一个变量,然后在switch语句之后返回该变量。

虽然这是一个有争议的观点,但我一直认为良好的设计和封装意味着只有一种进入和一种退出方式。这样更容易保证逻辑正确性,并且不会因函数的复杂度而意外地错过清理代码。

一个例外:如果在函数开始之前检测到了错误的参数(在获取任何资源之前),则提前返回是可以的。


1
这种写法可能适用于特定的 switch,但并不一定是最好的。比如说,在一个函数内部,switch 语句之后还有500行代码,只有在某些情况下才会执行这些代码。那么对于不需要执行这些代码的情况,执行所有额外的代码有什么意义呢?直接在这些 case 中使用 return 不是更好吗? - Fr0zenFyr

5
如果您有“查找”类型的代码,可以将switch-case子句单独封装在一个方法中。
我正在为自己开发的“爱好”系统中使用几个这样的方法。
private int basePerCapitaIncomeRaw(int tl) {
    switch (tl) {
        case 0:     return 7500;
        case 1:     return 7800;
        case 2:     return 8100;
        case 3:     return 8400;
        case 4:     return 9600;
        case 5:     return 13000;
        case 6:     return 19000;
        case 7:     return 25000;
        case 8:     return 31000;
        case 9:     return 43000;
        case 10:    return 67000;
        case 11:    return 97000;
        default:    return 130000;
    }
}

(没错,这是GURPS空间...)

我同意其他人的看法,在大多数情况下,方法中应避免使用两个以上的回车符号。我也认识到,这个方法可能更好地实现为一个数组或其他什么东西。但我发现switch-case-return与具有输入和输出之间1:1关系的查找表非常匹配,就像上面的例子一样(角色扮演游戏充满了它们,我相信它们在其他“业务”中也存在):D

另一方面,如果case语句更加复杂,或者在switch语句之后还会发生其他事情,我不建议在其中使用return,而是在switch中设置一个变量,以break结束,并在最后返回该变量的值。

(另一方面,您总是可以将switch重构成自己的方法...我怀疑它对性能会产生影响,而且我不会感到惊讶,如果现代编译器甚至可以将其识别为可以内联的内容...)


5
我通常不会使用死代码。在我看来,死代码往往表明编码粗糙或者缺乏理解。
当然,我也会考虑类似于以下的写法:
char const *rets[] = {"blah", "foo", "bar"};

return rets[something];

编辑:即使是经过编辑的帖子,这个通用的想法也可以很好地工作。
char const *rets[] = { "blah", "foo", "bar", "bar", "foo"};

if ((unsigned)something < 5)
    return rets[something]
return "foobar";

如果输入值稀疏(例如1、100、1000和10000),则最好使用稀疏数组。您可以将其实现为树或映射,这两种方式都可以很好地处理(当然,在这种情况下,switch 语句仍然有效)。


修改了帖子以反映为什么这个解决方案不太可行。 - houbysoft
@你的编辑:是的,但这会占用更多的内存等。在我看来,使用switch是最简单的方法,在这样一个小函数中我想要的就是它(它只做switch)。 - houbysoft
3
在得出需要额外内存的结论之前,请仔细查看。使用典型编译器时,对于相同的“foo”(例如),将使用两个指针指向单个数据块,而不是复制数据。您还可以期望代码更短。除非有大量重复的值,否则通常会节省内存。 - Jerry Coffin
我仍然会坚持我的解决方案,因为值列表非常长,使用 switch 语句看起来更好。 - houbysoft
1
事实上,当开关值连续且数据类型同质化时,更优雅和高效的解决方案是使用数组来避免使用开关。 - Dwayne Robinson

4

保留换行符——如果/当您稍后编辑代码时已经有换行符,则更不容易遇到问题。

话虽如此,许多人(包括我在内)认为从函数中间返回是一种不良实践。理想情况下,函数应该有一个入口点和一个出口点。


4

你觉得呢?移除它们是否可以?或者你会保留它们以增加“正确性”?

移除它们是可以的。在使用 return 的情况下,break 正好不应该被使用。


2

用数组存储会更好吧?

arr[0] = "blah"
arr[1] = "foo"
arr[2] = "bar"

如果这是关于实践的一般性问题,您应该在switch语句中保留break语句。如果将来不需要return语句,它会减少它落入下一个case的机会。

并且执行return arr[something];吗?


修改了帖子以反映为什么这个解决方案不太可行。 - houbysoft

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接