我很困惑,因为我已经在很多地方读到了所谓的“骨头”异常(由代码错误引起的异常)不应被捕获。相反,必须允许它们使应用程序崩溃:



烦恼异常,作者:埃里克·利珀特

在Eliding Async and Await下的注释,作者: Stephen Cleary

下面的答案使用自定义异常是一种好习惯吗,Draco18s不再信任SE

以上三个人中至少有两个人是权威。

我很惊讶。尤其是对于某些(重要!)用例,例如服务器端代码,我根本看不到为什么捕获这样的异常次优以及为什么必须允许应用程序崩溃。

就我而言我知道,在这种情况下,典型的解决方案是捕获异常,将HTTP 500返回给客户端,并拥有一个自动系统,该系统向开发团队发送紧急电子邮件,以便他们可以尽快解决问题-但是不会使应用程序崩溃(一个请求必须失败,在这里我们无能为力,但是为什么要取消整个服务并让其他人无法使用我们的网站呢?停机会造成巨大的损失!)。我不正确吗?

为什么我要问-我一直在努力完成一个爱好项目,这是.net核心中基于浏览器的游戏。据我所知,在许多情况下,框架为我提供了开箱即用的功能,这正是Eric Lippert和Stephen Cleary所建议的! -也就是说,如果处理请求引发,则框架会自动捕获异常并防止服务器崩溃。但是,在某些地方,框架没有这样做。在这样的地方,我用try {...} catch {...}包装自己的代码以捕获所有可能的“骨头”异常。

此类任务之一是AFAIK,是后台任务。例如,我现在正在实施后台禁令清除服务,该服务应每隔几分钟清除所有过期的临时禁令。在这里,我什至使用了几层全能的try块:

 try // prevent server from crashing if boneheaded exception occurs here
{
    var expiredBans = GetExpiredBans();
    foreach(var ban in expiredBans)
    {
        try // If removing one ban fails, eg because of a boneheaded problem, 
        {   // still try to remove other bans
            RemoveBan(ban);
        }
        catch
        {

        }
    }
}
catch
{

}
 


(是的,我的catch块现在为空-我知道忽略这些异常是不可接受的,在我的TODO列表中永久添加一些日志记录)

已经阅读了我上面链接的文章,我我再也不能毫无疑问地继续这样做了……我不是在朝自己的脚开枪吗?为什么/为什么不呢?

如果以及为什么不应该抓住笨拙的异常?

评论

这有点过时,但是我强烈建议尽快修复所有空的捕获块。当出现问题而又不知道发生在何处和何处时,任何此类空白块都会使您花费数小时。只需立即添加任何日志记录即可,例如,编写一个琐碎的类并在某处打印静态方法....稍后切换到更好的方法是不重要的。

“永远将日志记录在我的TODO列表中”正好是您永远不应该捕捉愚蠢异常的原因。我还会争辩说永远不要在任何一个中添加// TODO评论。您永远也不会真正回到正确地处理任何一个。 (最近我被遗留代码浪费了大约2天,而遗留代码将NPE吞没在一个空的catch块中。)
严重的问题:为什么GetExpiredBans和RemoveBan中的try-catch不起作用?这里的假设似乎是“这些可以在任何时间任意失败,我们总是可以忽略该失败”。好的,如果是这种情况,则将try-catch放入其中,然后调用者不需要它们。为什么您决定将try-catch放入呼叫者而不是被呼叫者?

第二个严重问题:这些捕获块多久执行一次?也就是说,您真正多久捕获一次“骨头”异常(例如空取消引用)?如果答案是“每年零次”,那么捕获块是不必要的。如果答案不为零,则修复所有错误,直到答案为零,然后答案将为零。

我觉得必须指出一些我在此页面地址上没有看到的答案或评论的内容。是的,您应该允许发生严重的错误,因为隐藏错误意味着在代码中隐藏错误。但是,它们之所以被称为“骨头”,是因为它们是您的错,一开始就不应该发生。如果发现服务器抛出了严重错误,则不要仅将其包装在try / catch中并尝试“正常失败”。重写代码,使它永远不会被扔掉。

#1 楼

沉默而致命地

编写企业软件时,您最终将学到一个基本真理:世界上最严重的错误不是导致程序崩溃的错误。世界上最严重的错误是导致您的程序默默地产生错误答案的错误,该错误答案未被人注意,但最终产生了巨大的负面影响(对您的雇主造成严重的财务影响)。因此,错误消息和崩溃是A Good ThingTM,因为它们表明您的程序检测到了问题。

