关于if块的独占性

我对良好的编码实践有疑问。 我理解if-else if和multiple if之间的区别(也就是说,当if-else if满足条件时,跳过其余的检查)。 我在这些方面找到了一段代码:

 if (A == 5) { do_something(); } else if (B == 7) { do_something_else(); } 

我明白这个代码不会检查B == 7如果A == 5.代码有效,这意味着B只有7,如果A不是5,但我认为这只是等待代码中断时变化。 我会做的是:

 if (A == 5) { do_something(); return or continue or break; } if (B == 7) { do_something_else(); return or continue or break; } 

我的问题是,当我有多个依赖于不同的独占变量的独占案例时,解决流量控制的最佳方法是什么? 我的印象是第一个代码(使用else ifs)很大程度上依赖于其他代码片段,而其他区域的更改可能会破坏它。 第二个看起来有点笨重。 一个开关可能是第三个选项,但我需要创建另一个结构来保持大小写和逻辑来分配它的值,我认为它会有点笨拙和反直觉。

你问过“独家”案件,但条件A == 5B == 7是它们不是排他性的; 他们是独立的。

为了完全普遍,您可能需要测试和处理所有四种情况:

 if(A == 5) { if(B == 7) { /* case 1 */ } else { /* case 2 */ } } else { if(B == 7) { /* case 3 */ } else { /* case 4 */ } } 

这是臭名昭着的“浓密”if / else块。 这是臭名昭着的,因为它几乎不可能立即让读者无法遵循,特别是如果涉及案件,或引入更多层次。 (我认为大多数风格指南会告诉你永远不要使用3级或更高级别的if / else树。我当然会这么说。)

我偶尔会使用这两种选择:

(1)完全解耦案件:

 if(A == 5 && B == 7) { /* case 1 */ } else if(A == 5 && B != 7) { /* case 2 */ } else if(A != 5 && B == 7) { /* case 3 */ } else if(A != 5 && B != 7) { /* case 4 */ } else { /* can't happen */ } 

这里的要点是让后来的读者最清楚地知道哪些条件与案例1,2,3和4一致。因此,你也可以列出最后一个, else if(A != 5 && B != 7)明确的情况(如我所示),即使到那时它基本上是“其他”。

(2)转换“两级”开关。 我不能说这是一种常见的技巧; 它有一种“太聪明”的味道,但它的健壮性和可读性:

 #define PAIR(b1, b2) (((b1) << 8) | (b2)) switch(PAIR(A == 5), (B == 7)) { case PAIR(TRUE, TRUE): /* case 1 */ break; case PAIR(TRUE, FALSE): /* case 2 */ break; case PAIR(FALSE, TRUE): /* case 3 */ break; case PAIR(FALSE, FALSE): /* case 4 */ break; } 

当条件是A == 5B == 7 ,我不建议这样做,因为当你在开关中时,“TRUE”和“FALSE”的意思并不明显,但有时,这种事情可以干净利落地阅读。 对于3个或更多级别的嵌套,它也是干净利落的,不像“浓密”if / else树,正如我所说的那样是众所周知的不可读的。

最强大的编程方式,
同时避免假设A==5B==7是考虑所有四种情况:

 if ((A == 5) && (B == 7)) { do_somethingAB(); /* or */ do_somethingA(); do_somethingB(); } else if (A == 5) { do_somethingA(); } else if (B == 7) { do_somethingB(); } else { do_somethingNeither(); /* or do nothing */ } 

我想你知道,这两段代码并不相同。 ( 如果它们都包含“返回或继续或中断”,它们是等效的,这使得问题更有趣,但这是一个不同的答案。)

一般来说,您选择哪一个(或者您选择如何重写它)必须完全依赖于您希望程序执行的操作。

当你写作

 if (A == 5) { do_something(); } else if (B == 7) { do_something_else(); } 

你还要说, 只有当 A不等于5时你才想要do_something_else 。那可能就是你想要的,或者它可能是一个bug。 如果你想在没有else情况下实现相同的效果,它必须如下所示:

 if (A == 5) { do_something(); } if (A != 5 && B == 7) { do_something_else(); } 

另一方面,您在问题中编写的第二段代码有可能同时执行do_something do_something_else

