【问题标题】:Conventions for exceptions or error codes [closed]异常或错误代码的约定[关闭]
【发布时间】:2010-09-20 04:18:29
【问题描述】:

昨天,我与一位同事就首选的错误报告方法进行了激烈的辩论。我们主要讨论了在应用层或模块之间报告错误时使用异常或错误代码。

您使用什么规则来决定是抛出异常还是返回错误代码以进行错误报告?

【问题讨论】:

    标签: language-agnostic language-design


    【解决方案1】:

    我通常更喜欢异常,因为它们有更多的上下文信息,并且可以(如果使用得当)以更清晰的方式将错误传达给程序员。

    另一方面,错误代码比异常更轻量级,但更难维护。错误检查可能会无意中被忽略。错误代码更难维护,因为您必须保留包含所有错误代码的目录,然后打开结果以查看引发了什么错误。错误范围在这里可能会有所帮助,因为如果我们唯一感兴趣的是我们是否存在错误,则检查起来更简单(例如,大于或等于 0 的 HRESULT 错误代码是成功的,并且小于零为失败)。它们可能会无意中被忽略,因为没有编程强制开发人员检查错误代码。另一方面,您不能忽略异常。

    总而言之,在几乎所有情况下,我更喜欢异常而不是错误代码。

    【讨论】:

    • “错误代码比异常更轻量级”取决于您衡量的内容和衡量方式。很容易提出测试,表明基于异常的 API 可以更快。
    • @smink,好点,但我们如何解决异常的开销?错误代码不仅仅是轻量级的,它们基本上是weightless;异常不仅仅是中等重量的,它们是重量的对象,包含我们不使用的堆栈信息和杂项。
    • @Pacerier:您只考虑抛出异常的测试。你 100% 正确,抛出 C++ 异常比返回错误代码慢得多。放下手,没有辩论。我们确实的不同之处在于其他 99.999% 的代码。除了例外,我们不必检查每个语句之间的错误代码返回,使 that 代码快 1-50%(或不快,取决于您的编译器)。这意味着完整的代码可以更快或更慢,这完全取决于代码的编写方式和抛出异常的频率。
    • @Pacerier:通过我刚刚编写的这个人工测试,基于异常的代码与 MSVC 和 Clang 中的错误代码一样快,但不是 GCC:coliru.stacked-crooked.com/a/e81694e5c508945b(时间在底部)。与其他编译器相比,我使用的 GCC 标志似乎生成异常缓慢的异常。我有偏见,所以请批评我的测试,并随意尝试其他变体。
    • @Mooing Duck,如果您同时测试了错误代码和异常,那么您必须启用了异常。如果是这种情况,您的结果将表明使用具有异常处理开销的错误代码并不比仅使用异常慢。错误代码测试需要在禁用异常的情况下执行才能获得有意义的结果。
    【解决方案2】:

    我更喜欢例外,因为

    • 它们中断了逻辑流
    • 它们受益于提供更多特性/功能的类层次结构
    • 如果使用得当,可能会出现各种错误(例如,InvalidMethodCallException 也是 LogicException,因为这两种情况都会发生在您的代码中存在应该在运行前检测到的错误时),并且
    • 它们可用于增强错误(即 FileReadException 类定义可以包含检查文件是否存在或被锁定等的代码)

    【讨论】:

    • 你的第四点不公平:转换为对象时的错误状态,也可以包含检查文件是否存在或被锁定等的代码。它只是@987654321的变体@
    • “它们打断了逻辑流程”。异常或多或少有goto的影响
    • @peterchaula 没有邪恶的功能,只是功能滥用。
    【解决方案3】:

    过去我加入了错误代码阵营(做过太多的 C 编程)。但现在我看到了曙光。

    是的,异常对系统来说有点负担。但是它们简化了代码,减少了错误(和 WTF)的数量。

    所以要使用异常,但要明智地使用它们。他们会成为你的朋友。

    作为旁注。我已经学会记录哪种方法可以抛出哪个异常。不幸的是,大多数语言都不需要这样做。但它增加了在正确级别处理正确异常的机会。

    【讨论】:

    • yap C 确实给我们留下了一些习惯;)
    【解决方案4】:

    方法签名应该向您传达方法的作用。就像是 长错误代码 = getErrorCode(); 可能很好,但是 长错误代码 = fetchRecord(); 很混乱。

    【讨论】:

      【解决方案5】:

      错误代码可以被函数的调用者忽略(而且通常是!)。异常至少迫使他们以某种方式处理错误。即使他们处理它的版本是有一个空的 catch 处理程序(叹气)。

      【讨论】:

      • 这对于某些语言是正确的,对于其他语言是错误的。例如,Java 强制您要么捕获异常,要么抛出异常,但还有其他语言(如 JavaScript 或 Python)不强制执行异常处理。
      【解决方案6】:

      当您的方法返回数值以外的任何值时,错误代码也不起作用...

      【讨论】:

      • 嗯,没有。请参阅 win32 GetLastError() 范例。我不是为它辩护,只是指出你是不正确的。
      • 其实还有很多其他的方法可以做到。另一种方法是返回一个包含错误代码和实际返回值的对象。另一种方法是进行引用传递。
      【解决方案7】:

      对于大多数应用程序,例外情况更好。例外情况是软件必须与其他设备通信。我工作的领域是工业控制。这里错误代码是首选和预期的。所以我的回答是视情况而定。

      【讨论】:

        【解决方案8】:

        错误代码的异常,毫无疑问。您可以从异常中获得许多与使用错误代码相同的好处,但还可以获得更多好处,而没有错误代码的缺点。唯一的例外是它的开销略大。但在当今时代,几乎所有应用程序的开销都可以忽略不计。

        这里有一些文章讨论、比较和对比这两种技术:

        其中有一些很好的链接可以让您进一步阅读。

        【讨论】:

          【解决方案9】:

          在高级别的东西中,异常;在低级的东西中,错误代码。

          异常的默认行为是展开堆栈并停止程序,如果我正在编写一个脚本并且我选择一个不在字典中的键,这可能是一个错误,我希望程序停止让我知道这一切。

          但是,如果我正在编写一段我必须知道在所有可能情况下的行为的代码,那么我需要错误代码。否则,我必须知道函数中的每一行可能引发的每个异常,才能知道它会做什么(阅读The Exception That Grounded an Airline 以了解这是多么棘手)。编写对每种情况(包括不愉快的情况)做出适当反应的代码既乏味又困难,但这是因为编写无错误代码既乏味又困难,而不是因为您传递了错误代码。

          Raymond ChenandJoel 都提出了一些雄辩的论点,反对对所有事情都使用异常。

          【讨论】:

          • +1 指出上下文与错误处理策略有关。
          • @Tom,好点,但保证会捕获异常。我们如何确保错误代码被捕获并且不会因为错误而被忽略?
          • 所以您反对异常的唯一论据是,当您忘记捕获异常时可能会发生一些不好的事情,但您没有考虑忘记检查返回值是否有错误的可能性?更不用说当您忘记捕获异常时会得到堆栈跟踪,而当您忘记检查错误代码时会一无所获。
          • C++17 引入了nodiscard 属性,如果未存储函数的返回值,该属性将发出编译器警告。有助于捕捉被遗忘的错误代码检查。示例:godbolt.org/g/6i6E0B
          • @Esailija 的评论正是在这里。此处反对异常的论点采用假设的基于错误代码的 API,其中记录了所有错误代码,假设程序员阅读文档,正确识别其应用程序中逻辑上可能的所有错误情况,并编写代码来处理每个错误,然后将该场景与假设的基于异常的 API 和程序员进行比较,其中由于某种原因其中一个步骤出错了......即使将所有这些步骤都正确地完成也同样容易(可以说更容易)基于异常的 API。
          【解决方案10】:

          我的理由是,如果您正在编写一个真正需要性能的低级驱动程序,那么请使用错误代码。但是,如果您在更高级别的应用程序中使用该代码并且它可以处理一些开销,那么使用一个接口包装该代码,该接口检查这些错误代码并引发异常。

          在所有其他情况下,例外可能是要走的路。

          【讨论】:

            【解决方案11】:

            我的方法是我们可以同时使用两者,即异常和错误代码。

            我习惯于定义几种类型的异常(例如:DataValidationException 或 ProcessInterruptExcepion),并在每个异常中定义每个问题的更详细描述。

            Java 中的一个简单示例:

            public class DataValidationException extends Exception {
            
            
                private DataValidation error;
            
                /**
                 * 
                 */
                DataValidationException(DataValidation dataValidation) {
                    super();
                    this.error = dataValidation;
                }
            
            
            }
            
            enum DataValidation{
            
                TOO_SMALL(1,"The input is too small"),
            
                TOO_LARGE(2,"The input is too large");
            
            
                private DataValidation(int code, String input) {
                    this.input = input;
                    this.code = code;
                }
            
                private String input;
            
                private int code;
            
            }
            

            通过这种方式,我使用异常来定义类别错误,并使用错误代码来定义有关问题的更详细信息。

            【讨论】:

            • 呃...throw new DataValidationException("The input is too small")?例外的优点之一是允许提供详细信息。
            【解决方案12】:

            我可能坐在栅栏上,但是……

            1. 这取决于语言。
            2. 无论您选择哪种型号,都应始终如一地使用它。

            在 Python 中,使用异常是标准做法,我很乐意定义自己的异常。在 C 中你根本没有例外。

            在 C++ 中(至少在 STL 中),异常通常只针对真正的异常错误引发(我自己几乎从未见过它们)。我认为没有理由在我自己的代码中做任何不同的事情。是的,忽略返回值很容易,但 C++ 也不会强迫您捕获异常。我认为您只需要养成这样做的习惯即可。

            我工作的代码库主要是 C++,我们几乎在所有地方都使用错误代码,但是有一个模块会针对任何错误引发异常,包括非常普通的错误,并且使用该模块的所有代码都非常糟糕。但这可能只是因为我们混合了异常和错误代码。始终使用错误代码的代码更容易使用。如果我们的代码始终使用异常,也许它不会那么糟糕。将两者混合起来似乎效果不佳。

            【讨论】:

              【解决方案13】:

              异常适用于异常情况——即,当它们不是正常代码流的一部分时。

              混合使用异常和错误代码是很合理的,其中错误代码代表某事的状态,而不是代码本身运行中的错误(例如检查子进程的返回代码)。

              但是当异常情况发生时,我相信异常是最有表现力的模型。

              在某些情况下,您可能更喜欢或不得不使用错误代码来代替异常,并且已经充分涵盖了这些情况(除了编译器支持等其他明显的限制)。

              但从另一个方向发展,使用异常可以让您为错误处理构建更高级别的抽象,这可以使您的代码更具表现力和自然。我强烈建议阅读 C++ 专家 Andrei Alexandrescu 撰写的这篇出色但被低估的文章,主题是他所谓的“执法”:http://www.ddj.com/cpp/184403864。虽然这是一篇 C++ 文章,但原则是普遍适用的,我已经将强制概念转换为 C# 相当成功。

              【讨论】:

                【解决方案14】:

                我永远不会混合这两种模型...当您从堆栈的一个使用错误代码的部分移动到使用异常的更高部分时,从一个模型转换到另一个模型太难了。

                例外情况是“任何阻止或禁止方法或子例程执行您要求它执行的操作”...不要将有关异常情况或异常情况或系统状态等的消息传回。使用 return值或参考(或输出)参数。

                异常允许使用依赖于方法函数的语义来编写(和使用)方法,即可以键入返回员工对象或员工列表的方法来执行此操作,您可以通过调用来使用它.

                Employee EmpOfMonth = GetEmployeeOfTheMonth();
                

                对于错误代码,所有方法都返回错误代码,因此,对于那些需要返回其他内容以供调用代码使用的方法,您必须传递一个引用变量以填充该数据,并测试返回错误代码的值,并在每个函数或方法调用时处理它。

                Employee EmpOfMonth; 
                if (getEmployeeOfTheMonth(ref EmpOfMonth) == ERROR)
                    // code to Handle the error here
                

                如果您的编码使每个方法只做一件简单的事情,那么只要该方法无法实现该方法的预期目标,您就应该抛出异常。以这种方式,异常比错误代码更丰富、更易于使用。您的代码更清晰 - “正常”代码路径的标准流程可以严格用于该方法能够完成您想要它做的事情的情况......然后清理代码或处理当发生阻止方法成功完成的不良情况时,可以将“异常”情况与正常代码隔离开来。此外,如果您无法处理发生的异常,并且必须将其通过堆栈传递到 UI,(或​​者更糟的是,从中间层组件到 UI),那么使用异常模型,您不需要编写堆栈中的每个干预方法来识别异常并将异常向上传递...异常模型会自动为您执行此操作...使用错误代码,这块拼图会很快变得繁重.

                【讨论】:

                • 很好的答案!开箱即用的解决方案及时!
                【解决方案15】:

                由于我使用 C++,并且拥有 RAII 以确保它们可以安全使用,因此我几乎只使用异常。它将错误处理从正常的程序流程中拉出来,并使意图更加清晰。

                不过,我确实会为特殊情况保留例外情况。如果我预计某个错误会发生很多,我会在执行之前检查操作是否成功,或者调用使用错误代码的函数版本(如TryParse()

                【讨论】:

                  【解决方案16】:

                  我希望所有错误情况都使用异常,除非失败是返回原始数据类型的函数的预期无错误结果。例如。在较大的字符串中查找子字符串的索引通常会在未找到时返回 -1,而不是引发 NotFoundException。

                  返回可能被取消引用的无效指针(例如,在 Java 中导致 NullPointerException)是不可接受的。

                  使用多个不同的数字错误代码(-1、-2)作为同一函数的返回值通常是不好的风格,因为客户端可能会执行“== -1”检查而不是“

                  这里要记住的一件事是随着时间的推移 API 的演变。一个好的 API 允许在不破坏客户端的情况下以多种方式更改和扩展故障行为。例如。如果客户端错误句柄检查了 4 个错误情况,并且您向函数添加了第五个错误值,则客户端处理程序可能不会对此进行测试并中断。如果您引发异常,这通常会使客户端更容易迁移到更新版本的库。

                  另一件要考虑的事情是在团队中工作时,在哪里为所有开发人员做出这样的决定划清界限。例如。 “高级内容的例外,低级内容的错误代码”是非常主观的。

                  在任何情况下,如果可能出现不止一种微不足道的错误,源代码绝不应该使用数字文字来返回错误代码或处理它(返回 -7,如果 x == -7 ... ),但始终是命名常量(如果 x == NO_SUCH_FOO,则返回 NO_SUCH_FOO)。

                  【讨论】:

                    【解决方案17】:

                    如果你在大项目下工作,你不能只使用异常或只使用错误代码。在不同的情况下,您应该使用不同的方法。

                    例如,您决定只使用异常。但是一旦你决定使用异步事件处理。在这种情况下使用异常进行错误处理是个坏主意。但是在应用程序中到处使用错误代码是很乏味的。

                    所以我认为同时使用异常和错误代码是正常的。

                    【讨论】:

                      【解决方案18】:

                      在某些情况下,以干净、清晰、正确的方式使用异常可能很麻烦,但绝大多数情况下,异常是显而易见的选择。异常处理相对于错误代码的最大好处是它改变了执行流程,这很重要,原因有二。

                      发生异常时,应用程序不再遵循其“正常”执行路径。这一点如此重要的第一个原因是,除非代码的作者做得很好并且真正避免做坏事,否则程序将停止并且不会继续做不可预测的事情。如果没有检查错误代码并且没有采取适当的措施来响应错误的错误代码,程序将继续做它正在做的事情,谁知道该操作的结果将是什么。在很多情况下,让程序做“任何事情”最终可能会非常昂贵。考虑一个程序,该程序检索公司销售的各种金融工具的绩效信息,并将该信息传递给经纪人/批发商。如果出现问题并且程序继续运行,它可能会将错误的性能数据发送给经纪人和批发商。我不了解其他人,但我不想成为坐在副总裁办公室解释为什么我的代码导致公司受到 7 位数监管罚款的人。向客户提供错误消息通常比提供看起来“真实”的错误数据更可取,而后一种情况更容易使用错误代码等激进得多的方法遇到。

                      我喜欢异常以及它们破坏正常执行的第二个原因是,它将“正常的事情正在发生”逻辑与“出现错误的逻辑”分开变得非常容易。对我来说,这是:

                      try {
                          // Normal things are happening logic
                      catch (// A problem) {
                          // Something went wrong logic
                      }
                      

                      ...比这个更可取:

                      // Some normal stuff logic
                      if (errorCode means error) {
                          // Some stuff went wrong logic
                      }
                      // Some normal stuff logic
                      if (errorCode means error) {
                          // Some stuff went wrong logic
                      }
                      // Some normal stuff logic
                      if (errorCode means error) {
                          // Some stuff went wrong logic
                      }
                      

                      还有其他一些关于异常的小事也很好。有一堆条件逻辑来跟踪函数中调用的任何方法是否返回了错误代码,并返回更高的错误代码是很多样板。事实上,很多样板都会出错。我对大多数语言的异常系统有更多的信心,而不是“刚毕业的”弗雷德写的一堆 if-else-if-else 语句,而且我还有很多更好的事情要做用我的时间比代码审查鼠窝。

                      【讨论】:

                        【解决方案19】:

                        我认为这还取决于您是否真的需要结果中的堆栈跟踪等信息。如果是的话,你肯定会选择 Exception ,它为对象提供了很多关于问题的信息。但是,如果您只对结果感兴趣并且不在乎为什么会出现该结果,那么请使用错误代码。

                        例如当您正在处理文件并遇到 IOException 时,客户端可能有兴趣知道从哪里触发、打开文件或解析文件等。所以最好返回 IOException 或其特定的子类。但是,如果您有登录方法并且您想知道它是否成功,那么您只需返回布尔值或显示正确的消息,返回错误代码。在这里,客户端对知道哪个逻辑部分导致了该错误代码不感兴趣。他只知道它的凭据是否无效或帐户锁定等。

                        我能想到的另一个用例是数据在网络上传输时。您的远程方法可以只返回错误代码而不是异常,以最大限度地减少数据传输。

                        【讨论】:

                          【解决方案20】:

                          首先,我同意 Tom 的 answer 的观点,即高级的东西使用异常,而低级的东西使用错误代码,只要它不是面向服务的架构 (SOA)。

                          在 SOA 中,可以跨不同机器调用方法,异常可能不会通过网络传递,相反,我们使用具有如下结构的成功/失败响应 (C#):

                          public class ServiceResponse
                          {
                              public bool IsSuccess => string.IsNullOrEmpty(this.ErrorMessage);
                          
                              public string ErrorMessage { get; set; }
                          }
                          
                          public class ServiceResponse<TResult> : ServiceResponse
                          {
                              public TResult Result { get; set; }
                          }
                          

                          并像这样使用:

                          public async Task<ServiceResponse<string>> GetUserName(Guid userId)
                          {
                              var response = await this.GetUser(userId);
                              if (!response.IsSuccess) return new ServiceResponse<string>
                              {
                                  ErrorMessage = $"Failed to get user."
                              };
                              return new ServiceResponse<string>
                              {
                                  Result = user.Name
                              };
                          }
                          

                          当这些在您的服务响应中始终如一地使用时,它会创建一个非常好的模式来处理应用程序中的成功/失败。这允许在服务内以及跨服务的异步调用中更轻松地处理错误。

                          【讨论】:

                            【解决方案21】:

                            我的一般规则是:

                            • 函数中只能出现一个错误:使用错误代码(作为函数的参数)
                            • 可能会出现多个特定错误:抛出异常

                            【讨论】:

                              【解决方案22】:

                              你应该同时使用。问题是决定何时使用每个

                              在少数情况下例外是显而易见的选择

                              1. 在某些情况下你不能对错误代码做任何事情,你只需要在调用堆栈的上层处理它,通常只需要记录错误,向用户显示某些内容或关闭程序。在这些情况下,错误代码需要您手动逐级冒泡错误代码,这显然更容易处理异常。关键是这是针对意外和无法处理的情况。

                              2. 然而,关于情况 1(发生意外和无法处理的事情,您只是不想记录它),例外可能会有所帮助,因为您可能添加上下文信息。例如,如果我在较低级别的数据帮助程序中收到 SqlException,我将希望在低级别(我知道导致错误的 SQL 命令)中捕获该错误,以便我可以捕获该信息并重新抛出 提供更多信息。请注意这里的神奇词:重新投掷,而不是吞下The first rule of exception handling: do not swallow exceptions。另外,请注意,我的内部 catch 不需要记录任何内容,因为外部 catch 将拥有整个堆栈跟踪并且可能会记录它。

                              3. 在某些情况下,您有一系列命令,并且如果其中任何一个失败您应该清理/处置资源(*),无论这是否是不可恢复的情况(应该被抛出)或可恢复的情况(在这种情况下,您可以在本地或在调用者代码中处理,但您不需要异常)。显然,将所有这些命令放在一次尝试中要容易得多,而不是在每个方法之后测试错误代码,然后在 finally 块中进行清理/处置。请注意,如果您希望错误冒泡(这可能是您想要的),您甚至不需要捕获它 - 您只需使用 finally 进行清理/处置 - 您应该只如果您想添加上下文信息,请使用 catch/retrow(参见项目符号 2)。

                                一个例子是事务块内的一系列 SQL 语句。同样,这也是一种“无法处理”的情况,即使您决定及早发现它(在本地处理它而不是冒泡到顶部),它仍然是一个致命的情况,最好的结果是中止一切或至少中止大部分过程。
                                (*) 这就像我们在旧 Visual Basic 中使用的 on error goto

                              4. 在构造函数中你只能抛出异常。

                              话虽如此,在所有其他情况下,如果您要返回一些信息,调用者可以/应该采取一些行动,使用返回码可能是更好的选择。这包括所有预期的“错误”,因为它们可能应该由直接调用者处理,并且几乎不需要在堆栈中向上冒泡太多级别。

                              当然,总是可以将预期的错误视为异常,然后立即捕获上一层,也可以在 try catch 中包含每一行代码,并对每个可能的错误采取措施。 IMO,这是一个糟糕的设计,不仅因为它更加冗长,而且特别是因为如果不阅读源代码,可能引发的异常并不明显 - 并且可以从任何深度方法引发异常,从而创建 invisible gotos。他们通过创建多个不可见的退出点来破坏代码结构,从而使代码难以阅读和检查。换句话说,您永远不应该将exceptions 用作flow-control,因为这会让其他人难以理解和维护。甚至很难理解所有可能的测试代码流。
                              同样:为了正确清理/处置,您可以使用 try-finally 而不捕获任何东西

                              关于返回码最流行的批评是“有人可以忽略错误代码,但在同样的意义上,有人也可以吞下异常。Bad exception handling is easy in both methods. But writing good error-code-based program is still much easier than writing an exception-based program。如果一个人出于任何原因决定忽略所有错误(旧的@ 987654329@),您可以使用返回码轻松做到这一点,如果没有大量的 try-catch 样板,您将无法做到这一点。

                              关于返回码的第二个最流行的批评是“很难冒泡” - 但这是因为人们不明白异常是针对不可恢复的情况,而错误码则不是。

                              在异常和错误代码之间做出决定是一个灰色区域。您甚至可能需要从一些可重用的业务方法中获取错误代码,然后您决定将其包装到异常中(可能添加信息)并让它冒泡。但是假设所有错误都应该作为异常抛出是一个设计错误。

                              总结一下:

                              • 当我遇到意外情况时,我喜欢使用异常,在这种情况下没有什么可做的,通常我们想中止一大块代码甚至整个操作或程序。这就像旧的“on error goto”。

                              • 当我预计调用者代码可以/应该采取一些行动的情况下,我喜欢使用返回码。这包括大多数业务方法、API、验证等。

                              异常和错误代码之间的这种区别是 GO 语言的设计原则之一,它对致命的意外情况使用“恐慌”,而常规的预期情况则作为错误返回。

                              关于 GO,它还允许 multiple return values ,这对使用返回码有很大帮助,因为您可以同时返回错误和其他内容。在 C#/Java 上,我们可以通过 out 参数、元组或(我最喜欢的)泛型来实现,它们与枚举结合可以为调用者提供清晰的错误代码:

                              public MethodResult<CreateOrderResultCodeEnum, Order> CreateOrder(CreateOrderOptions options)
                              {
                                  ....
                                  return MethodResult<CreateOrderResultCodeEnum>.CreateError(CreateOrderResultCodeEnum.NO_DELIVERY_AVAILABLE, "There is no delivery service in your area");
                              
                                  ...
                                  return MethodResult<CreateOrderResultCodeEnum>.CreateSuccess(CreateOrderResultCodeEnum.SUCCESS, order);
                              }
                              
                              var result = CreateOrder(options);
                              if (result.ResultCode == CreateOrderResultCodeEnum.OUT_OF_STOCK)
                                  // do something
                              else if (result.ResultCode == CreateOrderResultCodeEnum.SUCCESS)
                                  order = result.Entity; // etc...
                              

                              如果我在我的方法中添加一个新的可能返回值,我什至可以检查所有调用者是否在 switch 语句中覆盖了该新值。除了例外,你真的不能这样做。当您使用返回码时,您通常会提前知道所有可能的错误,并对其进行测试。除了例外,您通常不知道会发生什么。将枚举包装在异常中(而不是泛型)是一种替代方法(只要清楚每个方法将抛出的异常类型),但 IMO 仍然是糟糕的设计。

                              编辑 2020-10-11

                              从 C# 7.0(2017 年 3 月)开始,我更喜欢使用 new Tuples syntax,而不是泛型,它允许多个返回值(因此我们可以在方法返回结果或错误时使用类似 GO 的语法)。

                              
                              public enum CreateUserResultCodeEnum
                              {
                                  [Description("Username not available")]
                                  NOT_AVAILABLE,
                              }
                              
                              public (User user, CreateUserResultCodeEnum? error) CreateUser(string userName)
                                  // (try to create user, check if not available...)
                                  if (notAvailable)
                                      return (null, CreateUserResultCodeEnum.NOT_AVAILABLE);
                                  return (user, null);
                              }
                              
                              // How to call and deconstruct tuple:
                              (var user, var error) = CreateUser("john.doe");
                              if (user != null) ...
                              if (error == CreateUserResultCodeEnum.NOT_AVAILABLE) ...
                              
                              // Or returning a single object (named tuple):
                              var result = CreateUser("john.doe");
                              if (result.user != null) ...
                              if (result.error == CreateUserResultCodeEnum.NOT_AVAILABLE) ...
                              

                              编辑 2021-01-09

                              几天前I wrote this blog post 关于我们如何(在某些情况下!)使用多个返回而不是异常(如上面解释的 golang 约定,不应该替换所有异常,但应该让你决定何时使用异常以及何时使用返回码)。 在这篇文章的最后,我混合了两个模型——基本上我使用的是 ValueTuple 语法(非常简洁和优雅),但使用泛型 作为底层结构。 基本上我使用 implicit conversion operatortype deconstructorsValueTupleCommandResult&lt;TEntity, TError&gt; 之间来回转换。

                              【讨论】:

                                猜你喜欢
                                • 1970-01-01
                                • 1970-01-01
                                • 2012-12-07
                                • 1970-01-01
                                • 2018-11-27
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                相关资源
                                最近更新 更多