令人惊讶的宽限期

现在,这似乎与另一种企业美德相冲突,这是“优雅降级”。炸毁而根本不返回任何响应几乎看起来不像是“优美的降级”。这就是为什么许多人会尽力返回一些回应的原因。确实,这就是为什么许多框架(如Spring)会捕获所有顶级异常,并按照您的描述将它们包装为500。总的来说,我认为这还可以。毕竟,如果您仅可以终止/重启服务器线程,大多数例外实际上并不需要重启整个应用服务器。出于理智的原因,理智的框架将谨慎处理以免捕获Java Errors(如OutOfMemory)。

还有一点需要考虑:一旦超越了单个服务器,您可能会承受重担服务前的平衡器。并且当LB超时或获得关闭的连接时,通常会向其客户端返回500。因此,LB通常会自动将您的“服务器崩溃”转换为客户端5xx!两全其美。

最坏的情况

在您的情况下,如果不捕获异常,最糟糕的情况是什么?您的回答:“好吧,我的游戏服务器死了,没人能玩!!!”但这不是最坏的情况。最坏的情况是,每个人都在玩您的游戏,但悲痛者正在破坏它。玩家提交了一个错误报告,并告诉您禁令不起作用,但您可以查看日志,一切看起来都很好。否则,合法玩家将被追悼者禁止,而该禁令将无限期地持续下去,因为您的服务器很乐意忽略故障,因此禁令将无限期地持续下去。最糟糕的事情不是您的游戏崩溃了。这是您的玩家信任崩溃。祝您尝试重设。

评论


极好的答案。有了安全关键系统的背景知识,我可以为您的论点添加一个方案:即使在一架飞机最好使计算机坠毁,使飞行员没有任何信息,而不是冒出显示错误信息的风险。该问题必须在完全不同的级别上解决,例如通过冗余。

–哈特穆特·布劳恩
20 Jan 5 '20 at 10:15

这取决于实际情况。我正在考虑Ariane V的首次发射。当违规问题对任务绝对没有影响时,一个不受限制的例外就落到了火箭上。当真正的硬件受到控制时,推铲有时是错误的答案。 (导致/ 0错误的计算实际上仅是坐在垫板上时才有价值,在飞行中变得毫无意义。)

–Loren Pechtel
20 Jan 6 '20 at 3:43

@HugoZink通常是一个好主意,但取决于应用程序和问题。如果它影响单个请求,请记录该日志,警告sysadmins,然后将错误值返回给客户端。如果某种原因导致整个系统变得不可靠(内存错误,核心配置损坏等),请关闭系统。没有一种万能的解决方案。

– jwenting
20年1月6日上午10:11

第二种最坏的情况是您的服务器始终处于关闭状态,因此您失去了玩家的信任。哪个更糟:让食尸鬼一天横冲直撞,还是没人一天玩?这可能取决于游戏的持久状态。如果这是一场Quake风格的FPS,两回合之间没有任何保留,那就是“嘿,还记得其他团队有一半时间可以飞行吗?”。如果是Minecraft风格的建筑游戏,它将是“嘿,还记得我们失去了三个月的时间吗?”

–user253751
20 Jan 6 '20在11:36



我可以想到比这更糟的案件,涉及诉讼和刑事指控。

–橙色狗
20年1月6日在14:16

#2 楼

如果系统处于无法恢复的未定义状态,则应允许异常使系统崩溃。如果无法将系统恢复为确保数据完整性和安全性的已定义状态,则会崩溃,因此系统可以重新启动到该已定义状态。

每当您捕获异常时,您都将采取自己做所有恢复的责任。仅在绝对确定系统仍处于定义的状态并且无需执行任何操作时,才应将捕获保留为空。当调用代码对系统状态的理解比引发异常的代码更好时,就会发生这种情况。

这就是全部。愚蠢地称异常为无礼。在某些情况下,您根本不知道发生故障时系统状态会发生什么,因此您抛出并让调用代码找出来。如果调用代码无法弄清楚它,那么您将崩溃并让操作员解决它。

现在,如果您以某种方式知道失败不会使系统进入不良状态,那么您不会不需要放在第一位。投掷从来都不是不能完成要求您做的唯一方法。您可以返回-1,或者返回空字符串,或者返回null(ick),或者返回一个空对象,或者返回一个空集合,或者返回一个“也许”单子,或者打印“您的公主在另一个城堡里”,或者什么都不做,悄悄。是的,我知道。但是有时候没关系。为什么?因为有时不执行所要求的操作是正确的,可预期的且不有趣的。

