【问题标题】:Should my method throw its own exception, or let .NET throw if a file doesn't exist?如果文件不存在,我的方法应该抛出自己的异常,还是让 .NET 抛出?
【发布时间】:2015-10-05 14:36:48
【问题描述】:

这是我的代码:

public void ReadSomeFile(string filePath)
{
    if (!File.Exists(filePath))
        throw new FileNotFoundException();

    var stream = new FileStream(filePath, ....)
    .....
}

我是否应该自己抛出异常(参见File.Exists 检查)?如果文件不存在,FileStream 将已经抛出 FileNotFoundException。这里有什么好的编程习惯?代码分析说我们应该验证我们的参数。但是,如果我将该参数直接传递给另一个方法(我的或其他人的代码)并且该方法本身会引发异常,那么在我的代码中验证参数有什么好处?

【问题讨论】:

  • 抛出FileNotFoundException 毫无意义——事实上,它只是引发了竞争条件问题。您可以处理异常,让它传播,或者将其包装在您自己的异常中。这分别对应于“我知道如何处理这个”、“我不知道如何处理这个”和“我想在堆栈上处理这个更高的位置”。
  • 旁注:如果您真的需要验证参数,您可以检查filePath 是否有效(即绝对路径,或者至少不包含Path.GetInvalidFileNameChars()
  • @AlexeiLevenkov 我认为这也不是必需的。 FileStream 会处理这个,
  • 相关:来自Eric Lippert's blog - Vexing exceptions - 此案例属于“exogenous exceptions”
  • 这真的取决于你想要达到的目标。所以对此没有“正确答案”。这取决于

标签: c# exception


【解决方案1】:

if (File.Exists(f)) { DoSomething(f) }(或其否定)是一种反模式。该文件可以在这两个语句之间删除或创建,因此像这样检查它的存在意义不大。

除此之外,正如 cmets 中所指出的,虽然 File.Exists() 可能返回 true,但由于各种原因,文件的实际打开仍然可能失败。因此,您将不得不重复错误检查并在打开文件时抛出异常。

由于您不想重复自己而是保持代码 DRY,因此只需尝试打开文件并让 new FileStream() 抛出。然后您可以捕获异常,如果您愿意,可以重新抛出原始异常或抛出特定于应用程序的异常。

当然调用File.Exists() 是合理的,但不是这种模式。

【讨论】:

  • @CodeCaster 当然。其他一些进程可以在File.Open(f); throwing 和你捕获它之间创建文件。问题不在于在您处理它之前情况可能会发生变化(因为在失败情况下几乎是不可避免的),而是您应该错误检查一次以避免重复(并且您必须 无论如何打开错误检查)。 (另请注意,在与某些文件系统通信时,成功打开并不意味着文件不会在您身上被删除:因此即使在打开似乎工作之后也会发生失败。)
  • 除了竞争条件,此代码忽略文件存在但不可读的情况(由于权限等),甚至可能出现更多错误.
  • 虽然检查文件是否存在并立即尝试打开它通常是反模式,但如果其他逻辑将检查和打开分开,我不会认为它是反模式。在某些网络系统上,检查文件的明显可用性可能比获取打开文件所需的锁便宜。如果除非有多个文件可用,否则代码无法做任何有用的事情,那么在获取打开任何文件所需的锁之前确保所有文件似乎都可用可能很有用。
  • Python 有一个名为“请求宽恕比请求许可更容易”(EAFP)的策略。这意味着有时,由于竞争条件和您无法控制的事情,最好只要求您的程序做它想做的事情,并在那里处理所有可能的错误(异常),而不是在每一步检查是否条件是最优的。在这个例子中,我认为 EAFP 可以应用于最佳实践。
  • 也可能会发生 FileNotFoundException,因为用户无权查看它或文件夹不存在......所以如果你想帮助用户,你可以在 catch 中检查出于更具体的原因,例如Folder.Exists - 或者只是确保您友好的用户消息说“文件无法找到或访问”!
【解决方案2】:

您的方法称为ReadSomeFile,并以filename 作为输入,因此抛出FileNotFoundException 是合理的。由于您无法通过捕获异常然后自己抛出它来增加任何价值,所以让 .NET 抛出它。

但是,如果您的方法是 LoadData(databaseName) 并且它必须访问许多文件,那么捕获异常并引发自定义异常可能是有价值的,因为您可以将 databaseName 与其他有用信息一起添加到异常中。

【讨论】:

  • 即使在最后一种情况下,您也应该尝试捕获内部异常,而不是使用 File.Exists。
  • 实际回答问题的答案获得的赞成票要少得多
【解决方案3】:

除了已经给出的答案,您还可以说这取决于您期望发生的情况。

如果要读取一个不存在的日志文件,是要抛出错误,还是只抛出一个空字符串(或空字符串数组)?

如果返回默认值(如空字符串),我只需将函数的内容包装在 try-catch 中(但仅用于预期错误)并在 catch 块中返回默认值,同时返回实际try 块中的内容。

这将留下三种可能的情况:

  1. 返回文件内容;
  2. 返回默认值,因为发生了预期的错误;
  3. .NET 引发错误,因为您没有捕获该特定错误。

【讨论】:

    【解决方案4】:

    让正确的方法尝试打开文件,而您对完整没有任何想法 文件名,比如特殊文件名(例如Device filesUNC paths):

    在某些情况下,其他文件方法可能会失败,但打开文件是成功的。

    一些特殊文件名的例子是:

    • 骗局
    • NUL
    • COM1、COM2、COM3、COM4
    • \\server\share\file_path
    • \\teela\admin$\system32(到达 C:\WINNT\system32)
    • C:..\File.txt
    • \\.\COM1
    • %TEMP%
    • 还有更多...

    【讨论】:

    • 我对 Windows 了解不多,但我很确定 %TEMP% 是一个环境变量,而不是一个文件,并且“打开文件”功能不会扩展它。
    猜你喜欢
    • 1970-01-01
    • 2015-08-27
    • 1970-01-01
    • 1970-01-01
    • 2011-04-27
    • 2013-06-13
    • 1970-01-01
    • 1970-01-01
    • 2021-02-08
    相关资源
    最近更新 更多