在保持我同事的代码甚至是自称是高级开发人员的人的同时,我经常看到以下代码:
try
{
//do something
}
catch
{
//Do nothing
}
或者有时他们将日志信息写入日志文件,如下面的 try catch
块
try
{
//do some work
}
catch(Exception exception)
{
WriteException2LogFile(exception);
}
我只是想知道他们所做的是最佳做法吗?这让我感到困惑,因为在我看来,用户应该知道系统会发生什么 .
请给我一些建议 .
14 回答
最佳做法是在发生错误时抛出异常 . 因为发生了错误,所以不应该隐藏它 .
但在现实生活中,当你想要隐藏它时,你可以有几种情况
您依赖第三方组件,并且您希望在出现错误时继续该程序 .
您有一个商业案例,如果出现错误,您需要继续
您应该考虑这些例外设计指南
异常投掷
使用标准异常类型
例外和表现
https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/exceptions
对我来说,处理异常可以看作是业务规则 . 显然,第一种方法是不可接受的 . 第二个是更好的一个,如果上下文这样说,它可能是100%正确的方式 . 现在,例如,您正在开发Outlook Addin . 如果你添加了抛出未处理的异常,那么outlook用户现在可能知道它,因为一个插件失败后,outlook将不会自行销毁 . 你很难弄清楚出了什么问题 . 因此,在这种情况下,对我来说,第二种方法是正确的 . 除了记录异常之外,您可能决定向用户显示错误消息 - 我将其视为业务规则 .
我的异常处理策略是:
要通过挂钩到
Application.ThreadException event
来捕获 all unhandled exceptions ,然后决定:对于UI应用程序:使用道歉消息将其弹出给用户(winforms)
对于服务或控制台应用程序:将其记录到文件(服务或控制台)
然后我总是在
try/catch
中附上 every piece of code that is run externally :Winforms基础架构触发的所有事件(Load,Click,SelectedChanged ...)
由第三方组件触发的所有事件
然后我附上'try / catch'
ApplicationException("custom message", innerException)
来跟踪真实情况另外,我尽力 sort exceptions correctly . 有例外情况:
需要立即显示给用户
需要一些额外的处理才能将事情放在一起,以避免出现级联问题(例如:在
TreeView
填充期间将.EndUpdate放在finally
部分中)用户并不在意,但重要的是要知道发生了什么 . 所以我总是记录它们:
在事件日志中
或磁盘上的.log文件
在应用程序顶级错误处理程序中 design some static methods to handle exceptions 是一个很好的做法 .
我也强迫自己尝试:
记住 ALL exceptions are bubbled up to the top level . 没有必要在任何地方放置异常处理程序 .
可重用或深度调用的函数不需要显示或记录异常:它们要么自动冒泡,要么在我的异常处理程序中使用一些自定义消息重新抛出 .
最后:
坏:
无用:
最终尝试没有捕获是完全有效的:
我在顶层做的事情:
我在一些被称为函数中做了什么:
与异常处理(自定义异常)有很多关系,但我试图记住的规则足以满足我所做的简单应用程序 .
下面是一个扩展方法的示例,以便以舒适的方式处理捕获的异常 . 它们的实现方式可以链接在一起,并且很容易添加自己捕获的异常处理 .
最佳实践是异常处理永远不应该隐藏问题 . 这意味着
try-catch
块应该非常罕见 .有三种情况使用
try-catch
是有道理的 .始终尽可能低地处理已知异常 . 但是,如果你通常更好地练习首先测试它 . 例如,解析,格式化和算术异常几乎总是首先由逻辑检查更好地处理,而不是特定的
try-catch
.如果需要对异常执行某些操作(例如记录或回滚事务),则重新抛出异常 .
始终尽可能地处理未知异常 - 唯一应该使用异常而不是重新抛出异常的代码应该是UI或公共API .
假设您正在连接到远程API,在这里您可以预期会遇到某些错误(并且在这些情况下有所帮助),所以这是案例1:
请注意,没有捕获其他异常,因为它们不是预期的 .
现在假设您正在尝试将某些内容保存到数据库中 . 如果失败我们必须回滚,所以我们有案例2:
请注意,我们重新抛出异常 - 更高的代码仍需要知道某些内容已失败 .
最后我们有了UI - 这里我们不希望有完全未处理的异常,但我们也不想隐藏它们 . 这里我们有一个案例3的例子:
但是,大多数API或UI框架都有通用的方法来处理案例3.例如,ASP.Net有一个黄色错误屏幕,可以转储异常详细信息,但可以在 生产环境 环境中用更通用的消息替换 . 遵循这些是最佳实践,因为它为您节省了大量代码,但也因为错误记录和显示应该是配置决策而不是硬编码 .
这一切都意味着案例1(已知例外)和案例3(一次性UI处理)都有更好的模式(避免预期的错误或手动错误处理到UI) .
即使案例2可以被更好的模式替换,例如transaction scopes(回滚块中未提交的任何事务的块)会使开发人员更难以获得错误的最佳实践模式 .
例如,假设您有一个大规模的ASP.Net应用程序 . 错误记录可以通过ELMAH,错误显示可以是本地信息性的YSoD和 生产环境 中的一个很好的本地化消息 . 数据库连接都可以通过事务范围和
using
块进行 . 您不需要单个try-catch
块 .TL; DR:最佳做法实际上是根本不使用
try-catch
块 .阻塞错误是一个例外 .
首先,最好的做法应该是阻止错误 .
如果错误被阻止,则抛出异常 . 一旦抛出异常,就会出现异常情况 . 让用户知道它(您应该将整个异常重新格式化为UI中对用户有用的东西) .
您作为软件开发人员的工作是努力防止某些参数或运行时情况可能以异常结束的特殊情况 . 那就是 exceptions mustn't be muted, but these must be avoided .
例如,如果您知道某些整数输入可能带有无效格式,请使用
int.TryParse
而不是int.Parse
. 在很多情况下你可以做到这一点,而不是只说"if it fails, simply throw an exception" .抛出异常是昂贵的 .
毕竟,如果抛出异常,而不是在抛出异常后将其写入日志,最佳实践之一是在第一次机会异常处理程序中捕获它 . 例如:
ASP.NET: Global.asax Application_Error
其他: AppDomain.FirstChanceException event .
我的立场是,本地尝试/捕获更适合处理特殊情况,您可以将异常转换为另一个异常,或者当您想要将其“静音”为一个非常非常非常非常特殊的情况时(库错误)抛出一个你需要静音的无关异常,以便解决整个bug) .
对于其他案例:
尽量避免例外 .
如果这不可能:第一次机会异常处理程序 .
或使用PostSharp方面(AOP) .
在某些评论中回复@thewhiteambit
@thewhiteambit说:
首先,异常怎么可能不是一个错误?
没有数据库连接=>异常 .
要解析为某种类型=>异常的无效字符串格式
尝试解析JSON,而输入实际上不是JSON => exception
参数
null
,而对象是预期的=>异常某些库有bug =>引发意外异常
有一个套接字连接,它会断开连接 . 然后你尝试发送一个消息=>异常
......
我们可能会列出抛出异常的1k个案例,毕竟,任何可能的情况都是错误的 .
异常是一个错误,因为在一天结束时它是一个收集诊断信息的对象 - 它有一条消息,它会在出现问题时发生 .
在没有例外的情况下,没有人会抛出异常 . 异常应该是阻止错误,因为一旦他们尝试使用try / catch和exception来实现控制流,他们就意味着你的应用程序/服务将停止进入异常情况的操作 .
另外,我建议大家检查一下快速失败的范例published by Martin Fowler (and written by Jim Shore) . 这就是我总是理解如何处理异常的方法,甚至在我不久之前得到这个文档之前 .
通常例外会削减一些操作流程,并且处理它们以将它们转换为人类可理解的错误 . 因此,似乎异常实际上是一个更好的范例来处理错误情况并对它们进行处理以避免应用程序/服务完全崩溃并通知用户/消费者出错的地方 .
关于@thewhiteambit关注的更多答案
如果您的应用程序可能脱机工作而不将数据持久保存到数据库,则不应使用异常,因为使用_1226118实现控制流被视为反模式 . Offline work is a possible use case, so you implement control flow to check if database is accessible or not, you don't wait until it's unreachable .
解析事物也是预期的情况( not EXCEPTIONAL CASE ) . 如果你期待这个, you don't use exceptions to do control flow! . 您可以从用户那里获得一些元数据,以了解他/她的文化,并为此使用格式化程序! .NET supports this and other environments too, and an exception because number formatting must be avoided if you expect a culture-specific usage of your application/service .
本文只是作者的观点或观点 .
既然维基百科也可以只是关于作者的意见,我也不会这样做,但是检查一下Coding by exception篇文章中的某个段落:
它也在某处说:
异常使用不正确
老实说,我认为软件无法开发不认真对待用例 . 如果你知道......
您的数据库可以离线...
某些文件可以锁定...
可能不支持某些格式...
某些域验证可能会失败...
您的应用应该在离线模式下工作...
无论用例如何......
...... you won't use exceptions for that . 您可以使用常规控制流来查看这些用例 .
如果未涵盖某些意外用例,则代码将快速失败,因为 it'll throw an exception . 是的,因为异常是一种例外情况 .
另一方面,最后,有时您会覆盖抛出 expected exceptions 的特殊情况,但您不支持某些用例,或者您的代码无法使用某些给定的参数或环境数据/属性 .
您唯一一次应该担心用户关于代码中发生的事情,如果他们可以或需要做些什么来避免这个问题 . 如果他们可以更改表单上的数据,请按下按钮或更改应用程序设置以避免该问题,然后让他们知道 . 但是,用户无法避免的警告或错误只会让他们对您的产品失去信心 .
例外和日志适合您,开发人员,而不是您的最终用户 . 了解捕获每个异常时要做的正确事情远比仅仅应用一些黄金法则或依赖于应用程序范围的安全网要好得多 .
无意识编码是唯一的错误编码 . 事实上,你觉得在这些情况下可以做得更好,这表明你投入了良好的编码,但是避免在这些情况下尝试标记一些通用规则并理解首先抛出某些东西的原因以及什么你可以做到从中恢复 .
我知道这是一个老问题,但是这里没有人提到MSDN文章,并且实际上是为我清理了文档,MSDN上有一个very good document,当满足以下条件时你应该捕获异常:
我建议阅读整个“Exceptions and Exception Handling”部分以及Best Practices for Exceptions .
更好的方法是第二个(指定异常类型的方法) . 该这样做的好处是,您知道代码中可能会发生此类异常 . 您正在处理此类异常,您可以继续 . 如果出现任何其他异常,那么这意味着有些错误可以帮助您找到代码中的错误 . 应用程序最终会崩溃,但你会发现你错过了一些错误(bug)需要修复 .
第二种方法是好方法 .
如果您不想通过显示与它们无关的运行时异常(即错误)来显示错误并使应用程序用户感到困惑,那么只需记录错误,技术团队就可以查找问题并解决问题 .
我建议您为整个应用程序选择第二种方法 .
留下空白挡块是最糟糕的事情 . 如果出现错误,最好的处理方法是:
将其登录到文件\数据库等 .
尝试动态修复它(也许尝试其他方式进行该操作)
如果我们无法解决此问题,请通知用户存在一些错误,当然会中止操作
没有任何参数的
catch
只是 eating 例外并没有用 . 如果发生致命错误怎么办?如果你没有参数使用catch,就无法知道发生了什么 .一个catch语句应该捕获更多的 specific 例外
FileNotFoundException
然后在 end 你应该捕获Exception
,这将捕获任何其他异常并记录它们 .有时您需要处理对用户不说任何内容的异常 .
我的方式是:
在应用程序级别(即在global.asax中)捕获关键异常的未处理异常(应用程序无用) . 这些事情我没有 grab 这个地方 . 只需在应用程序级别上记录它们,让系统完成其工作 .
Catch "on place"并向用户显示一些有用的信息(输入错误的数字,无法解析) .
赶上地方,对"I will check for update info on the background, but the service is not running"等边缘问题什么都不做 .
它绝对不一定是最佳实践 . ;-)
使用例外,我尝试以下方法:
首先,我捕获特殊类型的异常,例如除零,IO操作等,并根据它编写代码 . 例如,除以我可以提醒用户的值的证明(例如,在中间计算中的简单计算器(不是参数)到达零除零)或者静默处理该异常,记录它并继续处理 .
然后我尝试捕获剩余的异常并记录它们 . 如果可能,允许执行代码,否则警告用户发生错误并要求他们邮寄错误报告 .
在代码中,这样的事情: