我应该如何准确地使用异常?(关于IT技术)

3
我对C#还不是很熟悉,我想知道应该如何使用异常?我的意思不是在机制层面上,而是在良好实践的水平上。
例如,我将使用我的计算器对其进行标记化,然后将其转换为RPN并解决以RPN给出的问题。
在标记化步骤中,有各种无效输入,比如“7.7.8”或“^&##”,我是否应该为未知符号和无效数字分别设置异常?如果只有一个异常,然后在其中包含错误类型的方法,提供给用户,这样做是否合适?
我真的没有找到太多关于这方面的资料,所以我想问问比我更有经验的人。
-----编辑:
感谢大家的精彩回答,今天我学到了很多 :) 关于我的问题,甚至更多。

2
异常很耗费资源,应该验证输入。如果检测到无效的输入,您需要在代码中处理它,而不是抛出异常。 - Dean Kuga
3
@kzen 我不同意。如果提供无效的输入,则应将ArgumentException抛回给调用者并让调用者处理它。异常可能很昂贵,但对于简单的计算器应用程序,为无效的输入抛出异常是很常见的。 - Scott Arrington
1
@kzen - 取决于输入的来源。如果是来自用户,是的,你应该验证并优雅地失败。如果来自另一个代码块,那么该代码块有问题,异常是通知开发人员的方式。 - Robert Levy
在这种情况下使用异常似乎极大地简化了程序的流程,我用异常处理无效的输入。昂贵的事情在这里并不特别重要。 - Matthew Blanchard
@Robert,这是对我原始陈述的有效补充... - Dean Kuga
既然您是C#的新手,这里有一些需要注意的事项:不要重新抛出异常 - Brian
8个回答

15

从前置条件和后置条件的角度考虑您的词法分析器。以下是两种设计词法分析器的方式:

  1. 词法分析器是一种设备,它接收一个格式良好的输入字符串并输出一个词法分析结果。输入字符串必须是格式良好的;如果不是,则词法分析器会因异常而失败。

  2. 词法分析器是一种设备,它接收一个输入字符串并产生一个词法分析结果。词法分析包括对错误条件进行识别和分析。

你是否知道你要传递的字符串是正确的,并且希望在出现异常情况时抛出异常?或者你不确定该字符串是否正确,希望词法分析器来确定呢?

对于前一种情况,请抛出异常。对于后一种情况,请将错误分析作为词法分析器输出的一部分。

基本上我想表达的是不要产生令人烦恼的异常。烦人的异常非常恼人。如果您的代码的调用者调用它来确定字符串是否正确,则他们不希望以异常形式获得该信息,而希望以表示错误分析的数据对象形式获得该信息。如果代码的目的是产生错误分析,则请将其作为该方法的正常操作产生。不要将分析嵌入异常并使某人捕获异常才能获取它。


很棒的是你花时间亲自回答这样的问题,就像一个普通人一样!! :) 谢谢。 - Joel Gauvreau
6
@Joel:感谢你的好言相助,但我向你保证我是(相对)正常的人类。 - Eric Lippert
这个决定让人困扰的原因在于你需要知道调用代码对你的方法做了什么。 - configurator

3
根据我的经验,您不应将异常用作代码的逻辑或控制流的一部分。只有在真正发生异常时才应该使用它。例如,在存在不应该出现的非法字符时,就可以使用它。但是,如果函数正在验证字符串,并且发现了非法字符,您不应该抛出异常,至少这是我的观点。
我通常在以下情况下使用异常:当应该存在的数据库或文件无法找到时,当缺少键/值对时等。

我基本上同意这个说法,但是在我的看法中,如果没有办法准确地修复无效的输入,那么方法中的无效输入是一个异常情况。如果您的方法不能为用户提供有效答案,因为他们提供了无效的输入,则应该抛出异常,以便他们知道而不是只返回“null”或其他一些毫无意义的东西。 - Scott Arrington
异常是告诉用户输入无效的非常糟糕的方式,如果未经处理,应用程序将会崩溃。如果您处理它,那么为什么要首先抛出呢?只需验证输入并向用户显示友好的消息即可。 - Joel Gauvreau
@KallDrexx 我过去处理这个问题的一种方式是返回一个Result<>对象,该对象除了返回值之外还包含一些附加信息。 - Joel Gauvreau
很难概括地说永远不要这样做,或者总是以某种方式处理它。在计算方法中,您可以事先验证字符串仅包含有效字符。如果实际计算没有意义,则更难进行验证,而不必重新实现整个过程以仅检查其是否有效,在这种情况下,请尝试并引发异常。有些计算可能需要很长时间才能运行,特别是如果它经过许多服务层,则应首先验证您可以验证的内容。 - Joel Gauvreau
1
重点是,如果您有一个公共的SqlConnection(string connectionString)例程,则该函数应在字符串格式不正确的连接字符串时引发错误,但如果该例程是public SqlConnection CheckConnectionString(string connectionString),则它绝对不应引发异常。当程序中发生未预见的情况时,应引发异常。它不应用于通知用户有关事物、字符串、数字、格式或其他任何内容的状态。我认为这是一个很好的经验法则。 - bjorsig
显示剩余5条评论

2
为程序员创建异常,而不是用户。考虑一下,对于调用你的方法的程序员来说,什么会有用呢?
如果有专门的错误处理代码需要为特定的异常原因编写,那么提供一个特定类型的异常,这样他们就可以针对它编写“catch”块。
如果他们应该编写的错误处理代码在各种异常原因中都是通用的,则提供更通用的异常类型,这样他们只需要针对其中一个进行编码。

1

我不同意“对于不应被忽略的错误使用异常”的说法。该指南的示例是使用异常来处理控制流的经典示例。 - Brian

1
我喜欢用一个经验法则来判断是否适合使用异常:如果一个函数承诺要做某事,但没有做到,那么它应该抛出异常。一个简单的例子是ParseTryParse。注意,Parse函数承诺解析一个字符串,如果无法解析(例如,因为字符串格式不正确),则抛出异常。而TryParse承诺尝试解析,因此如果无法解析,则不会抛出异常。简而言之,如果您的函数做出了承诺却未能实现,那么您应该抛出异常。
或者说:除非函数的前提条件不满足,否则不要抛出异常。我认为Eric Lippert的Vexing Exceptions是关于哪些类型的前提条件是不合适的讨论。

0

0

编程中的一般规则是KISS。只有在需要时才应该抛出/创建异常。如果没有人会捕获它,那么就没有必要创建整套异常。

在您的情况下,您可以:

  1. 抛出InvalidArgument或创建自己的异常,指示输入无效。
  2. 提供详细的错误消息,解释问题所在。

如果您发现正在捕获异常并解析消息并采取行动,则是创建自定义异常/异常的时候了。

您可以创建具有“类型”字段的自定义异常,但任何依赖于异常的设计都是不好的。


0

所有的解析异常都应该至少共享一个基类,这样调用者就可以捕获任何解析异常而不必在catch子句中列出它们(并可能忘记其中一个)。

你可以走分类子类的路线,但我认为在有需要之前不必做出如此明确的区分。如果调用程序对所有解析错误采取相同的操作,则它们都可以是同一类,仅显示消息不同。如果变得清楚某些错误需要以不同方式处理,则创建一个子类。围绕使用案例进行设计。

顺便说一下:我认为如果给定方法无法实现其承诺,应该抛出异常,从而保护开发人员免于在失败的情况下假设成功。决定要做出哪些承诺是一个更大的设计问题,你可以查看现有的解析API以获取灵感。


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