异常,错误代码和有区别的联合


80

我最近开始了C#编程工作,但是我在Haskell有很多背景。

但是我知道C#是一种面向对象的语言,我不想将圆钉插入方孔中。

我阅读了Microsoft的Exception Throwing文章,其中指出:

不要返回错误代码。

但是习惯了Haskell,我一直在使用C#数据类型OneOf,将结果作为“正确”值返回,或者将错误(通常是Enumeration)作为“左侧”值返回。

这很像EitherHaskell中的约定。

在我看来,这比例外情况更安全。在C#中,忽略异常不会产生编译错误,如果未捕获到异常,它们只会冒泡并使程序崩溃。这也许比忽略错误代码并产生不确定的行为要好,但是崩溃客户的软件仍然不是一件好事,尤其是当它在后台执行许多其他重要的业务任务时。

使用时OneOf,必须非常明确地解压缩它并处理返回值和错误代码。而且,如果在调用堆栈的那个阶段不知道如何处理它,则需要将其放入当前函数的返回值中,以便调用者知道可能会导致错误。

但这似乎不是微软建议的方法。

使用OneOf代替异常来处理“普通”异常(例如“找不到文件”等)是一种合理的方法还是一种可怕的做法?


值得注意的是,我听说过异常作为控制流被认为是一种严重的反模式,因此,如果“异常”是您通常会在不结束程序的情况下处理的,那么“控制流”是否就某种方式而言呢?我了解这里有些灰色区域。

注意,我不使用OneOf“内存不足”之类的东西,但我不希望从中恢复的条件仍然会引发异常。但是我觉得这是非常合理的问题,例如未解析的用户输入本质上是“控制流”,并且可能不应引发异常。


随后的想法:

从这次讨论中,我目前要讲的内容如下:

  1. 如果您希望直接调用者catch在大多数时间都可以处理异常并继续其工作(也许通过另一条路径),则它可能应该是返回类型的一部分。OptionalOneOf在这里有用。
  2. 如果您希望直接调用方在大多数情况下不会捕获异常,请抛出异常,以免手动将异常传递到堆栈中。
  3. 如果您不确定直接呼叫者将要做什么,则可以同时提供Parse和和TryParse

13
使用F#Result;-)
Astrinus

8
有点偏离主题,但请注意,您正在寻找的方法具有一般漏洞,即无法确保开发人员正确处理错误。当然,键入系统可以起到提醒作用,但是您会因为无法处理错误而丢失了炸毁程序的默认设置,这很可能使您最终陷入某种意外状态。但是,提醒是否值得避免这种违约是一个公开辩论。我倾向于认为编写所有代码(假设某个异常会在某个时候终止它)会更好。则提醒的价值较小。
jpmc26

9
相关文章:埃里克·利珀特(Eric Lippert)关于例外的四个“桶”。他给出的关于外生信号的示例表明,在某些情况下,您只需要处理异常即可FileNotFoundException
索伦D.Ptæus

7
我可能一个人呆着,但是我认为崩溃是件好事。除了具有正确跟踪的崩溃日志外,没有比这更好的证据表明您的软件存在问题。如果您的团队能够在30分钟或更短时间内纠正和部署补丁,那么让那些丑陋的伙伴露面可能不是一件坏事。
T. Sar

9
为什么没有一个答案澄清Either单子是不是一个错误代码,并且也不是OneOf?它们本质上是不同的,因此问题似乎是基于一种误解。(尽管以修改后的形式仍然是一个有效的问题。)
Konrad Rudolph

Answers:


131

但是崩溃客户软件仍然不是一件好事

这当然是一件好事。

您想要使系统处于未定义状态的任何东西都可以停止系统,因为未定义的系统会执行令人讨厌的事情,例如数据损坏,格式化硬盘以及发​​送威胁总统的电子邮件。如果您无法恢复并使系统回到定义的状态,那么崩溃是要做的事情。这就是为什么我们构建的系统崩溃而不是悄悄地将其自身拆散。现在确定,我们都希望有一个不会崩溃的稳定系统,但是只有当系统保持在已定义的可预测安全状态时,我们才真正想要它。

