一位智者曾经说过:
任何可能出错的事情都会
– 墨菲
一些程序员是智者,因此,明智的程序员曾经说过:
优秀的程序员是在横穿单向街道之前会双向看待的人。
– 道格·林德 ( Doug Linder)
在一个完美的世界中,事情按预期进行,您可能会认为保持消耗直到最后是一个好主意。 因此,可以在每个代码库中找到以下模式:
Java
for (;;) {
// something
}
C
while (1) {
// something
}
基本知识
10 something
20 GOTO 10
想看证明吗? 在github上搜索while(true)并查看匹配项的数量: https : //github.com/search? q=while+true&type =Code
永远不要使用无限循环
在计算机科学领域,围绕“停止问题”的话题进行了非常有趣的讨论。 阿兰·图灵很久以前就证明了停顿问题的实质是,它确实还不确定。 尽管人们可以快速评估以下程序永远不会停止:
for (;;) continue;
…并且以下程序将始终停止:
for (;;) break;
……计算机无法决定这些事情,即使是经验丰富的人,在查看更复杂的算法时也可能无法立即做出决定。
边干边学
在jOOQ中,我们最近通过困难的方式了解了停止问题:这样做。
在解决问题#3696之前,我们解决了SQL Server JDBC驱动程序中的错误(或缺陷)。 该错误导致未正确报告SQLException
链,例如,当以下触发器引发多个错误时:
CREATE TRIGGER Employee_Upd_2 ON EMPLOYEE FOR UPDATE
AS
BEGIN
Raiserror('Employee_Upd_2 Trigger called...',16,-1)
Raiserror('Employee_Upd_2 Trigger called...1',16,-1)
Raiserror('Employee_Upd_2 Trigger called...2',16,-1)
Raiserror('Employee_Upd_2 Trigger called...3',16,-1)
Raiserror('Employee_Upd_2 Trigger called...4',16,-1)
Raiserror('Employee_Upd_2 Trigger called...5',16,-1)
END
GO
因此,我们显式使用了这些SQLExceptions
,以便jOOQ用户对所有数据库都具有相同的行为:
consumeLoop: for (;;)
try {
if (!stmt.getMoreResults() &&
stmt.getUpdateCount() == -1)
break consumeLoop;
}
catch (SQLException e) {
previous.setNextException(e);
previous = e;
}
这对我们的大多数客户都有效,因为由此报告的异常链可能是有限的,而且可能也很小。 即使上面的触发器示例也不是真实示例,因此报告的实际错误数可能在1-5之间。
我只是说…… “可能”吗?
就像我们最初的智者所说:这个数字可能在1-5之间。 但是也可能是1000。或者是1000000。或更糟糕的是,是无限的。 与问题#3696的情况一样,当客户将jOOQ与SQL Azure一起使用时。 因此,在一个完美的世界中,不能报告无限数量的SQLException
,但这不是一个完美的世界,SQL Azure也有一个错误(可能仍然存在),该错误一次又一次报告相同的错误,最终导致jOOQ创建了一个巨大的SQLException
链,从而产生了OutOfMemoryError
,这可能比无限循环更好。 至少该异常易于检测和解决。 如果循环无限运行,则服务器可能已被我们所有客户的用户完全阻止。
现在,解决方法基本上就是这个:
consumeLoop: for (int i = 0; i < 256; i++)
try {
if (!stmt.getMoreResults() &&
stmt.getUpdateCount() == -1)
break consumeLoop;
}
catch (SQLException e) {
previous.setNextException(e);
previous = e;
}
忠实于俗语:
640 KB对于任何人都应该足够
唯一的例外
因此,正如我们之前所见,这个令人尴尬的示例表明, 任何可能出错的事情都可以做到 。 在可能发生initeite循环的情况下,请注意,这种错误将使整个服务器瘫痪。
加州理工学院的喷气推进实验室已将此成为其编码标准的基本规则 :
规则3(循环界限)
所有循环在最大循环迭代次数上应具有静态可确定的上限。 静态符合性检查工具应有可能确认界限的存在。 允许在每个接收或处理请求的任务或线程中使用单个非终止循环的异常。 这样的服务器循环应使用C注释:/ * @ non-terminated @ * /。
因此,除了极少数例外,您永远都不要通过不提供循环迭代的上限来使代码面临无限循环的风险(关于递归,顺便说一句)。
结论
今天检查您的代码库,查找while (true)
, for (;;)
任何可能while (true)
do {} while (true);
和其他声明。 仔细检查这些语句,看看它们是否可以停止-例如,使用break
, throw
或return
或continue
(外部循环)。
您或之前编写该代码的人可能像我们一样天真,相信……
……哦,这永远不会发生, 因为,当您认为什么也不会发生时,您会知道会发生什么 。