Pracowałem nad pewnym projektem w pracy (nie powiem nazwy firmy), i trafiłem na przypadek w którym co jakiś czas do naszej implementacji dochodzą nowe wyjątki. Na początku był jeden, potem dochodziły nowe, co jakiś czas, teraz z tego kawałka implementacji lecą 4 wyjątki. Są to różne case'y, ale domenowo zbliżone do siebie. Starałem się rozplanować architekturę, czy na pewno nie przesadzamy z tymi wyjątkami, próbowałem podejść do tego od strony Dependency Inversion, jak również Principle of Least Astonishment, Liskov Substitution, bo czułem że to że mamy 4 wyjątki tak podobne do siebie to coś podejrzanego; po kilku godzinach rozmyślań doszedłem do wniosku, że tak, te 4 różne wyjątki muszą być rozróżnione, dlatego że chcemy je obsługiwać w różnych sposób, ale też to że dodajemy co jakiś czas musi być ogarnięte w jakiś sposób, bo jeśli zachowamy liniowy trend to za 2 lata będzie ich 12 :D
Więc, wpadłem na kontrowersyjny pomysł. Wprowadziłem takie rozwiązanie w piątek, wyjaśniłem to mojemu zespołowi; na razie wprowadzamy to w jednym miejscu, zobaczymy jak pójdzie.
Pomysł to abstrakcyjny, polimorficzny wyjątek. Pomysł za nim jest mniej więcej taki sam jak przy Open/Closed principle, żebyśmy mogli dodawać nowe wyjątki bez potrzeby edycji miejsc w których są catche.
Wygląda to mniej więcej tak
abstract class DomainException extends RuntimeException {
public abstract void handle(Visitor visitor);
}
class DomainCaseFirst extends DomainException {
public void handle(Visitor visitor) {
visitor.handleFirst();
}
}
class DomainCaseSecond extends DomainException {
public void handle(Visitor visitor) {
visitor.handleFirst();
}
}
użycie implementacji wygląda tak
try {
domainLogic.act();
} catch (DomainException exception) {
exception.handle(this.visitor);
}
Z logiki biznesowej będziemy rzucać odpowiednie implementacje wyjątków. Zaleta tego rozwiązania jest taka, że można dodawać nowe implementacje do domeny biznesowej oraz nowe case'y handlowania, bez potrzeby edycji już istniejących użyć. Jeśli będziemy chcieli handlować wyjątki dokładniej, to możęmy zrobić catch'a na specyficzny case
try {
domainLogic.act();
} catch (DomainCaseSecond exception) {
// special handling
} catch (DomainException exception) {
// general handling
exception.handle(this.visitor);
}
Nie jestem pewien czy ten pomysł wypali - próbuję go pierwszy raz. Nie jestem pewien też czy ktoś na to już kiedyś wpadł (pewnie tak), ale nie znalazłem takich rzeczy - jeśli ktoś zna takie użycie już to proszę o linka. Nie wydaje mi się że pomysł z tym żeby używać wyjątków do kontroli przepływu był czymś dziwnym; polimorficzne wyjątki już prędzej. Na pewno umieszczenie logiki w wyjątkach to byłby bardzo dziwny pomysł, ale call do wizytora - chyba jest git? Nie jestem na 100% pewien tego podejścia.
Jeśli ktoś miałby pomysł na inne rozwiązanie tego problemu, to też chętnie usłyszę pomysły.
Może napiszę post za 6-12 miesięćy z update'em jak rozwiązanie się u nas sprawdza.