Правильная обработка исключений

Рейтинг: 1Ответов: 1Опубликовано: 15.03.2011

День добрый!

Подскажите, как наиболее оптимально и логично обрабатывать исключения в Java? Понятное дело, что каждый делает по–своему, но хотелось бы услышать советы опытных программистов.

Суть состоит в том, чтобы сделать такую систему сообщений для пользователя, чтобы иметь возможность классифицировать их по меньшей мере на три группы: «критические ошибки» - при возникновении которых работа программы должна быть прервана; «ошибки» - завершение работы блока программы (например, цикла); «предупреждение» - обычное информирование пользователя.

Собственно, исходя из сказанного, понятно, что обрабатываться эти ошибки должны на разных уровнях программы. Как сие организовать?

UPD1: Не сочтите глупцом, поэтому я и задал сей вопрос. Приложение было консольным т.к. ориентировано на работу на сервере. Делал я следующим образом:

  1. Создал класс, который умеет выводить сообщения в соответствии с заданным при старте уровнем логирования (всего 8 уровней, 256 комбинаций, бла-бла-бла)
  2. Создал класс для обработки исключительных ситуаций, который передавал текст и уровень в (1)
  3. В процессе работы программы (5 шагов обработки) подготавливались и обрабатывались данные, соответственно функция main(...) занималась исключительно последовательным запуском функций и передачей им параметров. В данной функции обрабатывались критические ситуации, выводилось сообщение (2) с полным stack-trace и программа завершалась (например, невозможность чтения файла, без которого дальше продолжать обработку невозможно).
  4. Также было создано несколько классов которые отдельно обрабатывали данные, в каждом из них могли произойти исключительные ситуации, но уже двух типов:

4.1. Ошибки (т.е. ситуация, когда выполнение какой-то функции должно быть прервано), например извлечение из файла ID некоторого множества. Т.е. продолжать обработку следующих множеств можно. Пример:

public void startPreparations(LinkedList<MyDataEntity> dataEntityList) {
    for(int i=0; i<dataEntityList.size(); i++) {
       MyDataEntity dataEntity = dataEntityList.get(i);
       try {
           int id = getId(dataEntity);
       } catch (MyDataEntityException mdee) {
          MyExceptionHandler(mdee, LogLevel.ERROR);
          continue;
       }
       ...
    }
}
private int getId(MyDataEntity dataEntity) throws MyDataEntityException {
    //... throw (new ...) где-то тут
}

Это лишь пример.

4.2. Предупреждения. В них похожая ситуация как и в (4.1) только предупреждения обрабатываются локально (например, в функции getId()) или без прерывания цикла (continue).

В общем как-то так. Скажите, насколько это решение было благоразумным?

Ответы

▲ 2Принят

В зависимости от ситуации и типа приложения.

По мне так в GUI-приложениях лучше всего сделать какой-нибудь ErrorNotificationManager, которому следует сообщать ошибку по мере отлова, а уж он пусть принимает решение, как отображать её. В момент передачи менеджеру можно и передавать аналог лог-левела, чтобы намекнуть ему о критичности ошибки в данном конкретном контексте.

Если же речь идёт о логгировании, то просто логгируйте с нужным логлевелом и получите то что надо.

UPD1

Я думаю, что нельзя дать общую рекомендацию о гранулярности try-catch на все случаи жизни. Всегда приходится искать разумный баланс. В некоторых случаях разумно передавать ошибку в виде колл-бэка. Как, например, это сделано в GWT AsyncCallback. В таких случаях, вызывающий точно знает насколько ошибка фатальна.

UPD2

Что вы, я мало кого считаю глупым :)

Итак, у нас имеется три вида ошибок: критические, просто ошибки и ошибки уровня warning.

О критических ошибках. В случае подобных ошибок можно заставить handler всё завершать (можно даже жёстко через System.exit). С другой стороны, можно протащить такую ошибку сквозь все уровни или сделать её от RuntimeException, чтобы не пришлось всюду протягивать её через throws. А ловить тогда прямо в корне. Если нет потоков, то на мой вкус второй вариант лучше. Если есть, то можно скомбинировать оба подхода (только без System.exit)

Просто ошибки и ворнинги. Если ворнинг не связан с необходимостью прервать некую деятельность, то можно и вовсе не делать исключения (думаю, это и так понятно). В остальных случаях ваш вариант вполне пригоден, хотя на мой вкус можно было бы оформить обработку поаккуратнее

Рассматривая ваш пример, мне кажется, его можно сделать красивее и тогда сомнения по поводу такого подхода могут быть развеяны:

public void startPreparations(List<MyDataEntity> dataEntityList) throws MyFatalPreparationException {
    for(MyDataEntity dataEntity : dataEntityList) {
       try {
           processId(getId(dataEntity));
       } catch (MyDataEntityException mdee) {
          MyExceptionHandler(mdee, LogLevel.ERROR);
       }
    }
}

private void processId(MyDataEntity dataEntity) throws MyFatalPreparationException {
    // ...
}
private int getId(MyDataEntity dataEntity) throws MyDataEntityException {
    //... throw (new ...) где-то тут
}