Когда выбирать отмеченные и непроверенные исключения - PullRequest
186 голосов
/ 26 августа 2008

В Java (или любом другом языке с отмеченными исключениями), когда вы создаете свой собственный класс исключений, как вы решаете, следует ли его проверять или не проверять?

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

Ответы [ 18 ]

206 голосов
/ 28 сентября 2013

Проверено Исключения велики, если вы понимаете, когда их следует использовать. API ядра Java не соблюдает эти правила для SQLException (а иногда и для IOException), поэтому они так ужасны.

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

Непроверенные исключения следует использовать для всего остального.

Я сломаю это для вас, потому что большинство людей неправильно понимают, что это значит.

  1. Предсказуемый, но непредсказуемый : вызывающая сторона сделала все возможное, чтобы проверить входные параметры, но некоторые условия вне их контроля привели к сбою операции. Например, вы пытаетесь прочитать файл, но кто-то удаляет его между моментом проверки, существует ли он, и временем начала операции чтения. Объявляя проверенное исключение, вы указываете вызывающей стороне предвидеть этот сбой.
  2. Разумно восстанавливаться с : нет смысла говорить вызывающим абонентам предвидеть исключения, из которых они не могут восстановиться. Если пользователь пытается прочитать из несуществующего файла, вызывающая сторона может запросить у них новое имя файла. С другой стороны, если метод завершается неудачей из-за программной ошибки (неверные аргументы метода или ошибочная реализация метода), приложение ничего не может сделать, чтобы устранить проблему в середине выполнения. Лучшее, что он может сделать - зарегистрировать проблему и подождать, пока разработчик не исправит ее позже.

Если исключение, которое вы выбрасываете, не соответствует всем из вышеуказанных условий, оно должно использовать исключение непроверенное.

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

Как для проверенных, так и для непроверенных исключений, используйте правильный уровень абстракции . Например, хранилище кода с двумя различными реализациями (база данных и файловая система) должно избегать раскрытия деталей, относящихся к реализации, путем выдачи SQLException или IOException. Вместо этого следует заключить исключение в абстракцию, охватывающую все реализации (например, RepositoryException).

55 голосов
/ 26 августа 2008

С Ученик Java :

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

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

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

46 голосов
/ 26 августа 2008

Правило, которое я использую: никогда не используйте непроверенные исключения! (или когда ты не видишь никакого пути вокруг этого)

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

42 голосов
/ 18 сентября 2008

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

С проверенными исключениями ваша система обработки ошибок является микроуправляемой и невыносимой в любой большой системе.

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

Допустим, я создаю API-интерфейс StringToInt, который преобразует строковое представление целого числа в Int. Должен ли я генерировать проверенное исключение, если API вызывается со строкой "foo"? Это восстановимо? Я не знаю, потому что в своем слое вызывающая сторона моего API StringToInt, возможно, уже проверила ввод, и если выдается это исключение, это либо ошибка, либо повреждение данных, и его невозможно восстановить для этого слоя.

В этом случае вызывающая сторона API не хочет перехватывать исключение. Он только хочет, чтобы исключение «всплыло». Если я выберу проверенное исключение, у этого вызывающего будет много бесполезного блока перехвата только для искусственного отбрасывания исключения.

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

28 голосов
/ 27 ноября 2008

Ты прав.

Непроверенные исключения используются для того, чтобы система быстро провалилась , что хорошо. Вы должны четко указать, что ожидает ваш метод, чтобы работать должным образом. Таким образом, вы можете проверить ввод только один раз.

Например:

/**
 * @params operation - The operation to execute.
 * @throws IllegalArgumentException if the operation is "exit"
 */
 public final void execute( String operation ) {
     if( "exit".equals(operation)){
          throw new IllegalArgumentException("I told you not to...");
     }
     this.operation = operation; 
     .....  
 }
 private void secretCode(){
      // we perform the operation.
      // at this point the opreation was validated already.
      // so we don't worry that operation is "exit"
      .....  
 }

Просто чтобы привести пример. Дело в том, что если система быстро выходит из строя, то вы будете знать, где и почему она вышла из строя. Вы получите трассировку стека, как:

 IllegalArgumentException: I told you not to use "exit" 
 at some.package.AClass.execute(Aclass.java:5)
 at otherPackage.Otherlass.delegateTheWork(OtherClass.java:4569)
 ar ......

И ты узнаешь, что случилось. OtherClass в методе "DelegateTheWork" (в строке 4569) вызвал ваш класс со значением "exit", даже если это не должно происходить и т. Д.

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

