2013-04-24 8 views
3

Для обработки исключений в Scala, я предпочитаю избежать основной try/catch и выгоды от функционального мышления с Validation от Scalaz (по аналогии с Either типа в некоторых случаях).Лучший способ написания Scala методы подписи дело с исключениями

Мое приложение предоставляет некоторые услуги. Представьте себе этот метод (не имеющий никакого смысла, но полезного для концепции) в моем сервисе. Он связывает Buyer (покупатель) с его новым Car и возвращает Car, содержащие эту ассоциацию, если все правила прошло с успехом:

def create(carDTO: CarDTO, buyerDTO: BuyerDTO): Validation[Either[TechnicalFailure, List[CarCreationFailure]], CarDTO] 

Объяснение: Создание Car может привести к одному из двух типов исключений:

  • Технические сбои (при сбоях баз данных, например) упаковка Throwable исключений.
  • Бизнес-ошибки (правила пользовательского приложения, предотвращающие несоответствие, например, Car с незаконным статусом). CarCreationFailure является одним и, конечно же, может быть продлен более precised сбой.

Мой вопрос особенно акцентирован на стороне клиента и имеет дело с более чем одним потенциалом Business Failure.

Если тип возвращаемого Validation[Either[TechnicalFailure, List[CarCreationFailure]], CarDTO] заменяется менее громоздким: ValidationNel[Throwable, CarDTO]

Здесь следует отметить ValidationNel (накапливающиеся ошибки/исключения в NonEmptyList).

Недостатком было бы то, что новый читатель не мог, на первый взгляд, предположить, что этот метод возвращает либо TechnicalFailure, либо CarCreationFailure (подкласс BusinessFailure so); просто слишком пугающий Throwable.

Он будет вынужден применить шаблон, соответствующий каждому Throwable типам, содержащимся в моем приложении, чтобы быть уверенным, чтобы никто не забыл ... => messy.

Какой самый чистый способ среди этих решений, а может быть ... другой?

+2

Это больше похоже на вопрос «обзор кода», чем вопрос SO. В любом случае, почему бы не создать супертрайт для «TechnicalFailure» и «CarCreationFailures» (который бы обернул «List [CarCreationFailure]»)? Затем система вашего типа знает, что может вернуться, и вы можете сопоставить шаблон, чтобы иметь дело с случаями (и вы можете использовать все в 'Validation'). –

+1

Вы почти _certainly_ не хотите поймать все «Throwable», если вы не пишете что-то вроде контейнера сервлетов. Посмотрите на 'Error' половину' Throwable', спросите себя, действительно ли вы хотите (и можете) обрабатывать все из них. –

+1

Согласитесь с @RexKerr, этот вопрос довольно общий, и трудно дать конкретный ответ. Разумеется, я бы подумал о том, чтобы сменить свой первый тип результата на «ValidationNel [Либо [TechnicalFailure, CarCreationFailure], CarDTO]». –

ответ

2

В подобных случаях я лично использую пользовательский класс под названием Tri, который работает как как Try и Either:

sealed trait Tri [+Value, +Problem] { ... } 
final case class Good[+Value](v: Value) extends Tri[Value, Nothing] { ... } 
final case class Bad[+Problem](p: Problem) extends Tri[Nothing, Problem] { ... } 
final case class Ugly(th: Throwable) extends Tri[Nothing, Nothing] { ... } 

с соответствующими методами для обработки наиболее распространенных операций один хочет. В этом случае я бы просто вернуть клиенту

Tri[CarDTO, List[CarCreationFailure]] 

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

create(car, buyer) match { 
    case Good(dto) => // Yay 
    case Bad(xs) => // Handle list of car creation failures 
    case Ugly(th) => // Handle exceptions 
} 

Я не знаю ни подобной функциональности в Scalaz7, хотя вы можете создать куски более легко, чем в Scalazless Scala, так что вы могли бы быть менее мотивированы (как у вас есть, но почему бы не использовать \/?) для создания такого рода трехстороннего класса обработки ошибок.

+0

На самом деле, с помощью этого решения вы моделируете концепцию «Либо», ориентированную на 3 элемента, а не на основные «правые» и «налево» => Почему не :). Кроме того, что означает «\ /», которое вы предложили? Спасибо :) – Mik378

+0

Хорошо, я только что посмотрел исходный код, '\ /' действует как «дизъюнкция». – Mik378

+0

Наконец, я предпочитаю следовать вашему решению в комментарии: wrapping 'TechnicalFailure' и' CreationalFailures' в родительский ' чертой »и по-прежнему пользуется« Validation »Scalaz7. Можете ли вы предоставить небольшой образец со знаменитым' \/', чтобы, может быть, улучшить мое решение? Большое спасибо, кстати. – Mik378

1

Мы используем нечто похожее (более простое и менее элегантное) для использования Рексом Керром.

Все бизнес-исключения/ошибки завернуты в пользовательский BusinessException, все остальные ошибки бросают исключения различного рода.

Мы используем основной Try и сервлет будет иметь код похожий (очень упрощенный)

(req getParameter "action" match { 

     case null   ⇒ Failure(new BusinessException("Missing required parameter 'action'")) 
     case "doThis" ⇒ doThis() 
     case "doThat" ⇒ doThat(req) 
     case x   ⇒ Failure(new BusinessException("Uknown Action '" + x + "'")) 
    }) match { 

     case Success(someResponse) ⇒ //send 200 with some response 
     case Failure(t) if t.isInstanceOf[BusinessException] => //send 400 with exception message 
     case Failure(t)  ⇒ //send 500 with exception message 
    } 

Такие методы, как doThis имеют подписи типа

def doThis(): Try[ Option[ String ] ] = Try { 
    //do stuff 
} 

Преимущество этого простого механизма является что легко переносить существующие исключения кода Java, которые являются нашим прецедентом.