评论


当崩溃使您意识到该异常时,更容易解决导致异常的问题(错误)。如果您默默地捕获异常并继续进行下去,则最终可能掩盖了一个问题,以后这个问题将变得更大。

– 1201ProgramAlarm
20年1月4日在20:20

您说的话似乎很有道理,但是...不过,在服务器代码中遵循此建议的结果是,我们没有返回HTTP 500并继续处理其他请求,而是使整个网站瘫痪了! AFAIK Web应用程序通常选择第一种行为,而不是第二种? (除非通常Webapp的一半代码是错误恢复代码?)

–gaazkam
20年1月4日在20:51

@gaazkam不正确。当网站向您发送500时,它之所以这样做是因为它已经恢复,并且该恢复的一部分是让您知道您认为自己打算完成的任何工作都已消失。如果它还没有恢复到安全的稳定状态,那么它应该做的最后一件事就是告诉您。

–candied_orange
20年1月4日在22:20

@candied_orange因此,通常,服务器端代码错误恢复代码的一半是?后端作者是否试图预见他们可能引入的每个可能的错误,并编写代码以从每个可能的错误中恢复以避免停机?

–gaazkam
20年1月4日在22:59

调用笨拙的异常毫无意义是不正确的。我故意称它们为笨拙的异常。滑稽的粗鲁是重点,因此这个词将令人难忘。您可能尚未内部化关于头脑异常的想法。总是可以避免发生异常的异常,并且几乎总是表明程序员忘记处理有效的值或代码路径。这些缺陷中的一些显然是微妙且难以发现的,这就是为什么它们在野外持续存在的原因,这是幽默的一部分。

–埃里克·利珀特
20年1月6日在15:31

#3 楼


问自己的唯一问题是:
“程序可以明智地继续吗?”
>“服务器”是处理消息的程序。通常,每个消息大多独立于其他消息。也就是说:如果对消息进行错误处理,则继续接收和处理将来的消息是很有意义的。
这将适用于HTTP服务器,作业服务器等。
某些错误可能不是明智地可恢复,例如OutOfMemory。但是,如果您可以从错误中恢复(包括可能以某种方式记录该错误),则应该这样做。

评论


我会说“明智地做些事情”而不是“明智地继续”-因为有明智的行为,例如“记录错误,然后退出(并可能通过服务监督重新启动)”可以称为“恢复”,所以很难在过程寿命范围内,以临时或实际术语或技术术语“连续”进行。

–mtraceur
20年1月6日在22:21

骨头破例的例外可能使人们对继续下去的敏感性产生怀疑。我的意思是,如果某人从自己的代码中获取异常而无法按预期方式工作,则在该代码可能会破坏用户帐户数据库的情况下,仅继续调用该代码似乎很危险。例如,在问题中,他们尝试{RemoveBan(ban); } catch {},大概用用户帐户修改数据库;如果由于编程逻辑错误而抛出该异常,那么该代码究竟在做什么,并且可以信任继续修改数据库吗?

–纳特
20年1月6日在22:21



@Nat,如果其他操作依赖于RemoveBan(),则它们不应继续。

– Paul Draper
20年1月7日,下午5:52

您怎么知道该程序可以明智地继续?如果那是因为您仔细评估了该错误源,那么它不再是“骨头”异常。如果这是您未计划的,则您的程序正在执行您未曾预期的事情,并且现在处于意外状态。没有告诉我们什么不变变量不再存在,什么东西可能处于不一致状态,以及……如果不理会数据损坏多久而不会引起注意,而不是由服务监督触发干净的重启。

–光谱
20年1月8日,0:03

@spectras,您所说的分层可以像堆栈层一样简单;即函数调用。没有更多的理由怀疑ArrayIndexOutOfBoundsException破坏了整个进程,而进程崩溃则破坏了整个操作系统。 (这样做可能会有副作用,但这并不常见。)

– Paul Draper
1月1日在1:49



#4 楼

你是绝对正确的。服务器端应用程序代码中的异常(是否为“骨头”)不应使Web服务器崩溃。

之所以感到困惑,是因为这些文章不清楚“捕获”或“崩溃”的实际含义。如果我们遵循建议绝不捕获“骨头异常”,那么单个应用程序错误应该冒出来并导致整个操作系统崩溃。这不是任何人想要的!