我听说作为控制流的异常被认为是严重的反模式

这是绝对正确的,但常常被误解。当他们发明异常系统时,他们担心会破坏结构化编程。结构化编程就是为什么我们有forwhileuntilbreak,和continue当所有我们需要的,做的所有这一切,是goto

Dijkstra告诉我们,非正式地使用goto(也就是说,随意跳动)会使阅读代码成为噩梦。当他们给我们例外系统时,他们担心自己在重塑goto。因此,他们告诉我们不要“将其用于流量控制”,希望我们能够理解。不幸的是,我们许多人没有。

奇怪的是,我们不像过去使用goto那样经常使用异常来创建意大利面条代码。该建议本身似乎造成了更多麻烦。

从根本上来说,例外是关于拒绝一个假设。当您要求保存文件时,假定该文件可以并且将被保存。当您无法使用该名称时,您可能会收到一个例外,该名称可能是名称不合法,HD已满,或者是因为老鼠通过您的数据线咬了。您可以以不同的方式处理所有这些错误,可以以相同的方式处理它们,也可以让它们停止系统。您的代码中有一条快乐的路,您的假设必须成立。一种或另一种例外会让您走开那条快乐的路。严格来说,是的,这是一种“流控制”,但这并不是他们警告您的。他们说的是这样的废话

在此处输入图片说明

“例外应该是例外”。这种重言巧语是天生的,因为异常系统设计人员需要时间来构建堆栈跟踪。与跳动相比,这很慢。它占用了CPU时间。但是,如果您要在启动下一个系统之前记录并停止系统,或者至少停止当前时间密集的处理,那么您就有时间消磨时间。如果人们开始使用“用于流控制”的例外,那么关于时间的所有假设都将落空。因此,出于性能考虑,确实给了我们“例外应该是例外的”。

比这更重要的是不要使我们感到困惑。您在上面的代码中发现无限循环花了多长时间?

不要返回错误代码。

当您使用的代码库通常不使用错误代码时,...是很好的建议。为什么?因为没有人会记得保存返回值并检查错误代码。当您使用C语言时,这仍然是一个很好的约定。

OneOf

您正在使用另一种约定。只要您设置约定而不是简单地与另一个约定打交道,那很好。在同一代码库中有两个错误约定会令人困惑。如果您以某种方式摆脱了使用其他约定的所有代码,请继续。

我本人喜欢这个约定。我在这里找到的最好的解释之一*

在此处输入图片说明

但是,尽管我很喜欢,但我仍然不会将其与其他约定混合使用。选择一个并坚持下去。1个

1:我的意思是不要让我同时考虑多个公约。


随后的想法:

从这次讨论中,我目前要讲的内容如下:

  1. 如果您希望直接调用方在大多数时间捕获并处理异常并继续其工作(也许通过另一条路径),则它可能应该是返回类型的一部分。Optional或OneOf在这里可能有用。
  2. 如果您希望直接调用方在大多数情况下不会捕获异常,请抛出异常,以免手动将异常传递到堆栈中。
  3. 如果您不确定直接调用者将要做什么,则可以同时提供两者,例如Parse和TryParse。

真的不是那么简单。您需要了解的基本知识之一是什么是零。

五月还剩几天?0(因为不是5月。已经是6月)。

例外是拒绝假设的一种方法,但不是唯一的方法。如果您使用异常来拒绝该假设,那么您将走开心的路。但是,如果您选择了值来沿幸福的道路发送信号,表明事情并没有想象中的那么简单,那么您可以一直坚持下去,只要它可以处理这些值即可。有时,0已经用来表示某些东西,因此您必须找到另一个值以将拒绝假设的想法映射到该值。您可能会从在好的旧代数中使用它的想法认识到这一点。Monad可以帮助您解决问题,但不一定总是Monad。

