简体   繁体   English

对何时抛出异常感到困惑

[英]Confused about when to throw an exception

I am working on a library designed to communicate (over RS232 serial communication) with external devices. 我正在开发一个用于与外部设备进行通信(通过RS232串行通信)的库。 I was thinking about error handling strategy and exceptions seemed to be right and industry standard way of reporting errors. 我在考虑错误处理策略,异常似乎是正确的,行业标准的报告错误方式。

So I read few guidelines on exceptions. 所以我读了一些关于例外的指南。 One pretty clearly states that I should not worry about performance hit: 一个很明确指出,我应该担心性能问题:

Do not use error codes because of concerns that exceptions might affect performance negatively. 不要使用错误代码,因为担心异常可能会对性能产生负面影响。

Other told me NOT to throw exception in normal cases: 其他告诉我在正常情况下不要抛出异常:

Do not use exceptions for normal or expected errors, or for normal flow of control. 不要将异常用于正常或预期的错误,也不要用于正常的控制流程。

I am not able to draw clear line between normal/expected and other cases. 我无法在正常/预期和其他情况之间划清界限。 For example in my library, a operation may fail because: 例如,在我的库中,操作可能会失败,因为:

  1. There is no response from device. 设备没有响应。 (no cable connected, device not turned on, wrong baud rate) (没有连接电缆,设备未打开,波特率错误)
  2. Operation request is rejected by device because it couldn't authenticate the request. 设备拒绝操作请求,因为它无法验证请求。
  3. Communication failed in between. 沟通失败了。 (someone tripped over the cable, device was powered off suddenly). (有人绊倒了电缆,设备突然断电)。

I can think all above as expected problems because they can happen in practice very often (infact many marketing morons call me to solve the ^problem^ in my software only to find out they didnt connect the cable to their laptop). 我可以认为上述所有问题都是预期的问题,因为它们可能经常在实践中发生(实际上很多营销蠢货叫我解决我的软件中的^问题^只是为了发现他们没有将电缆连接到他们的笔记本电脑)。 So may be exceptions should not be thrown because otherwise application programmer will have to catch those at plenty of places (a lot of catch blocks are also NOT nice to have I believe). 因此可能不应该抛出异常,因为否则应用程序员必须在很多地方捕获那些(很多我认为很多catch块也不好)。

On the other hand, I also tend to think that these are all errors which I somehow need to report to application programmer, and exception seems to be the way to do that. 另一方面,我也倾向于认为这些都是我在某种程度上需要向应用程序员报告的错误,异常似乎是这样做的方法。 If I don't use exceptions, I will need to report these problems using some error code or error enums. 如果我不使用异常,我将需要使用一些错误代码或错误枚举报告这些问题。 (ugly, I know). (丑陋,我知道)。

Which approach do you think I should take? 你认为我应该采取哪种方法?

You are developing a library , a component that will be utilized by other applications. 您正在开发一个 ,一个将被其他应用程序使用的组件。

Therefore in the expected cases you mention I would certainly use exceptions to communicate to the calling application that something is amiss. 因此,在预期的情况下,你提到我肯定会使用异常与调用应用程序通信,这是有问题的。 You should define a custom exception for each of these scenarios and then clearly document when they may occur. 您应该为每个场景定义一个自定义例外,然后清楚地记录它们何时可能发生。

This then allows the application client code to make the decision as to how best to proceed. 然后,这允许应用程序客户端代码决定如何最好地继续。 Only the client application can make this decision and clearly documented exceptions greatly aid this. 只有客户端应用程序才能做出此决定,并且明确记录的异常对此有很大帮

The best thing about a custom exception is that you can provide multiple meaningful / useful pieces of data relating to the problem/exception. 关于自定义异常的最好的事情是,您可以提供与问题/异常相关的多个有意义/有用的数据片段。 This data is also nicely encapsulated in one object. 这个数据也很好地封装在一个对象中。 Compare this to error codes and return values. 将其与错误代码和返回值进行比较。

