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

17

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

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

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

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

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

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

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

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

    
задан Jake 08.01.2009 в 19:10
источник

5 ответов

26

Вы правы.

Исключение должно быть на том же уровне абстракции, где используется. Именно по этой причине, поскольку 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»:

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

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

    
ответ дан OscarRyz 08.01.2009 в 19:39
источник
  • Если DataUnavailableException не является исключением RuntimeException в вашем примере, ваш пример (DataMemory будет конкретным) не будет компилироваться. И чтение ваших заметок заставляет меня думать, что это должно быть проверенное исключение, поскольку есть что-то, что можно сделать в отношении данных, недоступных .. –  Koray Tugay 14.12.2015 в 21:03
7

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

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

    
ответ дан Allain Lalonde 08.01.2009 в 19:16
источник
  • Я бы заставил DataException удержать IOException внутри него, поэтому уровень, который обрабатывает исключение, может развернуться, если это необходимо. –  Paul Tomblin 08.01.2009 в 19:18
  • Пол, вам не нужно делать это явно - с Java 1.4 вы можете связывать исключения с помощью метода initCause (Throwable) или путем реализации конструкторов, которые включают Throwable в качестве аргумента. –  sk. 08.01.2009 в 19:25
4

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

    
ответ дан Tom Hawtin - tackline 08.01.2009 в 19:15
источник
3

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

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

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

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

    
ответ дан Rolf 08.01.2009 в 20:17
источник
0

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

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

    
ответ дан Andrzej Doyle 08.01.2009 в 19:34
источник
  • Предоставляет ли Java переопределяющий метод в подклассе, чтобы отказаться от объявления throws? Если это так, подклассы, которые никогда не будут бросать, могут использоваться без необходимости обрабатывать исключение, если вы используете их по их фактическому типу (а не по-полиморфно через родителя). Не знаю, если это работает. –  rmeador 08.01.2009 в 19:45