Czy według was różne message wyjątków z polimorficznych implementacji to zło?

1

Wszyscy lubimy polimorfizm, definiujemy sobie interfejs, i dostarczamy tylko implementacje. Jeśli implementacje są w zgodzie z interfejsem (zwracają te same typy danych, rzucają te same wyjątki, przyjmują te same argumenty, rządzą się podobnymi prawami) to wszystko jest okej.

Sam osobiście bardzo nie lubie kiedy jakaś implementacja deklaruje jakiś interfejs, a potem nie jest w nim w zgodzie - nie da się tego normalnie używać.

Stąd pytanie.

Oczywistym złem jest, że interfejs deklaruje wyjątki A, B i C jako możliwe do rzucenia, a implementacja rzuca wyjątek X. To zło. Ale czy jest okej, że różne implementacje rzucają te same wyjątki z różnymi message'ami?

Przykład

interface FileEncrypter {
  /**
   * @throws FileNotFoundExceptions;
   * @throws FileReadOnlyException;
   * @throws AccessDeniedException;
   */ 
  public void encryptFile(String filename, String key);
}

class RsaEncrypter implements FileEncrypter {
  public void encryptFile(String filename, String key) {
    if (fileInacessible(filename)) {
      throw new FileReadOnlyException("Failed to encrypt file using safe RSA method - file is not writtable");
    }
  }
}

class Rot13Encrypter implements FileEncrypter {
  public void encryptFile(String filename, String key) {
    if (fileInacessible(filename)) {
      throw new FileReadOnlyException("Failed to encrypt file using unsafe Rot13 method - the file is not writtable");
    }
  }
}

Zagwostka

Noi teraz tak, teoretycznie - message wyjątku jest dostępny dla tego kogo złapie, więc możnaby napisać kod zależny od niego, ergo zmiana message'a technicznie jest breaking change.

Ale przecież nikt normalny nie robi logiki na podstawie message'u wyjątka, więc zmiana jego nie powinna powodować problemów.

Jak myślicie?

6

Na OOP się nie znam, ale masz mojego approve na różne message

9

Brzmi jakby pytać, czy kiedy interfejs mówi, że metoda zwraca int to czy jest błędem, że jedna implementacja zwróciła 5, a druga 12. Bo przecież ktoś mógłby napisać kod polegający na tym, że zawsze jest 5.

Na mój chłopski rozum to kontraktem jest typ wyjątku, a nie treść message'a.

1

Tak. W tym wypadku oczywiście lepiej byłoby wyciągnąć tą logikę w taki sposób, żeby parsowanie było z stringa a nie z pliku przez co mamy jeden błąd mniej do obsłużenia. Z drugiej strony za bieżącym rozwiązaniem może stać lepsza wydajność. Description powinien być tylko dla człowieka, jeśli maszyna ma interpretować dany exception to powinien być on ustrukturyzowany np. poprzez pola w strukturze specyficznego błędu

4

Dla mnie jest to nie tylko OK, ale jest tez pożądane. Tutaj jest to trochę bez znaczenia, ale gdybyś np. wewnątrz tych metod np. raz sięgał do pliku, raz do jakiegoś URLa, to lepiej mieć jasno powiedziane i zalogowane, że "nie dało się zapisać takiego pliku" i "serwer zgłosił 404".

0

😕

Naprawdę, nie wiem, o co tu się rozchodzi, zwłaszcza, że:

Wszyscy lubimy polimorfizm

Konkretna implementacja FileEncrypter może sobie nawet stworzyć podklasę FileReadOnlyException i to by było dopiero polimorficzne.

Domyślam się, że lekkie zaniepokojenie wynika z tego, że w obu implementacjach jakoby powtarzamy kod generujący to message. Ale na to są sposoby - np konstruktor bezargumentowy.

Druga uwaga to zauważenie, że obie implementacje mają taką samą strukturę. Tak jakby encrypter miał dwie odpowiedzialności - sprawdzenie uprawnień i same szyfrowanie. Więc tu może warto zastosować template method, albo zwykła pomocniczą funkcję.

1

To jest źle zadane pytanie. Skoro mówisz, że interfejs deklaruje typy wyjątków, to treść wyjątku nie ma znaczenia i poleganie na niej jest złamaniem LSP. Twoim kontraktem jest typ wyjątku, a nie treść wiadomości.

Teraz pytanie, czy treść wyjątku powinna być w kontrakcie, czy nie. Dla mnie tu nie ma dużej różnicy.

1 użytkowników online, w tym zalogowanych: 0, gości: 1