Performance can be an issue but only when the exception is thrown within a tight loop or some other high activity situation. 性能可能是一个问题,但只有在紧急循环或某些其他高活动情况下抛出异常时才会出现问题。 To avoid this you could also apply a pattern used by the .NET Framework, namely provide Try....() methods (eg TryParse() ) that return boolean indicating if an action succeeded or failed. 为了避免这种情况,您还可以应用.NET Framework使用的模式,即提供Try ....()方法(例如TryParse() ),返回布尔值,指示操作是成功还是失败。

Either way I would go with custom exceptions initially, and then do performance testing to actually see if there are certain parts of the library that may need optimization. 无论哪种方式,我最初都会使用自定义异常,然后执行性能测试以实际查看库中是否存在可能需要优化的某些部分。

I would use exceptions, in the following approach (inspired by design-by-contract) 我将使用例外,采用以下方法(受合同设计的启发)

  • Where possible, provide boolean inspection functions telling you whether an operation can be safely applied 在可能的情况下,提供布尔检查功能,告诉您是否可以安全地应用操作
  • for the given operation, consider such an inspection function as a precondition: if it holds, the operation can be done safely, if not, you throw an exception. 对于给定的操作,考虑这样的检查函数作为前提条件:如果它保持,则可以安全地执行操作,否则,抛出异常。

In this way, if the API user can code his key logic using if-then-else structures. 这样,如果API用户可以使用if-then-else结构对其关键逻辑进行编码。

If unexpected situations arise (due to subtle timing issues, eg) an exception will be thrown: the developer can catch this exception and deal with it. 如果出现意外情况(由于细微的时序问题,例如),将抛出异常:开发人员可以捕获此异常并处理它。 But note: this need not be at the place where the method was invoked: it can be higher / earlier in the call stack, at a central place where all strange exceptions are handled 但请注意:这不一定是在调用方法的地方:它可以在调用堆栈中更高/更早,在处理所有奇怪异常的中心位置

I have done some work on the automated analysis of error handling code in multi-million lines of C programs. 我在数百万行C程序中对错误处理代码的自动分析做了一些工作。 These were based on coding standards requiring hand-written inspection and propagation of error codes. 这些是基于需要手写检查和传播错误代码的编码标准。 It turns out developers don't like writing such code, they forget it, and they easily make mistakes in it. 事实证明,开发人员不喜欢编写这样的代码,他们会忘记它,并且很容易在其中犯错误。 In fact, we found 2 deviations of the coding standards (2 faults, one might say) per 1000 lines of C code. 实际上,我们发现每1000行C代码有2个编码标准偏差(2个故障,一个可能会说)。

In summary: (1) I'd use boolean inspectors (2) exceptions can be caught at places higher in the call stack; 总结:(1)我使用布尔检查器(2)可以在调用堆栈中更高的位置捕获异常; (3) relying on error codes is unsafe in practice. (3)在实践中依赖错误代码是不安全的。

Exceptions were designed for exactly that - exceptional circumstances. 例外情况就是针对特殊情况而设计的。 I try and think of it like this - if you assume everything that you depend on is working as normal for the majority of the time, and method X fails, then it's an exceptional circumstance because it doesn't normally happen and you should define exceptions to catch the situation. 我试着这样想 - 如果你假设你所依赖的一切在大多数情况下正常工作,而方法X失败,那么这是一个特例,因为它通常不会发生,你应该定义例外抓住这种情况。

So in your situation, you assume the device to be up and running. 因此,在您的情况下,您可以假设设备已启动并正在运行。 Therefore exceptional circumstances in this case are the device not accepting a connection, it rejecting a connection, it not accepting data you send it or you not receiving data it should be sending etc. If your devices are routinely turned off several times a day, then you expect them to be turned off, so use return codes or a "bool IsDeviceOn();" 因此,在这种情况下的特殊情况是设备不接受连接,它拒绝连接,它不接受您发送的数据,或者您没有接收它应该发送的数据等。如果您的设备通常每天关闭几次,那么你希望它们被关闭,所以使用返回码或“bool IsDeviceOn();” method to check it before you connect. 在连接之前检查它的方法。

