Представьте себе, что у вас есть метод, который выполняет какую-то операцию, которая потенциально может дать сбой, например, получение веб-страницы.
def getUrl(url: String): NodeSeq = {
val page = // ...
// ...
if (failure) throw new PageNotFoundException()
page
}
Если вы хотите использовать его, вам нужно сделать
val node = try {
getUrl(someUrl)
} catch {
case PageNotFoundException => NodeSeq.Empty
}
или аналогичный в зависимости от ситуации. Тем не менее, это выглядит несколько хорошо, чтобы сделать это. Но теперь представьте, что вы хотите сделать это для набора URL-адресов.
val urls = Seq(url1, ...)
val nodeseqs: Seq[NodeSeq] = try {
urls.map(getUrl)
} catch {
case PageNotFoundException => Seq.Empty
}
Хорошо, так что это возвращает пустую последовательность, когда одна из страниц не может быть загружена. Что если мы хотим получить как можно больше?
val urls = Seq(url1, ...)
val nodeseqs: Seq[NodeSeq] = urls.map { url =>
try {
getUrl(url)
} catch {
case PageNotFoundException => NodeSeq.Empty
}
}
Теперь наша логика перемешана с кодом обработки ошибок.
Сравните это со следующим:
def getUrl(url: String): Box[NodeSeq] = {
val page = // ...
// ...
if (failure) Failure("Not found")
else Full(page)
}
val urls = Seq(url1, ...)
val nodeseqs: Seq[Box[NodeSeq]] = urls.map(getUrl(url)).filter(_.isDefined)
// or even
val trueNodeseqs: Seq[NodeSeq] = urls.map(getUrl(url)).flatten
Использование Option
или Box
(или Either
или scalaz ’Validation
) дает вам гораздо больше возможностей для принятия решения о том, когда решать проблему, чем когда бы вы ни создавали исключения.
За исключением, вы можете только пройти через стек и поймать его как некоторую точку там. Если вы кодируете ошибку внутри типа, вы можете носить ее с собой столько времени, сколько пожелаете, и иметь дело с ней в ситуации, которую вы считаете наиболее подходящей.