相反,您应该将系统视为多层隔离。一层意外崩溃不应导致外层崩溃。操作系统将隔离单个进程中的崩溃,并且不会让一个进程中的崩溃导致其他进程或整个OS崩溃。 Web服务器通常会将崩溃隔离在单个请求中,而不让它们影响任何其他请求。

应用程序本身可能具有多层。例如。支持插件的应用程序可能会隔离单个插件中的崩溃。如果插件崩溃,则会禁用它,但应用程序的其余部分将继续。从Web服务器的角度来看,Web请求是独立的。一个请求中的崩溃不会导致任何其他进程失败或接收无效的输入。级别(框架)捕获它们。

在您自己的代码中捕获笨拙的异常并没有任何意义。异常异常意味着代码部分中存在意外错误。换句话说,代码做错了什么,但是您不知道到底是什么或为什么。最好的情况是,执行代码时什么也不会发生,最坏的情况是某些状态被破坏,这可能导致系统的其他部分行为错误并破坏更多的状态。

因此,如果代码检测到RemoveBan方法中存在错误……为什么要再次执行它?我无法想象任何情况下您都想重复执行某些您知道做错了事情的代码。

评论


“如果我们遵循从不捕获“骨头异常”,那么单个应用程序错误应该冒出来并导致整个操作系统崩溃。”抱歉,这完全是错误的。异常不能冒出来。对于内核而言,抛出,堆栈展开,捕获只是一堆机器代码,就像它从二进制文件加载的其余代码一样。进程的内部状态(由于错误而可能不一致)与内核无关。对于内核来说,分配给进程的只是一些内存页面。这种分离是我们使用虚拟内存的主要原因。

–cmaster-恢复莫妮卡
20年1月6日在20:36

@ cmaster-reinstatemonica显然地,.net异常永远无法逃脱CLR。我的观点是,即使Web服务器是用.net编写的,您也不会允许请求处理程序中的异常关闭整个服务器。您确实想捕获所有异常,甚至是头脑异常的异常。您只需要在正确的级别上进行操作即可。

–雅克B
20年1月7日在13:27

#5 楼

您学到了一件重要的事情:每当您在互联网上阅读必须绝对遵守的规则时,就必须开始思考并自己决定是否应在特定情况下遵守该规则。而且,您还应该考虑所理解的规则是否是一个好规则。

您可以考虑以下规则:除非绝对必要,否则服务器绝不应该崩溃。另一方面,如果您确保没有实质性的数据丢失,则绝对允许客户端崩溃,用户通常甚至不会注意到。

因此,对于客户端而言,崩溃对根本异常的响应完全不是错误的响应。对于服务器,这是一个较差的响应。尽管如此,您仍然需要权衡:您有一个完全对您而言是意外的异常。您不知道如何正确处理它(报告异常未处理它)。当您知道发生严重错误时,继续操作可能会对您造成多大的损失?因此,您是否要继续操作,而造成的损失未知且可能无限,是否要使服务器崩溃,从而造成某些损坏,还是要重新启动服务器(不是崩溃,而是要尽可能缓慢地重新启动服务器)。

评论


请注意,即使在服务器上,“您是通用开发人员”通常也不应捕获这些异常。您的框架应该为您做到。

–克莱里斯-谨慎乐观-
20 Jan 5 '20 at 4:41

我不确定是否允许客户端崩溃而用户没有注意到我是否同意。如果我没记错的话,应用商店会记录一次应用崩溃的次数,如果您的应用崩溃了,它将大大降低您的外观排名。对于游戏而言,崩溃是绝对不能接受的,对于涉及多人游戏/直播内容的任何事情,崩溃甚至更是如此。另一方面,服务器可能会具有监视服务,该服务至少会在崩溃后重新启动服务器,因此事情并没有那么黑与白,因为这听起来很合理

–火星
20年1月6日,下午1:48

@Mars一个客户端实际上崩溃是一件坏事。停止,显示一些单按钮错误模式对话框,并且在没有用户输入后返回到起始状态(屏幕)。

– beppe9000
20年1月7日在19:46

@ beppe9000解决了崩溃=排名较低,但用户体验变化不大的手机问题。这也将要求您首先捕获笨拙的异常

–火星
20年1月8日,0:12



我刚刚意识到,“必须绝对遵循”应该是“必须'绝对'遵循”。前者暗示是的,绝对必须遵循(在这种情况下,与您的具体情况无关!),后者暗示这是一个有疑问的主张