If it's something that you do expect to happen in normal circumstances, such as querying a device for its capabilities but the one you want isn't available, then use return codes, or a bool method - eg "bool DoesDeviceHaveThisCapability();" 如果您确实希望在正常情况下发生这种情况,例如查询设备的功能但是您想要的设备不可用,那么请使用返回代码或bool方法 - 例如“bool DoesDeviceHaveThisCapability();” Don't catch an exception when it doesn't. 如果没有,请不要捕获异常。

Another example (for GUI apps) is user input. 另一个例子(对于GUI应用程序)是用户输入。 Don't use exceptions for that, because you do expect a user to input something incorrectly - we're not perfect. 不要使用异常,因为你确实希望用户输入错误的东西 - 我们并不完美。

I have experienced massive performance issues due to using exceptions when they weren't truly exceptional circumstances. 我遇到了大量的性能问题,因为当它们不是真正特殊情况时会使用异常。 One example was when I was processing a 2GB data file 2-3 times a day. 一个例子是我每天处理2GB数据文件2-3次。 Some lines had valid prices in the format 0.00. 有些行的格式有效,格式为0.00。 Some didn't. 有些人没有。 I used a FormatException to catch those that didn't. 我使用FormatException来捕获那些没有的东西。

Two years later when I had the chance to get a performance analyser on it, that particular line that caught the exception was responsible for 80% of the time used to analyse the file. 两年后,当我有机会获得性能分析器时,捕获异常的特定行负责80%的时间用于分析文件。 I changed that to use the TryParse() method of int instead, and got a massive speed increase. 我改为使用int的TryParse()方法,并获得了大幅度的速度提升。

For your examples, I would probably use: 对于您的示例,我可能会使用:

  1. No response from device - exception if devices should be on 24/7, return code if routinely powered off 没有来自设备的响应 - 如果设备应该24/7开启则例外,如果常规断电则返回代码
  2. Operation unauthorised - exception 操作未经授权 - 例外
  3. Comms failed - exception 通讯失败 - 例外

