[译].NET中的异常处理最佳实践
By robot-v1.0
本文链接 https://www.kyfws.com/best-practices/exception-handling-best-practices-in-net-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 23 分钟阅读 - 11230 个词 阅读量 0[译].NET中的异常处理最佳实践
原文地址:https://www.codeproject.com/Articles/9538/Exception-Handling-Best-Practices-in-NET
原文作者:Daniel Turini
译文由本站 robot-v1.0 翻译
前言
Design guidelines for exception handling in .NET which will help you to create more robust software .NET中异常处理的设计准则,它将帮助您创建更强大的软件
内容(Contents)
- 介绍(Introduction)
- 计划最坏的情况(Plan for the worst)
- 安全编码(Code Safely)
- 不要抛出新的Exception()(Don’t throw new Exception())
- 不要在消息字段上放置重要的异常信息(Don’t put important exception information on the Message field)
- 每个线程放入一个catch(ex ex)(Put a single catch (Exception ex) per thread)
- 捕获的通用异常应发布(Generic Exceptions caught should be published)
- 记录Exception.ToString();永远不要只记录Exception.Message!(Log Exception.ToString(); never log only Exception.Message!)
- 每个线程不要捕获(异常)超过一次(Don’t catch (Exception) more than once per thread)
- 永远不要吞下异常(Don’t ever swallow exceptions)
- 清理代码应放在finally块中(Cleanup code should be put in finally blocks)
- 随处使用"使用"(Use “using” everywhere)
- 不要在错误条件下返回特殊值(Don’t return special values on error conditions)
- 不要使用异常来表示缺少资源(Don’t use exceptions to indicate absence of a resource)
- 不要将异常处理用作从方法返回信息的方法(Don’t use exception handling as means of returning information from a method)
- 使用异常处理不应忽略的错误(Use exceptions for errors that should not be ignored)
- 重新引发异常时不要清除堆栈跟踪(Don’t clear the stack trace when re-throwing an exception)
- 避免在不增加语义值的情况下更改异常(Avoid changing exceptions without adding semantic value)
- 异常应标记为[可序列化](*Exceptions should be marked [Serializable]*)
- 如有疑问,请不要断言,引发异常(When in doubt, don’t Assert, throw an Exception)
- 每个异常类至少应具有三个原始构造函数(Each exception class should have at least the three original constructors)
- 使用AppDomain.UnhandledException事件时要小心(Be careful when using the AppDomain.UnhandledException event)
- 不要重新发明轮子(Don’t reinvent the wheel)
- VB.NET(VB.NET)
- 结论(Conclusion)
- 历史(History)
介绍(Introduction)
“我的软件永不失败”.你相信吗?我几乎听到了所有人的声音,尖叫着我是个骗子. “永不失败的软件几乎是不可能的!"(“My software never fails”. Can you believe it? I’m almost hearing you all, screaming that I’m a liar. “Software that never fails is something near to impossible!") 与通常的看法相反,创建可靠,健壮的软件并非不可能.请注意,我并不是指旨在控制核电站的无错误软件.我指的是常见的商业软件,该软件可以长时间(数周或数月)在服务器甚至台式机上无人值守运行,并且可以正常工作而不会出现重大故障.可以预见的是,它的故障率很低,您可以轻松了解故障状况以快速修复它,并且它绝不会由于外部故障而损坏数据.(Contrary to common belief, creating reliable, robust software is not something near to impossible. Notice that I’m not referring to bug-free software, intended to control nuclear power plants. I’m referring to common business software, which can run unattended on a server, or even a desktop machine, for long periods of time (weeks or months) and work predictably without any significant glitch. By predictably, I mean that it has a low failure rate, you can easily understand failure conditions to fix it quickly, and it never damages data in response of an external failure.) 换句话说,软件是稳定的.(In other words, software that is stable.) 在软件中存在错误是可以原谅的,甚至是可以预期的.不可原谅的是,由于您没有足够的信息,您无法修复一个重复出现的错误.(Having a bug in your software is forgivable, and even expected. What’s unforgivable is having a recurring bug you can’t fix because you don’t have enough information.) 为了更好地理解我的意思,我看到了无数的商业软件,这些软件在DBMS的磁盘空间不足错误中报告如下内容:(To understand better what I’m saying, I’ve seen countless business software that, in an out of disk space error in the DBMS, reports something like this:) “无法更新客户详细信息.请与系统管理员联系,然后重试”.(“Could not update customer details. Contact the system administrator and try again later”.) 尽管此消息足以向业务用户报告未知的资源故障,但通常这是可用于调试错误原因的全部调试信息.没有任何记录,了解发生的情况将非常耗时,并且程序员通常会猜测很多可能的原因,直到找到真正的错误原因.(While this message may be an adequate of reporting an unknown resource failure to a business user, all too often this is the whole debugging information that is available to debug the error cause. Nothing was logged, and understanding what is happening will be time-consuming and often programmers will guess a lot of possible causes until they find the real error cause.) 请注意,在本文中,我将仅专注于如何更好地利用.NET异常:我不会讨论如何正确报告错误消息,因为我相信这属于UI域,并且在很大程度上取决于接口正在发展和目标受众;针对青少年的博客文本编辑器应以与套接字服务器完全不同的方式报告错误消息,套接字服务器只能由程序员直接使用.(Notice that in this article, I will concentrate only in how to make a better use of .NET exceptions: I won’t discuss how to properly report error messages, because I believe this belongs to UI domain, and it depends heavily on the interface being developed and the target audience; a blog text editor targeting teenagers should report error messages in a way completely different than a socket server, which will only be used directly by programmers.)
计划最坏的情况(Plan for the worst)
一些基本的设计概念将使您的程序更加健壮,并在出现意外错误的情况下改善用户体验. “在出现意外错误时改善用户体验"是什么意思?并不是用户会对您展示给他的奇妙对话框感到兴奋.这更多的是不破坏数据,不使计算机崩溃以及行为安全.如果您的程序可以通过磁盘空间不足错误而不造成任何损害,那么您可以改善用户体验.(A few basic design concepts will make your program much more robust, and will improve the user experience in the presence of an unexpected error. What do I mean by “improve the user experience in the presence of an unexpected error”? It’s not that the user will be thrilled by the marvelous dialog box you’ll show him. It’s more about don’t corrupting data, don’t crashing the computer, and behaving safely. If your program can pass through an out of disk space error without doing any harm, you improved the user experience.)
早点检查(Check it early)
强大的类型检查和验证是强大的工具,可以防止意外的异常以及记录和测试代码.在执行中发现问题的时间越早,修复起来就越容易.试图了解什么(Strong type checking and validation are powerful tools to prevent unexpected exceptions and to document and test code. The earlier in execution you detect a problem, the easier is to fix. Trying to understand what a) CustomerID
在做(is doing on the) ProductID
几个月后,InvoiceItems表上的"列"既不好玩也不容易.如果您使用类来存储客户数据,而不是使用原语(例如,(column on the InvoiceItems table after a few months isn’t fun neither easy. If you used classes for storing customer data, instead of using primitives (e.g.,) int
,(,) string
等等),编译器将永远不会允许您执行此类操作.(, etc), chances are that the compiler would never allow you to do such a thing.)
不信任外部数据(Don’t trust external data)
外部数据不可靠.必须对其进行广泛检查.数据是来自注册表,数据库,磁盘,套接字,刚写的文件还是键盘都没关系.应该检查所有外部数据,然后您才能依靠它.我经常看到程序信任配置文件,因为程序员从未想到有人会编辑该文件并破坏它.(External data is not reliable. It must be extensively checked. It doesn’t matter if the data is coming from the registry, database, from a disk, from a socket, from a file you just wrote or from the keyboard. All external data should be checked and only then you can rely on it. All too often I see programs that trust configuration files because the programmer never thought that someone would edit the file and corrupt it.)
唯一可靠的设备是:视频,鼠标和键盘.(The only reliable devices are: the video, the mouse and keyboard.)
任何时候需要外部数据时,都可能出现以下情况:(Anytime you need external data, you can have the following situations:)
- 安全权限不足(Not enough security privileges)
- 信息不存在(The information is not there)
- 信息不完整(The information is incomplete)
- 信息完整,但无效(The information is complete, but invalid) 它是注册表项,文件,套接字,数据库,Web服务还是串行端口,实际上并不重要.所有外部数据源迟早都会失败.计划安全故障并最大程度地减少损坏.(It really doesn’t matter if it’s a registry key, a file, a socket, a database, a web service or a serial port. All external data sources will fail, sooner or later. Plan for a safe failure and minimize damage.)
写入也可能失败(Writes can fail, too)
不可靠的数据源也是不可靠的数据存储库.保存数据时,可能会发生类似的情况:(Unreliable data sources are also unreliable data repositories. When you’re saving data, similar situations can happen:)
- 安全权限不足(Not enough security privileges)
- 设备不在那里(The device isn’t there)
- 空间不足(There’s not enough space)
- 设备有物理故障(The device has a physical fault) 这就是压缩程序创建一个临时文件并在完成后重命名的原因,而不是更改原始文件:如果磁盘(甚至软件)由于某种原因而失败,则不会丢失原始数据.(That’s why compression programs create a temporary file and rename it after they’re done, instead of changing the original one: if the disk (or even the software) fails for some reason, you won’t lose your original data.)
安全编码(Code Safely)
我的一个朋友经常说:“一个好的程序员就是从来没有在他的项目中引入不良代码的人”.我不认为这是成为一个优秀的程序员所需要的全部,但是可以肯定的是,它将使您几乎步入正轨.下面,我列出了有关异常处理的最常见"错误代码"列表,您可以在项目中引入这些代码.(A friend of mine often says: “A good programmer is someone who never introduce bad code in his projects”. I don’t believe that this is all that it’s needed to be a good programmer, but surely it will put you almost there. Below, I compiled a list of the most common “bad code” that you can introduce in your projects, when it comes to exception handling.)
不要抛出新的Exception()(Don’t throw new Exception())
拜托,不要这样做.(Please, don’t do it.) Exception
这门课太宽泛了,没有副作用就很难抓住.派生自己的异常类,但从中派生它(is a too broad class, and it’s hard to catch without side-effects. Derive your own exception class, but derive it from) ApplicationException
.这样,您可以为框架抛出的异常和自己抛出的异常设置专门的异常处理程序.(. This way you could set a specialized exception handler for exceptions thrown by the framework and another for exceptions thrown by yourself.)
修订说明(Revision note):戴维莱维特(David Levitt)在下面的评论部分中写信给我,尽管微软仍然吹捧使用(*: David Levitt wrote me, in the comments section below, that although Microsoft still touts using*)
System.ApplicationException 作为基类(*as a base class in*) [MSDN文档(*MSDN docs*)](http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemapplicationexceptionclasstopic.asp) ,正如布拉德
亚当斯(Brad Adams)所指出的那样,这已不再是一种好习惯(, this is no longer considered a good practice, as noted by Brad Adams, as you can see in) 他的博客(his blog) .这个想法是创建异常类层次结构,就像您经常使用类层次结构一样,它们应尽可能浅和宽.我之所以没有立即更改该文章,是因为在这里介绍它之前,我需要做更多的研究.在完成所有这些研究之后,我还不能确定浅层类层次结构是否适合处理异常,因此我决定将这两种观点都保留在这里.但是,无论您做什么,都不要扔新东西(. The idea is creating exception class hierarchies that are as shallow and wide as possible, as you often do with class hierarchies. The reason I didn’t change the article immediately was because I needed to do more research before I introduced it here. After all this research, I could not decide yet whether shallow class hierarchies are a good idea in Exception handling or not, so I decided to leave both opinions here. But, no matter what you do, don’t throw new) Exception()
并得到你自己的(and derive your own) Exception
需要时上课.(class when needed.)
不要在消息字段上放置重要的异常信息(Don’t put important exception information on the Message field)
类是例外.返回异常信息时,请创建字段以存储数据.如果您这样做失败,人们将需要解析"消息"字段以获取他们所需的信息.现在,考虑一下如果您需要本地化甚至纠正错误消息中的拼写错误,调用代码会发生什么.您可能永远不知道这样做会破坏多少代码.(Exceptions are classes. When you return the exception information, create fields to store data. If you fail on doing it, people will need to parse the Message field to get the information they need. Now, think what will happen to the calling code if you need to localize or even just correct a spelling error in error messages. You may never know how much code you’ll break by doing it.)
每个线程放一个catch(ex ex)(Put a single catch (Exception ex) per thread)
通用异常处理应在应用程序的中心位置进行.每个线程都需要一个单独的try/catch块,否则您将丢失异常,并且将遇到难以理解的问题.当应用程序启动多个线程进行某些后台处理时,通常会创建一个用于存储处理结果的类.不要忘记添加一个字段来存储可能发生的异常,否则您将无法将其传达给主线程.在"解雇"情况下,您可能需要在线程处理程序上复制主应用程序异常处理程序.(Generic exception handling should be done in a central point in your application. Each thread needs a separate try/catch block, or you’ll lose exceptions and you’ll have problems hard to understand. When an application starts several threads to do some background processing, often you create a class for storing processing results. Don’t forget to add a field for storing an exception that could happen or you won’t be able to communicate it to the main thread. In “fire and forget” situations, you probably will need to duplicate the main application exception handler on the thread handler.)
捕获的通用异常应发布(Generic Exceptions caught should be published)
真正使用什么日志并不重要-log4net,EIF,事件日志,TraceListener,文本文件等.真正重要的是:如果捕获到通用异常,则将其记录在某个地方.但是只记录一次-通常代码会被捕获异常的catch块所占据,并且最终会产生巨大的日志,其中包含过多的重复信息,因此无用.(It really doesn’t matter what you use for logging - log4net, EIF, Event Log, TraceListeners, text files, etc. What’s really important is: if you caught a generic Exception, log it somewhere. But log it only once - often code is ridden with catch blocks that log exceptions and you end up with a huge log, with too much repeated information to be useful.)
记录Exception.ToString();永远不要只记录Exception.Message!(Log Exception.ToString(); never log only Exception.Message!)
当我们谈论日志记录时,请不要忘记您应该始终记录日志(As we’re talking about logging, don’t forget that you should always log) Exception.ToString()
, 绝不(, and never) Exception.Message
.(.) Exception.ToString()
将为您提供堆栈跟踪,内部异常和消息.通常,此信息是无价的,如果您仅登录(will give you a stack trace, the inner exception and the message. Often, this information is priceless and if you only log) Exception.Message
,则只会出现"对象引用未设置为对象实例"之类的内容.(, you’ll only have something like “Object reference not set to an instance of an object”.)
每个线程不要捕获(异常)超过一次(Don’t catch (Exception) more than once per thread)
此规则很少有例外(无双关语).如果需要捕获异常,请始终对所编写的代码使用最特定的异常.(There are rare exceptions (no pun intended) to this rule. If you need to catch an exception, always use the most specific exception for the code you’re writing.)
我总是看到初学者认为好的代码是不会引发异常的代码.这不是真的.好的代码会根据需要抛出异常,并且仅处理它知道如何处理的异常.(I always see beginners thinking that good code is code that doesn’t throw exceptions. This is not true. Good code throws exceptions as needed, and handles only the exceptions it knows how to handle.)
作为此规则的示例,请看下面的代码.我敢打赌,写这篇文章的那个人读了这篇文章会杀了我,但这是从一个真实的例子中摘下来的.实际上,现实世界的代码要复杂一些-由于教学原因,我对其进行了很多简化.(As a sample of this rule, look at the following code. I bet that the guy who wrote it will kill me when he read this, but it was taken from a real-world example. Actually, the real-world code was a bit more complicated - I simplified it a lot for didactic reasons.)
头等舱((The first class () MyClass
)在装配体上,第二类(() is on an assembly, and the second class () GenericLibrary
)在另一个程序集上,这是一个充满通用代码的库.在开发机器上,代码运行正确,但是在QA机器上,即使输入的数字有效,代码也始终返回"无效数字”.() is on another assembly, a library full of generic code. On the development machine the code ran right, but on the QA machines, the code always returned “Invalid number”, even if the entered number was valid.)
你能说为什么会这样吗?(Can you say why this was happening?)
public class MyClass
{
public static string ValidateNumber(string userInput)
{
try
{
int val = GenericLibrary.ConvertToInt(userInput);
return "Valid number";
}
catch (Exception)
{
return "Invalid number";
}
}
}
public class GenericLibrary
{
public static int ConvertToInt(string userInput)
{
return Convert.ToInt32(userInput);
}
}
问题是过于通用的异常处理程序.根据MSDN文档,(The problem was the too generic exception handler. According to the MSDN documentation,) Convert.ToInt32
只抛出(only throws) ArgumentException
,(,) FormatException
和(and) OverflowException
.因此,这些是应该处理的唯一例外.(. So, those are the only exceptions that should be handled.)
问题出在我们的设置上,其中不包括第二个组件((The problem was on our setup, which didn’t include the second assembly () GenericLibrary
).现在,我们有一个(). Now, we had a) FileNotFoundException
当…的时候(when the) ConvertToInt
被调用,并且代码假定这是因为该数字无效.(was called, and the code assumed that it was because the number was invalid.)
下次您写”(The next time you write “) catch (Exception ex)
“,请尝试描述您的代码在(”, try to describe how your code would behave when an) OutOfMemoryException
被抛出.(is thrown.)
永远不要吞下异常(Don’t ever swallow exceptions)
您能做的最坏的事情是catch(Exception)并在上面放一个空的代码块.永远不要这样做.(The worst thing you can do is catch (Exception) and put an empty code block on it. Never do this.)
清理代码应放在finally块中(Cleanup code should be put in finally blocks)
理想情况下,由于您不处理大量的通用异常并且具有中央异常处理程序,因此您的代码中的finally块应该比catch块多得多.切勿在finally块之外执行清理代码,例如关闭流,恢复状态(作为鼠标光标).养成习惯.(Ideally, since you’re not handling a lot of generic exceptions and have a central exception handler, your code should have a lot more finally blocks than catch blocks. Never do cleanup code, e.g., closing streams, restoring state (as the mouse cursor), outside of a finally block. Make it a habit.) 人们经常忽略的一件事是try/finally块如何使您的代码更具可读性和健壮性.这是清理代码的好工具.(One thing that people often overlook is how a try/finally block can make your code both more readable and more robust. It’s a great tool for cleanup code.) 作为示例,假设您需要从文件中读取一些临时信息并将其作为字符串返回.无论发生什么情况,都需要删除此文件,因为它是临时的.这种返回和清除请求尝试/最终阻止.(As a sample, suppose you need to read some temporary information from a file and return it as a string. No matter what happens, you need to delete this file, because it’s temporary. This kind of return & cleanup begs for a try/finally block.) 让我们看看最简单的代码,而无需使用try/finally:(Let’s see the simplest possible code without using try/finally:)
string ReadTempFile(string FileName)
{
string fileContents;
using (StreamReader sr = new StreamReader(FileName))
{
fileContents = sr.ReadToEnd();
}
File.Delete(FileName);
return fileContents;
}
当引发异常时,例如(This code also has a problem when an exception is thrown on, e.g., the) ReadToEnd
方法:将一个临时文件保留在磁盘上.因此,实际上我看到有些人试图通过以下方式解决它:(method: it leaves a temporary file on the disk. So, I’ve actually saw some people trying to solve it coding as this:)
string ReadTempFile(string FileName)
{
try
{
string fileContents;
using (StreamReader sr = new StreamReader(FileName))
{
fileContents = sr.ReadToEnd();
}
File.Delete(FileName);
return fileContents;
}
catch (Exception)
{
File.Delete(FileName);
throw;
}
}
代码变得越来越复杂,并且开始复制代码.(The code is becoming complex and it’s starting to duplicate code.) 现在,看看try/finally解决方案有多清洁和强大:(Now, see how much cleaner and robust is the try/finally solution:)
string ReadTempFile(string FileName)
{
try
{
using (StreamReader sr = new StreamReader(FileName))
{
return sr.ReadToEnd();
}
}
finally
{
File.Delete(FileName);
}
}
fileContents变量在哪里?不再需要了,因为我们可以返回内容,并且清除代码在返回点之后执行.这是拥有可以运行的代码的优点之一(Where did the fileContents variable go? It’s not necessary anymore, because we can return the contents and the cleanup code executes after the return point. This is one of the advantages of having code that can run)**后(after)**该函数返回:您可以清除return语句可能需要的资源.(the function returns: you can clean resources that may be needed for the return statement.)
随处使用"使用”(Use “using” everywhere)
只需致电(Simply calling) Dispose()
一个物体上的光还不够.的(on an object is not enough. The) using
关键字即使出现异常也可以防止资源泄漏.(keyword will prevent resource leaks even on the presence of an exception.)
不要在错误条件下返回特殊值(Don’t return special values on error conditions)
特殊值存在很多问题:(There are lots of problems with special values:)
- 异常会使常见情况更快,因为当您从方法中返回特殊值时,需要检查每个方法的返回,这至少消耗一个处理器寄存器,从而导致代码变慢(Exceptions makes the common case faster, because when you return special values from methods, each method return needs to be checked and this consumes at least one processor register more, leading to slower code)
- 特殊值可以并且将被忽略(Special values can, and will be ignored)
- 特殊值不包含堆栈跟踪和丰富的错误详细信息(Special values don’t carry stack traces, and rich error details)
- 通常,没有合适的值可以从表示错误情况的函数中返回.您将从以下函数返回什么值来表示"被零除"错误?(All too often there’s no suitable value to return from a function that can represent an error condition. What value would you return from the following function to represent a “division by zero” error?)
public int divide(int x, int y)
{
return x / y;
}
不要使用异常来表示缺少资源(Don’t use exceptions to indicate absence of a resource)
Microsoft建议您在极端情况下使用返回特殊值.我知道我只是写了相反的话,我也不喜欢它,但是当大多数API保持一致时,生活会更轻松,因此我建议您谨慎使用这种样式.(Microsoft recommends that you should use return special values on extremely common situations. I know I just wrote the opposite and I also don’t like it, but life is easier when most APIs are consistent, so I recommend you to adhere to this style with caution.)
我查看了.NET框架,发现几乎所有使用此样式的API都是返回一些资源的API(例如,(I looked at the .NET framework, and I noticed that the almost only APIs that use this style are the APIs that return some resource (e.g.,) Assembly.GetManifestStream
方法).如果没有某些资源,所有这些API都将返回null.(method). All those APIs return null in case of the absence of some resource.)
不要将异常处理用作从方法返回信息的方法(Don’t use exception handling as means of returning information from a method)
这是一个糟糕的设计.异常不仅速度很慢(顾名思义,它们仅用于特殊情况),而且代码中的许多try/catch块使代码难以遵循.正确的类设计可以容纳常见的返回值.如果确实需要作为异常返回数据,则可能是您的方法做得太多,需要拆分.(This is a bad design. Not only exceptions are slow (as the name implies, they’re meant only to be used on exceptional cases), but a lot of try/catch blocks in your code makes the code harder to follow. Proper class design can accommodate common return values. If you’re really in need to return data as an exception, probably your method is doing too much and needs to be split.)
使用异常处理不应忽略的错误(Use exceptions for errors that should not be ignored)
我将使用一个真实的例子.开发API以便人们可以访问Crivo(我的产品)时,您应该做的第一件事就是调用(I’ll use a real world example for this. When developing an API so people could access Crivo (my product), the first thing that you should do is calling the) Login
方法.如果(method. If) Login
失败,或者不被调用,其他所有方法调用都会失败.我选择从Login方法抛出一个异常(如果失败),而不是简单地返回false,因此调用程序无法忽略它.(fails, or is not called, every other method call will fail. I chose to throw an exception from the Login method if it fails, instead of simply returning false, so the calling program cannot ignore it.)
重新引发异常时不要清除堆栈跟踪(Don’t clear the stack trace when re-throwing an exception)
堆栈跟踪是异常携带的最有用的信息之一.通常,我们需要对catch块进行一些异常处理(例如,回滚事务),然后重新引发异常.查看正确(和错误)的方法:错误的方法:(The stack trace is one of the most useful information that an exception carries. Often, we need to put some exception handling on catch blocks (e.g., to rollback a transaction) and re-throw the exception. See the right (and the wrong) way of doing it: The wrong way:)
try
{
// Some code that throws an exception
}
catch (Exception ex)
{
// some code that handles the exception
throw ex;
}
为什么会这样呢?因为,当您检查堆栈跟踪时,异常点将是”(Why is this wrong? Because, when you examine the stack trace, the point of the exception will be the line of the “) throw ex;
“,隐藏真正的错误位置.请尝试一下.(”, hiding the real error location. Try it.)
try
{
// Some code that throws an exception
}
catch (Exception ex)
{
// some code that handles the exception
throw;
}
有什么变化?代替 “(What has changed? Instead of “) throw ex;
“,这将引发一个新的异常并清除堆栈跟踪,我们只需”(”, which will throw a new exception and clear the stack trace, we have simply “) throw;
“.如果未指定异常,throw语句将简单地重新抛出catch语句捕获的相同异常.这将使堆栈跟踪保持完整,但仍允许您将代码放入catch块中.(”. If you don’t specify the exception, the throw statement will simply rethrow the very same exception the catch statement caught. This will keep your stack trace intact, but still allows you to put code in your catch blocks.)
避免在不增加语义值的情况下更改异常(Avoid changing exceptions without adding semantic value)
仅在需要添加一些语义值的情况下才更改异常-例如,您正在做DBMS连接驱动程序,因此用户不必关心特定的套接字错误,而只想知道连接失败.(Only change an exception if you need to add some semantic value to it - e.g., you’re doing a DBMS connection driver, so the user doesn’t care about the specific socket error and wants only to know that the connection failed.) 如果需要,请在InnerException成员上保留原始异常.不要忘记您的异常处理代码也可能存在错误,如果您具有InnerException,则可能会发现它更容易.(If you ever need to do it, please, keep the original exception on the InnerException member. Don’t forget that your exception handling code may have a bug too, and if you have InnerException, you may be able to find it easier.)
异常应标记为[可序列化](*Exceptions should be marked [Serializable]*)
很多情况下都需要异常可以序列化.从另一个异常类派生时,请不要忘记添加该属性.您永远不会知道何时从Remoting组件或Web Services中调用您的方法.(A lot of scenarios needs that exceptions are serializable. When deriving from another exception class, don’t forget to add that attribute. You’ll never know when your method will be called from Remoting components or Web Services.)
如有疑问,请不要断言,引发异常(When in doubt, don’t Assert, throw an Exception)
别忘了(Don’t forget that) Debug.Assert
从发布代码中删除.在进行检查和验证时,通常比将断言放入代码中引发异常更好.(is removed from release code. When checking and doing validation, it’s often better to throw an Exception than to put an assertion in your code.)
保存断言,以用于单元测试,内部循环不变式以及不会因运行时条件而失败的检查(如果考虑的话,这是非常罕见的情况).(Save assertions for unit tests, for internal loop invariants, and for checks that should never fail due to runtime conditions (a very rare situation, if you think about it).)
每个异常类至少应具有三个原始构造函数(Each exception class should have at least the three original constructors)
这样做很容易(只需复制并粘贴其他异常类中的定义),否则,将不允许您的类的用户遵循其中的一些准则.(Doing it is easy (just copy & paste the definitions from other exception classes) and failing to do that won’t allow users of your classes to follow some of these guidelines.) 我指的是哪些构造函数?最后的三个构造函数在(Which constructors I am referring to? The last three constructors described on) 这一页(this page) .(.)
使用AppDomain.UnhandledException事件时要小心(Be careful when using the AppDomain.UnhandledException event)
***修订说明:(Revision note:)***我被指出(I was pointed by) [菲利普哈克(*Phillip Haack*)](http://www.haacked.com/) 在(*in*) [我的博客(*my blog*)](http://dturini.blogspot.com/) 重要的遗漏.其他常见的错误来源是(*of this important omission. Other common source of mistakes is the*)
Application.ThreadException` 事件.使用它们时有很多注意事项:(event. There are lots of caveats when using them:)
- 异常通知发生得太迟了:收到通知后,您的应用程序将无法再响应该异常.(The exception notification occurs too late: when you receive the notification your application will not be able to respond to the exception anymore.)
- 如果主线程(实际上是从非托管代码开始的任何线程)发生异常,则应用程序将完成.(The application will finish if the exception occurred on the main thread (actually, any thread that started from unmanaged code).)
- 很难创建能够一致工作的通用代码.引用MSDN:(It’s hard to create generic code that works consistently. Quoting MSDN:)“此事件仅在启动应用程序时由系统创建的应用程序域发生.如果应用程序创建其他应用程序域,则在那些应用程序域中指定此事件的委托无效."(“This event occurs only for the application domain that is created by the system when an application is started. If an application creates additional application domains, specifying a delegate for this event in those applications domains has no effect.")
- 当代码运行这些事件时,除了异常本身之外,您将无法访问任何有用的信息.您将无法关闭数据库连接,回滚事务或任何有用的操作.对于初学者来说,使用全局变量的诱惑很大.(When the code is running those events, you won’t have access to any useful information other than the exception itself. You won’t be able to close database connections, rollback transactions, nor anything useful. For beginners, the temptation of using global variables will be huge.)确实,您永远不应将整个异常处理策略都基于这些事件.将它们视为"安全网”,并记录异常以进行进一步检查.以后,请确保更正不能正确处理异常的代码.(Indeed, you should never base your whole exception handling strategy on those events. Think of them as “safety nets”, and log the exception for further examination. Later, be sure to correct the code that is not handling properly the exception.)
不要重新发明轮子(Don’t reinvent the wheel)
有很多很好的框架和库可以处理异常.其中两个由Microsoft提供,我在这里提到:(There are lots of good frameworks and libraries to deal with exceptions. Two of them are provided by Microsoft and I mention here:)
- 异常管理应用程序块(Exception Management Application Block)
- Microsoft企业规范框架(Microsoft Enterprise Instrumentation Framework) 但是请注意,如果您不遵循严格的设计准则(如我在此处显示的准则),这些库将几乎无用.(Notice, though, that if you don’t follow strict design guidelines, like those I showed here, those libraries will be nearly useless.)
VB.NET(VB.NET)
如果您通读本文,您会发现我使用的所有示例都在C#中.那是因为C#是我的首选语言,并且因为VB.NET有一些自己的准则.(If you read through this article, you’ll notice that all the samples I used were in C#. That’s because C# is my preferred language, and because VB.NET has a few guidelines of its own.)
模拟C#“使用"语句(Emulate C# “using” statement)
不幸的是,VB.NET仍然没有(Unfortunately, VB.NET still doesn’t have the) using
声明. Whidbey会拥有它,但是在释放它之前,每次需要处置一个对象时,都应该使用以下模式:(statement. Whidbey will have it, but until it’s released, everytime you need to dispose an object, you should use the following pattern:)
Dim sw As StreamWriter = Nothing
Try
sw = New StreamWriter("C:\crivo.txt")
' Do something with sw
Finally
If Not sw is Nothing Then
sw.Dispose()
End if
End Finally
如果打电话时做不同的事情(If you’re doing something different when calling) Dispose
,可能您做错了什么,并且您的代码可能会失败和/或泄漏资源.(, probably you’re doing something wrong, and your code can fail and/or leak resources.)
不要使用非结构化错误处理(Don’t use Unstructured Error Handling)
非结构化错误处理也称为(Unstructured Error Handling is also known as) On Error Goto
. Dijkstra教授在1974年撰写的论文中做得很好(. Prof. Djikstra did it very well in 1974 when he wrote)“转到声明被认为有害”(“Go To statement considered harmful”).那是30多年前了!请尽快从您的应用程序中删除所有非结构化错误处理的痕迹.(. It was more than 30 years ago! Please, remove all traces of Unstructured Error Handling from your application as soon as possible.) On Error Goto
声明会咬你,我向你保证.(statements will bite you, I’ll assure you.)
结论(Conclusion)
我希望本文能帮助某人更好地编码.我希望本文不仅仅是一个封闭的实践列表,它是讨论如何处理代码中的异常以及如何使程序更强大的讨论的起点.(I hope that this article helps someone to code better. More than a closed list of practices, I hope that this article be a starting point for a discussion of how to deal with exceptions in our code, and how to make our programs more robust.) 我简直不敢相信我写的所有这些都没有任何错误或有争议的意见.我很想听听您对这个主题的意见和建议.(I can’t believe that I wrote all of this without any mistake or controversial opinion. I’d love to hear your opinion and suggestions about this topic.)
历史(History)
- 2005年2月9日-初始版本(9 Feb 2005 - Initial version)
- 2005年2月21日-添加了有关的信息(21 Feb 2005 - Added information about)
ApplicationException
,(,)AppDomain.UnhandledException
和(and)Application.ThreadException
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
VB C# Windows .NET DotGNU Visual-Studio VS.NET2003 Dev QA Architect 新闻 翻译