2014-08-31 19 views
6

Czy interfejs API Scala powinien generować wyjątki lub zwracać wartość Try? Czy istnieje oficjalna wytyczna w tej sprawie?Scala: Błąd rzutu vs Powrót Spróbuj?

def doSomethingMayThrow(): A = ??? 
def doSomethingWontThrow(): Try[A] = ??? 
+0

Idiomatically, alias w 'functionnal' sposób, należy użyć ',' Future' Try' lub inne opakowanie typu sukces/błąd podobny do walidacji scalaz. – cchantep

Odpowiedz

11

Nigdy nie zgłaszaj wyjątków dla możliwych do naprawienia błędów.

Zwracanie odpowiedniej struktury danych reprezentującej możliwą awarię (Future, Try, Either itd.) Jest zawsze lepsze niż odrzucanie wyjątków w środowisku naturalnym. Poinformuje dzwoniącego o możliwości awarii i zmusi ich do zarządzania.

Wyjątki powinny być zgłaszane tylko w przypadku nieodwracalnych błędów, takich jak awaria sprzętu i podobne.

Zróbmy przykład:

def throwOnOdds(x: Int): Int = 
    if (x % 2 == 0) x/2 else throw new Exception("foo") 

val x = throwOnOdds(41) + 2 // compiles and explodes at runtime 

teraz, zróbmy to lepiej

def throwOnOdds(x: Int): Try[Int] = 
    if (x % 2 == 0) Success(x/2) else Failure(new Exception("foo")) 

val x = throwOnOdds(41) + 2 // doesn't compile 

Nie obsługi niepowodzenie prowadzi do błędu kompilacji, która jest o wiele lepiej niż w czasie wykonywania jednego. Oto jak się z nim obchodzić

throwOnOdds(41) match { 
    case Success(n) => // do something with n 
    case Failure(e) => // handle exception 
} 
+0

Z perspektywy czasu ta odpowiedź jest błędna. Po pierwsze, powinno to być 'Try [Int]' lub 'Albo [Exception, Int]' not' Try [Exception, Int] '. Po drugie, zostanie skompilowany i uruchomiony z powodzeniem i spowoduje tylko błąd kompilacji, jeśli spróbujesz użyć 'x' jako int. – clay

+1

@ clay wow! Teraz, kiedy to przeczytam, jest tyle złych rzeczy, masz absolutną rację. Poprawiłem odpowiedź, dzięki za złapanie! –

+0

"Nigdy nie wyrzucaj wyjątków dla możliwych do naprawienia błędów" to mocne stwierdzenie. Jednak nie mogę znaleźć przeciwnego argumentu. Jednak nie jest do przyjęcia, czy "zmuszanie rozmówcy do zarządzania wyjątkami" jest słuszne. Realistycznie wolę robić '.get' na' Try' w większości przypadków. Mówię, że nie wiem, jak sobie poradzić z wyjątkiem i podpisania, że ​​może to spowodować awarię aplikacji lub potencjalnie uszkodzone wyniki. Co jest w porządku, o ile uznasz "wyjątek" za "wyjątkową sytuację, która nie powinna wystąpić w normalnych warunkach". – Mike

2

Zobacz monadyczne typy danych. Używanie monadycznych typów danych jest o wiele bardziej wyraziste i wyraźne niż odrzucanie wyjątków i byłoby preferowanym sposobem deklaratywnego obsługiwania wszystkich przypadków bez niejasnych skutków ubocznych. http://en.wikipedia.org/wiki/Monad_(functional_programming)

Zaletą użycia niepowodzenia w stosunku do sukcesu oraz użycia mapy i płaskiej mapy do wyrażenia "szczęśliwej ścieżki" jest to, że wyjątki/awarie stają się wyraźne w łańcuchu.

Gdzie można sprawdzić, czy wywołanie funkcji Sometomet MayThrow może wywoływać efekt uboczny (np. Odrzucanie wyjątku), jest bardzo jasne, gdy używa się monadycznego typu danych.

Pomoże to spojrzeć na rzeczywistą sprawę. Użyję tego, ponieważ jest to coś, nad czym ostatnio pracowałem:

Rozważ monadyczną przyszłość w scenariuszu buforowania - jeśli pamięć podręczna zwraca wynik, możesz przetworzyć wynik. Jeśli pamięć podręczna nie zwróci wyniku, możemy przejść do rzeczywistej usługi, z której próbujemy buforować wyniki, i możemy wyrazić to bardzo wyraźnie, bez żadnych niejasnych domniemanych efektów ubocznych, takich jak wyjątek:

Tutaj odzyskać Jest jak flatMap w przypadku błędu (zwróć Future [Model] zamiast niepowodzenia). recovery jest jak mapa w przypadku błędu (zwróć model na przyszłość zamiast awarii). Następnie mapa bierze wszystko, co wynika z powstałego modelu i przetwarza go - wszystkie przypadki są jasno zdefiniowane w kodzie, dzięki czemu można uzyskać klarowność wszystkich przypadków i warunków w pojedynczym wyrażeniu.

(userCacheActor ? GetUserModel(id="abc")) 
    .recoverWith(userServiceActor ? GetUserModel(id="abc")) 
    .recover(new ErrorModel(errorCode=100, body="service error") 
    .map(x: Response => createHttpResponseFromModel(x)) 

def createHttpResponseFromModel(x: Model) => 
    x match { 
     case model: ErrorModel => ??? 
     case model: UserModel => ??? 
    } 

Znowu wszystko jest ekspresyjnie zanotowana - co zrobić z niepowodzenia trafienie pamięci podręcznej, co zrobić, jeśli usługa nie może odpowiedzieć w tym scenariuszu, co zrobić z modelu wynik na koniec całe przetwarzanie w każdym przypadku.

Często mówi się o płaskiej mapie jako "hydraulik" monady lub "hydraulika" szczęśliwej ścieżki. flatMap pozwala ci wziąć kolejną Monadę i ją zwrócić. Możesz więc "wypróbować" wiele scenariuszy i napisać szczęśliwy kod ścieżki, zbierając na końcu wszelkie błędy.

scala> Option("Something").flatMap(x => Option(x + " SomethingElse")) 
    .flatMap(x => None).getOrElse("encountered None somewhere") 
res1: String = encountered None somewhere 


scala> scala.util.Try("tried") 
    .flatMap(x => scala.util.Try(x + " triedSomethingElse")) 
    .flatMap(x => scala.util.Try{throw new Exception("Oops")}) 
    .getOrElse("encountered exception") 
res2: String = encountered exception