例如2

IList<int> ParseAllTheInts(String s) { ... }

您能想到有什么好的理由必须对它进行设计,以使其有意抛出任何东西吗?猜猜无法解析int会得到什么?我什至不需要告诉你。

这是一个好名字的标志。抱歉,TryParse不是我的好名字。

当答案可能同时不止是一件事时,我们通常避免抛出什么也不会抛出异常,但是由于某种原因,如果答案是一件事还是一无所获,我们就会坚持要求它给我们一件事或抛出:

IList<Point> Intersection(Line a, Line b) { ... }

平行线真的需要在这里引起异常吗?如果此列表永远不会包含一个以上的点,真的那么糟糕吗?

也许从语义上讲您不能接受。如果是这样,那太可惜了。但是,也许Monads没有像List这样的任意大小,会让您感觉更好。

Maybe<Point> Intersection(Line a, Line b) { ... }

Monads是一些特殊用途的集合,旨在以特定方式使用,而无需测试它们。我们应该找到处理它们的方法,无论它们包含什么。这样,幸福的道路保持简单。如果您打开并测试每一个Monad,那么您使用的是错误的。

我知道,这很奇怪。但这是一个新工具(对我们而言)。所以给它一些时间。当您停止在螺钉上使用锤子时,锤子会更有意义。


如果您可以纵容我,我想发表评论:

为什么没有答案能说明Emoner monad不是错误代码,也不是OneOf?它们本质上是不同的,因此问题似乎是基于一种误解。(尽管以修改后的形式仍然是一个有效的问题。)– Konrad Rudolph 6月18日在18:08时

这是绝对正确的。Monad比异常,标志或错误代码更接近集合。如果明智地使用它们,它们确实可以为此类物品制造精美的容器。


9
如果红色轨道位于盒子下面,从而不穿过盒子,那会更合适吗?
扁平的

4
从逻辑上讲,您是正确的。但是,此特定图中的每个框代表单子绑定操作。绑定包括(也许创建!)红色轨道。我建议观看完整的演示文稿。这很有趣,斯科特是一位出色的演讲者。
Gusdor

7
我认为异常更像是COMEFROM指令而不是GOTO,因为异常throw实际上不知道/不说我们将跳转到哪里;)
Warbo

3
如果可以建立边界,那么混合约定就没有错,这就是封装的全部意义所在。
罗伯特·哈维

4
就像您在引用句子后停止阅读该问题一样,此答案的整个第一部分读起来很强烈。该问题承认,崩溃程序胜于进入未定义的行为:它们将运行时崩溃与持续的,未定义的执行进行对比,而不是与编译时错误进行对比,后者使您甚至无法在不考虑潜在错误的情况下构建程序并对其进行处理(如果没有其他事情要做,最终可能会导致崩溃,但这将是崩溃,因为您想要做到这一点,而不是因为您忘记了它)。
KRyan

35

C#不是Haskell,您应该遵循C#社区的专家共识。相反,如果您尝试遵循C#项目中的Haskell做法,则会疏远团队中的其他所有人,最终您可能会发现C#社区以不同方式做事的原因。一个很大的原因是C#无法方便地支持歧视工会。

我听说作为控制流的异常被认为是严重的反模式,

这不是普遍接受的事实。每种支持异常的语言都可以选择抛出异常(调用者可以随意处理)或返回一些复合值(调用者必须处理)。

在整个调用堆栈中向上传播错误条件需要在每个级别都有条件,从而使那些方法的循环复杂性加倍,因此单元测试用例的数量也加倍。在典型的业务应用程序中,许多异常是无法恢复的,可以让它们传播到最高级别(例如,Web应用程序的服务入口点)。


9
传播单子不需要任何东西。
Basilevs

