Подтвердить что ты не робот

Стиль Java: правильная обработка исключений

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

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

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

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

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

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

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

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

4b9b3361

Ответ 1

Вы правы.

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

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

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. Например, вы должны писать в файл, но файл был удален, и вы не можете его повторно создать или повторить попытку, тогда программа должна выйти из строя (вы ничего не можете с этим поделать), скорее всего, с Runtime.

См. эти два элемента из "Эффективной Java":

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

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

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

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

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

Ответ 5

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

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