如何在C#中开发库时进行正确的异常处理

本文关键字:异常处理 开发 | 更新日期: 2023-09-27 18:25:10

假设我想创建一个DLL库,以一种易于使用的方式提供通用文件系统服务。这个库可以在第三方.NET应用程序中使用。假设这个库只向公众提供这个接口:

public interface IFileSystemService
{
    void CreateDirectory(string path);
    void DeleteDirectory(string path);
    bool DirectoryExists(string path);
    IEnumerable<string> EnumerateDirectories(string path);
    void DeleteFile(string path);
    bool FileExists(string path);
    void CopyFile(string sourcePath, string destinationPath, bool overwriteExisting);
    // ... and other methods   
}

当使用System.IO命名空间(例如FileInfo、DirectoryInfo、Directory、File等)中的标准.NET类来实现此接口时,异常处理的最佳实践是什么?如何使这个库健壮并易于客户端代码使用?我如何做到这一点,以便用于错误处理的客户端代码干净而不过于复杂?

如何在C#中开发库时进行正确的异常处理

最佳实践是尽你所能从异常中恢复,但如果你什么都做不了,就让异常传播到调用者,让他们处理它。

至于如何使库健壮且易于使用,请尽可能多地为库编写文档。您可以使用XML文档将哪些异常直接包含在DLL 的元数据中

(评论来自Directory.CreateDirectory(string)

public interface IFileSystemService
{
    /// <summary>
    /// Creates all directories and subdirectories as specified by <paramref name="path"/>.    /// 
    /// </summary>
    /// 
    /// <returns>
    /// A <see cref="T:System.IO.DirectoryInfo"/> as specified by <paramref name="path"/>.    /// 
    /// </returns>
    /// <param name="path">The directory path to create.</param>
    /// <exception cref="T:System.IO.IOException">The directory specified by <paramref name="path"/> is read-only.</exception>
    /// <exception cref="T:System.UnauthorizedAccessException">The caller does not have the required permission.</exception>
    /// <exception cref="T:System.ArgumentException"><paramref name="path"/> is a zero-length string, contains only white space, or contains one or more invalid characters as defined by <see cref="F:System.IO.Path.InvalidPathChars"/>.
    /// 
    /// -or-
    /// <paramref name="path"/> is prefixed with, or contains only a colon character (:).</exception>
    ///<exception cref="T:System.ArgumentNullException"><paramref name="path"/> is null.</exception>
    ///<exception cref="T:System.IO.PathTooLongException">The specified path, file name, or both exceed the system-defined maximum length. For example, on Windows-based platforms, paths must be less than 248 characters and file names must be less than 260 characters.</exception>
    ///<exception cref="T:System.IO.DirectoryNotFoundException">The specified path is invalid (for example, it is on an unmapped drive).</exception>
    ///<exception cref="T:System.NotSupportedException"><paramref name="path"/> contains a colon character (:) that is not part of a drive label ("C:'").</exception>
    void CreateDirectory(string path);
    //Do similar comments for the rest.
    void DeleteDirectory(string path);
    bool DirectoryExists(string path);
    IEnumerable<string> EnumerateDirectories(string path);
    void DeleteFile(string path);
    bool FileExists(string path);
    void CopyFile(string sourcePath, string destinationPath, bool overwriteExisting);
    // ... and other methods   
}

当使用System.IO命名空间中的标准.NET类(例如FileInfo、DirectoryInfo、Directory、File等)来实现此接口时,异常处理的最佳实践是什么

就我而言,最好的做法是让异常冒出来。作为一个"框架"实现者,您不知道API用户的异常处理的意图是什么。因为您不知道这个用户的意图,所以最好让异常被抛出。

  1. 通过让异常冒泡,您可以获得异常实际发生位置的完整堆栈跟踪
  2. 您还允许基于一个上下文处理想要捕获的异常

根据Eric Lippert的博客文章,我再也找不到Vexing Exceptions了,我记得他说最好看到这样的代码:

if (condition) throw new Exception("message");

比看:

try { something(); }
catch (Exception ex) { throw new Exception(); }

因为每次重新引发异常时都要重新设置堆栈跟踪。或者,你可以这样做:

try { something(); }
catch (Exception ex) { 
    handleTheExceptionInternallyForYourPurpose();
    throw ex; // simply let the exception flow go its way to the top, to the caller.
}

另外,这完全取决于你对行为的期望。假设您不希望允许空值作为path参数,那么在这种情况下,您可能希望抛出ArgumentNullException

public void CreateDirectory(string path) {
    if (path == null) throw new ArgumentNullException("path");
    if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("path");
    // create your directory here based on the path provided...
}

如何使此库健壮并易于客户端代码使用

我打赌你不能为其他程序员编程。您可能拥有世界上最好的库,经验较少或不尊重SRP的库可能会导致代码混乱。遵循一些准则并尊重最佳实践和原则是每个程序员的责任。可悲的是,这些原则很少得到遵守,至少在我这个世界的角落里是这样。无论你做什么,根据你的背景,总会有更好的方法来做事。

如何做到这一点,以便用于错误处理的客户端代码干净而不过于复杂

复杂的东西对代码来说总是很复杂的,所以没有办法逃脱。通过坚持SOLID原则,并使用明天的最佳实践,您将拥有一个简单易懂的库。

XML注释对于库也是一种很好的方法,这样IntelliSense就可以在键入时显示它。