如何使用try catch进行exception处理是最佳实践
在维护我的同事代码的同时,即使是自称是高级开发人员的代码,我也会经常看到以下代码:
try { //do something } catch { //Do nothing }
或者有时他们将日志logging信息写入日志文件,如下面的try catch
块
try { //do some work } catch(Exception exception) { WriteException2LogFile(exception); }
我只是想知道他们做了什么是最好的做法? 这让我感到困惑,因为我认为用户应该知道系统会发生什么。
请给我一些build议。
我的exception处理策略是:
-
通过挂接到
Application.ThreadException event
来捕获所有未处理的exception ,然后决定:- 对于UI应用程序:用一个道歉消息(winforms)将其popup给用户
- 对于服务或控制台应用程序:将其logging到文件(服务或控制台)
然后我总是附上在try/catch
外部运行的每段代码 :
- Winforms基础结构(Load,Click,SelectedChanged …)触发的所有事件
- 所有由第三方组件发起的事件
然后我附上“try / catch”
- 我所知道的所有操作可能无法正常工作 (IO操作,可能的零分割计算…)。 在这种情况下,我抛出一个新的
ApplicationException("custom message", innerException)
来跟踪真正发生的事情
另外,我尽我所能正确sortingexception 。 有一些例外:
- 需要立即显示给用户
- 需要一些额外的处理来将它们放在一起,以避免级联问题(即:在
TreeView
填充过程中,在finally
节中放置.EndUpdate) -
用户不关心,但知道发生了什么是重要的。 所以我总是logging他们:
- 在事件日志中
- 或者在磁盘上的.log文件中
devise一些静态方法来处理应用程序顶级error handling程序中的exception是一个很好的做法。
我也强迫自己尝试:
- 记住所有exception都会冒泡到顶层 。 没有必要把exception处理程序放在任何地方。
- 可重复使用或深层调用的函数不需要显示或loggingexception:它们要么自动冒泡,要么在exception处理程序中用一些自定义消息重新抛出。
最后:
坏:
// DON'T DO THIS, ITS BAD try { ... } catch { // only air... }
无用:
// DONT'T DO THIS, ITS USELESS try { ... } catch(Exception ex) { throw ex; }
终于没有抓住一个尝试是完全有效的:
try { listView1.BeginUpdate(); // If an exception occurs in the following code, then the finally will be executed // and the exception will be thrown ... } finally { // I WANT THIS CODE TO RUN EVENTUALLY REGARDLESS AN EXCEPTION OCCURED OR NOT listView1.EndUpdate(); }
我在顶层做什么:
// ie When the user clicks on a button try { ... } catch(Exception ex) { ex.Log(); // Log exception -- OR -- ex.Log().Display(); // Log exception, then show it to the user with apologies... }
我所做的一些所谓的function:
// Calculation module try { ... } catch(Exception ex) { // Add useful information to the exception throw new ApplicationException("Something wrong happened in the calculation module :", ex); } // IO module try { ... } catch(Exception ex) { throw new ApplicationException(string.Format("I cannot write the file {0} to {1}", fileName, directoryName), ex); }
exception处理(自定义exception)有很多事情要做,但是我想牢记的规则对于我所做的简单应用程序来说已经足够了。
这里是一个扩展方法的例子,以一个舒适的方式处理捕获exception。 他们被实现了一种可以被链接在一起的方式,并且很容易添加你自己的捕获的exception处理。
// Usage: try { // boom } catch(Exception ex) { // Only log exception ex.Log(); -- OR -- // Only display exception ex.Display(); -- OR -- // Log, then display exception ex.Log().Display(); -- OR -- // Add some user-friendly message to an exception new ApplicationException("Unable to calculate !", ex).Log().Display(); } // Extension methods internal static Exception Log(this Exception ex) { File.AppendAllText("CaughtExceptions" + DateTime.Now.ToString("yyyy-MM-dd") + ".log", DateTime.Now.ToString("HH:mm:ss") + ": " + ex.Message + "\n" + ex.ToString() + "\n"); return ex; } internal static Exception Display(this Exception ex, string msg = null, MessageBoxImage img = MessageBoxImage.Error) { MessageBox.Show(msg ?? ex.Message, "", MessageBoxButton.OK, img); return ex; }
最佳做法是exception处理不应该隐藏问题 。 这意味着try-catch
块应该是非常罕见的。
有三种情况使用try-catch
是有道理的。
-
始终处理已知的例外,尽可能低。 但是,如果您希望发生exception,则通常先进行testing。 例如,parsing,格式化和算术exception几乎总是由逻辑检查首先处理,而不是一个特定的
try-catch
。 -
如果您需要对exception执行某些操作(例如logging或回退事务),则重新抛出exception。
-
始终处理未知的exception,尽可能高 – 唯一应该使用exception并且不会重新抛出exception的代码应该是UI或公共API。
假设你连接到一个远程API,在这里你知道预期某些错误(并且在那些情况下有事情),所以这是情况1:
try { remoteApi.Connect() } catch(ApiConnectionSecurityException ex) { // User's security details have expired return false; } return true;
请注意,没有其他exception被捕获,因为它们不是预期的。
现在假设你正在试图将某些东西保存到数据库中。 如果失败了,我们必须把它回滚,所以我们有案例2:
try { DBConnection.Save(); } catch { // Roll back the DB changes so they aren't corrupted on ANY exception DBConnection.Rollback(); // Re-throw the exception, it's critical that the user knows that it failed to save throw; }
请注意,我们重新抛出exception – 更高的代码仍然需要知道某些失败。
最后,我们有了用户界面 – 这里我们不想完全没有处理exception,但我们也不想隐藏它们。 这里我们有一个例子3的例子:
try { // Do something } catch(Exception ex) { // Log exception for developers WriteException2LogFile(ex); // Display message to users DisplayWarningBox("An error has occurred, please contact support!"); }
但是,大多数API或UI框架都具有通用的方法来处理案例3.例如,ASP.Net具有一个黄色的错误屏幕,用于转储exception详细信息,但可以在生产环境中用更通用的消息来replace。 遵循这些是最佳实践,因为它为您节省了大量的代码,而且还因为错误logging和显示应该是configuration决定而不是硬编码。
这一切都意味着情况1(已知的例外)和情况3(一次性的UI处理)都具有更好的模式(避免预期的错误或手部error handling的UI)。
即使情况2可以被更好的模式所取代,例如事务处理范围 ( using
回滚块中没有提交的任何事务的块)使得开发人员难以得到最佳实践模式是错误的。
例如,假设你有一个大规模的ASP.Net应用程序。 错误日志logging可以通过ELMAH进行 ,错误显示可以是本地信息量大的YSoD ,也可以是生产中的一个很好的本地化信息。 数据库连接都可以通过事务范围和using
块。 你不需要一个try-catch
块。
TL; DR:最好的做法实际上是不使用try-catch
块。
例外是一个阻塞错误 。
首先,最好的做法应该是不要抛出任何types的错误的例外,但只有当它是一个阻塞错误 。
如果错误被阻塞 ,则抛出exception。 一旦这个exception已经被抛出,就没有必要隐藏它,因为它是非常特殊的:让用户知道它(你应该把整个exception重新格式化为对UI中用户有用的东西)。
您作为软件开发人员的工作将始终不会陷入特殊情况 ,即某些参数或运行时情况可能以exception结束。 也就是说, 例外不能被消除,但必须避免这些例外 。
例如,如果您知道某些整数input可能带有无效格式,请使用int.TryParse
而不是int.Parse
。 有很多情况下,你可以做到这一点,而不是只是说“如果失败,只是抛出一个exception”。
抛出exception是昂贵的。
如果抛出一个exception,而不是一旦抛出exception就写入日志,最好的方法之一就是捕获一个exception处理器 。 例如:
- ASP.NET:Global.asax Application_Error
- 其他: AppDomain.FirstChanceException事件 。
我的build议是本地尝试/捕获更适合处理特殊情况下,您可以将exception翻译成另一个,或者当你想“静音”它非常非常非常特殊的情况下(一个库引发一个无关的exception你需要静音,以解决整个bug)。
对于其他情况:
- 尽量避免例外。
- 如果这是不可能的:一次机会exception处理程序。
- 或者使用PostSharp方面(AOP)。
对某个评论回答@thewhiteambit …
@thewhiteambit说:
exception不是致命的错误,他们是例外! 有时他们甚至不是错误,但认为他们致命的错误是完全错误的理解是什么exception。
首先,一个exception如何不能成为一个错误呢?
- 没有数据库连接=>exception。
- 无效的string格式parsing为某种types=>exception
- 尝试parsingJSON,而input实际上不是JSON =>exception
- 当期望对象时参数为
null
=>exception - 某些库有一个bug =>会引发意外的exception
- 有一个套接字连接,它被断开。 然后你尝试发送消息=>exception
- …
我们可能会列出1k个案例,当抛出一个exception时,任何可能的案例都将是一个错误 。
一个例外是一个错误,因为在一天结束时是一个收集诊断信息的对象,它有一个消息,当出现错误时会发生。
没有例外的情况下,没有人会抛出exception。 而exception应该阻止错误,因为一旦抛出exception,如果不尝试使用try / catch和exception来实现控制stream,那么它们意味着你的应用程序/服务将停止进入exception情况的操作 。
另外,我build议大家检查一下Martin Fowler(由Jim Shore撰写)发表的失败模式。 这就是我以前总是懂得如何处理exception的情况,
[…]认为他们致命错误是完全错误的理解是什么例外。
通常情况下,exception会削减某些操作stream程,并将其处理为将其转换为人为可理解的错误。 因此,看起来像一个exception实际上是一个更好的范例来处理错误的情况下,并在他们的工作,以避免应用程序/服务的完整崩溃,并通知用户或消费者,出了问题。
关于@thewhiteambit关心的更多答案
例如,在缺less数据库连接的情况下,程序可能会exception地继续写入本地文件,一旦数据库再次可用,就会将更改发送到数据库。 你可能会尝试用exception的语言本地解释再次parsing你的无效的string到数字的转换,就像你尝试默认的英语语言parsing(“1,5”)失败,你再次用德语解释一遍罚款,因为我们使用逗号而不是点作为分隔符。 你看到这些exception不能被阻塞,他们只需要一些exception处理
-
如果您的应用程序可能脱机工作而没有将数据保存到数据库,则不应该使用exception ,因为使用
try/catch
实现控制stream被认为是反模式。 脱机工作是一种可能的用例,因此您可以通过实施控制stream来检查数据库是否可访问,您不要等到数据库无法访问 。 -
parsing的东西也是一个预期的情况( 不是EXCEPTIONAL CASE )。 如果你期待这一点, 你不要使用exception做控制stream! 。 你从用户那里得到一些元数据,知道他/她的文化是什么,而你使用格式化程序呢! .NET也支持这个和其他的环境,并且一个例外,因为数字格式化必须避免,如果你期望一个文化特定的应用程序/服务的使用 。
未处理的exception通常会变成错误,但exception本身不是codeproject.com/Articles/15921/Not-All-Exceptions-Are-Errors
这篇文章只是作者的一个观点或者一个观点。
由于维基百科也可以只是composer php的意见,我不会说这是教条 ,但检查一些段落某处的exception文章说:
[…]使用这些例外来处理特定的错误,继续程序被称为例外编码。 这种反模式可以快速降低软件的性能和可维护性。
它也说:
exception使用不正确
通常由exception编码可能导致软件中出现进一步的问题,例外情况不正确。 除了对一个独特的问题使用exception处理外,不正确的exception使用甚至在引发exception之后通过执行代码来进一步处理。 这种可怜的编程方法类似于很多软件语言中的goto方法,但只是在检测到软件中的问题之后才出现。
老实说,我认为软件不能开发不重视用例。 如果你知道…
- 您的数据库可以离线…
- 有些文件可以被locking
- 某些格式可能不被支持…
- 某些域validation可能会失败
- 你的应用程序应该离线模式下工作…
- 不pipe用例如何
… 你不会使用例外 。 您可以使用常规的控制stream程支持这些用例。
如果没有涉及到一些意想不到的用例,你的代码将会很快失败,因为它会抛出一个exception 。 对,因为例外是一个例外情况 。
另一方面,最后,有时你会涉及抛出预期exception的exception情况 ,但是你不会抛出它们来实现控制stream。 你这样做是因为你想通知上层,你不支持一些用例,或者你的代码无法处理一些给定的参数或环境数据/属性。
唯一的一次,你应该担心你的用户在代码中发生的事情是,如果有什么他们可以或需要做的,以避免这个问题。 如果他们可以更改表单上的数据,请按下button或更改应用程序设置以避免此问题,然后让他们知道。 但是用户无法避免的警告或错误只会让他们失去对产品的信心。
例外和日志是为你,开发人员,而不是你的最终用户。 当你发现每一个exception情况时,理解正确的事情要比应用一些黄金法则或依靠整个应用程序的安全网要好得多。
无意识的编码是唯一一种错误的编码。 事实上,你觉得在这些情况下可以做得更好的事实表明,你已经投入了良好的编码,但是避免在这些情况下试图盖上一些通用的规则,并理解某些东西首先放在哪里的原因以及什么你可以做,从中恢复。
我知道这是一个老问题,但是这里没有人提到MSDN文章,它是真正为我清除它的文档,MSDN有一个很好的文档 ,当以下条件为真时,你应该捕获exception:
您可以很好地理解可能抛出exception的原因,并且可以实现特定的恢复,例如在捕获FileNotFoundException对象时提示用户input新的文件名。
您可以创build并抛出一个新的,更具体的例外。
int GetInt(int[] array, int index) { try { return array[index]; } catch(System.IndexOutOfRangeException e) { throw new System.ArgumentOutOfRangeException( "Parameter index is out of range."); } }
- 在将其传递给其他处理之前,您希望部分处理exception。 在下面的例子中,在重新抛出exception之前,使用catch块来添加一个条目到错误日志中。
try { // Try to access a resource. } catch (System.UnauthorizedAccessException e) { // Call a custom error logging procedure. LogError(e); // Re-throw the error. throw; }
我build议阅读完整的“ exception和exception处理 ”部分以及exception的最佳实践 。
更好的办法是第二个(你特别说明exceptiontypes)。 这个优点是你知道这种exception可以在你的代码中出现。 您正在处理这种types的exception,你可以恢复。 但是,如果出现其他exception,则意味着出现了错误,这将帮助您在代码中find错误。 应用程序将最终崩溃,但你会知道你错过了一些东西(错误),需要修复。
留空挡块是最糟糕的事情。 如果出现错误,最好的处理方法是:
- login到文件\数据库等。
- 尝试修复它在飞行(也许尝试做替代方式做这个操作)
- 如果我们不能解决这个问题,通知用户有一些错误,当然中止操作
有时你需要对待exception,女巫对用户说什么。 我的方法是:
- 在应用程序级别(即在global.asax)中捕获未发现的exception以处理严重exception(应用程序无法使用)。 这些豁免我不抓住的地方。 只需将它们login到应用程序级别,让系统完成其工作。
- Cath“放置”并向用户显示一些有用的信息(input错误的号码,不能parsing)
- 抓住地方,对边缘问题做任何事情,如“我会检查后台的更新信息,但服务没有运行”。
这绝对不一定是最好的做法。 😉
对我来说,处理exception可以看作是商业规则。 显然,第一种方法是不可接受的。 第二个是比较好的,如果上下文这么说,它可能是100%正确的方式。 现在,例如,您正在开发一个Outlook Addin。 如果你的插件抛出未处理的exception,outlook用户现在可能会知道它,因为一个插件失败,Outlook不会自行销毁。 而且你很难弄清楚哪里出了问题。 所以,在这种情况下,第二种方法对我来说是正确的。 除了loggingexception之外,您可能会决定向用户显示错误消息 – 我认为这是一个业务规则。
第二种方法是好的,如果你不想显示错误,并通过显示与它们无关的运行时exception(即错误)来混淆应用程序的用户,只要logging错误和技术团队寻找问题并解决它
try { //do some work } catch(Exception exception) { WriteException2LogFile(exception);//it will write the or log the error in a text file }
所以我build议去整个应用程序的第二种方法。
有例外我尝试以下:
首先,我抓住了特殊types的exception,例如除零,IO操作等等,并根据这些代码编写代码,例如除以零,这取决于我可以提醒用户的价值的来源(例如一个简单的计算器在一个中间计算(不是参数)到零除零)或静静地处理该exception,logging并继续处理。
然后,我尝试捕获其余的exception并logging它们,如果可能的话允许执行代码。 如果不提醒用户发生错误,并要求他们发送错误报告。
在代码这样的事情
try{ //Some code here } catch(DivideByZeroException dz){ AlerUserDivideByZerohappened(); } catch(Exception e){ treatGeneralException(e); } finally{ //if a IO operation here i close the hanging handlers for example }
没有任何争论的catch
就是简单地吃掉这个exception,没用。如果发生致命错误,如果使用catch而没有参数,没有办法知道发生了什么。
一个catch语句应该捕获更多的特定exception,比如FileNotFoundException
,然后在最后你应该抓住Exception
来捕获任何其他的exception并logging下来
最佳做法是在发生错误时抛出exception。 因为发生错误,所以不应该被隐藏。
但是在现实生活中,当你想隐藏这个时,你可以有几种情况
- 您依赖第三方组件,并且如果出现错误,您希望继续执行该程序。
- 你有一个商业案例,你需要继续在错误的情况下
您应该考虑这些例外devise指南
- exception投掷
- 使用标准exceptiontypes
- 例外和性能
https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/exceptions