Obsługa wyjątku IOException zgłaszanego przez wiele metod w klasie

0

Witajcie!
Mam sobie taką klasę i jedno pytanie względem obsługi wyjątków.

public class Connector {

    private final static Logger logger = Logger.getLogger(Connector.class);

    private RemoteUser remoteUser;
    private Connection connection;
    private Session session;
    private boolean isAbleToExecuteCommands = false;

    public Connector(RemoteUser remoteUser) {
        logger.info("Initializing");
        this.remoteUser = remoteUser;
    }

    public void connectToRaspberryPi() throws IOException {
        createConnection();
        tryToConnect();
        tryToAuthenticate();
        createSession();
        isAbleToExecuteCommands = true;
    }

    private void createConnection() {
        logger.info("Creating connection");
        connection = new Connection(remoteUser.getRemoteHostAddress());
    }

    private void tryToConnect() throws IOException {
        logger.info("Trying to connect to device");
        connection.connect();
    }

    private void tryToAuthenticate() throws IOException {
        logger.info("Trying to authenticate...");
        if (!authenticate()) {
            throw new IllegalArgumentException("The password is incorrect!");
        }
    }

    private boolean authenticate() throws IOException {
        return connection.authenticateWithPassword(
                remoteUser.getRemoteUserName(),
                remoteUser.getPassword());
    }

    private void createSession() throws IOException {
        logger.info("Creating session");
        session = connection.openSession();
    }

    public void executeCommand(String command) throws IOException {
        if (isAbleToExecuteCommands) {
            logger.info("Executing command:");
            logger.info(command);
            session.execCommand(command);
            showCommandExecutionResults();
        } else {
            throw new IllegalStateException("The connection and session haven't been initialized!");
        }
    }

    private void showCommandExecutionResults() throws IOException {
        logger.info("Reading output...");
        InputStream stdout = new StreamGobbler(session.getStdout());
        BufferedReader br = new BufferedReader(new InputStreamReader(stdout));

        String line = "";
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
        logger.info("End of output");
    }

    public void closeSessionAndConnection() {
        try {
            logger.info("Closing session");
            session.close();
            logger.info("Closing connection");
            connection.close();
        } catch (NullPointerException ex) {
            throw new IllegalStateException("Connection or session hasn't been initialized!");
        }

        logger.info("Closed successfully");
    }

    protected Connection getConnection() {
        return connection;
    }
}

Interesuje mnie głównie metoda:

    public void connectToRaspberryPi() throws IOException {
        createConnection();
        tryToConnect();
        tryToAuthenticate();
        createSession();
        isAbleToExecuteCommands = true;
    }

Uznałem, że skoro 3 zawarte w niej metody mogą rzucić wyjątek, to wrzucę je do jednej, która również doda do swojej sygnatury throws IOException. Zacząłem się jednak zastanawiać czy takie rozwiązanie jest dobre. Pytanie brzmi jak to logicznie ogarnąć i sprawić, żeby user wiedział, gdzie program się wysypuje?
Czy każda z metod powinna obsługiwać wyjątek IOException i rzucać inny, np. IllegalStateException z konkretną wiadomością o błędzie?
Czy metoda connectToRaspberryPi() powinna obsługiwać wszystkie metody w jednym bloku try ... catch?
Z góry dzięki za pomoc.

0

Dla usera connectToRaspberryPi() jest jedną transakcją, która albo się powiedzie albo nie, to że składa się z kilku podtransakcji jest szczegółem implementacyjnym. W tym momencie w przypadku błędu dostanie generyczne IOException - w stacktrace będzie dokładna informacja o tym co i gdzie poszło nie tak, niezależnie od tego która z trzech prywatnych metod zawiodła. Jeżeli Twoja metoda connectToRaspberryPi() rzuca checked exception takie jak IOException to spodziewasz się, że user w jakiś sposób zareaguje na błąd i będzie chciał mu zaradzić. Przy błędzie łączenia się gdzieś takim krokiem może być reconnect robiony kilka razy co jakiś czas. Problem w tym, że user może robić reconnect w przypadku gdy np. tryToAuthenticate() odbija z powodu złego hasła i wiadomo że nigdy się ta operacja nie powiedzie. Dlatego możesz się pokusić o rozszerzenie IOException i stworzenie własnych typów takich jak RaspberryPiConnectException i RaspberryPiAuthException gdzie pierwsze rzucane byłoby w przyapdku gdy tryToConnect() lub createSession() zawiedzie - wtedy user może reagować reconnectem, a drugie sygnalizowałoby że nastąpił problem z uwierzytelnianiem.

BTW:

        try {
            logger.info("Closing session");
            session.close();
            logger.info("Closing connection");
            connection.close();
        } catch (NullPointerException ex) {
            throw new IllegalStateException("Connection or session hasn't been initialized!");
        }