То же самое происходит с исключениями NullPointerException. Если у вас есть класс на 700 строк с 15 методами, который использует 30 атрибутов, и ни один из них не может иметь значение null, вместо проверки в каждом из этих методов на обнуляемость вы можете сделать все эти атрибуты доступными только для чтения и проверить их в конструкторе или фабричный метод.

 public static MyClass createInstane( Object data1, Object data2 /* etc */ ){ 
      if( data1 == null ){ throw NullPointerException( "data1 cannot be null"); }

  }


  // the rest of the methods don't validate data1 anymore.
  public void method1(){ // don't worry, nothing is null 
      ....
  }
  public void method2(){ // don't worry, nothing is null 
      ....
  }
  public void method3(){ // don't worry, nothing is null 
      ....
  }

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

В этом сценарии вы или ваши коллеги ничего не можете сделать, чтобы помочь ему. Но все же вы должны что-то сделать и не дать приложению просто умереть и исчезнуть в глазах пользователя. Для этого вы используете проверенное исключение и обрабатываете исключение. Что вы можете делать, когда это происходит? Большую часть времени вы просто пытаетесь зарегистрировать ошибку, возможно, сохранить свою работу (работу приложения) и представить сообщение пользователю. , (Сайт blabla не работает, повторите попытку позже и т. Д.)

Если проверенное исключение чрезмерно используется (добавив «throw Exception» во все сигнатуры методов), тогда ваш код станет очень хрупким, потому что все будут игнорировать это исключение (потому что оно слишком общее) и качество кода будет серьезно скомпрометирован.

Если вы злоупотребите непроверенным исключением, произойдет нечто подобное. Пользователи этого кода не знают, если что-то пойдет не так, появится много попыток {...} catch (Throwable t).

19 голосов
/ 27 ноября 2008

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

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

Это философия, используемая многими фреймворками. В частности, приходят на ум Spring и Hibernate - они преобразуют известное проверенное исключение в непроверенное исключение именно потому, что проверенные исключения чрезмерно используются в Java. Одним из примеров, который я могу вспомнить, является JSONException от json.org, который является проверенным исключением и в основном раздражает - его следует отключить, но разработчик просто не продумал его.

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

19 голосов
/ 16 сентября 2008

Вот мое «последнее правило».
Я использую:

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

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


Для обоих этих исключений я создам свое собственное исключение без проверки и с проверкой для моего приложения (хорошая практика, , как упомянуто здесь ), за исключением очень распространенного исключения без проверки (например, NullPointerException)

Так, например, цель этой конкретной функции ниже - сделать (или получить, если уже существует) объект,
что означает:

  • ДОЛЖЕН существовать контейнер объекта для создания / получения (ответственность ЗВОНОКА
    => непроверенное исключение и очистить комментарий Javadoc для этой вызываемой функции)
  • остальные параметры не могут быть нулевыми
    (выбор кодера, чтобы поместить это в CALLER: кодер не будет проверять нулевой параметр, но кодер ДОКУМЕНТИРУЕТ ЭТО)
  • результат не может быть пустым
    (ответственность и выбор кода вызываемого абонента, выбор которого будет представлять большой интерес для вызывающего абонента
    => проверенное исключение, потому что каждый вызывающий объект ДОЛЖЕН принять решение, если объект не может быть создан / найден, и это решение должно быть применено во время компиляции: они не могут использовать эту функцию, не имея дело с этой возможностью, то есть с этим проверено исключение).
* +1047 * Пример: * +1048 *
/**
 * Build a folder. <br />
 * Folder located under a Parent Folder (either RootFolder or an existing Folder)
 * @param aFolderName name of folder
 * @param aPVob project vob containing folder (MUST NOT BE NULL)
 * @param aParent parent folder containing folder 
 *        (MUST NOT BE NULL, MUST BE IN THE SAME PVOB than aPvob)
 * @param aComment comment for folder (MUST NOT BE NULL)
 * @return a new folder or an existing one
 * @throws CCException if any problems occurs during folder creation
 * @throws AssertionFailedException if aParent is not in the same PVob
 * @throws NullPointerException if aPVob or aParent or aComment is null
 */
