比C ++中的异常处理更好的语言功能?

时间:2010-09-13 15:42:45

标签: c++ language-agnostic exception programming-languages exception-handling

(不确定它是否只是一个C ++的东西)

异常处理is hard to learn in C++ and is certainly not a perfect solution but in most cases (other than some specific embedded software contexts) it's certainly the better solution we currently have for exception handling

未来怎么样?

是否有其他已知方法可以处理大多数语言中未实现的错误,或者只是大专院校的研究?

换句话说:是否(通常)知道更好(不完美可行)的方法来处理编程语言中的错误?

5 个答案:

答案 0 :(得分:10)

嗯,总是有返回码,errno等。一般的问题是那些不知道特定呼叫可能失败的程序员可以忽略或忘记这些。程序员也经常忽略或错过例外情况。不同之处在于,如果您没有捕获异常,程序就会死亡。如果不检查返回代码,程序将继续对无效数据进行操作。 Java试图通过创建已检查的异常来强制程序员捕获所有异常,如果您没有准确指定它们何时可以传播并最终捕获它们,则会导致编译错误。事实证明这非常令人讨厌,因此程序员会尽可能地将catch(...){/* do nothing*/}(在C ++ parlence中)的异常捕获到尽可能接近其源,并且结果并不比忽略返回代码更好。

除了这两种错误技术之外,一些函数语言还支持使用各种monadic返回类型,这些类型可以封装错误和返回值(例如Scala的Either类型,Option类型或{{ 3}})。这些优点是 only 使用成功返回值的方法是在monad中执行代码,monad确保在出现故障时不运行代码。 (对于那些不是Haskell或Scala程序员的人来说,解释起来相当复杂。)我没有那么多地使用过这个模型,但是我认为对于某些人来说这会像检查异常一样烦人。

基本上,IMO,错误检查是一种态度问题。您有三种选择:

  1. 意识到你必须处理它,高兴地接受这个事实,并努力编写正确的错误处理代码。 (其中任何一个)
  2. 使用迫使您处理它的语言功能,并因为您不想处理它而烦恼,特别是当您确定错误永远不会发生时。 (已检查的例外情况,Monads)
  3. 使用langauge功能,您可以轻松忽略它,并编写不安全的代码,因为您忽略了它。 (未经检查的例外,返回代码)
  4. 通过使用强制您处理它的语言功能来获取选项2和3中最差的选项,但是以明确忽略它的方式处理每个错误。 (已检查的例外情况,Monads)
  5. 显然,你应该尝试成为#1类型的程序员。

答案 1 :(得分:6)

假设您希望代码根据是否发生错误而执行不同的操作,您基本上有三个选项:

1)在代码中的任何地方显示它(C样式错误返回值检查)。主要的缺点是它很冗长。

2)使用非本地控制流将错误处理代码与“通常路径”(例外)分开。主要的缺点是跟踪代码可以接下来的所有位置,特别是如果记录的接口并不总是列出所有这些。 Java的实验以及“处理”后一个问题的已检查异常也未完全成功。

3)坐上错误直到“以后”(IEEE风格的粘滞错误位和安静的NaN,流上的C ++错误标志),并且仅在方便调用者时检查它们。主要的缺点是设置和清除错误需要每个人仔细使用,并且错误站点上可用的信息可能会在处理时丢失。

接受你的选择。 (1)看起来臃肿而复杂,并且新手通过不正确检查错误来搞乱它,但每行代码都很容易推理。 (2)看起来小而简单,但是每一行代码都可能导致跳到谁知道哪里,所以新手通过不正确地实现异常保证来搞乱它,并且每个人有时会在错误的地方捕获异常或根本不捕获异常。 (3)设计得很好很好,但你永远不知道每行代码实际上的几种可能性中的哪一种,所以在像C ++这样的富含UB的环境中也容易搞乱。

我认为基本问题基本上很难:处理错误会明显增加代码中的分支。在特定的代码中,处理错误会悄悄地增加您需要推理的状态量。

例外也有“它真的特别吗?”问题。您可以通过仅在整个程序无法恢复的情况下抛出异常来防止异常导致混乱的控制流。但是你不能将它们用于可以从程序的POV中恢复但不能从子系统的POV中恢复的错误,因此对于这些情况,你可以回到(1)或(3)的缺点。

答案 2 :(得分:5)

我不能说它比异常更好,但另一种选择是Erlang开发人员实现容错的方式,称为“让它失败”。总结一下:每个任务都是作为一个单独的“过程”产生的(Erlang的术语是大多数人所说的“线程”)。如果某个进程遇到错误,它就会死掉,并且通知会返回到控制进程,该进程可以忽略它或采取某种纠正措施。

这可能导致代码更简单,更健壮,因为整个程序不会因为缺少错误处理而崩溃或退出。 (请注意,这种健壮性依赖于Erlang语言和运行时环境的一些其他功能。)

Joe Armstrong的论文,其中包括一个关于他如何设想容错Erlang系统的论文,可供下载:http://www.erlang.org/download/armstrong_thesis_2003.pdf

答案 3 :(得分:1)

Common Lisp的condition system被认为是一个强大的超集,超出了你的例外情况。

答案 4 :(得分:0)

在我看到的系统中异常处理的基本问题是,如果例程X调用例程Y,它调用例程Z,这会抛出异常,Y没有干净的方法让其调用者在许多情况下区分:

  1. 由于Y不知道的某些原因,呼叫失败,但X可能;从Y的角度来看,如果X知道为什么Z失败了,X应该期待恢复。
  2. 由于Y不知道的某些原因,调用失败,但是它的失败导致Y将某些数据结构保留为无效状态。
  3. 由于Y确实知道的某些原因,呼叫失败了;从它的角度来看,如果调用者可以处理调用不会返回预期结果的事实,则X应该恢复。
  4. 呼叫失败,因为CPU正在着火。

我认为,这种困难源于这样一个事实,即异常类型集中在出现问题的问题上 - 这个问题在很多情况下基本上与如何解决这个问题正交。我希望看到的是包含虚拟“isSatisfied”方法的异常,并且尝试吞下异常,其isSatisfied方法返回false以抛出包含的异常,其isSatisfied方法将链接嵌套异常。某些类型的异常(如尝试将重复键添加到未损坏的字典)将提供无参数AcknowledgeException()方法来设置isSatisfied。其他涉及数据损坏或其他问题的例外要求AcknowledgeCoruption()方法传递损坏的数据结构,或者破坏损坏的数据结构。一旦在堆栈展开过程中破坏了损坏的数据结构,宇宙就会再次开心。

我不确定最佳架构是什么,但提供一种方法,通过这种方式,异常可以传达系统状态损坏或完整的程度,这将大大有助于缓解现有架构的问题。