Można zastąpić:

            logger.info("Closing session");
            Optional.ofNullable(session).ifPresent(Session::close);
            logger.info("Closing connection");
            Optional.ofNullable(connection).ifPresent(Connection::close);

Łapanie NPE to kiepska praktyka

0

Pytanie podstawowe, to czy chcesz by użytkownik wiedział co poszło nie tak, czy raczej wolisz żeby wiedział tylko, że "coś poszło nie tak"?
Wiedza, że "coś poszło nie tak" pozwala na zajrzenie do loga i obejrzenia stack trace, które daje informację czysto techniczną i pozwalającą na interpretację przez człowieka.

Posiadanie hierarchii wyjątków biznesowych pozwala na precyzyjne ich generowanie, wyłapywanie i reagowanie w sposób automatyczny, np. prezentowanie użytkownikowi końcowemu przyjaznego komunikatu zamiast IllegalStateExcepion.

Sam powinieneś określić co będzie lepsze w Twoim przypadku, czy wyjątki czysto techniczne, czy może mnie lub bardziej złożona hierarchia wyjątków biznesowych.

0

@yarel: Szczerze mówiąc nie powiedziałeś nic :P
User ma wiedzieć co poszło nie tak. Trochę tak jak wyświetla Ci się w Mavenie.
Build failure bo...

Dla mniej zapoznanych userów i tak mam zamiar zrobić GUI.

Posiadanie hierarchii wyjątków biznesowych

Czyli jakich? Co to według Ciebie jest wyjątek biznesowy?
Stawiam, że wyjątek z własną nazwą, biznesowy jest wyrażeniem mylącym, bo z biznesem to nie ma nic wspólnego.
Stworzyłem własne wyjątki, będą łatwiejsze w interpretacji niż np. IllegalStateException z wiadomością (która może być niewystarczająco dokładna).
W każdym razie co radzisz konkretnie?

2
  1. najbardziej po javowemu jest opakować wyjątek we własny wyjątek i wyrzucić ten własny wyjątek, zwany dla niepoznaki wyjątkiem biznesowym.
  2. można podejść do tego trochę podobnie jak w 1, ale zamiast własnego wyjątku opakować IOE w RuntimeException i dorobić sobie jakiś mechanizm wyłapujący tego typu wyjątki na wysokim poziomie. Takie coś zazwyczaj dostarczają frameworki.
  3. zamiast wyrzucać wyjątek zwrócić Either w odpowiedniej projekcji. Ma to, tą zaletę, że można sobie wtedy całość obudować w strumień i mieć z głowy. Oczywiście w standardowej bibliotece nie ma Either, za to można użyć np. vavr.io i doposażyć nasz kod w pattern matching z tej samej biblioteki.
1
Burdzi0 napisał(a):

@yarel: Szczerze mówiąc nie powiedziałeś nic :P
User ma wiedzieć co poszło nie tak. Trochę tak jak wyświetla Ci się w Mavenie.
Build failure bo...

Maven akurat często wpuszcza w maliny i błąd wskazywany nie zawsze mówi co poszło nie tak ;)

Dla mniej zapoznanych userów i tak mam zamiar zrobić GUI.
I jak to GUI ma reagować na wyjątki. Leci IOException i co dalej?

Posiadanie hierarchii wyjątków biznesowych

Czyli jakich? Co to według Ciebie jest wyjątek biznesowy?
Stawiam, że wyjątek z własną nazwą, biznesowy jest wyrażeniem mylącym, bo z biznesem to nie ma nic wspólnego.

Wyjątek biznesowy = dotyczący domeny/biznesu, który jest kontekstem działania Twojego programu. Jeśli jest to gra w szachy to wyjątek biznesowy to np. "Niedozwolony ruch", a nie ArrayIndexOutOfBounds, zaś dla analizatora ruchu sieciowego to może być "Niepoprawny nagłówek pakietu", a nie NullPointerException.

Stworzyłem własne wyjątki, będą łatwiejsze w interpretacji niż np. IllegalStateException z wiadomością (która może być niewystarczająco dokładna).
W każdym razie co radzisz konkretnie?

A jaki masz konkretny problem? Skąd mam wiedzieć jak na te wyjątki ma reagować klient? Chyba Ty powinieneś to określić. :)

Jeśli chodzi o preferencje, to ja wolę mieć hierarchię wyjątków:

  • wszystkie wyjątki dziedziczą z klasy bazowej np. MojBazowyException, np.
    Exception<-MojbazowyException<-ValidationException<-InvalidFormat
    Exception<-MojbazowyException<-ValidationException<-InvalidRelationshiop
    Exception<-MojbazowyException<-SecurityException

To z myślą o tym, żeby:

  • użytkownik miał możliwość zareagowania na wyjątki w elastyczny sposób
    bez konieczności parsowania tekstu
  • była możliwość generowania komunikatów w różnych językach

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