static public Folder makeOrGetFolder(final String aFoldername, final Folder aParent,
    final IPVob aPVob, final Comment aComment) throws CCException {
    Folder aFolderRes = null;
    if (aPVob.equals(aParent.getPVob() == false) { 
       // UNCHECKED EXCEPTION because the caller failed to live up
       // to the documented entry criteria for this function
       Assert.isLegal(false, "parent Folder must be in the same PVob than " + aPVob); }

    final String ctcmd = "mkfolder " + aComment.getCommentOption() + 
        " -in " + getPNameFromRepoObject(aParent) + " " + aPVob.getFullName(aFolderName);

    final Status st = getCleartool().executeCmd(ctcmd);

    if (st.status || StringUtils.strictContains(st.message,"already exists.")) {
        aFolderRes = Folder.getFolder(aFolderName, aPVob);
    }
    else {
        // CHECKED EXCEPTION because the callee failed to respect his contract
        throw new CCException.Error("Unable to make/get folder '" + aFolderName + "'");
    }
    return aFolderRes;
}
13 голосов
/ 03 мая 2014

Вот очень простое решение вашей проверенной / непроверенной дилеммы.

Правило 1. Думайте о непроверенном исключении как о тестируемом условии перед выполнением кода. например…

x.doSomething(); // the code throws a NullPointerException

где х ноль ... ... код должен иметь следующее ...

if (x==null)
{
    //do something below to make sure when x.doSomething() is executed, it won’t throw a NullPointerException.
    x = new X();
}
x.doSomething();

Правило 2: рассматривайте Проверенное Исключение как непроверяемое условие, которое может возникнуть во время выполнения кода.

Socket s = new Socket(“google.com”, 80);
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream();

… в приведенном выше примере URL (google.com) может быть недоступен из-за неработоспособности DNS-сервера. Даже в тот момент, когда DNS-сервер работал и преобразовал имя «google.com» в IP-адрес, при наличии подключения к google.com в любое время после слов сеть может выйти из строя. Вы просто не можете все время проверять сеть перед чтением и записью в потоки.

В некоторых случаях код просто должен выполняться, прежде чем мы узнаем, есть ли проблема. Вынуждая разработчиков писать свой код таким образом, чтобы заставить их справляться с этими ситуациями с помощью Checked Exception, я вынужден был склонить голову создателю Java, который изобрел эту концепцию.

Как правило, почти все API в Java следуют 2 приведенным выше правилам. Если вы попытаетесь записать файл, диск может заполниться до завершения записи. Возможно, что другие процессы вызвали переполнение диска. Просто нет способа проверить эту ситуацию. Для тех, кто взаимодействует с оборудованием, где в любое время использование оборудования может дать сбой, Проверенные исключения кажутся изящным решением этой проблемы.

Для этого есть серая область. В случае, если требуется много тестов (ошеломляющий оператор if с большим количеством && и ||), выбрасываемое исключение будет CheckedException просто потому, что слишком сложно получить права - вы просто не можете сказать эту проблему это ошибка программирования. Если тестов намного меньше 10 (например, if (x == null)), то ошибка программиста должна быть UncheckedException.

Вещи становятся интересными, когда имеешь дело с переводчиками языка. Согласно приведенным выше правилам, следует ли считать синтаксическую ошибку проверенным или непроверенным исключением? Я бы сказал, что если синтаксис языка можно протестировать до его запуска, это должно быть исключение UncheckedException. Если язык не может быть протестирован - аналогично тому, как ассемблерный код выполняется на персональном компьютере, то синтаксическая ошибка должна быть проверенным исключением.

2 приведенных выше правила, вероятно, уберут 90% вашей озабоченности, из которой можно выбирать. Чтобы обобщить правила, следуйте этой схеме ... 1) если выполняемый код может быть проверен до того, как он будет выполнен, для его правильной работы, и если возникает исключение - например, ошибка программиста, то исключение должно быть UncheckedException (подкласс RuntimeException). 2) если выполняемый код не может быть проверен до его правильного выполнения, то Исключение должно быть Проверенным Исключением (подкласс Исключения).

8 голосов
/ 12 июля 2011

Вы можете назвать это проверенным или непроверенным исключением; однако, оба типа исключений могут быть обнаружены программистом, поэтому лучший ответ таков: запишите все ваших исключений как unchecked и задокументируйте их. Таким образом, разработчик, который использует ваш API, может выбрать, хочет ли он или она перехватить это исключение и что-то сделать. Проверенные исключения являются пустой тратой времени каждого, и это делает ваш код ужасным кошмаром. Правильное модульное тестирование затем вызовет любые исключения, которые вам, возможно, придется поймать и что-то сделать.

7 голосов
/ 19 декабря 2012

Проверено Исключение: Если клиент может восстановиться после исключения и хочет продолжить, используйте проверенное исключение.

Не проверено Исключение: Если клиент не может ничего сделать после исключения, тогда вызовите исключение без проверки.

Пример: если вы должны выполнять арифметическую операцию в методе A () и на основе выходных данных из A (), вам нужно выполнить другую операцию. Если выходные данные из метода A () являются нулевыми, чего вы не ожидаете во время выполнения, тогда вы должны генерировать исключение нулевого указателя, которое является исключением времени выполнения.

См. здесь

...