Language agnostic 如何区分我可以向用户显示的异常和我可以';T

Language agnostic 如何区分我可以向用户显示的异常和我可以';T,language-agnostic,exception-handling,exception,Language Agnostic,Exception Handling,Exception,我有一些业务逻辑,可以捕获一些逻辑上无效的情况,例如,试图撤销已经撤销的交易。在这种情况下,正确的操作是通知用户: 交易已撤销 或 无法撤消正在撤消的事务 或 您没有撤消交易的权限 或 此事务位于已关闭的会话上 或 此交易太旧,无法撤消 问题是,我如何将这些异常情况传达回调用代码,以便它们能够显示给用户 我是否为每种情况创建单独的异常: catch (ETransactionAlreadyReversedException) MessageBox.Show('Transaction al

我有一些业务逻辑,可以捕获一些逻辑上无效的情况,例如,试图撤销已经撤销的交易。在这种情况下,正确的操作是通知用户:

交易已撤销

无法撤消正在撤消的事务

您没有撤消交易的权限

此事务位于已关闭的会话上

此交易太旧,无法撤消

问题是,我如何将这些异常情况传达回调用代码,以便它们能够显示给用户

我是否为每种情况创建单独的异常:

catch (ETransactionAlreadyReversedException)
    MessageBox.Show('Transaction already reversed')
catch (EReversingAReversingTransactionException)
    MessageBox.Show('Cannot reverse a reversing transaction')
catch (ENoPermissionToReverseTranasctionException)
    MessageBox.Show('You do not have permission to reverse transactions')
catch (ECannotReverseTransactionOnAlredyClosedSessionException)
    MessageBox.Show('This transaction is on a session that has already been closed')
catch (ECannotReverseTooOldTransactionException)
    MessageBox.Show('This transaction is too old to be reversed')
这样做的缺点是,当有新的逻辑案例向用户显示时:

NSL创建的传输无法反转

我并不是简单地向用户显示一条消息,而是作为一个未经处理的expetion泄漏出去,而实际上它应该用另一个
MessageBox
来处理

另一种方法是创建单个异常类:

`EReverseTransactionException`
了解到此类型的任何异常都是逻辑检查,应使用消息框进行处理:

catch (EReverseTransactionException)
但我们仍然理解,任何其他异常,例如涉及内存ECC奇偶校验错误的异常,都会继续未经处理


换句话说,我不会将
ReverseTransaction()
方法抛出的所有错误转换为
EReverseTransactionException
,只转换为用户逻辑上无效的原因。

我建议使用带有原因标识符的单一异常。原因本身可能是一个异常,它被封装在用户异常中,虽然我会考虑这主要是为了调试的目的,或者是为了获得更多的细节。p> 您的主要异常包括一个标识符,它简洁地标识了用户做错了什么。它可以用作检索本地化消息以向用户显示、链接到用户文档、帮助、疑难解答和其他帮助的基础

消息ID还可用作错误代码,可在报告问题时使用,也可在支持团队的支持文档中记录解决方案


我对所有用户级异常使用超类,允许使用ID来识别情况或原因。所有ID都有文档记录,并且每个ID都至少有一个引发异常的测试用例。

我发现有各种各样的异常:

  • 暂时性异常-你刚才尝试的不起作用,但如果你再试一次,它可能会起作用。用于数据库当前不可用等情况
  • InvalidRequestException-您只是要求做一些无法完成的事情(您的示例适合这里)
  • 系统异常-系统有问题,我们忘记了您刚才说的一切,您的会话已结束,您需要重新开始
    我将有这三种主要类型的异常,并捕捉它们中的每一种,在每种情况下都有明显的特定操作。我所有的异常都来自这三种类型。

    对我来说,经验法则是用户查看准确的错误消息是否有用。这在不同类型的应用程序之间差异很大。数百万“普通用户”使用的桌面应用程序与数百名训练有素的专业人士使用的企业web应用程序截然不同

    对于前者,最好显示一条通用的“系统错误,请重新启动”类型的消息,而不是用户不理解的技术细节,并且通常不会麻烦转发给支持部门(除非可以通过按下按钮来完成)

    我们的项目属于后一种情况,用户通常会将问题转发给支持人员。因此,我们尝试改进错误消息,以包含与支持团队相关的信息。由于我们的应用程序是一个遗留应用程序,因此我们对内存奇偶校验错误的担心要比代码中的普通空指针异常和逻辑错误少得多


    至于不同的异常类型的数量,我是一个简单性的粉丝,所以我尝试用最少的必要数量的异常类型,每个不同的错误类别一个。什么构成了一个单独的类别,也取决于如何和何时出现bug,以及如何和何时处理bug。由于您的所有案例都与相同的用例相关联,因此我将使用一种带有特定详细信息的异常类型。

    您是否从基本的
    异常
    (或
    eeexception
    或您语言中的任何等价物)派生所有异常

    我通过从
    ClientException
    (用户提供的无效输入)或
    BusinessRuleException
    (输入是有效的,但会无意中违反一些重要的业务或域规则)派生所有业务逻辑错误来处理此问题

    从这些根中派生的任何异常都可以捕获并显示给用户。其他异常将转到全局异常处理程序,除非代码明确知道如何处理它们


    (实际上,这并不完全准确。真正发生的是全局异常处理程序本身识别这些异常并以不同的方式处理它们。但原理是相同的。)

    当需要(或预期需要)不同类型的行为来处理不同的异常时,应该创建单独的异常。如果您只想显示不同的消息,但所有消息的基本行为都相同,那么您可能只想从
    std::runtime\u error
    派生一个异常类:

    class transaction_error : public std::runtime_error { 
    public:
        transaction_error(std::string const &caption) : std::runtime_error(caption) {}
    };
    
    你会扔一些类似的东西:

    throw transaction_error("Transaction already reversed");
    
    try { 
        execute_transaction(transaction_data);
    }
    catch(transaction_error const &e) { 
        MessageBox(NULL, e->what(), "Transaction Error", MB_OK);
    }
    
    …然后抓到类似的东西:

    throw transaction_error("Transaction already reversed");
    
    try { 
        execute_transaction(transaction_data);
    }
    catch(transaction_error const &e) { 
        MessageBox(NULL, e->what(), "Transaction Error", MB_OK);
    }
    

    我使用内存ecc奇偶校验错误来避免关于如何处理非业务规则错误的潜在圣战(例如,当出现主键冲突时该怎么办,