异常应该是case类吗?

48

我的自定义异常类型应该是 case class 吗?

优点是我可以使用抽取器。

缺点是我会得到不正确的相等语义,但我可以通过覆盖 equals 来避免这种情况。

因此,在概念层面上,将它们设置为 case class 是否有意义呢?


我正在寻找的是一个宏,它只创建一个伴生对象,包括apply和unapply方法,但没有其他内容,尤其是没有继承限制。这在其他地方也非常有用。 - Jürgen Strobel
3个回答

39

当然,这是非常主观的,但在我的看法中,将异常类作为case类是很好的实践。 主要原因是当你捕获一个异常时,你正在进行模式匹配,而case类在模式匹配中使用起来更加方便。 以下是一个例子,利用了在使用case类异常时,在catch块中可以使用完整的模式匹配能力:

object IOErrorType extends Enumeration {
  val FileNotFound, DeviceError, LockedFile = Value
}
case class IOError(message: String, errorType: IOErrorType.Value) extends  Exception(message)

def doSomeIO() { throw IOError("Oops, file not found!", IOErrorType.FileNotFound)  }

try {
  doSomeIO()
} catch {
  case IOError( msg, IOErrorType.FileNotFound ) =>
    println("File not found, please check the path! (" + msg + ")")
}

在这个例子中,我们只有一个异常,但它包含一个errorType字段,用于当您想要知道发生的确切错误类型时使用(通常这是通过异常的层次结构进行建模的,我并不是说这种方式更好还是更糟,这个例子只是为了说明问题)。因为IOError是一个case类,所以我可以简单地使用case IOError(msg, IOErrorType.FileNotFound)仅捕获错误类型为IOErrorType.FileNotFound的异常。如果没有与case类一起免费获取的提取器,我将不得不每次捕获异常,然后在实际上不感兴趣的情况下重新抛出,这肯定更加冗长。

你说case类会给你带来不正确的相等语义,我不这么认为。作为异常类的编写者,可以决定哪些相等语义是有意义的。毕竟,当您捕获异常时,catch块是您通常基于类型而决定捕获哪些异常的地方,但也可以基于其字段的值或其他任何东西进行捕获,就像我的示例一样。关键在于异常类的相等语义与此关系很小。


21

通过为例外情况创建子类继承层次结构来指示错误条件的更大具体性是一种常见的习语,但如果使用case类创建例外情况,就会失去这个习惯用法。因为case类无法被继承。


3
好的观点。但是如果需要,您仍然可以创建抽象异常类型(特征或抽象类)的层次结构,并使所有叶子类型成为case类。不过,这要求您完全“拥有”整个层次结构,换句话说,客户端代码不应扩展您的异常类。如果不是这种情况,则使用case类来表示异常可能确实不是最佳选择。 - Régis Jean-Gilles
2
虽然你可以定义一个通用的特征。 - 0__
4
案例类可以被子类化。唯一的限制是子类本身不能是案例类。 - Mushtaq Ahmed
3
是的,但是这样会导致一些不一致的层级关系,其中一些异常情况可以使用提取器捕获,而其他位于较低层次结构中的异常情况必须通过类型捕获。最好像0__建议的那样引入共同的特征。但前提是值得这么做,因为如果您真的拥有一个 开放的非平整的 异常层次结构,您可能会更喜欢避免使用 case classes (鉴于在这里从 case classes 中获得的收益并不重要,所以这是一个有效的问题,人们在不同的情况下会做出不同的选择)。 - Régis Jean-Gilles
1
与普通的类一样,您可以创建一些标记特征,并对它们进行继承。 - senz

5
我喜欢Régis Jean-Gilles的回答。但是,如果您有不创建案例类的好理由(请参见Dave Griffith的回答),则可以使用普通类和unapply实现与上面示例相同的效果:
object IOErrorType extends Enumeration {
  val FileNotFound, DeviceError, LockedFile = Value
}
object IOError {
  def unapply(err: IOError): Option[(String, IOErrorType.Value)] = Some(err.message, err.errorType)
}
class IOError(val message: String, val errorType: IOErrorType.Value) extends  Exception(message)

def doSomeIO() { throw new IOError("Oops, file not found!", IOErrorType.FileNotFound) }

try {
  doSomeIO()
} catch {
  case IOError( msg, IOErrorType.FileNotFound ) =>
    println("File not found, please check the path! (" + msg + ")")
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接