处理异常的最佳做法
设计良好的错误处理代码块集可使程序更可靠并且不容易崩溃,因为应用程序可处理这样的错误。 下表包含有关处理异常的最佳做法的建议:
知道何时设置 Try/Catch 块。 例如,可以以编程方式检查可能发生的条件,而不使用异常处理。 在其他情况下,使用异常处理捕捉错误条件是适当的。
下面的示例使用 if 语句检查连接是否关闭。 如果连接未关闭,可以使用此方法而不是引发异常。
If conn.State <> ConnectionState.Closed Then
conn.Close()
End IF
if (conn.State != ConnectionState.Closed)
{
conn.Close();
}
if (conn->State != ConnectionState::Closed)
{
conn->Close();
}
在下面的示例中,如果连接未关闭,则引发异常。
Try
conn.Close()
Catch ex As InvalidOperationException
Console.WriteLine(ex.GetType().FullName)
Console.WriteLine(ex.Message)
End Try
try
{
conn.Close();
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.GetType().FullName);
Console.WriteLine(ex.Message);
}
try
{
conn->Close();
}
catch (InvalidOperationException^ ex)
{
Console::WriteLine(ex->GetType()->FullName);
Console::WriteLine(ex->Message);
}
所选择的方法依赖于预计事件发生的频率。 如果事件确实是异常的并且是一个错误(如意外的文件尾),则使用异常处理比较好,因为正常情况下执行的代码更少。 如果事件是例行发生的,使用编程方法检查错误比较好。 在此情况下,如果发生异常,将需要更长的时间处理。
在可潜在生成异常的代码周围使用 Try/Finally 块,并将 Catch 语句集中在一个位置。 以这种方式,Try 语句生成异常,Finally 语句关闭或释放资源,而 Catch 语句从中心位置处理异常。
始终按从最特定到最不特定的顺序对 Catch 块中的异常排序。 此方法在将特定异常传递给更常规的 Catch 块之前处理该异常。
以“Exception”这个词作为异常类名的结尾。 例如:
Public Class MyFileNotFoundException
Inherits Exception
End Class
public class MyFileNotFoundException : Exception
{
}
public ref class MyFileNotFoundException : public Exception
{
};
当创建用户定义的异常时,必须确保异常的元数据对远程执行的代码可用,包括当异常跨应用程序域发生时。 例如,假设应用程序域 A 创建应用程序域 B,后者执行引发异常代码。 应用程序域 A 若想正确捕获和处理异常,它必须能够找到包含应用程序域 B 所引发的异常的程序集。 如果包含应用程序域 B 引发的异常的程序集位于应用程序域 B 的应用程序基目录下,而不是位于应用程序域 A 的应用程序基目录下,则应用程序域 A 将无法找到异常,公共语言运行时将引发 FileNotFoundException。 为避免此情况,可以两种方式部署包含异常信息的程序集:
将程序集放在两个应用程序域共享的公共应用程序基中
- 或 -
如果两个应用程序域不共享一个公共应用程序基,则用强名称给包含异常信息的程序集签名并将其部署到全局程序集缓存中。
在 C# 和 C++ 中创建您自己的异常类时,至少使用三个公共构造函数。 有关示例,请参见如何:创建用户定义的异常。
在大多数情况下,使用预定义的异常类型。 仅为编程方案定义新异常类型。 引入新异常类,使程序员能够根据异常类在代码中采取不同的操作。
对于大多数应用程序,从 Exception 类派生自定义异常。 最初要求自定义异常应该从 ApplicationException 类派生;但是在实践中并未发现这样有很大意义。
在每个异常中都包含一个本地化描述字符串。 当用户看到错误消息时,该消息从引发的异常的描述字符串派生,而不是从异常类派生。
使用语法上正确的错误消息(包括结束标点符号)。 在异常的描述字符串中,每个句子都应以句号结尾。
为编程访问提供 Exception 属性。 仅当存在附加信息有用的编程方案时,才在异常中包含附加信息(不包括描述字符串)。
对非常常见的错误情况返回 null。 例如,如果没找到文件,Open 将返回 null;但如果文件被锁定,则引发异常。
类的设计应使在正常使用中从不引发异常。 例如,FileStream 类公开另一种确定是否已到达文件末尾的方法。 这避免了在读取超过文件尾时引发的异常。 下面的示例显示如何读到文件尾。
Class FileRead
Public Sub ReadAll(fileToRead As FileStream)
' This if statement is optional
' as it is very unlikely that
' the stream would ever be null.
If fileToRead Is Nothing Then
Throw New System.ArgumentNullException()
End If
Dim b As Integer
' Set the stream position to the beginning of the file.
fileToRead.Seek(0, SeekOrigin.Begin)
' Read each byte to the end of the file.
For i As Integer = 0 To fileToRead.Length - 1
b = fileToRead.ReadByte()
Console.Write(b.ToString())
' Or do something else with the byte.
Next i
End Sub
End Class
class FileRead
{
public void ReadAll(FileStream fileToRead)
{
// This if statement is optional
// as it is very unlikely that
// the stream would ever be null.
if (fileToRead == null)
{
throw new System.ArgumentNullException();
}
int b;
// Set the stream position to the beginning of the file.
fileToRead.Seek(0, SeekOrigin.Begin);
// Read each byte to the end of the file.
for (int i = 0; i < fileToRead.Length; i++)
{
b = fileToRead.ReadByte();
Console.Write(b.ToString());
// Or do something else with the byte.
}
}
}
class FileRead
{
public:
void ReadAll(FileStream^ fileToRead)
{
// This if statement is optional
// as it is very unlikely that
// the stream would ever be null.
if (fileToRead == nullptr)
{
throw gcnew System::ArgumentNullException();
}
int b;
// Set the stream position to the beginning of the file.
fileToRead->Seek(0, SeekOrigin::Begin);
// Read each byte to the end of the file.
for (int i = 0; i < fileToRead->Length; i++)
{
b = fileToRead->ReadByte();
Console::Write(b.ToString());
// Or do something else with the byte.
}
}
};
如果根据对象的当前状态,属性集或方法调用不适当,则引发 InvalidOperationException。
如果传递的参数无效,则引发 ArgumentException 或从 ArgumentException 派生的类。
堆栈跟踪从引发异常的语句开始,到捕捉异常的 Catch 语句结束。 当决定在何处放置 Throw 语句时需考虑这一点。
使用异常生成器方法。 类从其实现中的不同位置引发同一异常是常见的情况。 为避免过多的代码,应使用帮助器方法创建异常并将其返回。 例如:
Class FileReader
Private fileName As String
Public Sub New(path As String)
fileName = path
End Sub
Public Function Read(bytes As Integer) As Byte()
Dim results() As Byte = FileUtils.ReadFromFile(fileName, bytes)
If results Is Nothing
Throw NewFileIOException()
End If
Return results
End Function
Function NewFileIOException() As FileReaderException
Dim description As String = "My NewFileIOException Description"
Return New FileReaderException(description)
End Function
End Class
class FileReader
{
private string fileName;
public FileReader(string path)
{
fileName = path;
}
public byte[] Read(int bytes)
{
byte[] results = FileUtils.ReadFromFile(fileName, bytes);
if (results == null)
{
throw NewFileIOException();
}
return results;
}
FileReaderException NewFileIOException()
{
string description = "My NewFileIOException Description";
return new FileReaderException(description);
}
}
ref class FileReader
{
private:
String^ fileName;
public:
FileReader(String^ path)
{
fileName = path;
}
array<Byte>^ Read(int bytes)
{
array<Byte>^ results = FileUtils::ReadFromFile(fileName, bytes);
if (results == nullptr)
{
throw NewFileIOException();
}
return results;
}
FileReaderException^ NewFileIOException()
{
String^ description = "My NewFileIOException Description";
return gcnew FileReaderException(description);
}
};
或者,使用异常的构造函数生成异常。 这更适合全局异常类,例如 ArgumentException。
引发异常,而不是返回错误代码或 HRESULT。
引发异常时清理中间结果。 当异常从方法引发时,调用方应该能够假定没有副作用。