23
@Basilevs它需要支持传播monad的同时保持代码的可读性,而C#没有。您可能会重复获得if-return指令或lambda链。
塞巴斯蒂安·雷德尔

4
@SebastianRedl lambdas在C#中看起来还可以。
Basilevs

@Basilevs从语法角度来看是的,但我怀疑从性能角度来看它们可能没有吸引力。
法拉普

5
在现代C#中,您可能可以部分使用局部函数来提高速度。在任何情况下,IO都会大大降低大多数业务软件的运行速度。
Turksarama

26

您在一个有趣的时间来到了C#。直到最近,该语言仍牢牢地放在命令式编程领域。使用异常来传达错误绝对是正常现象。使用返回码时,缺少语言支持(例如,围绕有区别的联合和模式匹配)。相当合理地,Microsoft的官方指南是避免返回代码并使用异常。

但是,始终存在TryXXX方法形式的例外,它们会返回boolean成功结果并通过out参数提供第二个值结果。这些与函数编程中的“尝试”模式非常相似,不同之处在于结果是通过out参数而不是Maybe<T>返回值得出的。

但是情况正在改变。函数式编程正变得越来越流行,并且诸如C#之类的语言正在响应。C#7.0为该语言引入了一些基本的模式匹配功能。C#8将引入更多内容,包括开关表达式,递归模式等。与此同时,C#的“功能库”也有所增长,例如我自己的Succinc <T>库,该也为歧视的联合提供支持。 。

这两件事结合在一起,意味着以下代码逐渐流行。

static Maybe<int> TryParseInt(string source) =>
    int.TryParse(source, out var result) ? new Some<int>(result) : none; 

public int GetNumberFromUser()
{
    Console.WriteLine("Please enter a number");
    while (true)
    {
        var userInput = Console.ReadLine();
        if (TryParseInt(userInput) is int value)
        {
            return value;
        }
        Console.WriteLine("That's not a valid number. Please try again");
    }
}

目前,它仍然是一个小众市场,尽管即使在过去的两年中,我也注意到从C#开发人员的普遍敌视到此类代码的显着变化,到人们对使用此类技术的兴趣不断增长。

我们还没有说“ 不要返回错误代码”。很陈旧,需要退休。但是,朝着这个目标迈进的道路正在顺利进行。因此,请选择:如果您喜欢做事的方式,则坚持旧的方法,以抛弃同性恋者的方式抛出例外;或开始探索一个新的世界,其中来自功能语言的约定在C#中变得越来越流行。


22
这就是为什么我讨厌C#:)他们不断增加为猫皮的方法,当然,旧的方法永远也不会消失。最终,任何事情都没有约定俗成,一个完美主义者会坐上几个小时来决定哪种方法“更聪明”,而新程序员必须先学习一切,然后才能阅读别人的代码。
Aleksandr Dubinsky

24
@AleksandrDubinsky,您遇到了一般编程语言(不仅仅是C#)的核心问题。新的语言被创造出来,简洁,新鲜并充满了现代思想。很少有人使用它们。随着时间的流逝,他们获得了用户和新功能,这些功能附加在无法删除的旧功能之上,因为这会破坏现有代码。肿胀加剧。因此,人们创造了简洁,新鲜且充满现代思想的新语言……
David Arno

7
@AleksandrDubinsky在添加1960年代的功能时不讨厌它。
ctrl-alt-

6
@AleksandrDubinsky是的。由于Java尝试添加的东西,一旦(仿制药),他们已经失败了,我们现在要住在一起,导致让他们学到的教训,并退出添加任何东西的乌七八糟:)
Agent_L

3
@Agent_L:您知道现在已经添加java.util.Stream了Java (半个IEnumerable一样)和lambda,对吗?:)
cHao

5

我认为使用DU返回错误是完全合理的,但是然后我会说,就像我写的OneOf一样:)但是我还是要说,因为这是F#等的常见做法(例如Result类型,正如其他人所提到的那样) )。

