2014-06-02 17 views
14

Rzucanie wyjątków w czasie budowy jest ogólnie postrzegane jako mało grzeczne, ale nie jestem pewien, jak tego uniknąć, próbując wymusić ograniczenia na wartości klas przypadków .Jaki jest poprawny sposób wymuszania ograniczeń na wartości klas przypadków?

Powiedzmy na przykład, że muszę reprezentować zakres i że obie granice muszą być dodatnie. Natychmiastowa realizacja będzie:

case class Range(from: Int, to: Int) 

To jednak nie upewnić się, że zarówno from i to są pozytywne, ani że to jest większa niż from.

Moim pierwszym odruchem jest wdrożenie go w następujący sposób:

case class Range(from: Int, to: Int) { 
    require(from >= 0) 
    require(to >= 0) 
    require(to >= from) 
} 

To jednak sprawia, że ​​konstruktor Range „s niebezpieczne.

Czy istnieje wspólny wzorzec zachowania łatwości użycia klas przypadków, wymuszania ograniczeń wartości i unikania wyjątków wyrzucania?

+0

"wymuszać ograniczenia wartości i unikać wyrzucania wyjątków?" - co rozumiesz przez egzekwowanie?Co chcesz zrobić, gdy ktoś spróbuje skonstruować Range z nieprawidłową wartością from/to? –

+0

Dzięki Paul, to jest sedno mojego pytania. Co powinienem zrobić? Próbuję zapobiec tworzeniu nieprawidłowych instancji i wydaje się, że istnieje zgoda co do tego, że konstruktorzy lub metody konstrukcyjne nie powinni rzucać. Nie wiem, jak to zrobić bez drugiego, czy istnieje rozwiązanie, którego nie widzę? –

+0

Idealną odpowiedzią jest programowanie na poziomie typu (ale stroma krzywa uczenia się). Powinien to być błąd podczas kompilacji, aby coś takiego stworzyć. Jednak bez tego wydaje się, że masz cztery możliwości: zignoruj ​​problem (zaakceptuj nieprawidłowe parametry), wyrzuć wyjątek, wymuś parametry do prawidłowych zakresów, oznacz instancję jako niepoprawną z flagą (i wyrzuć wyjątki po wywołaniu dowolnej metody to?). Ja, rzuciłbym wyjątek w konstruktorze ... –

Odpowiedz

7

To dość subiektywne, ale spróbuję.

To zależy od tego, jak zbudowana jest twoja klasa. W przypadku prostej klasy zakresu użyteczności używanej zwyczajowo w wewnętrznym kodzie, każdy programista używający jej powinien prawdopodobnie być świadomy, że podawanie nieprawidłowych wartości zakresu może prowadzić do dziwnych wyników. Możesz założyć, że rozsądne użycie klas naprawdę nie napotyka tego problemu, a wyrzucenie wyjątku w tych rzadkich przypadkach może nie być złym zwyczajem, aby zasygnalizować komuś naprawdę spieprzone. Jeśli z kontekstu jest oczywiste, jakie wartości należy podawać klasie, nie uważam, że szczególnie źle jest rzucać wyjątki, jeśli ludzie nie spełniają oczekiwań wobec zdrowia psychicznego.

Porównaj to zachowanie z NullPointerException s lub ArithmethicException s, które czasami zdarzają się w Scali. Czasem po prostu nie ma sensu programować defensywnie, gdy spotyka się wystarczająco "szaleństwo".

Z drugiej strony, jeśli twoja klasa jest wypełniona wartościami, nad którymi masz mniej kontroli, tj. Są bezpośrednią konsekwencją wprowadzania danych przez użytkownika, musisz po prostu przejąć większą kontrolę nad swoją konstrukcją. Tworzenie obiektu towarzysz z funkcją, która zwraca Option lub Either:

object Range { 
    def createSafe(from: Int, to: Int): Option[Range] = { 
     if(from >= 0 && to >= 0 && to >= from) 
      Some(Range(from, to)) 
     else 
      None 
    } 
} 

Można wtedy nawet ponowne wykorzystanie logiki walidacji w konkretyzacji swojej klasie przypadku nadrzędnymi apply jako druga odpowiedź sugeruje.

object Range { 
    def createSafe ... 
    def apply(from: Int, to: Int): Range = { 
     createSafe(from, to).getOrElse(throw new IllegalArgumentException(...)) 
    } 
} 
+1

Proszę zauważyć, że prawdopodobnie istnieje nieskończona rekurencja w mojej odpowiedzi ('apply' wywołuje' createSafe', który wywołuje 'apply', który ...), ale dostaję mój dryf. – DCKing

3

Można przeciążać operatora w obiekcie towarzysz apply aby osiągnąć to, co chcesz zrobić:

object Range{ 
    def apply(from: Int, to: Int) ={ 
    val _from = Math.max(0, from) 
    val _to = Math.max(0, to) 
    if(_from < _to) new Range(_from, _to) 
    else new Range(_to, _from) 
    } 
} 

Należy pamiętać, że używanie to jako nazwy zmiennej może prowadzić do pewnych "interesujących" wyników.

+0

Chociaż moja sugestia, aby użyć programowania typu jest bardziej ogólna umiejętność, której warto się nauczyć, ta sugestia będzie o wiele łatwiejsza do wdrożenia w tym konkretnym przypadku. :) – joescii

+3

Po to, aby upewnić się, że rozumiem to poprawnie: uważa się, że bardziej pożądane jest zmuszanie nieprawidłowych danych wejściowych do ważniejszych, niż niepowodzenie? Na przykład 'Zakres (-1, -20)' powinien zwrócić 'Zakres (1, 20)', chociaż wiemy z absolutną pewnością, że nie jest to, czego żądał wywołujący (niezależnie od tego, czy to, co chciał, było możliwe, czy nie) ? –

+0

Przysłówek dziwnie zakładam, że to rozwiązanie jest poszukiwane. Podejdę również ścieżką "wymagać". Dlaczego powinienem zmieniać parametry? – Kigyo

Powiązane problemy