设计良好的错误处理代码块集可使程序更可靠并且不容易崩溃,因为应用程序可处理这样的错误。下表包含有关处理异常的最佳做法的建议:
知道何时设置 Try/Catch 块。例如,可以以编程方式检查可能发生的条件,而不使用异常处理。在其他情况下,使用异常处理捕捉错误条件是适当的。
下面的示例使用 if 语句检查连接是否关闭。如果连接未关闭,可以使用此方法而不是引发异常。
[Visual Basic]
If conn.State <> ConnectionState.Closed Then
conn.Close()
End If
[C#]
if(conn.State != ConnectionState.Closed)
conn.Close();
在下面的示例中,如果连接未关闭,则引发异常。
[Visual Basic]
Try
conn.Close()
Catch ex As InvalidOperationException
'Do something with the error or ignore it.
End Try
[C#]
try {
conn.Close();
}
catch(InvalidOperationException ex) {
//Do something with the error or ignore it.
}
所选择的方法依赖于预计事件发生的频率。如果事件确实是异常的并且是一个错误(如意外的文件尾),则使用异常处理比较好,因为正常情况下执行的代码更少。如果事件是例行发生的,使用编程方法检查错误比较好。在此情况下,如果发生异常,将需要更长的时间处理。
在可潜在生成异常的代码周围使用 Try/Finally 块,并将 Catch 语句集中在一个位置。以这种方式,Try 语句生成异常,Finally 语句关闭或释放资源,而 Catch 语句从中心位置处理异常。
始终按从最特定到最不特定的顺序对 Catch 块中的异常排序。此方法在将特定异常传递给更常规的 Catch 块之前处理该异常。
以“Exception”这个词作为异常类名的结尾。例如:
[Visual Basic]
Public Class EmployeeListNotFoundException
Inherits Exception
[C#]
public class MyFileNotFoundException : ApplicationException {
}
当创建用户定义的异常时,必须确保异常的元数据对远程执行的代码可用,包括当异常跨应用程序域发生时。例如,假设应用程序域 A 创建应用程序域 B,后者执行引发异常代码。应用程序域 A 若想正确捕捉和处理异常,它必须能够找到包含应用程序域 B 引发的异常的程序集。如果包含应用程序域 B 引发的异常的程序集位于应用程序域 B 的应用程序基下,而不是位于应用程序域 A 的应用程序基下,则应用程序域 A 将无法找到异常,公共语言运行库将引发 FileNotFoundException。为避免此情况,可以两种方式部署包含异常信息的程序集:
将程序集放在两个应用程序域共享的公共应用程序基中,或者
如果两个应用程序域不共享一个公共应用程序基,则用强名称给包含异常信息的程序集签名并将其部署到全局程序集缓存中。
在 C# 和 C++ 的托管扩展中创建您自己的异常类时,至少使用三个公共构造函数。有关示例,请参阅使用用户定义的异常。
在大多数情况下,使用预定义的异常类型。仅为编程方案定义新异常类型。引入新异常类,使程序员能够根据异常类在代码中采取不同的操作。
不要从 Exception 基类派生用户定义的异常。对于大多数应用程序,从 ApplicationException 类派生自定义异常。
在每个异常中都包含一个本地化描述字符串。当用户看到错误信息时,该信息从引发的异常的描述字符串派生,而不是从异常类派生。
使用语法上正确的错误信息(包括结束标点符号)。在异常的描述字符串中,每个句子都应以句号结尾。
为编程访问提供 Exception 属性。仅当存在附加信息有用的编程方案时,才在异常中包含附加信息(不包括描述字符串)。
对非常常见的错误情况返回空。例如,如果没找到文件,File.Open 返回空;但如果文件被锁定,则引发异常。
类的设计应使在正常使用中从不引发异常。例如,FileStream 类公开另一种确定是否已到达文件尾的方法。这避免了在读取超过文件尾时引发的异常。下面的示例显示如何读到文件尾。
[Visual Basic]
Class FileRead
Sub Open()
Dim stream As FileStream = File.Open("myfile.txt", FileMode.Open)
Dim b As Byte
' ReadByte returns -1 at EOF.
While b = stream.ReadByte() <> True
' Do something.
End While
End Sub
End Class
[C#]
class FileRead {
void Open() {
FileStream stream = File.Open("myfile.txt", FileMode.Open);
byte b;
// ReadByte returns -1 at EOF.
while ((b == stream.ReadByte()) != true) {
// Do something.
}
}
}
如果根据对象的当前状态,属性集或方法调用不适当,引发 InvalidOperationException。
如果传递错误的参数,引发 ArgumentException 或从 ArgumentException 派生的类。
堆栈跟踪从引发异常的语句开始,到捕捉异常的 Catch 语句结束。当决定在何处放置 Throw 语句时需考虑这一点。
使用异常生成器方法。类从其实现中的不同位置引发同一异常是常见的情况。为避免过多的代码,应使用帮助器方法创建异常并将其返回。例如:
[Visual Basic]
Class File
Private fileName As String
Public Function Read(bytes As Integer) As Byte()
If Not ReadFile(handle, bytes) Then
Throw NewFileIOException()
End If
End Function 'Read
Function NewFileIOException() As FileException
Dim description As String = __unknown ' Build localized string, including fileName.
Return New FileException(description) '
End Function 'NewFileIOException
End Class 'File
[C#]
class File {
string fileName;
public byte[] Read(int bytes) {
if (!ReadFile(handle, bytes))
throw NewFileIOException();
}
FileException NewFileIOException() {
string description = // Build localized string, including fileName.
return new FileException(description);
}
}
或者,使用异常的构造函数生成异常。这更适合全局异常类,如 ArgumentException。
引发异常,而不是返回错误代码或 HRESULT。
引发异常时清理中间结果。当异常从方法引发时,调用方应该能够假定没有副作用。
……