《代码完整》一书附有以下引文:
“将正常情况放在
if
而不是之后else
”
这意味着在这种else
情况下,应该放置与标准路径的异常/偏离。
但是,实用程序员教会我们“尽早崩溃”(第120页)。
我应该遵循哪一条规则?
if
分支返回,请首先使用它。并避免else
其余的代码,如果前提条件失败,您已经返回。代码更易于阅读,缩进更少...
《代码完整》一书附有以下引文:
“将正常情况放在
if
而不是之后else
”
这意味着在这种else
情况下,应该放置与标准路径的异常/偏离。
但是,实用程序员教会我们“尽早崩溃”(第120页)。
我应该遵循哪一条规则?
if
分支返回,请首先使用它。并避免else
其余的代码,如果前提条件失败,您已经返回。代码更易于阅读,缩进更少...
Answers:
“早期崩溃”与文本中哪一行代码更早有关。它告诉您在处理的最早步骤中检测到错误,这样您就不会无意间根据已经存在的错误状态进行决策和计算。
在if
/ else
构造中,仅执行一个块,因此不能说它们构成了“较早”或“较晚”的步骤。因此,如何订购它们是易读性的问题,并且“不及早失效”不会成为决定。
if/else
构造中,这可能并不重要。但是在循环中或在每个块中包含很多语句的调用可能会以最常见的条件优先运行得更快。
如果您的else
语句仅包含失败代码,则很可能不存在该失败代码。
而不是这样做:
if file.exists() :
if validate(file) :
# do stuff with file...
else :
throw foodAtMummy
else :
throw toysOutOfPram
做这个
if not file.exists() :
throw toysOutOfPram
if not validate(file) :
throw foodAtMummy
# do stuff with file...
您不想仅仅为了包含错误检查而深深地嵌套代码。
而且,正如其他人已经说过的那样,两条建议并不矛盾。一种是关于执行顺序,另一种是关于代码顺序。
if
异常流量。这样的Guard语句是处理大多数编码样式中的错误条件的首选形式。else
else
您应该同时遵循它们。
“早期崩溃” /“早期失败”建议意味着您应该尽快测试输入内容是否存在错误。
例如,如果您的方法接受应该为正数(> 0)的大小或计数,则早期失败建议意味着您在方法开始时就测试该条件,而不是等待算法产生废话结果。
将正常情况放在第一位的建议意味着,如果您测试某种状况,那么最可能的路径应该放在第一位。这有助于提高性能(因为处理器的分支预测将更加正确)和可读性,因为在尝试弄清楚函数在正常情况下的工作时,您不必跳过代码块。
当您测试前提条件并立即纾困(通过使用断言或if (!precondition) throw
构造)时,此建议实际上并不适用,因为在读取代码时不会跳过任何错误处理。
if(cond){/*more likely code*/}else{/*less likely code*/}
运行速度比if(!cond){/*less likely code*/}else{/*more likely code*/}
分支预测要快。我认为分支预测不会偏向if
或else
语句,而只会考虑历史。因此,如果else
更有可能发生,那么它应该能够预测出同样好的结果。这个假设是错误的吗?
我认为@JackAidley 已经说过了要点,但让我这样制定:
在常规代码流中,您具有:
if (condition) {
statement;
} else if (less_likely_condition) {
less_likely_statement;
} else {
least_likely_statement;
}
more_statements;
在“尽早出错”的情况下,您的代码突然显示为:
/* demonstration example, do NOT code like this */
if (condition) {
statement;
} else {
error_handling;
return;
}
如果您发现了这种模式– return
在else
(甚至是if
)块中,请立即对其进行重新处理,以使所涉及的代码中没有该else
块:
/* only code like this at University, to please structured programming professors */
function foo {
if (condition) {
lots_of_statements;
}
return;
}
在现实世界…
/* code like this instead */
if (!condition) {
error_handling;
return;
}
lots_of_statements;
这样可以避免嵌套太深,并满足“尽早爆发”的情况(有助于保持头脑清醒,使代码流向清晰),并且不会违反“将更多可能的东西放入if
零件中”,因为根本就没有else
零件。
C
和清理受到一个类似问题的答案的启发(错了),这是使用C进行清理的方法。您可以在其中使用一个或两个出口点,这里是两个出口点之一:
struct foo *
alloc_and_init(size_t arg1, int arg2)
{
struct foo *res;
if (!(res = calloc(sizeof(struct foo), 1)))
return (NULL);
if (foo_init1(res, arg1))
goto err;
res.arg1_inited = true;
if (foo_init2(&(res->blah), arg2))
goto err;
foo_init_complete(res);
return (res);
err:
/* safe because we use calloc and false == 0 */
if (res.arg1_inited)
foo_dispose1(res);
free(res);
return (NULL);
}
如果清理工作较少,可以将它们折叠到一个出口点:
char *
NULL_safe_strdup(const char *arg)
{
char *res = NULL;
if (arg == NULL)
goto out;
/* imagine more lines here */
res = strdup(arg);
out:
return (res);
}
goto
如果可以处理的话,这种用法是非常好的。建议不要使用的建议goto
针对无法自行决定使用的是好,可接受,不良,意大利面条式代码或其他用途的人。
上面谈到了无例外的语言,我非常喜欢我自己(我可以更好地使用显式错误处理,并且惊喜更少)。引用igli:
<igli> exceptions: a truly awful implementation of quite a nice idea.
<igli> just about the worst way you could do something like that, afaic.
<igli> it's like anti-design.
<mirabilos> that too… may I quote you on that?
<igli> sure, tho i doubt anyone will listen ;)
但是,这里有一个建议,说明您如何使用一种例外的语言来表现出色,以及何时要很好地使用它们:
您可以return
通过引发异常来替换大多数早期的。但是,您的常规程序流,即程序未遇到的任何代码流,嗯,一个异常……一个错误条件或类似情况,都不会引发任何异常。
这意味着…
# this page is only available to logged-in users
if not isLoggedIn():
# this is Python 2.5 style; insert your favourite raise/throw here
raise "eh?"
……还可以,但是……
/* do not code like this! */
try {
openFile(xyz, "rw");
} catch (LockedException e) {
return "file is locked";
}
closeFile(xyz);
return "file is not locked";
… 不是。基本上,异常不是控制流元素。这也使Operations对您看起来很奇怪(“那些Java™程序员总是告诉我们这些异常是正常的”),并且可能阻碍调试(例如,告诉IDE仅在发生任何异常时中断)。异常通常要求运行时环境释放堆栈以产生回溯等。可能有更多的理由对此加以反对。
归结为:在支持异常的语言中,使用与现有逻辑和样式匹配的任何东西,感觉自然。如果要从头开始编写内容,请尽早达成协议。如果要从头开始编写库,请考虑一下您的消费者。(也永远不要abort()
在库中使用……)但是,根据经验,如果操作通常在操作后(或多或少)继续进行,则不会抛出异常。
首先尝试使整个开发团队都同意在程序中使用所有Exception。基本上,计划一下。请勿大量使用它们。有时,即使在C ++,Java™,Python中,返回错误也更好。有时不是;用思想去使用它们。
goto fail;
隐藏在标识中。
(@mirabilos的回答非常好,但是我想如何得出相同的结论:)
稍后,我正在考虑自己(或其他人)阅读我的函数的代码。当我阅读第一行时,我无法对我的输入做任何假设(除非我不会进行任何检查)。所以我的想法是:“好吧,我知道我要用我的论点做事。但是首先让我们'清理它们'-即杀死不喜欢我的控制路径。”但是同时,我认为正常情况不是有条件的;我想强调它是正常的。
int foo(int* bar, int baz) {
if (bar == NULL) /* I don't like you, leave me alone */;
if (baz < 0) /* go away */;
/* there, now I can do the work I came into this function to do,
and I can safely forget about those if's above and make all
the assumptions I like. */
/* etc. */
}
这种条件排序取决于所讨论代码部分的关键性,以及是否存在可以使用的默认值。
换一种说法:
A.关键部分,没有默认值=>早期失败
B.非关键部分和默认值=>在else部分中使用默认值
C.中间案例=>根据需要决定每个案例