Scala: лучший способ обработать исключения из библиотеки Java? - PullRequest
23 голосов
/ 15 февраля 2012

Допустим, я использую библиотеку 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? Разве нет лучшего способа?

Ответы [ 2 ]

23 голосов
/ 15 февраля 2012

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

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

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

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

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

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

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

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

a.Используйте сопоставление с образцом, которое позволит вам глубже отделить 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...
}

b.Преобразуйте в Option после регистрации ошибки:

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

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

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

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

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

4.Исключения, выдаваемые для указания значения без возврата

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

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

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

6 голосов
/ 15 февраля 2012

Я всегда предпочитаю 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
}
...