2015-03-18 2 views
4

Я новичок в Scala, немного запутавшись в различных способах обработки исключений и поиска лучших советов по этой теме. Я пишу простой метод для извлечения Клиента с использованием существующего блокирующего SDK. Возможные результаты:Выбор правильной обработки исключений в scala

  1. Клиент найден
  2. Клиент не найден (возвращается как NotFoundException из SDK)
  3. Ошибка во время разговора с удаленным сервером (SDK проливает другое исключение)

Так что я хочу, чтобы мой метод, чтобы иметь тип возвращаемого Future[Option[Customer]] и возврата для каждого случая выше:

  1. Успешное будущее: Некоторые из них (заказчик)
  2. Успешное будущее: Нет
  3. Failed Future

Вот что я написал с помощью TRY/поймать:

private def findCustomer(userId: Long): Future[Option[Customer]] = future { 
    try { 
    Some(gateway.find(userId)) 
    } catch { 
    case _: NotFoundException => None 
    } 
} 

Это работает отлично и кажется чистой, чтобы меня, но, похоже, это не действительно «способ Scala» - мне сказали избежать использования try/catch. Поэтому я искал способ переписать его, используя вместо этого Try.

Здесь 2 варианта, которые (я думаю) ведут себя точно так же, используя Try.

Вариант А:

private def findCustomer(userId: Long): Future[Option[Customer]] = future { 
    Try(
    Some(gateway.find(userId)) 
).recover { 
    case _: NotFoundException => None 
    }.get 
} 

Вариант B:

private def findCustomer(userId: Long): Future[Option[Customer]] = future { 
    Try(
    Some(gateway.find(userId)) 
).recover { 
    case _: NotFoundException => None 
    } 
} flatMap { 
    case Success(s) => Future.successful(s) 
    case Failure(f) => Future.failed(f) 
} 

Я не большой поклонник А (хотя это более кратким, чем В), так как .get кажется немного коварный. B определенно является наиболее явным, но отображение Try дел на соответствующие результаты Future кажется скучным.

Как опытный программист Scala написал бы это?

ответ

5

Я думаю, что ваша первоначальная версия с использованием try/catch отлично подходит, потому что это оболочка вокруг существующего SDK.

В качестве альтернативы, вы можете использовать метод recover на Future:

def findCustomer(userId: Long): Future[Option[Customer]] = 
    Future(Option(gateway.find(userId))).recover { 
    case e: NotFoundException => None 
    } 
+0

Красивая! Я думаю, что 'Future {...} recover {...}' именно то, что я искал, кратким, выразительным и идиоматичным. Благодаря! –

+1

Это хорошо, но вы должны изменить параметр Some => Option для защиты от Some (null). –

+0

@TaylorLeese, вы правы. Я использовал 'Some', потому что знал, что метод будет генерировать, когда пользователь не существует, но не имеет смысла использовать' Option'. –

2

Один из вариантов - избежать цепочки Try and Future. В каком-то смысле будущее - это асинхронный Try.

Вы можете использовать Future [Customer] напрямую и рассматривать NotFoundException как нечто, чтобы восстановить, а не значение None. Как правило, вы должны задействовать работу над будущим, без необходимости обрабатывать случаи сбоя (путем сопоставления его и т. Д.). Все зависит от того, как вы собираетесь его использовать (как ваша логика развивается, когда будущее завершено). Другими словами, возможно, это кажется запутанным для вас, потому что это так, и вы вынуждаете его форсировать возвращаемый тип Future [Option [Customer]].

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

1

Вы могли бы искать:

Future.fromTry(myTry) 

Так для примера вы можете сделать:

Future.fromTry { 
    Try(Some(gateway.find(userId))).recover { 
     case _: NotFoundException => None 
    } 
} 

Чтобы только поймать NotFoundException, как и в решении. Единственная проблема здесь в том, что метод не будет выполняться асинхронно. Если это необходимо, подумайте об использовании Future.recover, как предложено Ionut.

Другой вариант идиоматическое стоит упомянуть здесь использовать Either[F, S], где S является тип успешного возвращения и F может содержать ошибки (которые вы, возможно, захотите распространяться). Таким образом, вы можете использовать Either[ Exception, Option[String]] или Either[String, Option[String]], где первый String является сообщением об ошибке.

+1

Проблема с этим подходом (с использованием 'Future.fromTry') заключается в том, что вычисление выполняется не асинхронно. Созданное «Будущее» уже завершено. –

+1

Кроме того, соглашение для 'Либо [A, B]' должно состоять в том, чтобы 'A' означало сбой, а' B' - успех. Итак, 'или [F, S]'. –

+0

Тот факт, что есть * * аргумент об этом, заставляет меня думать, что я не должен использовать Либо. Я бы предпочел полагаться на встроенный язык (например, «Try», который уже позволяет вернуть либо «Успех», либо «Сбой»), чем на соглашении, в котором люди, похоже, не согласны :) Спасибо за указатель! –

0

Если вы готовы принять некоторые scalaz. дизъюнкция от скалаза очень полезна и естественна при обработке неслыханных сценариев. Это похоже на scala Either, но раскол развала \/ является правильным предвзятым. у вас будет значение успеха справа и исключение слева. wrapping ваш блок кода с \/.fromTryCatch возвращает исключение в левой части дизъюнкции. право всегда будет иметь значение успеха. отображение над дизъюнкцией упрощается, чем Scala. Либо, так как дизъюнкция правильная, и она легко дает вам значение справа.

import scalaz.\/ 

private def findCustomer(userId: Long): Future[\/[Throwable,Option[Customer]] ]= future { 
    \/.fromTryCatch { 
    Some(gateway.find(userId)) 

    } 
} 
Смежные вопросы