Czy ponowne zgłoszenie wyjątku przecieka abstrakcją?

12

Mam metodę interfejsu, która stwierdza w dokumentacji, że wygeneruje określony typ wyjątku. Implementacja tej metody wykorzystuje wyjątek. Wychwytywany jest wyjątek wewnętrzny i zgłaszany jest wyjątek zgłoszony w umowie interfejsu. Oto mały przykład kodu, aby lepiej wyjaśnić. Jest napisany w PHP, ale jest dość prosty do naśladowania.

// in the interface

/**
 * @return This method returns a doohickey to use when you need to foo
 * @throws DoohickeyDisasterException
 */
public function getThatDoohickey();

// in the implementation

public function getThatDoohickey() {

    try {
        $SomethingInTheClass->doSomethingThatThrowsAnException();
    } catch (Exception $Exc) {
        throw new DoohickeyDisasterException('Message about doohickey failure');
    }

    // other code may return the doohickey

}

Używam tej metody, aby zapobiec wyciekaniu abstrakcji.

Moje pytania brzmią: czy przekazanie zgłoszonego wyjątku wewnętrznego jako poprzedniego wyjątku przeciekałoby abstrakcję? Jeśli nie, czy byłoby po prostu ponownie użyć komunikatu z poprzedniego wyjątku? Jeśli byłoby to nieszczelne, czy mógłbyś podać jakieś wskazówki, dlaczego tak myślisz?

Aby wyjaśnić, moje pytanie wymagałoby przejścia do następującego wiersza kodu

throw new DoohickeyDisasterException($Exc->getMessage(), null, $Exc);
Charles Sprayberry
źródło
Wyjątki powinny dotyczyć przyczyn, a nie tego, kto je wyrzuca . Jeśli twój kod może mieć a file_not_found, to powinien wyrzucić file_not_found_exception. Wyjątki nie powinny być specyficzne dla biblioteki.
Kaczka Mooing

Odpowiedzi:

11

Moje pytania brzmią: czy przekazanie zgłoszonego wyjątku wewnętrznego jako poprzedniego wyjątku przeciekałoby abstrakcję? Jeśli nie, czy byłoby po prostu ponownie użyć komunikatu z poprzedniego wyjątku?

Odpowiedź brzmi: „to zależy”.

W szczególności zależy to od wyjątku i jego znaczenia. W gruncie rzeczy przepisanie go oznacza, że ​​dodajesz go do swojego interfejsu. Czy zrobiłbyś to, gdybyś napisał kod, który sam nazywasz, czy to po to, by uniknąć zmiany nazwy wyjątku?

Jeśli twój wyjątek zawiera ślad aż do głównej przyczyny, w wywoływanym kodzie, który może wyciec dane poza abstrakcję - ale ogólnie uważam, że jest to dopuszczalne, ponieważ wyjątek jest obsługiwany przez osobę wywołującą (i nikogo to nie obchodzi, gdzie to jest pochodzi od) lub pokazywany użytkownikowi (i chcesz poznać przyczynę pierwotną debugowania).

Często jednak samo dopuszczenie wyjątku jest bardzo rozsądnym wyborem realizacji, a nie problemem abstrakcji. Zapytaj tylko „czy rzuciłbym to, gdyby nie kod, który dzwonię?”

Daniel Pittman
źródło
8

Ściśle mówiąc, jeśli wyjątek „wewnętrzny” ujawnia cokolwiek na temat wewnętrznego działania implementacji, to przeciekasz. Technicznie więc zawsze powinieneś łapać wszystko i zgłaszać własny typ wyjątku.

ALE.

Przeciw temu można wysunąć kilka ważnych argumentów . W szczególności:

  • Wyjątki są wyjątkowymi rzeczami ; już na wiele sposobów naruszają zasady „normalnego działania”, więc równie dobrze mogą naruszać enkapsulację i abstrakcję na poziomie interfejsu.
  • Korzyść z posiadania znaczącego śledzenia stosu i bezpośredniej informacji o błędzie często przewyższa poprawność OOP, pod warunkiem, że nie przeciekasz informacji wewnętrznych programu poza barierę interfejsu użytkownika (co byłoby ujawnieniem informacji).
  • Zawijanie każdego wyjątku wewnętrznego w odpowiedni typ wyjątku zewnętrznego może prowadzić do wielu niepotrzebnych kodów wzorcowych , co przeczy całemu celowi wyjątków (mianowicie implementacji obsługi błędów bez zanieczyszczania regularnego przepływu kodu wzorcową obsługą błędów).