一般来说,如果if / else链中的所有条件都在相同的条件下变化,而不是一些涉及例如ABexception混合,那么它是最好的(最清晰且最不容易混淆)。

当备选方案真正且刻意排除时,您可以使用if / else块,并且当您想强调这一事实时。 当备选方案不是排他性的,或者只是巧合或意外排除时,您可以选择使用单独的if块(不与else链接)。 例如,我故意编写代码

 if(A == 5) { do_something(); } if(A != 5) { do_some_unrelated_thing(); } 

当两个事物彼此无关时,我可能会这样做,这意味着在程序逻辑的某些未来修订中,它们可能毕竟不是排他性的。 或者,我可能会这样做,如果do_something不是一个单一的,但是是一个冗长的,精心设计的块,最后我担心读者可能不记得我们为什么或不做某事,并且另一方面,我们可能想做点别的事情。 出于类似的原因,我偶尔会写

 if(A == 5) { do_something(); } if(A == 5) { do_some_unrelated_thing(); } 

在这种情况下,要完成的两件事情彼此无关,而且这样做的原因可能会有所不同。

[这是我的第三个答案。 事实上,我一直在误读你的问题,而且没有理解你所提出的要点,这表明我可能根本就不应该回答。

我认为你要问的关键点是关于案例是独立的情况, 但是由于每个子句都包含一个“熄灭”的控制流语句这一事实你得到了else效果break ,或者continue ,或return ,或类似的东西。

在这个特定情况下,我今天的偏好是不使用else 。 我们写的时候

 if(A == 5) { do_something(); return or continue or break; } if(B == 7) { do_something_else(); return or continue or break; } 

很明显,这两个条件彼此无关,除了它们都是做“完成”子任务的事情,并留下负责执行该子任务的代码块。

当我们分别写两个案例(没有其他)时,我们不仅明确表示它们是独立的,而且可以重新排序,或者可以在它们之间引入另一个案例,等等。

但话说回来,他们可以重新订购吗? 两种情况A == 5B == 7两种情况都是正确的可能性有多大? 在那种情况下,与do_something相比, do_something_else做了多少重要? 如果这两种情况不能重新排序,如果首先测试B并且可能做do_something_else是错误的,我认为明确的else是优选的,将两种情况联系在一起并且更清楚地要求首先测试A

像任何风格问题一样,支持和反对这种事情的论据最终都是非常主观的。 你不可能以某种方式找到一个单一的,压倒性的令人信服的答案。

处理此问题的一种方法是使用do { ... } while (0); 技术。

这是您的原始代码:

 if (A == 5) { do_something(); } else if (B == 7) { do_something_else(); } 

else if在同一行上做else if是[IMO]有点黑客,因为它隐藏了真正的缩进:

 if (A == 5) { do_something(); } else if (B == 7) { do_something_else(); } 

使用前面提到的技术,我已经使用了很多:

 do { if (A == 5) { do_something(); break; } if (B == 7) { do_something_else(); break; } } while (0); 

当我们增加if / else梯形图中的级别数时,这变得更加明显:

 if (A == 5) { do_something(); } else if (B == 7) { do_something_else(); } else if (C == 9) { do_something_else_again(); } else if (D == 3) { do_something_for_D(); } 

再次,这是缩进:

 if (A == 5) { do_something(); } else if (B == 7) { do_something_else(); } else if (C == 9) { do_something_else_again(); } else if (D == 3) { do_something_for_D(); } 

使用do/while/0块,我们得到更简单/更清洁的东西:

 do { if (A == 5) { do_something(); break; } if (B == 7) { do_something_else(); break; } if (C == 9) { do_something_else_again(); break; } if (D == 3) { do_something_for_D(); break; } } while (0); 

注意:我已经用c编程了35年以上,而且我还没有找到一个更标准的do/while使用的情况(例如do { ... } while () )不能使用标准forwhile循环更干净/有效地替换。 有些语言甚至没有do/while循环。 因此,我认为do循环可以重用。


do/while/0另一个用途是允许预处理器宏定义的内容显示为单个块:

 #define ABORTME(msg_) \ do { \ printf(stderr,"ABORT: %s (at line %d)\n",msg_,__LINE__); \ dump_some_state_data(); \ exit(1); \ } while (0) if (some_error_condition) ABORTME("some_error_condition");