我的方法应该抛出自己的异常,还是让.NET在文件不存在时抛出异常?

76

这是我的代码:

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

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

我是否应该自己抛出异常(请看File.Exists条件判断)? 如果文件不存在,FileStream已经会抛出FileNotFoundException了。在这里什么是良好的编程实践呢?代码分析说我们应该验证参数。但如果我直接将参数传递给另一个方法(我的或其他人的代码),并且该方法本身会抛出异常,那么在我的代码中验证参数的优点是什么呢?


25
抛出FileNotFoundException没有意义,实际上会引发竞争条件问题。你可以处理异常、让它传播或将其包装在自己的异常中。这分别对应于"我知道该怎么做"、"我不知道该怎么做"和"我想在堆栈更高的地方处理此异常"。 - Luaan
顺便提一下:如果您确实需要验证参数,可以检查filePath是否有效(即绝对路径,或者至少不包含Path.GetInvalidFileNameChars())。 - Alexei Levenkov
2
@AlexeiLevenkov 我认为也不需要。FileStream会处理它。 - fhnaseer
3
相关内容:来自Eric Lippert的博客 - 恼人的异常 - 这种情况属于“外生异常”。 - Kobi
这取决于你想要实现什么。因此,对于这个问题并没有“正确答案”。它取决于具体情况。 - swe
4个回答

118
"

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

除此之外,正如评论中指出的那样,虽然File.Exists()可能会返回true,但实际上打开文件仍然可能因各种原因失败。因此,在打开文件时需要重复错误检查和抛出。

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

当然,调用File.Exists()可能是有道理的,但不在这种模式下。

"

3
@CodeCaster 确定。在 File.Open(f) 抛出异常和你捕获它之间,其他一些进程可能会创建该文件。问题不是情况在处理之前会发生改变(因为在失败的情况下几乎无法避免),而是你应该进行一次错误检查以避免重复(并且你必须在打开时进行错误检查)。 (还要注意,当与某些文件系统交互时,成功打开并不意味着文件不会被删除:因此即使在打开似乎有效的情况下也可能发生故障。) - Yakk - Adam Nevraumont
2
除了竞态条件之外,这段代码还忽略了文件存在但不可读(由于权限等原因)的情况,甚至可能存在更多错误。 - el.pescado - нет войне
8
通常情况下,检查文件是否存在并立即尝试打开它会被视为一种反模式,但如果其他逻辑将检查和打开分开,则我不认为这是一种反模式。在某些网络系统中,检查文件的可用性可能比获取打开所需锁定更便宜。如果代码无法执行任何有用操作,除非多个文件都可用,那么在获取打开任何文件所需的锁之前,确保所有文件似乎都可用可能是有用的。 - supercat
3
Python有一个名为“宁求原谅不要征得许可”的策略(EAFP)。它意味着有时候,由于竞态条件和超出你控制的事情,最好只是让你的程序做它想做的事情,并处理所有可能的错误(异常),而不是在每一步检查条件是否达到最优。在这个例子中,我认为可以应用EAFP以实现最佳实践。 - sleblanc
1
还有可能会出现FileNotFoundException,因为用户没有权限查看它或文件夹不存在...所以如果你想帮助用户,在catch中可以检查更具体的原因,例如Folder.Exists - 或者只需确保友好的用户消息说“文件无法找到或访问”! - AndrewD
显示剩余7条评论

15

你的方法被称为ReadSomeFile,并以filename作为输入,因此抛出FileNotFoundException是合理的。由于你不能通过捕获异常再自己抛出来添加任何价值,所以就让.NET抛出它。

但是如果你的方法是LoadData(databaseName),并且需要访问许多文件,则捕获异常并抛出自定义异常可能是有价值的,因为你可以将databaseName和其他有用的信息添加到异常中。


即使在最后一种情况下,您也应该尝试捕获内部异常而不是使用File.Exists。 - Stig Hemmer
这是一个普遍现象,答案实际上可以回答问题,但得到的赞数却很少。 - Andriy Tylychko

1
除了已经给出的答案,你还可以说这取决于你希望发生什么。
如果你想读取一个日志文件但它不存在,你想抛出一个错误还是只返回一个空字符串(或空字符串数组)?
如果返回一个默认值(比如一个空字符串),我会简单地用一个try-catch包装函数的内容(但只针对预期的错误),并在catch块中返回默认值,同时在try块中返回实际内容。
这将留下三种可能的情况:
1.返回文件的内容;
2.返回默认值,因为发生了预期的错误;
3..NET抛出一个错误,因为你没有捕获到该特定错误。

0

当你对文件名一无所知时,让正确的方法尝试打开文件,例如特殊文件名(例如设备文件UNC路径):

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

一些特殊文件名的示例包括:

  • CON
  • NUL
  • COM1、COM2、COM3、COM4
  • \\server\share\file_path
  • \\teela\admin$\system32(可达到C:\WINNT\system32)
  • C:..\File.txt
  • \\.\COM1
  • %TEMP%
  • 等等...

2
我对Windows不是很了解,但我十分确定%TEMP%是一个环境变量,而不是一个文件,并且“打开文件”功能不会展开它。 - Functino

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接