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

Scala: лучший способ обработки исключений из библиотеки Java?

Скажем, я использую библиотеку Java из моего проекта Scala. Эта библиотека Java генерирует исключения по всему месту, но я не чувствую себя комфортно, просто позволяя им распространяться "в мире Scala", поскольку нет способа убедиться, какие исключения может использовать метод Scala (за исключением документирования их). Так что это код, который я обычно пишу:

def doesNotThrowExceptions(parameter: String): Either[Throwable, T] =
  catching(classOf[IOException], classOf[NoSuchAlgorithmException]) either {
    // Code calling the Java library
    // Code generating a value of type T
  }

Затем, как правило, я буду использовать Either.RightProjection.flatMap для цепных методов, возвращающих Either[Throwable, ...] или Either.RightProjection.map, чтобы смешивать методы, возвращающие Either[Throwable, ...] и другие методы. Или просто Either.fold сделать что-то со значением Throwable. Так или иначе, это все еще не кажется абсолютно правильным.

Это самый "идиоматический" способ справиться с исключениями Java в Scala? Разве нет лучшего способа?

4b9b3361

Ответ 1

Я не уверен, что есть один самый идиоматический способ справиться с исключениями Java, потому что есть по крайней мере четыре разных случая, которые они будут брошены:

  • Что-то, что ни вы, ни разработчик библиотеки не ожидали пойти не так, пошли не так.
  • То, что надеялся дизайнер библиотеки, не помогло, и вам нужно знать подробности.
  • То, что вы надеялись, не помогло бы, и вам не нужно знать подробности.
  • Иногда метод создает значение, а иногда нет, и он сообщает об этом, вызывая исключение.

Лучшие практики, возможно, различаются для каждого из этих случаев

1. Действительно исключительные исключения

Scala имеет полнофункциональную обработку исключений. Нет ничего плохого в том, чтобы позволить непредвиденному исключению распространяться невостребованным, пока вы не достигнете уровня, где вы можете что-то сделать. Упаковка любого возможного исключения в Either может тратить много времени. Просто документируйте то, что, как вы знаете, не обрабатываете, и используйте try/catch на соответствующем высоком уровне (например, метод saveEverything должен, вероятно, перейти в блок try/catch (или обернуть его содержимое в одном), потому что независимо от того, что было неправильно, если сэкономить все не удалось, вы, вероятно, захотите попытаться спасти ситуацию, а не просто умереть).

В частности, вы, вероятно, захотите обработать Error таким образом и только пакет Exception, а не все Throwable s, в Either.

2. Исключения, которые вам нужно знать о

Это тот случай, о котором вы говорите, и вы уже дали несколько хороших предложений о том, как с ними бороться. Как вы уже заметили, вы можете использовать catching для упаковки исключений в Either. Затем вы можете

а. Используйте сопоставление шаблонов, что позволит вам более подробно разделить ваш Either:

doesNotThrowExceptions("par").right.map(transformData) match {
  case Left(ioe: IOException) => /* ... */
  case Left(nsae: NoSuchAlgorithmException) => /* ... */
  case Right(x) => /* ... */
  case Left(e) => throw e  // Didn't expect this one...
}

б. Сконвертировать в Option после регистрации ошибки:

doesNotThrowExceptions("par").left.map{ e =>
  println("You're not going to like this, but something bad happened:")
  println(e)
  println("Let see if we can still make this work....")
}.right.toOption

с. Если исключения являются действительно важной частью вашего контроля потока, Either может быть недостаточно. Вместо этого вы можете определить свой собственный класс Either с более чем Left и Right. Или вы можете вложить левую часть Either:

try { Right(/* java code */) }
catch {
  case ioe: IOException => Left(Left(ioe))
  case nsae: NoSuchAlgorithmException => Left(Right(nsae))
}

д. Используйте Scalaz Validation, который очень похож на Either, но немного приспособлен к обработке исключений.

3. Исключения, когда вам нужно только знать, что что-то пошло не так, и

4. Исключения, сброшенные, чтобы указать no-return-value

Даже если это концептуально две разные категории, вы обрабатываете их одинаково:

catching(classOf[IOException], classOf[NoSuchAlgorithmException]) opt { ... }

чтобы получить Option назад. Тогда map, flatMap и т.д.

Ответ 2

Я всегда предпочитаю scalaz.Validation над scala.Either. Эти два являются алгебраически идентичными структурами, но первое функционально богаче (имеет более полезные методы и типы экземпляров класса).

Отметьте эту ссылку за отличную презентацию Криса Маршалла на scalaz.Validation и другие сладости Scalaz.

Чтобы поговорить с кодом, использующим классический механизм обработки исключений, я определил следующие методы обогащения и экземпляр класса типов:

implicit def catchW[A](underlying: Catch[A]) = new CatchW(underlying)

class CatchW[A](underlying: Catch[A]) {
  def validation(body: => A): Validation[Throwable, A] = {
    underlying.withApply(_.fail)(body.success)
  }

  def vnel(body: => A): ValidationNEL[Throwable, A] = {
    underlying.withApply(_.failNel)(body.success)
  }
}

implicit def catchMonoid[A] = new Monoid[Catch[A]] {
  val zero = noCatch
  def append(x: Catch[A], y: => Catch[A]) = x or y
}