Стиль Java: правильная обработка исключений - PullRequest
17 голосов
/ 08 января 2009

Я концептуально зацикливаюсь на выборе структуры обработки исключений для моего проекта.

Предположим, у вас есть, например:

public abstract class Data {
   public abstract String read();
}

И два подкласса FileData, которые считывают ваши данные из некоторого указанного файла, и StaticData, который просто возвращает некоторые предварительно определенные постоянные данные.

Теперь, после чтения файла, IOException может быть сгенерировано в FileData, но StaticData никогда не сгенерирует. Большинство руководств по стилю рекомендуют распространять Исключение вверх по стеку вызовов, пока не будет доступно достаточное количество контекста, чтобы эффективно с ним справиться.

Но я не хочу добавлять предложение throws в абстрактный метод read (). Зачем? Поскольку данные и их сложный механизм ничего не знают о файлах, они просто знают о данных. Более того, могут существовать другие подклассы данных (и даже больше), которые никогда не генерируют исключения и доставляют данные без ошибок.

С другой стороны, необходимо исключение IOException, поскольку, если диск нечитаемый (или какой-то другой), выдается ошибка . Таким образом, единственный выход, который я вижу, это перехват IOException и создание на его месте некоторого RuntimeException.

Это правильная философия?

Ответы [ 5 ]

26 голосов
/ 08 января 2009

Ты прав.

Исключение должно быть на том же уровне абстракции, где используется. Это причина того, что в java 1.4 Throwable поддерживается цепочка исключений. Нет смысла выдавать исключение FileNotFoundException для службы, которая, например, использует базу данных, или для службы, которая не зависит от хранилища.

Это может быть так:

public abstract class Data {
   public abstract String read() throws DataUnavailableException;
}

class DataFile extends Data { 
    public String read() throws DataUnavailableException {
        if( !this.file.exits() ) {
            throw new DataUnavailableException( "Cannot read from ", file );
         }

         try { 
              ....
         } catch( IOException ioe ) { 
             throw new DataUnavailableException( ioe );
         } finally {
              ...
         }
 }


class DataMemory extends Data { 
    public String read()  {
        // Everything is performed in memory. No exception expected.
    }
 }

 class DataWebService extends Data { 
      public string read() throws DataUnavailableException {
           // connect to some internet service
           try {
              ...
           } catch( UnknownHostException uhe ) {
              throw new DataUnavailableException( uhe );
           }
      }
 }

Имейте в виду, что если вы программируете с учетом наследования, вам следует тщательно проектировать для конкретных сценариев и тестировать реализации с этими сценариями. Очевидно, что если будет сложнее кодировать библиотеку общего назначения, потому что вы не знаете, как она будет использоваться. Но в большинстве случаев приложения ограничены определенной областью.

Должно ли ваше новое исключение быть Runtime или Checked? Это зависит от того, что общее правило заключается в том, чтобы выбрасывать Runtime для ошибок программирования и проверять исправимые условия.

Если исключение можно избежать путем правильного программирования (например, NullPointerException или IndexOutOfBounds), используйте Runtime

Если исключение вызвано тем, что какой-то внешний ресурс не контролируется программистом (например, сеть не работает) И есть что-то, ЧТО можно сделать (показать сообщение о повторной попытке через 5 минут или что-то в этом роде), тогда проверенное исключение следует использовать.

Если исключение не контролируется программистом, но НИЧЕГО не может быть сделано, вы можете использовать RuntimeException. Например, вы должны записать файл, но файл был удален, и вы не можете создать его заново или повторить попытку, после чего программа может завершиться сбоем (вы ничего не можете с этим поделать), скорее всего, во время выполнения.

См. Эти два элемента из Effective Java:

  • Использовать проверенные исключения для восстанавливаемых условий и исключения во время выполнения для ошибок программирования
  • Бросить исключения, соответствующие абстракции

Надеюсь, это поможет.

7 голосов
/ 08 января 2009

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

В вашем конкретном случае я бы поймал лежащие в основе исключения и перебросил их как новый класс исключений DataException или DataReadException.

4 голосов
/ 08 января 2009

Бросьте IOException, завернутый в тип исключения, соответствующий классу "Data". Дело в том, что метод read не всегда может предоставить данные, и он, вероятно, должен указывать, почему. Исключение обертывания может расширяться RuntimeException и поэтому не требует объявления (хотя оно должно быть надлежащим образом задокументировано).

3 голосов
/ 08 января 2009

Используйте исключения времени выполнения, в сочетании с взрывной ловушкой наверху. Сначала это немного страшно, но становится лучше, когда вы к этому привыкнете.

В моих веб-приложениях все, что выбрасывается, - это среда выполнения. Пунктов «throws» почти нет, и у меня есть блоки управления только в тех местах, где я действительно могу (или хочу) обработать исключение. На самом высоком уровне есть перехватчик Throwable, который отображает страницу технической ошибки и записывает запись в журнал.

Почтовик Log4J отправляет мне запись журнала и 10 записей журнала, предшествующих ей. Поэтому, когда клиент звонит, я обычно уже знаю, что возникла проблема.

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

0 голосов
/ 08 января 2009

Вы должны объявить какое-то исключение в абстрактном методе read (). Абстрактный класс фактически объявляет интерфейс - и вы уже знаете из ваших двух конкретных подклассов, что реализация может быть не в состоянии успешно вернуться из-за условия исключения.

Таким образом, объявление какого-либо исключения в абстрактном методе Data.read () совершенно правильно. Не поддавайтесь искушению просто объявить, что он генерирует IOException, поскольку он не должен быть привязан к конкретным реализациям (иначе вам придется объявить, что он может также генерировать SQLException в случае, если вы когда-нибудь решите иметь базу данных). чтение подкласса, SAXException в случае, если у вас когда-либо есть читатель на основе XML (который использует SAX), и так далее). Вам понадобится ваше собственное исключение, которое адекватно фиксирует это на абстрактном уровне - что-то вроде рекомендованного выше DataException, или потенциально повторно использует существующее пользовательское исключение из того же пакета, если это имеет смысл.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...