With RS232, unless you have hardware handshaking enabled (and most of the time, people don't), you just won't see any more data coming in from the line. 使用RS232,除非您启用了硬件握手(大多数情况下,人们都没有),否则您将看不到来自线路的更多数据。 There's no way for you to tell if a device is even connected, other than the fact that nothing is being sent to the PC. 除了没有任何东西被发送到PC之外,你无法判断设备是否连接均匀。

I'd classify 1 and 3 together as a RS232TimeoutError, and 2 as an RS232AuthenticationError, probably. 我可能将1和3一起分类为RS232TimeoutError,将2分类为RS232AuthenticationError。

Generally speaking, a TimeoutError indicates that the remote device has locked up or just isn't connected. 一般来说,TimeoutError表示远程设备已锁定或未连接。 An authentication error is kind of a ProtocolError, but subtly different in that the communication is fine, but the remote device "just said no" to creating a connection with the PC. 身份验证错误是一种ProtocolError,但略有不同,因为通信正常,但远程设备“只是拒绝”与PC建立连接。

I think setting those as exceptions is well-justified: you would never expect a timeout error during normal operations, nor would you expect an authentication error. 我认为将它们设置为异常是合理的:在正常操作期间,您永远不会期望超时错误,也不会期望出现身份验证错误。

Whether or not you throw an exception is a consequence of the function's type. 是否抛出异常是函数类型的结果。

  • If the function returns an X and you fail to determine a valid X, throw an exception. 如果函数返回X并且您无法确定有效的X,则抛出异常。
  • If the function is an action (eg. Connect) and you fail to complete the action, throw an exception. 如果该函数是一个动作(例如Connect)并且您未能完成该动作,则抛出异常。
  • If the function is of the TryX variety, don't throw an exception. 如果该函数属于TryX类型,请不要抛出异常。

So I guess I'm saying you should push the problem from "Should I throw an exception?" 所以我想我说你应该把问题从“我应该抛出异常吗?” to "What methods will the people calling my library want?" “人们打电话给我的图书馆想要什么方法?” with the caveat that the exceptions you throw should be obvious based on the methods you provide. 但需要注意的是,根据您提供的方法,您抛出的异常应该是显而易见的。

Do not use exceptions for normal or expected errors, or for normal flow of control. 不要将异常用于正常或预期的错误,也不要用于正常的控制流程。

Within your method implementations, avoid purposely causing an exception in order to change execution flow, handle special logic, special cases, or handle normal or expected errors. 在方法实现中,避免故意导致异常,以便更改执行流,处理特殊逻辑,特殊情况或处理正常或预期的错误。 For example, exception handling should be removed in the following function. 例如,应在以下函数中删除异常处理。 (It is handling normal or expected errors, but as the note says Convert.ToString is not really going to fail.) There is a minor performance hit because of the time needed to "setup" exception handling within the method. (它正在处理正常或预期的错误,但正如注释所说,Convert.ToString实际上不会失败。)由于在方法中“设置”异常处理所需的时间,因此性能会受到轻微影响。 It is not a significant hit, yet if you are calling this function in a loop, then it may become significant. 它不是一个重要的打击,但如果你在循环中调用这个函数,那么它可能会变得很重要。 If this method is in a library then let any exceptions bubble up to the user of the library. 如果此方法在库中,则允许任何异常冒泡到库的用户。 (Custom exceptions are different, see Ash's answer.) (自定义异常不同,请参阅Ash的答案。)

Public Function Nz(ByVal value As String, ByVal valueIfNothing As String) As String
    Try
        Dim sValue As String = System.Convert.ToString(value) 'using Convert.ToString on purpose
        If IsNothing(sValue) Then
            Return valueIfNothing 
        Else
            Return sValue
        End If
    Catch ex As Exception
        'Convert.ToString handles exceptions, but just in case...
        Debug.Fail("Nz() failed. Convert.ToString threw exception.")
        Return String.Empty
    End Try
End Function

Here is a "better" implementation of the method: 这是该方法的“更好”实现:

Public Function Nz(ByVal value As String, ByVal valueIfNothing As String) As String
    Dim sResult As String = String.Empty
    Dim sValue As String = System.Convert.ToString(value) 'using Convert.ToString on purpose
    If IsNothing(sValue) Then
        sResult = valueIfNothing 
    Else
        sResult = sValue
    End If
    Return sResult
End Function

Do not use error codes because of concerns that exceptions might affect performance negatively. 不要使用错误代码,因为担心异常可能会对性能产生负面影响。

Avoid designing everything as a function that returns true/false with "out" parameters just to avoid "imagined" performance concerns with using exceptions. 避免将所有内容设计为使用“out”参数返回true / false的函数,以避免使用异常时出现“想象”的性能问题。

The choice doesn't have to be between throwing an exception, or returning an error code. 选择不必在抛出异常或返回错误代码之间。 Try returning an exception object in an exceptional condition. 尝试在异常情况下返回异常对象。 The performance hit is not in creating an exception, but in throwing it. 性能打击不是创建异常,而是抛出异常。 The caller can then check for a "null" return value. 然后,调用者可以检查“null”返回值。 The returned exception could be a function return, or one of several "out" parameters. 返回的异常可以是函数返回,也可以是几个“out”参数之一。

If a method fails to perform its primary function in a way that a caller will be prepared to deal with, the function should generally indicate such failure via return value (or, rarely, by writing to a variable that's passed by reference). 如果某个方法无法以调用者准备处理的方式执行其主要功能,则该函数通常应通过返回值指示此类失败(或者,很少通过写入通过引用传递的变量)。 If a function fails in a way that a caller probably won't be prepared to deal with, it's better to throw an exception. 如果函数以调用者可能不准备处理的方式失败,最好抛出异常。 If some callers would be prepared to deal with a failure, and other callers would not, then it's best to use the try/do pattern. 如果某些呼叫者准备好处理故障,而其他呼叫者则不准备,那么最好使用try / do模式。 Essentially, one provides two methods: DoSomething and TryDoSomething. 基本上,一个提供两种方法:DoSomething和TryDoSomething。 DoSomething promises that it will either succeed or throw an exception. DoSomething承诺它会成功或抛出异常。 TryDoSomething promises that it will not throw an exception unless something truly unexpected happens; TryDoSomething承诺,除非发生真正意外的事情,否则它不会抛出异常; it will indicate "expected" failures via return value. 它将通过返回值指示“预期”故障。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM