8

我在 MSDN 中四处寻找,发现这篇文章有一个有趣的建议:不要让公共成员根据某些选项抛出或不抛出异常。

例如:

Uri ParseUri(string uriValue, bool throwOnError)

现在我当然可以看到,在 99% 的情况下,这将是可怕的,但它的偶尔使用是否合理?

我见过的一种情况是在访问数据库或配置文件中的数据时使用“AllowEmpty”参数。例如:

object LoadConfigSetting(string key, bool allowEmpty);

在这种情况下,替代方法是返回 null。但是随后调用代码将充满空引用检查。(如果您愿意,该方法还将排除实际允许 null 作为特定可配置值的能力)。

你怎么认为?为什么这会是个大问题?

4

8 回答 8

10

我认为基于布尔值进行投掷/不投掷决定绝对是一个坏主意。即因为它要求开发人员查看一段代码以了解 API 的功能知识以确定布尔值的含义。这本身就很糟糕,但是当它改变底层错误处理时,开发人员在阅读代码时很容易出错。

在这种情况下,拥有 2 个 API 会更好、更易读。

Uri ParseUriOrThrow(string value);

bool TryParseUri(string value, out Uri uri);

在这种情况下,这些 API 的作用是 100% 清楚的。

关于为什么布尔值作为参数不好的文章:http: //blogs.msdn.com/jaredpar/archive/2007/01/23/boolean-parameters.aspx

于 2009-03-09T01:03:36.860 回答
3

通常最好选择一种错误处理机制并坚持使用它。允许这种触发器代码并不能真正改善开发人员的生活。

在上面的例子中,如果解析失败并且 throwOnError 为 false 会发生什么?现在用户必须猜测是否返回NULL,或者上帝知道......

确实,作为更好的错误处理方法,异常和返回值之间存在着持续的争论,但我很确定对于保持一致并坚持你所做的任何选择已经达成共识。API 不会让用户感到惊讶,错误处理应该是接口的一部分,并且与接口一样明确定义。

于 2009-03-09T01:00:23.530 回答
1

从可读性的角度来看,这有点令人讨厌。开发人员倾向于期望每个方法都抛出异常,如果他们想忽略异常,他们会自己捕获它。使用“布尔标志”方法,每个方法都需要实现这种异常抑制语义。

但是,我认为 MSDN 文章严格指的是“throwOnError”标志。在这些情况下,要么错误在方法本身内部被忽略(不好,因为它被隐藏了),要么返回某种空/错误对象(不好,因为你没有使用异常来处理错误,这是不一致的并且本身就是错误的-易于)。

而你的例子对我来说似乎很好。异常表示方法未能执行其职责 - 没有返回值。然而,'allowEmpty' 标志改变了方法的语义——所以本来应该是一个异常('Empty value')的东西现在是预期的并且是合法的。另外,如果您抛出异常,您无法轻松返回配置数据。所以在这种情况下似乎没问题。

于 2009-03-09T01:04:35.563 回答
1

在任何公共 API 中,有两种方法来检查错误情况确实是一个坏主意,因为如果发生错误会发生什么就变得不明显了。仅仅通过查看代码将无济于事。您必须了解标志参数的语义(没有什么可以阻止它成为表达式)。

如果检查 null 不是一个选项,并且如果我需要从这个特定的故障中恢复,我更愿意创建一个特定的异常,以便以后可以捕获它并适当地处理它。在任何其他情况下,我都会抛出一般异常。

于 2009-03-09T01:06:13.407 回答
0

与此相符的另一个示例可能是某些值类型上的 TryParse 方法集

bool DateTime.TryParse(string text, out DateTime)

于 2009-03-09T01:05:27.450 回答
0

拥有一个 donTThrowException 参数会破坏整个异常点(在任何语言中)。如果调用代码想要:

public static void Main()
{
        FileStream myFile = File.Open("NonExistent.txt", FileMode.Open, FileAccess.Read);
}

欢迎他们(C# 甚至没有检查异常)。在 Java 中,同样的事情可以通过以下方式完成:

public static void main(String[] args) throws FileNotFoundException
{
        FileInputStream fs = new FileInputStream("NonExistent.txt");
}

无论哪种方式,决定如何处理(或不处理)异常是调用者的工作,而不是被调用者的工作。

于 2009-03-09T01:21:23.213 回答
0

在链接到的文章中有一条注释,异常不应用于控制流 - 这似乎暗示在示例问题中。异常应该反映方法级别的失败。有一个可以抛出错误的签名似乎没有经过深思熟虑的设计。

Jeffrey Richters 通过 C# 预订 CLR 指出 - “当方法无法完成其名称所指示的任务时,您应该抛出异常”。

他的书还指出了一个非常常见的错误。人们倾向于编写代码来捕获所有内容(他的话“一个普遍存在的错误,即没有接受过正确使用异常的适当培训的开发人员往往会过于频繁且不正确地使用 catch 块。当您捕获异常时,您是在说明你预料到了这个异常,你明白它为什么会发生,你知道如何处理它。”)

这使我尝试为我可以预期并且可以在我的逻辑中处理的异常编写代码,否则它应该是一个错误。

验证您的论点并防止异常,并且只捕获您可以处理的内容。

于 2009-03-09T02:45:14.463 回答
0

我敢肯定,有一个参数来指示故障是否应该导致异常或简单地返回错误指示通常很有用,因为这样的参数可以很容易地从外部例程传递到内部例程。考虑类似的事情:

Byte[] ReadPacket(bool DontThrowIfNone) // 记录为返回 null if none { int len = ReadByte(DontThrowIfNone); // 记录为如果没有返回 -1 if (len

如果在读取数据时发生类似 TimeoutException 的情况会导致异常,则应在 ReadByte() 或 ReadMultiBytesbytes() 中引发此类异常。但是,如果应将这种数据缺失视为正常现象,则 ReadByte() 或 ReadMultiBytesbytes() 例程不应引发异常。如果简单地使用 do/try 模式,则 ReadPacket 和 TryReadPacket 例程需要具有几乎相同的代码,但其中一个使用 Read* 方法,另一个使用 TryRead* 方法。恶心。

使用枚举而不是布尔值可能会更好。

于 2011-10-17T17:43:50.023 回答