–火星
20年1月8日,0:15



#6 楼

我认为您没有意识到的是,错误的现实后果可能比仅使服务器宕机严重得多。例如:


删除对公司运作必不可少的数据库
允许访问不应被授予的机密信息
批准财务不应该批准的交易
不批准应该批准的金融交易
在计算机控制下销毁物理设备
在计算机控制下杀死患者接受放射治疗
引起飞行控制问题导致两架飞机坠毁,造成346人丧生

好吧,这份清单有点稻草人。忽略异常只是许多可能产生的错误,它们可能会产生可怕的后果。但是请务必记住,引发异常是在告诉您有关程序运行的一个或多个假设是无效的,并且您确实需要考虑这样做的可能后果。

由于您正在编写一项业余爱好游戏,几乎不可能获得这些成果,因此您可以吹牛ra而忽略此问题。但是,如果您编写此业余项目的部分原因是要教育自己,并为编写供他人使用的软件做准备,那么您必须仔细考虑一下这些内容,并练习编写代码,就像其功能正常一样。很重要。是的,这还有很多工作。

不,您不必为每个可能的异常编写处理程序和恢复代码。您可以处理那些您认为重要并且知道如何处理的问题。但是,除非您绝对确定异常不会影响程序的持续正确运行,否则必须将其传递给下一个更高级别的控制。这使它有机会恢复或中止程序并防止错误的功能。

评论


或者在Windows中,让用户单击“确定”以非法传递Microsoft保证不会使用的信息。

– WGroleau
20 Jan 5 '21在21:52



#7 楼


我很惊讶。特别是对于某些(重要的!)用例,例如服务器端代码,我根本看不到为什么捕获这样的异常次优以及为什么必须允许应用程序崩溃。


崩溃没有错,实际上,尽早使应用​​程序崩溃可能非常有帮助。 @PaulDrappers回答“程序可以合理地继续吗?”确实是查看它的关键方法。

例如,如果我的应用程序配置不正确-那么我希望它在启动时立即崩溃。通过快速而明显地失败,这使开发人员/ DevOps可以快速识别并纠正问题。另一种方法是返回500个错误,这种情况不太明显,如果间歇性检查甚至可以通过健康检查,直到客户投诉为止。

另一方面,如果我在单个请求中偶尔遇到错误,我通常会“崩溃”单个请求(例如返回500),否则,让应用程序继续运行。毕竟,单个数据库超时通常不应使网站崩溃。

尽管这取决于应用程序-如果它在容错管道(例如某种作业或消息处理器)中一次处理单个任务,则让应用程序死掉可能是适当的(例如,协调器可能有我们要遵循的失败策略。)

无论什么情况,我都会尝试记录每个错误,无论它是否被处理或冒泡。

评论


触摸。这也是一个有趣的事实:您的服务器有时会以计划外的方式崩溃:)以及其他所有情况。

– Kubanczyk
20 Jan 5 '20在:55



#8 楼

您的示例准确地说明了为什么您不应该捕获“所有可能的”异常。

如果GetExpiredBans调用失败,则代码将继续执行,就好像成功了一样。 Unbanner服务器已启动并正在运行,并且看起来不错,但实际上根本无法工作。

现在,如果您知道RemoveBan由于网络问题而偶尔失败,那么您可以捕获该特定异常并重试,或者跳过该播放器并转到下一个播放器。这是一个可以理解的问题,您知道所需的行为。

如果您的代码引发了您不希望的异常,那么最好停止并亲眼看看。您提出HTTP服务器返回500s而不是崩溃的情况。

该服务器正在运行外部程序,即您的网页。不是Web服务器上有错误,而是其他人的代码。如果运行有错误的程序,您不会指望DOS崩溃,返回错误代码并继续前进与该页面崩溃相同。

评论


我敢冒险说,自动禁止用户之类的次要功能失败不会严重导致服务器崩溃,尽管严重到足以向系统管理员发送电子邮件警报(或者可能是SMS或Twitter消息)来看看。

– jwenting
20年1月6日上午10:13

@jwenting您对未知异常的后果和原因的假设,这是问题的根源。添加发送紧密循环的短信只会加剧此问题

–伊万
20年1月6日上午10:21

在这种特殊情况下,发生意外错误的理想结果是服务器继续运行,用户不受限制,并且管理员/开发人员得到了一些问题的通知,因此他们希望可以在下次自动解除限制之前对其进行修复周期。

