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

时间:2011-11-14 17:36:14

标签: c# api exception

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

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

我不太介意这个设计,但另一方面,就在今天,我正在阅读Joshua Bloch的“How to Design a Good API and Why it Matters”,他在那里说“抛出异常以表明特殊情况......不要强迫客户端使用控制流的异常。“ (和“相反,不要默默地失败。”)

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

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

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

  • 这是一个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   
}

e.g。当用户试图通过信用卡收费时,该卡是否被拒绝了特殊情况?我一开始就问这个问题,我是否会在兔子洞里走得太远?

3 个答案:

答案 0 :(得分:6)

这是一个需要考虑的简短列表。虽然不全面,但我相信这些东西可以帮助您编写更好的代码。底线:不一定将异常处理视为邪恶。相反,在编写它们时,请问自己:我真正了解我正在解决的问题有多好?通常情况下,这将有助于您成为更好的开发人员。

  1. 其他开发者能否阅读此内容?普通开发人员可以合理地理解吗?示例:ServiceConnectionException与令人困惑的ServiceDisconnectedConnectionStatusException
  2. 在抛出异常的情况下,例外是怎样的情况?调用者必须做什么才能实现该方法?
  3. 这个异常是致命的吗?如果抓到这个异常,可以做任何事吗?线程中止,内存不足......你无法做任何有用的事情。不要抓住它。
  4. 异常令人困惑吗?假设您有一个名为Car GetCarFromBigString(string desc)的方法,它接受一个字符串并返回一个Car对象。如果该方法的大多数用例是从Car生成string对象,则在{{1}无法确定Car时不要抛出异常}}。相反,请编写类似string
  5. 的方法
  6. 这可以轻易预防吗?我可以检查一下,假设数组或变量的大小为空吗?
  7. 为了代码可读性,我们可以看一下你的上下文。

    bool TryGetCarFromBigString(string desc, out Car)

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

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

    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; }
    }
    

答案 1 :(得分:3)

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

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

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

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

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

答案 2 :(得分:2)

我非常喜欢“抛出异常来表示特殊条件”的想法。他们必须有这个名字是有原因的。

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

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