To powiedziawszy, wychwytywanie i owijanie wyjątków często ma sens, choćby po to, aby dodać dodatkowe informacje do dzienników lub zapobiec wyciekaniu informacji wewnętrznych do użytkownika końcowego. Obsługa błędów jest nadal formą sztuki i trudno sprowadzić ją do kilku ogólnych zasad. Niektóre wyjątki powinny być propagowane, inne nie, a decyzja zależy również od kontekstu. Jeśli kodujesz interfejs użytkownika, nie chcesz przepuszczać niczego do użytkownika bez filtrowania; ale jeśli kodujesz bibliotekę, która implementuje jakiś protokół sieciowy, prawdopodobnie należy zrobić pewne wyjątki (w końcu, jeśli sieć nie działa, niewiele możesz zrobić, aby poprawić informacje lub odzyskać i kontynuować) ; tłumaczenia NetworkDownExceptionna FoobarNetworkDownExceptionto tylko bezsensowne cruft).

tdammers
źródło
6

Po pierwsze, to, co tu robisz, nie oznacza ponownego wyjątku. Ponowne sprawdzenie byłoby dosłownie rzucić ten sam wyjątek, taki jak ten:

try {
    $SomethingInTheClass->doSomethingThatThrowsAnException();
} catch (Exception $Exc) {
    throw $Exc;
}

Oczywiście jest to trochę bezcelowe. Rethrowing ma miejsce w sytuacjach, w których chcesz zwolnić część zasobów lub zarejestrować wyjątek lub cokolwiek innego, co możesz zrobić, bez zatrzymywania wyjątku przed propagacją na stosie.

To, co już robisz, zastępuje wszystkie wyjątki, bez względu na przyczynę, wyjątkiem DoohickeyDisasterException. Który może, ale nie musi, być tym, co zamierzasz zrobić. Ale sugerowałbym, że za każdym razem, gdy to robisz, powinieneś również zrobić to, co sugerujesz i przekazać oryginalny wyjątek jako wyjątek wewnętrzny, na wypadek, gdyby był użyteczny w śledzeniu stosu.

Ale nie chodzi tu o nieszczelne abstrakcje, to kolejny problem.

Aby odpowiedzieć na pytanie, czy powtórnie zgłoszony wyjątek (a nawet nieprzechwycony wyjątek) może być nieszczelną abstrakcją, powiedziałbym, że zależy to od twojego kodu wywołującego. Jeśli ten kod wymaga wiedzy o abstrakcyjnym frameworku, to jest to nieszczelna abstrakcja, ponieważ jeśli zamienisz ten framework na inny, musisz zmienić kod poza swoją abstrakcję.

Na przykład, jeśli DoohickeyDisasterException jest klasą z frameworka, a Twój kod wywołujący wygląda tak, to masz nieszczelną abstrakcję:

try {
    $wrapper->getThatDoohickey();
} catch (DoohickeyDisasterException $Exc) {
    echo "The Doohickey is all wrong!";
    echo $Exc->getMessage();
    gotoAHappyPlaceAndSingHappySongs();
}

Jeśli zamienisz swój framework innej firmy na inny, który używa innej nazwy wyjątku, musisz znaleźć wszystkie te odwołania i je zmienić. Lepiej jest stworzyć własną klasę wyjątków i zawrzeć w niej wyjątek ramowy.

Z drugiej strony, jeśli wyjątek DoohickeyDisasterException jest twoją sprawą, to nie przeciekasz abstrakcji, powinieneś bardziej skupić się na mojej pierwszej kwestii.

pdr
źródło
2

Reguła, którą próbuję przejść, to: zawiń to, jeśli to spowodowałeś. Podobnie, jeśli błąd miałby zostać utworzony z powodu wyjątku środowiska wykonawczego, powinien to być jakiś DoHickeyException lub MyApplicationException.

Oznacza to, że jeśli przyczyną błędu jest coś zewnętrznego w stosunku do twojego kodu, a nie spodziewasz się, że to się nie powiedzie, wtedy z wdziękiem zawiedzie i wyrzuci go ponownie. Podobnie, jeśli jest to problem z twoim kodem, zawiń go w (potencjalnie) niestandardowym wyjątku.

Na przykład, jeśli plik jest oczekiwać , aby być na dysku, czy jest to usługa oczekiwać , aby być dostępne, a następnie obsługiwać go wdziękiem i ponownie rzucać błąd, tak aby konsument kodzie można dowiedzieć się, dlaczego ten zasób jest niedostępny. Jeśli utworzyłeś za dużo danych dla jakiegoś typu, wtedy twoim błędem jest zawijanie i wrzucanie.

Steven Evers
źródło