10

我正在与一位同事讨论何时在 WCF 服务中抛出错误以及何时不抛出错误。

一种观点是,我们只在服务操作由于某些错误而无法完成工作时才抛出错误;并且某些东西可能因此而处于无效状态。所以,一些例子:

  • ValidateMember(string name, string password, string country) -> 如果没有传递强制参数会抛出错误,因为验证本身无法执行;-> 如果发生某些内部错误,例如数据库已关闭,将抛出错误 -> 在所有其他情况下将返回状态协定,指定验证结果(MemberValidated、WrongPassword、MemberNotKnown,...)

  • GetMember(int memberId) -> 只会在出现故障时抛出错误,在所有其他情况下,如果未找到,它将返回成员或 null

另一种意见是当 GetMember 没有找到成员时,我们也应该抛出错误,或者在 ValidateMember 的情况下密码错误。

你怎么看?

4

5 回答 5

12

我对此的看法...

失败的原因有以下三种:

  1. 服务代码抛出异常,例如数据库错误、代码中的逻辑错误。这是你的错。
  2. 客户端代码未能根据您的文档正确使用您的服务,例如,它没有设置所需的标志值,它未能传递一个 ID。这是客户端软件开发商的错。
  3. 最终用户在屏幕上输入了一些愚蠢的东西,例如缺少出生日期、负工资。这是最终用户的错。

如何选择将实际故障合同映射到每个故障原因取决于您。例如,我们这样做:

  • 对于原因 1 和 2,所有客户端代码需要知道的是服务失败。我们定义了一个非常简单的“致命错误”故障合约,它只包含一个唯一的错误 ID。错误的完整详细信息记录在服务器上。
  • 对于原因 3,最终用户需要确切地知道他/她做错了什么。我们定义了一个“验证错误”故障合约,其中包含一组友好的错误消息,供客户端代码显示在屏幕上。

我们为原因 3借用了Microsoft EntLib 类,并使用异常屏蔽以声明方式处理原因 1 和 2。它使代码非常简单。

澄清:

我们在服务内部处理这三个原因:

  1. 服务代码中引发意外异常。我们在顶层捕获它(实际上是异常屏蔽捕获它,但原理是一样的)。记录完整的详细信息,然后FaultException<ServiceFault>向客户端抛出仅包含错误 ID 的信息。
  2. 我们验证输入数据并故意抛出异常。它通常是ArgumentException,但任何适当的类型都可以。一旦它被抛出,它的处理方式与(1)完全相同,因为我们想让它在客户端看来是一样的。
  3. 我们验证输入数据并故意抛出异常。这一次,是一个FaultException<ValidationFault>。我们将异常屏蔽配置为通过 unwrapped 传递这个,因此它在客户端上显示为FaultException<ValidationFault>not FaultException<ServiceFault>

最终结果:

  • 服务内部根本没有捕获块(漂亮干净的代码)。
  • 客户端只需要捕获FaultException<ValidationFault>它是否想向用户显示消息。所有其他异常类型,包括FaultException<ServiceFault>由客户端的全局错误处理程序作为致命错误处理,因为服务中的致命错误通常也意味着客户端中的致命错误。
于 2010-12-14T16:59:51.557 回答
3

这是一个常见的例行故障,那么抛出故障就是一个错误。应该编写软件来处理日常事务,例如输入错误的密码。故障处理是针对不被视为程序正常设计一部分的异常故障。

例如,如果您的程序是按照它始终可以访问数据库的想法编写的,并且该数据库不可访问,那么“修复”就远远超出了您的软件限制。应该抛出一个错误。

故障处理通过编程语言的结构使用不同的逻辑流程,并且仅在您“离开”编程问题的正常处理时使用它,您将使您的解决方案以一种方式利用编程语言的特性似乎更自然。

于 2010-12-14T15:54:42.017 回答
2

我认为将错误处理和故障处理分开是一种很好的做法。任何错误情况都应由您的程序处理 - 故障处理是为异常情况保留的。作为将两者分开的指南,我发现在考虑此类情况时记住只有三种类型的错误(处理数据和消息时)和只有一种类型的故障很有用。错误类型与不同类型的验证有关:

  1. 消息验证 - 您可以从消息内容中确定数据是有效还是无效。

    示例:旨在作为出生日期的内容 - 您可以从数据中判断它是否有效。

  2. 上下文验证——你只能通过参考消息结合系统状态来判断内容是否无效。

    示例:加入公司的有效日期早于该人的出生日期。

  3. 对系统撒谎——只有当后来的消息引发异常时,您才能确定消息是错误的。

    示例:存储的有效出生日期和对该人的出生证明的检查表明这是不正确的。纠正对系统的谎言通常需要在系统之外采取行动,例如诉诸法律或纪律补救措施。

您的系统必须处理所有类别的错误 - 尽管在第三种情况下,这可能仅限于发出警报。

相比之下,故障(异常)只有一个原因——数据损坏(包括数据截断)。示例:未传递验证参数。

这里适当的机制是故障或异常处理——基本上将问题交给系统中能够处理它的其他部分(这就是为什么应该有一个未处理故障的最终目的地)。

于 2010-12-14T16:29:15.873 回答
2

在过去,我们曾经有一个规则,即例外只针对例外和意想不到的事情。您不想过多使用它们的原因之一是它们“花费”了大量的计算能力。

但是如果你使用异常,你可以减少代码量,不需要很多 if else 语句,只要让异常冒泡就行了。

这取决于您的项目。最重要的是有一个项目标准,每个人都这样做。

于 2010-12-14T17:46:45.097 回答
1

我的观点是,只要方法应该做的事情无法实现,就应该抛出异常/错误。因此,验证逻辑不应该引发异常,除非无法进行验证(即出于技术原因),但绝不能仅仅因为数据无效(在这种情况下,它将返回验证代码/消息或任何帮助调用者更正的内容数据)。

现在 GetMember 案例是一个有趣的案例,因为它完全是关于语义的。该方法的名称表明可以通过传递 id 来检索成员(例如,与 TryGetMember 方法相比)。当然,如果找不到 id 或数据库没有响应但传递给此方法的错误 id 可能是在调用之前某处出现问题的迹象,则该方法不应抛出相同的异常。除非用户可以直接从界面中输入成员 ID,在这种情况下,应该在调用方法之前进行验证。

我听到很多关于性能问题的消息。我刚刚使用 C# 和 trow/catch 1000 异常做了一个简单的测试。1K Exeptions 花费的时间是 23 毫秒。即每个例外 23µ。我认为性能不再是这里的第一个论点,除非您计划每秒引发超过 2000 个异常,在这种情况下您的性能将下降 5%,我可以开始考虑。

我的拙见...

于 2010-12-14T17:07:00.883 回答