【问题标题】:Does this code-block from Scala documentation make sense?Scala 文档中的这个代码块有意义吗?
【发布时间】:2019-01-17 13:43:14
【问题描述】:

这个例子来自https://www.scala-lang.org/api/current/scala/util/control/ControlThrowable.html:

import scala.util.control.ControlThrowable

try {
  // Body might throw arbitrarily
} catch {
  case c: ControlThrowable => throw c // propagate
  case t: Exception        => log(t)  // log and suppress
}

我理解为什么不能捕获Throwable,但在本例中,我们可以毫无问题地删除case c: ControlThrowable => throw c // propagate,因为下一个案例捕获Exception,因此无论如何都不会捕获ControlThrowables。我在这里遗漏了什么吗?

【问题讨论】:

  • 在 Scala 中捕获可投掷物被认为是不好的做法。 ControlThrowable 用于在程序员拥有用于流控制的Throwable 的极少数情况下使用。见here
  • 就像我说的,我理解捕获Throwable 是不好的做法。这在我发送的链接中也有解释。但删除该行并不意味着所有Throwables 都会被捕获——ExceptionThrowable 的子类型。
  • @jrook ControlThrowable 被 Scala 内部使用。程序员几乎从不使用它。

标签: scala


【解决方案1】:

这不是没用的。

考虑一下:

class FooException extends Exception with ControlThrowable

这将被第一个块捕获并重新抛出,永远不会到达第二个块。编写这样的异常可能是个坏主意,但是,当 ControlThrowable 首次引入(然后称为 ControlException)时,这是 not uncommon in the standard library

【讨论】:

  • 那么我应该期待任何 Exceptions 在 2.11+ 中也是 ControlThrowable 还是已经成为过去?
【解决方案2】:

我想扩展 Brian McCutchon 的回答。我相信有几个不同的(高度哲学的)方面值得考虑。

有人可能会争辩说,您可以直接从 Throwable 继承的原因是因为 Java 类型系统没有办法禁止这种扩展,并且 Throwables 层次结构并不是为了添加更多的类而设计的不是ErrorException 的子类。这可能就是为什么 NonLocalReturnExceptionBreakException 之类的东西最初是 RuntimeException 的子类型而不是直接 Throwable 的原因。

另外一点是,在引入这样的标记时,它必须是trait。同样,在 Scala 类型系统中,没有办法强制将混入此 trait 的类Exception 的子类。

这两个事实加在一起意味着ControlThrowable(然后是ControlException)的子类可能并且实际上在历史上是Exception的子类。牢记这一点,很明显,在这两种情况下捕获的类型集并不是设计上不相交的。是的,在introduction of ControlException 之后几乎一年后,它是changed to be ControlThrowable,但那时没有办法强制所有其他(自定义)子类进行相同的切换。

关于此示例中的此类代码的另一个哲学观点是,即使程序中没有ControlThrowable 的子类型是Exception 的子类型,并且第一种情况确实不会影响第二种情况(今天很可能是这种情况),它仍然表明开发人员已经考虑过这个特定的细节。很明显,代码应该为其他人编写,就像为计算机编写一样。

最后一点,今天您可能应该使用NonFatal,其中包括对ControlThrowable 的测试。

【讨论】:

  • 有没有我可以混入ControlThrowable的场景?我认为这个 trait 纯粹是供 Scala 内部使用的。
  • @lfk,仅供内部使用,但没有办法禁止您使用。我可以想象一些场景,高级(和基于宏的)库可能希望拥有一些非本地控制流设施并使用类似的技术。此类库的一个领域可能是某种异步支持,例如scala-async(AFAIK 它不使用ControlThrowable,但我可以想象类似的东西可能会使用它)另一个类似的领域是某种协作并发,也就是 co-例程支持。
【解决方案3】:

代码原写为ControlThrowable's source code comment

就像您提到的那样,您可以删除该行,除非您有可能产生扩展 ControlThrowable 的自定义异常,但此代码旨在告诉您不要捕获和抑制 ControlThrowable。 如果代码是

try {
  // Body might throw arbitrarily
} catch {
  case c: ControlThrowable => throw c // propagate
  case t: Throwable        => log(t)  // log and suppress
}

那么,可能更容易理解代码想要表达的意思。

仅供参考,scala 2.12 有一个错误,无法抑制 ControlThrowable

import scala.util.control.Breaks
import scala.util.control.ControlThrowable
val b = new Breaks

b.breakable {
  try {
    for (num <- 1 to 10) {
      num match {
        case 5 => throw new RuntimeException("5")
        case 6 => b.break
        case x => println(x)
      }
    }
  } catch {
     case c: Throwable => println(c)
  }
}

此代码应打印最多 10 个数字,因为 b.break 产生 ControlThrowable 并在 b.breakable 得到它之前被抑制。但是由于这个错误,它最多打印 5 个。

因此,使用 scala 2.12,无论如何都会传播 ControlThrowable。它不需要case c: ControlThrowable =&gt; throw c // propagate 行。但出于迁移目的,你不应该这样写。

其实这个bug会在2.13.x中修复https://github.com/scala/scala/pull/7413

【讨论】:

  • 是的,在Throwable 之前捕获ControlThrowable 对我来说非常有意义,事实上我的代码中就有。
猜你喜欢
  • 2010-10-31
  • 1970-01-01
  • 2011-06-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-20
  • 2014-04-17
相关资源
最近更新 更多