表明服务器请求失败的最佳实践方法?

我正在编写一个连接到服务的API,该服务要么返回简单的“成功”消息,要么返回100多种不同类型的失败之一。

最初我想写一个向这个服务发送请求的方法,如果它成功了,那么该方法什么都不返回,但如果它因任何原因失败,它会抛出一个exception。

我不太介意这个设计,但另一方面,就在今天,我正在阅读Joshua Bloch的“ 如何设计一个好的API以及它为何重要 ”,他在那里说“抛出exception以表明特殊条件…… Don’ t强制客户端使用控制流的exception。“ (和“相反,不要默默地失败。”)

另一方面,我注意到我正在使用的HttpWebRequest似乎在请求失败时抛出exception,而不是返回包含“500 Internal Server Error”消息的Response。

在这种情况下报告错误的最佳模式是什么? 如果我在每次失败的请求中抛出exception,那么我将来会在某些时候感受到巨大的痛苦吗?

编辑:非常感谢您对目前为止的回复。 一些细化:

  • 它是一个DLL,将提供给客户端在他们的应用程序中引用。
  • 使用的类似例子是ChargeCreditCard(CreditCardInfo i) – 显然当ChargeCreditCard()方法失败时,它是一个巨大的交易; 我只是不确定是否应该停止印刷机或将责任传递给客户。

编辑第二:基本上我并不完全相信使用这两种方法中的哪一种:

 try { ChargeCreditCard(cardNumber, expDate, hugeAmountOMoney); } catch(ChargeFailException e) { // client handles error depending on type of failure as specified by specific type of exception } 

要么

 var status = TryChargeCreditCard(cardNumber, expDate, hugeAmountOMoney); if(!status.wasSuccessful) { // client handles error depending on type of failure as specified in status } 

例如,当用户试图对信用卡收费时,卡被拒绝是否真的是特殊情况? 我一开始就问这个问题,我是否会在兔子洞里走得太远?

这是一个需要考虑的事项的简短列表。 虽然不全面,但我相信这些东西可以帮助你编写更好的代码。 底线:不一定将exception处理视为邪恶。 相反,在写作时,问问自己:我如何真正理解我正在解决的问题? 通常,这将帮助您成为更好的开发人员。

  1. 其他开发人员能否阅读此内容? 一般的开发人员可以合理地理解它吗? 示例: ServiceConnectionException与令人困惑的ServiceDisconnectedConnectionStatusException
  2. 在抛出exception的情况下,情况有多特殊 ? 调用者必须做什么才能实现该方法?
  3. 这个exception是致命的吗? 如果它被捕获,可以用这个exception做任何事吗? 线程中止,内存不足……你无法做任何有用的事情。 别抓住它。
  4. exception令人困惑吗? 假设您有一个名为Car GetCarFromBigString(string desc) ,它接受一个字符串并返回一个Car对象。 如果该方法的大多数用例是从该string生成Car对象,则在无法从string确定Car时不要抛出exception。 相反,写一个方法,如bool TryGetCarFromBigString(string desc, out Car)
  5. 这可以轻易预防吗? 我可以检查一下,假设数组或变量的大小为空吗?

为了代码可读性,让我们来看看你的上下文。

 bool IsServiceAlive() { bool connected = false; //bool is always initialized to false, but for readability in this context try { //Some check Service.Connect(); connected = true; } catch (CouldNotConnectToSomeServiceException) { //Do what you need to do } return connected; } //or void IsServiceAlive() { try { //Some check Service.Connect(); } catch (CouldNotConnectToSomeServiceException) { //Do what you need to do throw; } } static void Main(string[] args) { //sample 1 if (IsServiceAlive()) { //do something } //sample 2 try { if (IsServiceAlive()) { //do something } } catch (CouldNotConnectToSomeServiceException) { //handle here } //sample 3 try { IsServiceAlive(); //work } catch (CouldNotConnectToSomeServiceException) { //handle here } } 

您可以在上面看到,如果上下文只是一个二进制测试,那么捕获示例3中的CouldNotConnectToSomeServiceException并不一定会产生更好的可读性。 但是,两者都有效。 但这真的有必要吗? 如果你无法连接,你的程序是否会被清除? 真的有多重要? 这些都是您需要考虑的因素。 由于我们无法访问您的所有代码,因此很难说清楚。

让我们来看看最有可能导致问题的其他一些选项。

 //how will the code look when you have to do 50 string comparisons? Not pretty or scalable. public class ServiceConnectionStatus { public string Description { get; set; } } 

 //how will your code look after adding 50 more of these? public enum ServiceConnectionStatus { Success, Failure, LightningStormAtDataCenter, UniverseExploded } 

我认为您需要考虑设计中的一些事项:

1)如何访问API? 如果您通过Web服务公开它,那么抛出exception可能不是一个好主意。 如果API在您提供供人们在其应用程序中引用的DLL中,则exception可能正常。

2)为了使失败响应对API使用者有用,需要使用返回值传输多少额外数据? 如果您需要在失败消息(即用户ID和登录)中提供可用信息而不是嵌入了该信息的字符串,那么您可以使用自定义exception或包含错误代码和其他可用信息的“ErrorEncountered”类。 如果您只需要传回代码,则表示成功(0)或失败(任何非零值)的ENum可能是合适的。

3)在原始响应中忘记了这一点:.Net框架中的exception代价很高。 如果您的API将在一段时间内被调用一次,则无需考虑因素。但是,如果为在高流量站点中提供的每个网页调用API,例如,您绝对不希望抛出exception以指示请求失败。

所以简短的回答是,它确实取决于具体情况。

我真的很喜欢“抛出exception来表示特殊情况”的想法。 他们必须有这个名字是有原因的。

在常规应用程序中,您可以在File.Open()之前使用File.Exists() File.Open()来防止抛出exception。 由于exception很难处理,因此预期的错误。

但是,在客户端 – 服务器环境中,您可能希望防止必须发送两个请求并创建FileOpenResponse类来发送状态和数据(例如文件句柄,在本例中)。