Подтвердить что ты не робот

Должны ли исключения быть классы классов?

Должны ли мои пользовательские типы исключений быть case class es?

В плюсе я получаю экстракторы.

На минусовой стороне я получаю неверную семантику равенства. Но я могу избежать этого, переопределив equals.

Итак, имеет смысл на концептуальном уровне сделать их case class es?

4b9b3361

Ответ 1

Это, конечно, очень субъективно, но, на мой взгляд, хорошая практика состоит в том, чтобы классы исключений были классами case. Основное обоснование заключается в том, что когда вы поймаете исключение, вы выполняете сопоставление с образцом, а классы case гораздо приятнее использовать при сопоставлении шаблонов. Здесь приведен пример, который использует преимущество использования полной мощности сопоставления шаблонов в блоке catch при использовании исключения класса case:

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 - это то, где вы решаете, какие исключения для catch обычно основываются только на типе, но могут быть основаны на значении его полей или что-то еще, как в моем примере. Дело в том, что семантика равенства класса исключения имеет мало общего с этим.

Ответ 2

Одной из распространенных идиом, которые вы теряете, делая классы исключений, является шаблон создания иерархии подкласса исключений с подклассом, используемый для указания большей специфичности условия ошибки. Классы классов нельзя подклассифицировать.

Ответ 3

Мне нравится ответ от Режиса Жана-Жиля. Но если у вас есть веские причины не создавать класс case (см. Ответ Дэйва Гриффита), вы можете архивировать то же, что и в примере выше, с нормальным классом и 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 + ")")
}