–user253751
20年1月6日,11:33

@ user253751当然,实现此目标的最佳方法是使程序崩溃并通过您的监视解决方案生成警报。如果“服务器继续运行”是不希望的,因为它每次都在循环中遇到相同的错误

–伊万
20年1月6日,11:45

@Ewan为什么那不是理想的?

–user253751
20年1月6日,11:47

#9 楼

我个人会处理所有例外情况。

并通过电子邮件将其发送给自己。

不必通过电子邮件发送,您可以将其记录下来,但是如果您不知道这些错误在发生什么,

通过各种方法,请尝试恢复错误或掩盖错误-对用户而不对您自己。

对于AJAX请求,这可能意味着发送500响应。对于GUI,经典的MicroSoft“发生了不好的事情”(隐含的意思是,“我们的编码人员知道这是什么,但我们不会告诉您”)。

IMO,您需要通知您的“用户”,无论是人还是软件,但您也需要告知自己开发/维护团队。这些动作可能是相同的动作,也可能是两个不同的动作,但都需要告知。

#10 楼

实际上,有两种不同类型的异常需要以不同的方式进行处理。

首先,存在“非骨头”异常,例如“找不到文件”。这些异常是可以预料的,即使您事先检查文件是否存在,也不能证明使用时会存在该文件。

这些通常会尽可能地降低/降低特定性,每个人都已经理解了这一点,因为每个人都说要处理“异常”,因此我们忽略它。

另一个是意外的异常。这涵盖了编程错误和某些您不希望遇到的情况错误(例如,操作系统从您的下方撕裂了磁盘)。这些就是您所说的“骨头”异常,但是它们确实会发生。

重要的是,这些异常不会被忽略。如果让团队关注的唯一方法是使应用程序崩溃,那么请这样做,但是,如果您可以通过另一种方法来吸引他们的关注,我建议在每个主线程循环中捕获最通用的“ Exception”类型并处理这样可以引起团队的注意,然后尝试继续。您会惊讶于这种情况经常使您的应用程序在修复时能够完美运行。它甚至允许我从内存不足的情况中恢复过来。

与仅以引起注意的方式进行处理相比,不赶上它真的不好。

也非常重要:

在Java中(至少)默认情况下,当线程引发异常时,它会被静默吃掉,它不会使您的应用程序崩溃或没有任何迹象表明您的程序部分失败,但是如果那是一个长期运行的线程,它提供的所有功能都完全(看不见)消失了!

这可以通过安装默认的异常处理程序来解决,但是要小心,因为如果没有处理程序或try / catch,仅允许这种异常以静默方式杀死线程是最糟糕的解决方案-这可能是您最昂贵的事情给另一个开发人员(或您自己!)花了几周的时间来查找线程和空捕获占用的异常!

#11 楼

异常分为两大类:由程序员滥用API引起的异常(例如,无效参数)和由外部原因引起的异常(例如,未找到文件)。应始终捕获来自外部原因的异常;永远不要发现因滥用API而引起的异常。这是因为从一开始就避免出现此类错误是现实可行的,并且由于此类错误通常会立即显现出来,因此应予以识别和修复,而不是在代码中进行处理。

