我正在开发一个用于与外部设备进行通信(通过RS232串行通信)的库。我在考虑错误处理策略,异常似乎是正确的,也是行业标准报告错误的方式。
所以我读了一些关于例外的指南。 One非常清楚地表明我应该不担心性能损失:
请勿使用错误代码,因为担心异常可能会对性能产生负面影响。
Other告诉我在正常情况下不要抛出异常:
不要将异常用于正常或预期的错误,也不要用于正常的控制流程。
我无法在正常/预期和其他情况之间划清界限。例如,在我的库中,操作可能会失败,因为:
我可以认为上述所有问题都是预期的问题,因为它们可能经常在实践中发生(实际上很多营销蠢货叫我解决我的软件中的^问题^只是为了发现他们没有将电缆连接到他们的笔记本电脑)。所以可能不应该抛出异常,因为否则应用程序员必须在很多地方捕获它们(我认为很多catch块也不好)。
另一方面,我也倾向于认为这些都是我在某种程度上需要向应用程序员报告的错误,并且异常似乎是这样做的方式。如果我不使用异常,我将需要使用一些错误代码或错误枚举报告这些问题。 (丑陋,我知道)。
您认为我应采取哪种方式?
答案 0 :(得分:5)
您正在开发库,这是一个将被其他应用程序使用的组件。
因此,在预期的情况下,你提到我肯定会使用异常与调用应用程序通信,这是有问题的。您应该为每个场景定义一个自定义例外,然后清楚地记录它们何时可能发生。
然后,这允许应用程序客户端代码决定如何最好地继续。只有客户端应用程序才能做出此决定,并且明确记录的异常对此有很大帮助。
关于自定义异常的最好的事情是,您可以提供与问题/异常相关的多个有意义/有用的数据片段。这个数据也很好地封装在一个对象中。将其与错误代码和返回值进行比较。
性能可能是一个问题,但只有在紧急循环或某些其他高活动情况下抛出异常时才会出现问题。为了避免这种情况,您还可以应用.NET Framework使用的模式,即提供返回布尔值的Try ....()方法(例如TryParse()
),指示操作是成功还是失败。
无论哪种方式,我最初都会使用自定义异常,然后进行性能测试,以实际查看是否存在可能需要优化的库的某些部分。
答案 1 :(得分:3)
我会使用例外,采用以下方法(受合同设计的启发)
通过这种方式,如果API用户可以使用if-then-else结构对其关键逻辑进行编码。
如果出现意外情况(由于细微的时间问题,例如),将抛出异常:开发人员可以捕获此异常并处理它。但请注意:这不一定是在调用方法的地方:它可以在调用堆栈中更高/更早,在处理所有奇怪异常的中心位置
我在数百万行C程序中对错误处理代码的自动分析做了一些工作。这些是基于需要手写检查和传播错误代码的编码标准。事实证明,开发人员不喜欢编写这样的代码,他们会忘记它,并且很容易在其中犯错误。事实上,我们发现每1000行C代码有2个编码标准偏差(2个故障,有人可能会说)。
总结:(1)我使用布尔检查器(2)可以在调用堆栈中较高的位置捕获异常; (3)依赖错误代码在实践中是不安全的。
答案 2 :(得分:2)
异常是针对特殊情况而设计的。我试着这样想 - 如果你假设你所依赖的一切在大多数情况下正常工作,而方法X失败,那么这是一个特例,因为它通常不会发生,你应该定义例外了解情况。
因此,在您的情况下,您假设设备已启动并正在运行。因此,在这种情况下的特殊情况是设备不接受连接,它拒绝连接,它不接受您发送的数据,或者您没有接收它应该发送的数据等。如果您的设备通常每天关闭几次,那么你希望它们被关闭,所以使用返回码或“bool IsDeviceOn();”在连接之前检查它的方法。
如果您在正常情况下期望发生某种情况,例如查询设备的功能但是您想要的设备不可用,那么请使用返回码或bool方法 - 例如“bool DoesDeviceHaveThisCapability();”如果没有,请不要捕捉异常。
另一个例子(对于GUI应用程序)是用户输入。不要使用异常,因为你确实希望用户输入错误的东西 - 我们并不完美。
由于在不是真正例外的情况下使用例外,我遇到了大量的性能问题。一个例子是我每天处理2GB数据文件2-3次。有些行的格式有效,格式为0.00。有些人没有。我使用FormatException来捕获那些没有的。
两年后,当我有机会在其上获得性能分析器时,捕获异常的特定行负责80%的时间用于分析文件。我改为使用int的TryParse()方法,并且速度大幅提升。
对于您的示例,我可能会使用:
答案 3 :(得分:1)
使用RS232,除非您启用了硬件握手(大多数时候,人们都没有),否则您将看不到来自该线路的更多数据。除了没有任何东西被发送到PC之外,你无法判断设备是否连接均已连接。
我可能将1和3一起归类为RS232TimeoutError,并将2归为RS232AuthenticationError。
一般来说,TimeoutError表示远程设备已锁定或未连接。身份验证错误是一种ProtocolError,但略有不同,因为通信正常,但远程设备“只是拒绝”与PC建立连接。
我认为将这些设置为异常是合理的:在正常操作期间,您永远不会期望超时错误,也不会期望身份验证错误。
答案 4 :(得分:1)
是否抛出异常是函数类型的结果。
所以我想我说你应该解决这个问题“我应该抛出异常吗?” “人们打电话给我的图书馆想要什么方法?”但需要注意的是,根据您提供的方法,您抛出的例外情况应该明显。
答案 5 :(得分:1)
不要使用普通或异常 预期的错误,或正常的流量 控制。
在方法实现中,避免故意导致异常,以便更改执行流程,处理特殊逻辑,特殊情况或处理正常或预期的错误。例如,应在以下函数中删除异常处理。 (它正在处理正常或预期的错误,但正如注释所说,Convert.ToString实际上不会失败。)由于在方法中“设置”异常处理所需的时间,因此会有轻微的性能损失。它不是一个重要的打击,但如果你在循环中调用这个函数,那么它可能会变得很重要。如果此方法在库中,则允许任何异常冒泡到库的用户。 (自定义异常不同,请参阅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
以下是该方法的“更好”实现:
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
答案 6 :(得分:1)
请勿使用错误代码 关注异常可能会影响 表现不利。
避免将所有内容设计为使用“out”参数返回true / false的函数,以避免使用异常时出现“想象”的性能问题。
答案 7 :(得分:0)
选择不必在抛出异常或返回错误代码之间。尝试在异常情况下返回异常对象。性能打击不是创建异常,而是抛出异常。然后,调用者可以检查“null”返回值。返回的异常可以是函数返回,也可以是几个“out”参数之一。
答案 8 :(得分:0)
如果某个方法无法以调用者准备处理的方式执行其主要功能,则该函数通常应通过返回值指示此类失败(或者,很少通过写入通过引用传递的变量) 。如果函数以调用者可能不准备处理的方式失败,最好抛出异常。如果某些呼叫者准备好处理故障,而其他呼叫者则不准备,那么最好使用try / do模式。基本上,一个提供两种方法:DoSomething和TryDoSomething。 DoSomething承诺它会成功或抛出异常。 TryDoSomething承诺,除非发生真正意外的事情,否则它不会抛出异常;它将通过返回值指示“预期”故障。