Windows窗体应用程序中的exception处理最佳实践?

我目前正在编写我的第一个Windows窗体应用程序。 现在我已经阅读了一些C#书籍,所以我对C#有什么语言特性需要处理exception有比较好的理解。 他们都非常理论,所以我还没有得到的是如何将基本概念翻译成我的应用程序中的一个很好的exception处理模型的感觉。

谁愿意分享这个主题上的任何智慧珍珠? 发表任何你看到过的像我自己这样的新手常见的错误,以及处理exception的一般build议,这样我的应用程序就会更加稳定和健壮。

我目前正在努力解决的主要问题是:

  • 我应该什么时候重新抛出exception?
  • 我应该尝试拥有某种中央error handling机制吗?
  • 与预先testing诸如磁盘上的文件是否存在相比,处理可能抛出的exception是否具有性能优势?
  • 所有的可执行代码是否应该放在try-catch-finally块中?
  • 有没有什么时候空的catch块可以被接受?

所有的build议感激地收到!

再多一点点…

你绝对应该有一个集中的exception处理策略。 这可以像在一个try / catch中包装Main()一样简单,快速地向用户发送优雅的错误消息。 这是“最后的手段”exception处理程序。

如果可行的话,抢先检查总是正确的,但并不总是完美的。 例如,在检查文件存在的代码和打开文件的下一行之间,文件可能已被删除,或者其他问题可能会妨碍您的访问。 你仍然需要在这个世界上尝试/捕捉/最终。 同时使用抢先检查和try / catch / finally。

永远不要“吞下”一个例外,除非在绝对有logging的情况下,当你绝对肯定地确信抛出的例外是可居住的。 这几乎不会是这样的。 (如果是的话,确保你只吞咽特定的exception类 – 不要吞下System.Exception 。)

在构build库(由您的应用程序使用)时,不要吞噬exception,也不要害怕让exception冒泡。 除非你有一些有用的补充,否则不要重新抛出。 永远不要(在C#中)这样做:

 throw ex; 

正如你将擦除调用堆栈。 如果您必须重新抛出(这是偶尔必需的,例如在使用企业库的exception处理块时),请使用以下命令:

 throw; 

在一天结束的时候,正在运行的应用程序抛出的绝大多数exception应该暴露在某个地方。 他们不应该暴露给最终用户(因为他们通常包含专有或其他有价值的数据),而是通常logging下来,pipe理员通知exception。 用户可以看到一个通用的对话框,也许有一个参考号码,以保持简单。

.NET中的exception处理比科学更具有艺术性。 每个人都会有自己的最爱在这里分享。 这些只是我从第一天起使用.NET获得的一些技巧,不止一次地拯救了我的培根。 你的旅费可能会改变。

这里有一个很好的CodeProject文章 。 这里有几个亮点:

  • 计划最坏的*
  • 早点检查
  • 不要相信外部数据
  • 唯一可靠的设备是:video,鼠标和键盘。
  • 写入也可能失败
  • 代码安全
  • 不要抛出新的exception()
  • 不要在消息字段中input重要的exception信息
  • 每个线程放一个catch(Exception ex)
  • 应该发布通用exception
  • Log Exception.ToString(); 永远不会只loggingException.Message!
  • 每个线程不要超过一次(例外)
  • 永远不要吞下例外
  • 清理代码应该放在块中
  • 到处使用“使用”
  • 不要在错误情况下返回特殊值
  • 不要使用exception来指示缺less资源
  • 不要使用exception处理作为从方法返回信息的手段
  • 对不应忽视的错误使用例外
  • 重新抛出exception时不要清除堆栈跟踪
  • 避免更改exception而不添加语义值
  • 例外应标记[可序列化]
  • 如有疑问,请勿断言,抛出exception
  • 每个exception类应该至less有三个原始的构造函数
  • 使用AppDomain.UnhandledException事件时要小心
  • 不要重新发明轮子
  • 不要使用非结构化error handling(VB.Net)

所有在这里发布的build议是好的,值得一听。

有一件事我想要扩展的是你的问题:“处理可能抛出的exception相比于预先testing磁盘上的文件是否存在等性能要好得多。

天真的经验法则是“try / catch块很贵”。 这不是真的。 尝试并不昂贵。 这就是捕获,系统必须创build一个Exception对象并将其装载到堆栈跟踪中,这非常昂贵。 在很多情况下,这个exception是非常特殊的,将代码封装在try / catch块中是完全正确的。

例如,如果你正在填充一个字典,这个:

 try { dict.Add(key, value); } catch(KeyException) { } 

往往比这样做更快:

 if (!dict.ContainsKey(key)) { dict.Add(key, value); } 

对于你添加的每一个项目,因为只有当你添加一个重复的键时才会抛出exception。 (LINQ聚合查询这样做。)

在你给的例子中,我会使用try / catch几乎没有想到。 首先,仅仅因为文件存在,当你打开它时并不意味着它会存在,所以你应该真的处理这个exception。

其次,我认为更重要的是,除非你的a)程序打开了数以千计的文件,并且b)它试图打开的文件不存在的可能性并不低,创buildexception的性能并不是你“将永远注意到。 一般来说,当你的程序试图打开一个文件,它只是试图打开一个文件。 在这种情况下,编写更安全的代码几乎肯定会比编写最快的代码更好。

请注意,Windows窗体有它自己的exception处理机制。 如果单击窗体中的button,并且其处理程序抛出处理程序中未捕获的exception,则Windows窗体将显示其自己的“未处理的exception”对话框。

为了防止显示未处理的exception对话框并捕获这样的exception,以便logging和/或提供您自己的错误对话框,您可以在调用Main()方法中的Application.Run()之前附加到Application.ThreadException事件。

这里有一些我遵循的准则

  1. 失败 – 快速:这更像是一个exception生成指南,对于你所做的每一个假设,你进入一个函数的每一个参数都要进行一次检查,以确保你从正确的数据开始,并假设你重做是正确的。 典型的检查包括:参数不为空,参数在预期范围内等

  2. 当重新抛出保留堆栈跟踪 – 这只是简单地转换为抛出时使用抛出,而不是抛出新的Exception()。 或者,如果您觉得可以添加更多信息,则将原始exception作为内部exception进行包装。 但是,如果你只捕获一个exception,只能logging它,然后肯定使用throw;

  3. 不要捕捉你无法处理的exception,所以不要担心OutOfMemoryException之类的事情,因为如果它们发生,你将无法做任何事情。

  4. 钩住全局exception处理程序,并确保尽可能多地logging信息。 对于winforms挂钩appdomain和线程未处理的exception事件。

  5. 只有在分析代码时才会考虑性能,并且发现它会导致性能瓶颈,默认情况下会优化可读性和devise。 所以关于文件存在检查的原始问题,我会说这取决于,如果你可以做一些关于文件不在那里,那么是做检查,否则如果你要做的就是抛出一个exception,如果文件的不在那里,我不明白这一点。

  6. 当然有时候需要空白的catch块,我认为别人说的话没有用过几个版本的代码库。 但他们应该评论和审查,以确保他们真的需要。 最典型的例子是开发人员使用try / catch将string转换为整数,而不是使用ParseInt()。

  7. 如果您期望代码的调用者能够处理错误情况,那么创build自定义的exception,详细说明未出现的情况并提供相关信息。 否则,只要尽可能地使用内置的exceptiontypes即可。

我喜欢不接触任何我不想处理的东西的哲学,不pipe在我的特定环境下的处理方式。

我讨厌它,当我看到如下代码:

 try { // some stuff is done here } catch { } 

我不时看到这一点,当有人吃东西的时候很难发现问题。 我有一个同事这样做,它往往最终成为一个问题源源不断的贡献者。

如果有某种特定的类需要做出反应,但是这个问题需要冒出来,然后称之为发生的方法,我会重新抛出。

我认为代码应该是主动编写的,例外应该是针对特殊情况,而不是为了避免条件的testing。

我只是在出路,但会给你一个简短的运行在哪里使用exception处理。 当我回来时,我会试图解决你的其他问题:)

  1. 明确检查所有已知的错误条件*
  2. 如果您不确定是否能够处理所有情况,请在代码中添加一个try / catch
  3. 如果您正在调用的.NET接口引发exception,请在代码中添加try / catch代码
  4. 如果代码跨越复杂性阈值,请添加try / catch代码
  5. 如果要进行完整性检查,请在代码周围添加一个try / catch:您声称这个应该永远不会发生
  6. 作为一般规则,我不使用exception来代替返回码。 这对.NET很好,但不是我。 我对这个规则也有例外(嘿嘿),这也取决于你正在使用的应用程序的体系结构。

*在合理范围内。 没有必要检查是否说宇宙射线击中你的数据导致一些位翻转。 了解什么是“合理的”是工程师获得的技能。 这很难量化,但很容易直观。 也就是说,我可以很容易地解释为什么我在任何特定的情况下使用try / catch,但是我很难用这种相同的知识来补充别人。

我倾向于摆脱严重的exception架构。 try / catch并没有像这样的性能命中,当抛出exception时,触发器进来了,代码可能需要在处理它之前先调用几个层次的调用堆栈。

试图坚持的黄金法则是尽可能地接近源头。

如果你必须重新抛出一个exception,试着添加一个exception,重新抛出一个FileNotFoundException并没有什么帮助,但是抛出一个ConfigurationFileNotFoundException将允许它被捕获并在链上的某个地方执行。

我试图遵循的另一个规则是不使用try / catch作为程序stream的一种forms,所以我在使用它们之前确认文件/连接,确保对象已经启动等。 尝试/ catch应该是例外,你无法控制的东西。

至于空的catch块,如果在产生exception的代码中做了任何重要的事情,你应该至less重新抛出这个exception。 如果没有代码抛出exception不运行的后果,你为什么写在第一位。

例外是昂贵的,但必要的。 您不需要将所有内容都包含在try catch中,但是您需要确保exception总是被最终捕获。 它的大部分将取决于你的devise。

如果让例外情况boost,也不要重蹈覆辙。 永远不要让错误被忽视。

例:

 void Main() { try { DoStuff(); } catch(Exception ex) { LogStuff(ex.ToString()); } void DoStuff() { ... Stuff ... } 

如果DoStuff出现问题,您将需要保释。 这个exception将会被抛到main,你会看到ex的堆栈轨迹中的事件序列。

我应该什么时候重新抛出exception?

到处都是,但最终用户的方法…像button点击处理程序

我应该尝试拥有某种中央error handling机制吗?

我写了一个日志文件…对于WinForm应用程序来说非常简单

与预先testing诸如磁盘上的文件是否存在相比,处理可能抛出的exception是否具有性能优势?

我不知道这一点,但我相信这是一个很好的做法,例外…我的意思是你可以问一个文件是否存在,如果它不抛出FileNotFoundException

所有的可执行代码是否应该放在try-catch-finally块中?

叶氏

有没有什么时候空的catch块可以被接受?

是的,假设你想显示一个date,但你不知道这个date是如何存储(日/月/年,月/日/年等),你尝试parsing它,但如果失败只是继续前进..如果这与你无关…我会说是的,有

我很快学到的一件事就是绝对地把所有与我的程序stream程(即文件系统,数据库调用,用户input)之外的任何代码交互的代码用try-catch块封装起来。 Try-catch可能导致性能下降,但通常在代码中的这些地方不会引起注意,并且会安全地付费。

我已经在用户可能做某些事情的地方使用了空的catch-blocks,但是它可能会抛出一个exception…想到的一个例子是在GridView中,如果用户DoubleLicks灰色的占位符单元格左上angular将触发CellDoubleClick事件,但单元格不属于一行。 在这种情况下,你不需要发布消息,但是如果你没有捕获它,就会向用户抛出一个未处理的exception错误。

重新抛出一个exception时抛出它的关键字自己。 这将抛出捕获exception,仍然可以使用堆栈跟踪来查看它来自哪里。

 Try { int a = 10 / 0; } catch(exception e){ //error logging throw; } 

这样做会导致堆栈跟踪在catch语句中结束。 (避免这个)

 catch(Exception e) // logging throw e; } 

ñ我的经验,当我知道我将要创造他们时,我已经看到适合捕捉exception。 例如,当我在一个Web应用程序,我正在做一个Response.Redirect,我知道我会得到一个System.ThreadAbortException。 既然这是故意的,我只是有一个特定types的捕获,只是吞下去。

 try { /*Doing stuff that may cause an exception*/ Response.Redirect("http:\\www.somewhereelse.com"); } catch (ThreadAbortException tex){/*Ignore*/} catch (Exception ex){/*HandleException*/} 

我深深同意下列规则:

  • 永远不要让错误被忽视。

原因是:

  • 当你第一次写下代码时,很可能你不会完全了解三方代码,.NET FCL lirary或你的同事的最新贡献。 事实上,你不能拒绝写代码,除非你知道每一个例外的可能性。 所以
  • 我一直发现,我使用try / catch(Exception ex)只是因为我想保护自己免受未知的事情,正如你注意到,我赶上exception,而不是更具体的如OutOfMemoryException等。而且,我总是做例外被ForceAssert.AlwaysAssert(false,ex.ToString())popup给我(或QA);

ForceAssert.AlwaysAssert是我个人的Trace.Assert方式,不pipe是否定义了DEBUG / TRACEmacros。

开发周期可能是:我注意到Assert对话框或其他人向我抱怨,然后我回到代码找出引发exception的原因,并决定如何处理它。

通过这种方式,我可以在短时间内写下我的代码,保护我不受陌生的领域的影响,但是如果发生了exception的事情,我们总是会注意到这一点,从而使系统更安全,更安全。

我知道你们中许多人不会同意我的看法,因为开发者应该知道他/她的代码的每一个细节,坦率地说,我也是过去的纯粹主义者。 但现在我知道上述政策更为实际。

对于WinForms代码,我总是遵守的一条金科玉律是:

  • 总是尝试/捕获(例外)你的事件处理程序代码

这将保护您的用户界面始终可用。

对于性能命中,性能损失只发生在代码被捕获时,执行try代码而没有引发实际的exception没有显着的影响。

例外应该发生的机会很less,否则它不是例外。

欲了解更多信息,请访问: http : //www.msdotnet.co.in/2013/05/exception-handling-in-c.html

这是学习学生的很好的平台。

你必须考虑用户。 应用程序崩溃是用户想要的最后一件事情。 因此,任何可能失败的操作都应该在UI级有一个try catch块。 在每种方法中都没有必要使用try catch,但是每次用户执行某些操作时,都必须能够处理一般exception。 这并不意味着在第一种情况下可以免于检查所有事件以防止exception,但是没有错误的复杂应用程序并且操作系统可以轻易地添加意外问题,因此您必须预料到意外并确保用户是否想要使用操作不会有数据丢失,因为应用程序崩溃。 没有必要永远让你的应用程序崩溃,如果你发现exception,它永远不会处于不确定的状态,并且用户总是因为崩溃而感到不便。 即使exception处于最高级别,不会崩溃意味着用户可以快速重现exception,或者至lesslogging错误消息,从而极大地帮助您解决问题。 肯定比获得一个简单的错误消息,然后只看到Windows错误对话框或类似的东西更多。

这就是为什么你一定不要只是自负,认为你的应用程序没有错误,这是不能保证。 而且,包装一些关于适当代码的try catch块并显示错误消息/logging错误是一个非常小的努力。

作为一个用户,每当眉头或办公室的应用程序或任何崩溃,我当然会非常生气。 如果exception太高,应用程序无法继续,最好显示该消息,告诉用户该做什么(重新启动,修复一些操作系统设置,报告错误等),而不是简单地崩溃,就是这样。