我不认为我通常返回的错误是特殊情况,而是正常情况,但希望很少遇到可能需要或不需要处理但应进行建模的情况。

这是项目页面上的示例。

public OneOf<User, InvalidName, NameTaken> CreateUser(string username)
{
    if (!IsValid(username)) return new InvalidName();
    var user = _repo.FindByUsername(username);
    if(user != null) return new NameTaken();
    var user = new User(username);
    _repo.Save(user);
    return user;
}

向这些错误抛出异常似乎过大了-除了在方法签名中不明确或提供详尽匹配的缺点外,它们还有性能开销。

OneOf在C#中惯用到什么程度,这是另一个问题。语法是有效的并且相当直观。DU和面向铁路的编程是众所周知的概念。

我会为那些可能在某些地方坏了的东西保存例外。


您的意思是OneOf要使返回值更丰富,例如返回Nullable。在一开始不是很特殊的情况下,它将替换例外。
Aleksandr Dubinsky

是的,并且提供了一种简单的方法来在调用方中进行详尽的匹配,而这是使用基于继承的Result层次结构无法实现的。
mcintyre321

4

OneOf与Java中的检查异常等效。有一些区别:

  • OneOf不适用于产生因其副作用而调用方法的方法(因为可能会有意义地调用它们,而结果却被忽略)。显然,我们所有人都应该尝试使用纯函数,但这并不总是可能的。

  • OneOf没有提供有关问题发生位置的信息。这很好,因为它可以节省性能(由于填充堆栈跟踪,Java中抛出的异常代价很高,而在C#中,抛出异常将是相同的),并迫使您在OneOf自身的错误成员中提供足够的信息。这也很不好,因为此信息可能不足,您可能很难找到问题的根源。

OneOf 和检查异常具有一个重要的共同“特征”:

  • 它们都迫使您在调用堆栈中的任何地方处理它们

这样可以避免你的恐惧

在C#中,忽略异常不会产生编译错误,如果未捕获到异常,它们只会冒泡并使程序崩溃。

但是正如已经说过的那样,这种恐惧是不合理的。基本上,程序中通常只有一个地方,您需要捕获所有内容(可能您已经在使用框架了)。由于您和您的团队通常需要花费数周或数年的时间来开发该程序,所以您不会忘记它,对吗?

忽略异常的最大优点是可以在您想处理它们的任何地方处理它们,而不是在堆栈跟踪中的任何地方处理它们。这消除了很多样板工作(只需查看一些声明“ throws ....”或包装异常的Java代码),还可以减少代码的错误:由于任何方法都可能抛出错误,因此您需要意识到这一点。幸运的是,正确的操作通常什么都不做,即让它冒泡到可以合理处理的地方。


+1,但OneOf为什么不具有副作用?(我想这是因为如果您不分配返回值,它将通过检查,但由于我不知道OneOf,也不确定C#,我不确定-澄清会改善您的答案。)
冒充

1
通过使返回的错误对象包含有用的信息(例如OneOf<SomeSuccess, SomeErrorInfo>,其中SomeErrorInfo提供了错误的详细信息),可以使用OneOf返回错误信息。
mcintyre321

@dcorking编辑。当然,如果您忽略了返回值,那么您对所发生的问题一无所知。如果使用纯函数执行此操作,那就很好(您只是浪费时间)。
maaartinus

2
@ mcintyre321当然,您可以添加信息,但是您必须手动进行操作,否则可能会变得懒惰和遗忘。我想在更复杂的情况下,堆栈跟踪会更有用。
maaartinus

4

使用OneOf代替异常来处理“普通”异常(如“找不到文件”等)是一种合理的方法还是一种可怕的做法?

值得注意的是,我听说过异常作为控制流被认为是一种严重的反模式,因此,如果“异常”是您通常会在不结束程序的情况下处理的,那么“控制流”是否就某种方式而言呢?

错误有很多方面。我确定了三个方面:它们是否可以预防,它们发生的频率以及是否可以从中恢复。同时,错误信号通知主要具有一个维度:决定在多大程度上击败呼叫者以迫使他们处理错误,或者让错误“悄悄地”冒出气泡作为例外。

不可预防,频繁和可恢复的错误是真正需要在呼叫站点处理的错误。他们最好证明迫使呼叫者面对他们。在Java中,我使它们成为受检查的异常,并且通过使它们成为Try*方法或返回有区别的并集可以达到类似的效果。当一个函数具有返回值时,区分联合尤为有用。它们是returning的更精确的版本nulltry/catch块的语法不是很好(括号太多),使替代项看起来更好。而且,异常会稍微慢一些,因为它们会记录堆栈跟踪。

可预防的错误(由于程序员的错误/过失而未能阻止)和不可恢复的错误与普通的异常一样工作得很好。很少发生的错误也可以作为普通的异常更好地使用,因为程序员可能经常会认为它不值得去尝试(这取决于程序的目的)。

重要的一点是,通常是由使用站点来确定方法的错误模式如何沿着这些维度进行拟合,在考虑以下内容时应牢记这一点。

以我的经验,当错误无法预防,频繁且可恢复时,强制调用者面对错误情况是很好的,但是当调用者在不需要或不想要时被迫跳过箍时这会非常烦人。这可能是因为调用者知道错误不会发生,或者是因为他们(尚未)希望使代码具有弹性。在Java中,这解释了避免频繁使用检查异常并返回Optional(这与Maybe类似,最近在许多争议中引入)的焦虑。如有疑问,请引发异常,然后让调用者决定如何处理它们。

最后,请记住,关于错误条件的最重要的事情是要对它们进行彻底的记录,这远远超过任何其他考虑因素,例如如何发出信号。


2

其他答案已经足够详细地讨论了异常与错误代码,因此我想针对该问题添加另一种观点:

OneOf不是错误代码,它更像是monad

它的使用方式OneOf<Value, Error1, Error2>是一个代表实际结果或某些错误状态的容器。就像一个Optional,不同的是,当不存在任何值时,它可以提供更多为什么的详细信息。

错误代码的主要问题是您忘记检查它们。但是在这里,您不检查错误就无法访问结果。

唯一的问题是当您不关心结果时。OneOf文档中的示例提供:

public OneOf<User, InvalidName, NameTaken> CreateUser(string username) { ... }

...然后它们为每个结果创建不同的HTTP响应,这比使用异常要好得多。但是,如果您调用这样的方法。

public void CreateUserButtonClick(String username) {
    UserManager.CreateUser(string username)
}

那么您确实有问题,例外是更好的选择。比较铁的saveVS save!


1

您需要考虑的一件事是C#中的代码通常不是纯净的。在充斥着副作用的代码库中,并且如果编译器不关心您对某些函数的返回值所做的事情,那么您的方法可能会引起极大的痛苦。例如,如果您有删除文件的方法,则即使没有理由“在快乐的道路上”检查任何错误代码,也要确保应用程序在该方法失败时发出通知。这与要求您显式忽略不使用的返回值的功能语言有很大的不同。在C#中,返回值始终被隐式忽略(唯一的例外是属性获取器IIRC)。

MSDN告诉您“不返回错误代码”的原因正是这样的-没有人强迫您甚至读取错误代码。编译器根本无法帮助您。但是,如果函数是无副作用的,则可以安全地使用类似的东西Either-重点是,即使您忽略错误结果(如果有),也只能在不使用“正确结果”的情况下进行操作要么。有几种方法可以实现此目的-例如,您可能只允许通过传递用于处理成功错误情况的委托来“读取”值,并且可以轻松地将其与“ 铁路定向编程”之类的方法结合使用(在C#中工作正常)。

int.TryParse在中间的某个地方。很纯 它定义了两个结果在任何时候的值-因此您知道,如果返回值是false,则输出参数将设置为0。它仍然不会阻止您在不检查返回值的情况下使用输出参数,但是至少可以确保即使函数失败,结果也将是什么。

但是,这里绝对关键的一件事是一致性。如果有人更改该函数以产生副作用,则编译器将不会节省您的时间。还是抛出异常。因此,尽管这种方法在C#中非常好(并且我确实使用过),但是您需要确保团队中的每个人都理解并使用它。C#编译器未强制函数式编程正常运行所需的许多不变式,您需要确保自己遵循这些不变式。如果您不遵循Haskell默认执行的规则,则必须使自己意识到可能发生的事情-这对于您引入代码的任何功能范例都应牢记。


0

正确的声明是:不要对异常使用错误代码! 并且不要将异常用于非异常。

如果发生某种情况,您的代码无法从中恢复(即使其正常工作),那是一个例外。您的立即代码与错误代码没有任何关系,只是将其交给日志或以某种方式提供给用户。但是,这恰好是例外的原因-它们处理所有移交,并有助于分析实际发生​​的情况。

但是,如果发生了某些事情,例如您可以处理的无效输入(通过自动重新格式化或要求用户更正数据(然后您想到了这种情况)),那么实际上并不是一个例外-期待它。您可以随意使用错误代码或任何其他体系结构来处理这些情况。只是不是例外。

(请注意,我对C#的经验不是很丰富,但是我的答案应该在一般情况下根据异常的概念层次来决定。)


7
我从根本上不同意这个答案的前提。如果语言设计人员不打算将异常用于可恢复的错误,则catch关键字将不存在。而且,由于我们在C#上下文中进行交谈,因此值得注意的是,.NET框架并未遵循此处所倡导的哲学。可能是“您可以处理的无效输入”的最简单可想象的示例是用户在期望数字的字段中键入非数字,并int.Parse引发异常。
Mark Amery

@MarkAmery对于int.Parse,它无法从中恢复任何东西,也不会有任何期望。catch子句通常用于避免从外部视图完全失败,它不会向用户询问新的数据库凭据等,它将避免程序崩溃。这是技术故障,而不是应用程序控制流程。
Frank Hopkins

3
函数是否在条件发生时引发异常是否更好的问题取决于函数的直接调用者是否可以有效地处理该条件。在可能的情况下,抛出立即调用者必须捕获的异常代表了调用代码作者的额外工作,以及处理该代码的机器的额外工作。但是,如果不想为调用者准备处理条件,则可以通过例外来减轻调用者为其明确编写代码的麻烦。
超级猫

@Darkwing“您可以随时使用错误代码或任何其他体系结构来处理这些情况。” 是的,您可以自由地这样做。但是,.NET CL本身使用异常而不是错误代码,因此您不会从.NET获得任何支持。因此,不仅在很多情况下您被迫捕获.NET异常并返回相应的错误代码,而不是让异常冒出来,而且还迫使团队中的其他人进入另一个他们必须使用的错误处理概念。与异常并行的是标准错误处理概念。
Aleksander

-2

这是一个“可怕的想法”。

这很可怕,因为至少在.net中,您没有详尽的可能例外列表。任何代码都可能引发任何异常。特别是如果您正在执行OOP并且可能在子类型而不是声明的类型上调用重写的方法

因此,您必须将所有方法和函数都更改为OneOf<Exception, ReturnType>,然后如果要处理不同的异常类型,则必须检查类型If(exception is FileNotFoundException)等。

try catch 等于 OneOf<Exception, ReturnType>

编辑----

我知道您建议返回,OneOf<ErrorEnum, ReturnType>但是我觉得这是一个区别,没有区别。

您正在组合返回码,预期的异常和按异常分支。但是总体效果是相同的。


2
“正常”异常也是一个可怕的主意。线索就是名字
Ewan

4
我不建议返回Exceptions。
克林顿

您是否建议通过异常控制流之一?
Ewan
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.