当您拥有语言支持(即,检查的异常与未检查的异常)或使用能够明确区分它们的API(即,不同的异常基类)时,这些类别的异常要容易得多。在语言和IDE中避免API的滥用也容易得多。它们提供对完整文档(例如IntelliJ IDEA中的Java / Javadoc)的即时访问。在仅提供对文档的严重受限摘要(例如C#/ Visual Studio)进行即时访问的语言和IDE中,要困难得多。根据我的经验,良好的IDE支持可以立即访问完整的文档,从而大大减少了API滥用错误的数量。 br /> Co请立即参阅文档,以避免滥用API。推论:避免使用文献记载不足的API。
不要捕获由于滥用API而引起的异常。结论:避免使用不能清楚地区分误用和外部错误的API。


评论


内存不足是外部还是滥用?那要看。格式错误的数据是外部的还是滥用的?可能是前者,但后者可能是正确的。

–重复数据删除器
20年1月6日19:43



@Deduplicator:是的。在现实世界中,许多程序需要处理来自潜在不可靠或不可信来源的数据。有时在处理之前对数据进行消毒是有意义的,但是在某些情况下,不进行处理就对数据进行验证与在检查整个过程中的有效性时处理数据的工作量基本上是一样的,而在处理之前对有效数据进行消毒将浪费工作。

–超级猫
20年1月6日在22:01

@Deduplicator API设计人员应回答这些问题,并相应地区分异常。

– StackOverthrow
20年1月6日在23:08

@ user560822不幸的是,API设计人员通常根本没有要决定的数据。因此,较低层发信号通知错误而不是处理错误。

–重复数据删除器
20年1月6日在23:13

@Deduplicator当然可以。为了使用您的示例,API设计人员确定并记录谁应该执行验证。如果他们让低层执行验证(通常会这样做),则API将抛出异常,指示外部错误。如果较低层可能抛出的异常是错误的类型,则表明此错误,那么好的API会将其转换为正确的类型(捕获,包装和重新抛出)。

– StackOverthrow
20 Jan 6 '20在23:24

#12 楼

您不知道如何在特定地点处理的异常不应在那里处理。应该将它传递到调用堆栈,直到可以正确处理为止。如果在任何地方都无法预期,那么至少可以在某个地方取消操作而不会导致“错误答案”或崩溃。

#13 楼

异常(不管是否有骨头)都是异常
如果有特殊情况,按照定义,您没有考虑过,也不知道如何处理。
这样想。商店结帐处的销售助理知道如何销售商品,进行交流等。
他们不知道如何处理记者采访。正确的方法是向其经理抛出异常。他们的经理可能知道如何处理,如果可以,将捕获异常并处理记者采访。否则,经理应该怎么办?
将异常传递给经理。最终(如果没有更早处理),这将到达首席执行官/企业所有者/董事,必须在那里处理。即使他们可能也不知道。在这种情况下,异常会逃逸,并且无法处理记者面试。
最糟糕的情况是,由不知道如何处理记者的人之一来处理记者面试。想象一下,报告员报告的销售助理或本地经理的措辞不佳?
商业软件,无论是在办公室中还是在在线游戏中,都在声誉上以及对财务上造成了巨大破坏。本质上,该软件只是使流程自动化,而该流程原本必须由实际的工人手动完成。因此,您希望它表现得像一个有礼貌的员工。它只应处理具有良好定义的响应/活动的情况。其他所有内容都应移交给其经理(通常是其支持团队)以进行澄清(新代码)和/或采取行动(手动任务)。

评论


但是我们确实知道,如果在删除过期禁令方面出了什么问题,那么让游戏继续进行,而让那些玩家被禁,比崩溃会更好。我们也知道,如果这很可能破坏数据库,那么最好是使游戏崩溃而不是破坏数据库。

–user253751
20年1月6日,11:34

嗯...不。知道理想的属性与知道状态以及可以做些什么不同。我知道我的计算机可以按照我的意思去做,但这并不意味着它不会像我写的那样去做。最好不要仅仅因为某些无知的原因就取消禁令,而杀死系统。但是,究竟是哪些无理的原因?记不清?渐进锁定失败? DiskSpaceFull? DivideByZero? AssemblyNotFound?这些中的任何一个都可以阻止禁令的取消,究竟哪个使系统处于良好状态?尝试任务架构。

– Kain0_0
20年1月6日于13:42

上述所有的。如果其中任何一个是实际游戏的问题,则将记录禁令解除的问题,然后相同的问题将导致实际游戏崩溃(并记录)。

–user253751
20年1月6日在14:23

...或者您的方案中有一些信息被您排除在外,例如记录了吞下的异常...或者您非常幸运。不能保证会引发这些错误,请考虑AssemblyNotFound。缓存层可以保留该知识并解决问题。假设在游戏代码执行之前失败的列表上的禁止删除操作失败,则该缓存已准备就绪。稍后,当您清理过期的禁止列表,然后在某个时候重新启动服务器时,所有游戏服务器都会开始出现故障。再说一次,哪一个例外是愚蠢的?

– Kain0_0
20年1月6日在22:36



为什么每次调用丢失的程序集时都不会出现AssemblyNotFound异常?

–user253751
20年1月7日,11:09

#14 楼

要增加一点风味:

虽然应该允许程序失败,但也没有必要使其灾难性地失败。

如果您的语言允许您创建自己的程序,自己的例外,一种很棒的方法是执行以下操作(Python):

 # Exception is the base "user" exception class
class MyAppException(Exception): pass
class MySpecificError(MyAppException): pass
class MyOtherError(MyAppException): pass

...

if __name__ == '__main__':
    try:
        do_it()  # or whatever starts your app
    except MyAppException as e:
        # This catches any exceptions that *you* raised, and
        # is entirely the programmers fault for not catching before here!
        print('Well, we forgot to catch *this* exception:', e)
    except Exception as e:
        # This catches anything exceptions that may or may or may not
        # be within the programmers control, but include things like
        # KeyErrors or IndexErrors
        print('This was unexpected:', e)
    except:
        # Maybe not this one - it catches SystemExit & others. But, you *could*.
        print('Well, this was very unexpected')
    finally:
        # Do some cleanup stuff - close sockets, or whatever, and then quit
        exit(1)  # or some other failure code - it's up to you.
 


如果您想真正使崩溃崩溃,也可以在Python中重新引发异常。

采用这种方法,您会知道:


如果错过了开发人员应该知道的任何例外情况
如果您错过了可能应该知道的任何例外情况,但是却没有想到。
如果还有其他事情完全失败了-太阳耀斑或某些真正超出您控制范围的东西。但是,您不必非得死掉就死-可以倒抽一口气,“好吧,那是出乎意料的!”弯腰之前。以我的经验,这甚至是一个好主意,因为您可以尝试修复它或解决问题。或者只是确定它确实是无法恢复的,而您只是要就此进行警告。

评论


关于python,我最喜欢的事情之一是它已经默认完成了我想要的工作。未捕获的异常将向stderr写入回溯,并使用返回代码1退出进程。只有当我想通过特殊用途的集成“大声回溯并消亡”时,我才捕获到Bear异常。对于* nix系统,集成了stderr和rc!= 0,crontab将在失败时向stderr发送邮件,系统主管将stderr写入日志文件并记录失败,大多数(基于shell的)任务运行者都很好地理解了此行为。

–雷神召唤师
20年1月7日,下午4:36

#15 楼

您必须考虑处理异常的原因/目的,例如:


执行某种纠正或恢复操作,以便应用程序可以有意义地继续运行;这意味着该异常及其后果已得到很好的理解
执行某种清理,以便将应用程序还原到一致状态;再次,必须充分理解异常的后果
捕获有关异常的信息以进行日志记录和以后的分析
要优雅地失败,例如在Windows中发出某种用户友好/对用户有意义的消息适当的UI,而不是异常直接报告的任何原始消息或代码;在此过程中,通常最好捕获更多技术细节以供开发团队进行分析。

如果异常处理程序没有有用的用途,则该处理程序不应存在,并且应该允许异常“冒泡”。

“ Bonehead”异常表示最初对应用程序进行编码时未预料到的条件。意外异常应尽可能多地捕获细节,以便能够确定原因并采取适当的措施。这可以由编程框架自动完成。如果是这样,那么您可能不想显式处理它们。

#16 楼

我知道我参加晚会很晚,但是我认为现有答案中缺少一些细微差别。引用众多武术电影中的圣人建议:


阻止拳打的最好方法是不要出现


它将其用于软件工程中,我们可以这样修改引号的用途:


处理异常的最佳方法是永远不要将它们放在首位


即使您提供的第一个链接中从未明确指出它,但从本质上讲,这就是作者试图引导读者前往的原因。
说明为什么异常被认为是不好的:


它们要从中恢复很沉重(堆栈信息需要花费一些时间才能放松)
它们从未设计用于正常程序流
它们旨在让您做您尽最大努力进行恢复


这种恢复可能只是在关闭之前关闭打开的资源并释放锁



,则异常不应传播给用户,尤其是在网络环境中:


它公开了应用程序的内部体系结构,使其容易受到将来的攻击
对于Web,您将返回500个未定义的服务器,而不是像400 Bad Request或404 Not Found这样的更合适的响应错误

那么我们该怎么办?


尽可能地减少对异常的使用(即返回布尔值的TryParse而不是引发异常的Parse)被抛出
如果您有外部依赖项,请在启动期间在代码中验证您的环境-最好是启动失败,然后在运行时确定该服务或应用程序仍然无法正常工作。


#17 楼

这再简单不过了。

只有3种类型的代码:


永远不会抛出异常的代码。
您知道的代码生成异常。例如,连接到外部RPC服务器时,有时有时会超时。您应该为这种类型的代码添加try...catch
您没想到但会引发错误的代码。由于您不希望它会引发错误,因此无处可添加try...catch,因为您无法添加无限数量的try...catch,这根本不可能。一旦发生错误,您只需调试并修复即可。

这有多难?