抛出异常来强制退出递归有效吗?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了抛出异常来强制退出递归有效吗?相关的知识,希望对你有一定的参考价值。

以下只是伪代码。

  function x(node){
    if(node.val == 42) 
       return true; // What if I throw an exception here ?
    val1 = node.left ? x(node.left) : false ;
    val2 = node.right ? x(node.right): false; 
    return val1 || val2 ;
 }

让我们说我试图在树上找到42。使用上面的代码,如果我返回一个有效值,它将冒泡到整个递归链,然后最终返回。

我的假设是,如果我只是抛出异常而不是在第3行返回true,它实际上不会通过递归链冒出来并直接返回给调用者。

抛出一个Exception来打破整个链条是一个好习惯。因为我们在line 4说,它确实在一些嵌套堆栈中找到了42,它仍然会在line 5上追求递归。如果我只是在line 3抛出一个异常,我们可以避免那种不必要的计算。

问题更多的是编译器内部,如果我只抛出一个未处理的异常,它是否仍会冒泡到递归堆栈(这使得这种方法毫无意义)或直接返回到父调用的程序计数器。

答案

出于几个不同的原因,我不建议抛出异常来打破递归链。

  • 效率。一般来说,抛出异常比从函数返回要慢得多。具体来说,当抛出异常时,运行时需要使用当前堆栈跟踪填充异常,需要查看要调用的处理程序,需要搜索要执行的finally块或try-with-resources语句,跟踪对象引用对于垃圾收集目的等,仍然需要遍历调用堆栈,就像在调用链中返回值一样,并且几乎肯定不会比更标准的方法更快。
  • 可用性。如果抛出异常来中止递归链,那么调用函数的人需要编写对该异常做出反应的代码。这意味着它们不需要像写if (myCall()) {...}那样做,而是需要有单独的分支,一个用于调用正常返回值的一个分支,另一个用于抛出它的情况。如果他们忘记这样做,运行时的代码可能会因为异常而失败,而事实上它实际上是按预期运行的。更糟糕的是,你实际上会颠倒异常的正常使用。如果你的代码在成功时抛出异常并在失败时返回一个值,那么读取你的代码的人可能会变成“嗯?”弄清楚你的意思之前至少一次。
  • 明晰。异常应该用于那些非常特殊的情况!异常机制旨在报告在没有可行方法返回值的情况下返回信息。重新利用异常系统来处理常规控制流会使代码更难以阅读和维护,并违反最低惊喜原则。

虽然你最初的问题主要是关于效率问题,但我认为其他两点 - 可用性和清晰度 - 仍然强烈反对不以这种方式做事。

以上是关于抛出异常来强制退出递归有效吗?的主要内容,如果未能解决你的问题,请参考以下文章

不使用递归如何抛出堆栈溢出异常?

用户拒绝授权不得强制退出App,这能有效整治流氓软件的霸王条款吗?

缺少属性时如何强制 System.Text.Json 序列化程序抛出异常?

使用实体框架迁移时 SQL Server 连接抛出异常 - 添加代码片段

当 Main 抛出异常时,Environment.ExitCode 不受尊重。如何返回非零退出代码以及抛出异常?

片段中的getView()导致抛出异常,不确定原因