исключения - runtime exception java




Случай с проверенными исключениями (20)

В течение ряда лет я не смог получить достойный ответ на следующий вопрос: почему некоторые разработчики так против отмеченных исключений? У меня было много разговоров, читайте в блогах, читайте, что должен был сказать Брюс Эккель (первый человек, которого я видел, высказывался против них).

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

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

В целом (из того, как была разработана Java)

  • Ошибка для вещей, которые никогда не должны быть пойманы (у VM есть арахисная аллергия, и кто-то сбросил на нее банку арахиса)
  • RuntimeException - это то, что программист сделал неправильно (программист ушел с конца массива)
  • Исключение (за исключением RuntimeException) - это то, что находится вне контроля программиста (диск заполняется во время записи в файловую систему, ограничение на дескриптор файла для процесса достигнуто, и вы не можете открыть больше файлов)
  • Throwable - это просто родительский тип всех типов исключений.

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

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

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

Для толпы «это затрудняет рефакторинг», что указывает на то, что правильный уровень абстракции не был выбран. Вместо того, чтобы объявлять, что метод генерирует исключение IOException, IOException следует преобразовать в исключение, которое больше подходит для того, что происходит.

У меня нет проблемы с wrapping Main с catch (Exception) (или в некоторых случаях catch (Throwable), чтобы гарантировать, что программа может выйти изящно - но я всегда поймаю конкретные исключения, которые мне нужны. Это позволяет мне, по крайней мере, отобразить соответствующее сообщение об ошибке.

Вопрос, на который люди никогда не отвечают, таков:

Если вы выкидываете подклассы RuntimeException вместо подклассов Exception, то откуда вы знаете, что вы должны ловить?

Если ответ является catch Exception, вы также имеете дело с ошибками программиста так же, как и системные исключения. Это кажется мне неправильным.

Если вы поймаете Throwable, вы обрабатываете системные исключения и ошибки VM (и тому подобное) одинаково. Это кажется мне неправильным.

Если ответ заключается в том, что вы поймаете только те исключения, которые, как вы знаете, выбрасываются, то откуда вы знаете, что их бросают? Что происходит, когда программист X выдает новое исключение и забыл его поймать? Мне это кажется очень опасным.

Я бы сказал, что программа, которая отображает трассировку стека, неверна. Неужели люди, которые не любят проверенные исключения, не чувствуют себя так?

Итак, если вам не нравятся проверенные исключения, вы можете объяснить, почему нет И ответить на вопрос, на который не ответили, пожалуйста?

Изменить: я не ищу советы о том, когда использовать любую модель, то, что я ищу, - это то, почему люди распространяются из RuntimeException, потому что им не нравится расширение от Exception и / или почему они улавливают исключение, а затем реконструируют исключение RuntimeException, а не добавьте броски к их методу. Я хочу понять мотивацию неправедных проверенных исключений.


SNR

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

Обновление пользовательского интерфейса из не-UI-потока в Java:

try {  
    // Run the update code on the Swing thread  
    SwingUtilities.invokeAndWait(() -> {  
        try {
            // Update UI value from the file system data  
            FileUtility f = new FileUtility();  
            uiComponent.setValue(f.readSomething());
        } catch (IOException e) {  
            throw new UncheckedIOException(e);
        }
    });
} catch (InterruptedException ex) {  
    throw new IllegalStateException("Interrupted updating UI", ex);  
} catch (InvocationTargetException ex) {
    throw new IllegalStateException("Invocation target exception updating UI", ex);
}

Обновление пользовательского интерфейса из неинтерфейса в C #:

private void UpdateValue()  
{  
   // Ensure the update happens on the UI thread  
   if (InvokeRequired)  
   {  
       Invoke(new MethodInvoker(UpdateValue));  
   }  
   else  
   {  
       // Update UI value from the file system data  
       FileUtility f = new FileUtility();  
       uiComponent.Value = f.ReadSomething();  
   }  
}  

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

Перерыв в тюрьме

Для реализации даже самых базовых реализаций, таких как интерфейс списка Java, проверены исключения как инструмент для проектирования по контракту. Рассмотрим список, который поддерживается базой данных или файловой системой или любой другой реализацией, которая выдает проверенное исключение. Единственная возможная реализация заключается в том, чтобы поймать проверенное исключение и переустановить его как исключенное исключение:

@Override
public void clear()  
{  
   try  
   {  
       backingImplementation.clear();  
   }  
   catch (CheckedBackingImplException ex)  
   {  
       throw new IllegalStateException("Error clearing underlying list.", ex);  
   }  
}  

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

Заключение

  • Захват исключений отличается от их обработки.
  • Проверенные исключения добавляют к коду шум.
  • Обработка исключений хорошо работает на C # без них.

Об этом я previously писал .


Категории исключений

Когда я говорю об исключениях, я всегда ссылаюсь на статью блога исключений Eex Lippert's Vexing . Он помещает исключения в эти категории:

  • Fatal - эти исключения не являются вашей ошибкой : вы не можете предотвратить это, и вы не можете разумно справиться с ними. Например, OutOfMemoryErrorили ThreadAbortException.
  • Boneheaded - Эти исключения являются вашей ошибкой : вы должны были предотвратить их, и они представляют ошибки в вашем коде. Например ArrayIndexOutOfBoundsException, NullPointerExceptionили любой IllegalArgumentException.
  • Vexing - эти исключения не являются исключительными , а не ваша вина, вы не можете их предотвратить, но вам придется иметь дело с ними. Они часто являются результатом неудачного проектного решения, такие , как бросание NumberFormatExceptionиз Integer.parseIntвместо того , обеспечивая Integer.tryParseIntметод , который возвращает логическое значение FALSE на синтаксического анализа отказа.
  • Экзогенные - эти исключения, как правило, исключительные , а не ваша вина, вы не можете (разумно) их предотвратить, но вы должны их обработать . Например, FileNotFoundException.

Пользователь API:

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

Проверенные исключения

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

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

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

Проблемы API

Но любой API, который проверил досадные и фатальные исключения (например, JCL), будет создавать ненужные нагрузки на пользователей API. Такие исключения должны обрабатываться, но исключение настолько распространено, что оно не должно было быть исключением в первую очередь, или при его обработке ничего нельзя сделать. И это заставляет Java-разработчики ненавидеть проверенные исключения.

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

Заключение

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

Поэтому не стоит ненавидеть Java и проверенные исключения. Вместо этого ненавидьте API, которые злоупотребляют проверенными исключениями.


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

try {
  // do stuff
} catch (AnnoyingcheckedException e) {
  throw new RuntimeException(e);
}

В 99% случаев я ничего не могу с этим поделать. Наконец блоки делают необходимую очистку (или, по крайней мере, должны).

Я также потерял счет количества раз, когда я видел это:

try {
  // do stuff
} catch (AnnoyingCheckedException e) {
  // do nothing
}

Зачем? Потому что кто-то должен был с этим справиться и ленился. Это было неправильно? Конечно. Это происходит? Абсолютно. Что, если бы это было исключение? Приложение просто умерло (что предпочтительнее проглатывания исключения).

И тогда мы приводим в ярость код, который использует исключения как форму управления потоком, например java.text.Format . Bzzzt. Неправильно. Пользователь, помещающий «abc» в поле чисел в форме, не является исключением.

Хорошо, я думаю, это было три причины.


Дело в проверенных исключениях заключается в том, что они не являются действительно исключениями из-за обычного понимания концепции. Вместо этого они являются альтернативными значениями возврата API.

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

public [int or IOException] writeToStream(OutputStream stream) {
    [void or IOException] a= stream.write(mybytes);
    if (a instanceof IOException)
        return a;
    return mybytes.length;
}

Поскольку Java не может выполнять альтернативные значения возврата или простые встроенные кортежи в качестве возвращаемых значений, проверенные исключения являются разумным ответом.

Проблема в том, что много кода, в том числе большое количество стандартной библиотеки, неправильно использует исключения для реальных исключительных условий, которые вы вполне можете захотеть поймать на нескольких уровнях. Почему IOException не исключение RuntimeException? На любом другом языке я могу позволить исключение IO, и если я ничего не сделаю для этого, мое приложение остановится, и я получу удобную трассировку стека, чтобы посмотреть. Это лучшее, что может случиться.

Возможно, два метода из примера, который вы хотите уловить все IOExceptions из всего процесса записи в поток, прервать процесс и перейти в код сообщения об ошибках; в Java вы не можете этого сделать, не добавляя «throws IOException» на каждый уровень вызова, даже уровни, которые сами не выполняют IO. Такие методы не должны знать об обработке исключений; добавив исключения в свои подписи:

  1. излишне увеличивает сцепление;
  2. делает сигнатуры интерфейса очень хрупкими для изменения;
  3. делает код менее удобочитаемым;
  4. настолько раздражает, что обычной реакцией программиста является поражение системы, делая что-то ужасное, как «throws Exception», «catch (Exception e) {}» или обертывание всего в RuntimeException (что усложняет отладку).

И тогда есть много смешных библиотек, таких как:

try {
    httpconn.setRequestMethod("POST");
} catch (ProtocolException e) {
    throw new CanNeverHappenException("oh dear!");
}

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

Другим особенно плохим эффектом является инверсия элемента управления, где компонент A передает обратный вызов в общий компонент B. Компонент A хочет, чтобы исключение выбрало из обратного вызова в место, где оно называлось компонентом B, но оно не может потому что это изменило бы интерфейс обратного вызова, который был зафиксирован на B. Это может сделать только это, обертывая реальное исключение в RuntimeException, которое является еще большим шаблоном обработки исключений для записи.

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


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

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

Возьмите хороший интерфейс Java List . У нас есть общие реализации в памяти, такие как ArrayList и LinkedList . У нас также есть скелетный класс AbstractList который позволяет легко создавать новые типы списков. Для списка только для чтения нам нужно реализовать только два метода: size() и get(int index) .

В этом примере класс WidgetList читает некоторые объекты фиксированного размера типа Widget (не показаны) из файла:

class WidgetList extends AbstractList<Widget> {
    private static final int SIZE_OF_WIDGET = 100;
    private final RandomAccessFile file;

    public WidgetList(RandomAccessFile file) {
        this.file = file;
    }

    @Override
    public int size() {
        return (int)(file.length() / SIZE_OF_WIDGET);
    }

    @Override
    public Widget get(int index) {
        file.seek((long)index * SIZE_OF_WIDGET);
        byte[] data = new byte[SIZE_OF_WIDGET];
        file.read(data);
        return new Widget(data);
    }
}

list.get(123) виджеты с помощью знакомого интерфейса List , вы можете извлекать элементы ( list.get(123) ) или перебирать список ( for (Widget w : list) ... ), не зная о самом WidgetList . Этот список можно передать любым стандартным методам, которые используют общие списки, или переносить их в Collections.synchronizedList . Кодекс, который его использует, не нуждается ни в понимании, ни в заботе о том, сделаны ли «Виджеты» на месте, поступают из массива или читаются из файла, базы данных или по всей сети или из будущего подпространства. Он будет работать корректно, потому что интерфейс List правильно реализован.

Кроме этого нет. Вышеупомянутый класс не компилируется, потому что методы доступа к файлам могут IOException исключение IOException , исключенное исключение, которое вы должны «уловить или указать». Вы не можете указать его как брошенное - компилятор не позволит вам, потому что это нарушит договор интерфейса List . И нет никакого полезного способа, WidgetList сам WidgetList может обрабатывать исключение (как я расскажу позже).

Видимо, единственное, что нужно сделать, это перехватывать и отменять исключения, как некое исключение:

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw new WidgetListException(e);
    }
}

public static class WidgetListException extends RuntimeException {
    public WidgetListException(Throwable cause) {
        super(cause);
    }
}

((Редактирование: Java 8 добавила класс UncheckedIOException именно для этого случая: для обнаружения и повторного ввода IOException в границы полиморфных методов. Вид доказывает мою мысль!))

Поэтому проверенные исключения просто не работают в таких случаях. Вы не можете их бросить. То же самое для умной Map поддерживаемой базой данных, или реализацией java.util.Random подключенной к источнику квантовой энтропии через COM-порт. Как только вы попытаетесь сделать что-нибудь новое с реализацией полиморфного интерфейса, концепция проверенных исключений не удалась. Но проверенные исключения настолько коварны, что они все равно не оставят вас в покое, потому что вам все равно придется поймать и реконструировать любой из методов более низкого уровня, загромождать код и загромождать трассировку стека.

Я нахожу, что вездесущий интерфейс Runnable часто поддерживается в этом углу, если он вызывает то, что выбрасывает проверенные исключения. Он не может генерировать исключение, как есть, поэтому все, что он может сделать, это загромождать код, улавливая и перерабатывая как RuntimeException .

На самом деле, вы можете бросить необъявленные проверенные исключения, если прибегают к хакам. JVM во время выполнения не заботится о проверенных правилах исключения, поэтому нам нужно обмануть только компилятор. Самый простой способ сделать это - злоупотреблять дженериками. Это мой метод для него (имя класса показано, потому что (до Java 8) требуется в синтаксисе вызова для общего метода):

class Util {
    /**
     * Throws any {@link Throwable} without needing to declare it in the
     * method's {@code throws} clause.
     * 
     * <p>When calling, it is suggested to prepend this method by the
     * {@code throw} keyword. This tells the compiler about the control flow,
     * about reachable and unreachable code. (For example, you don't need to
     * specify a method return value when throwing an exception.) To support
     * this, this method has a return type of {@link RuntimeException},
     * although it never returns anything.
     * 
     * @param t the {@code Throwable} to throw
     * @return nothing; this method never returns normally
     * @throws Throwable that was provided to the method
     * @throws NullPointerException if {@code t} is {@code null}
     */
    public static RuntimeException sneakyThrow(Throwable t) {
        return Util.<RuntimeException>sneakyThrow1(t);
    }

    @SuppressWarnings("unchecked")
    private static <T extends Throwable> RuntimeException sneakyThrow1(
            Throwable t) throws T {
        throw (T)t;
    }
}

Ура! Используя это, мы можем вытащить проверенное исключение из любой глубины в стек, не объявляя его, не обертывая его в RuntimeException и не загромождая трассировку стека! Повторное использование примера «WidgetList»:

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw sneakyThrow(e);
    }
}

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

try {
    ...
} catch (Throwable t) { // catch everything
    if (t instanceof IOException) {
        // handle it
        ...
    } else {
        // didn't want to catch this one; let it go
        throw t;
    }
}

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

К счастью, throw t; утверждение здесь законно, даже если тип t проверен, благодаря правилу, добавленному в Java 7 о повторном захвате исключений.

Когда проверенные исключения соответствуют полиморфизму, противоположный случай также является проблемой: когда метод специфицирован как потенциально бросающий проверенное исключение, но переопределенная реализация не делает этого. Например, методы OutputStream абстрактного класса OutputStream задают все OutputStream throws IOException . ByteArrayOutputStream - это подкласс, который записывает в массив в памяти вместо истинного источника ввода-вывода. Его переопределенные методы write не могут вызывать IOException s, поэтому у них нет предложения throw, и вы можете их вызвать, не беспокоясь о требовании catch-or-spec.

Кроме того, не всегда. Предположим, что у Widget есть метод для его сохранения в поток:

public void writeTo(OutputStream out) throws IOException;

Объявление этого метода для принятия простого OutputStream - это правильная вещь, поэтому его можно использовать полиморфно со всеми видами выходов: файлы, базы данных, сеть и т. Д. И в памяти массивов. Однако с массивом в памяти существует ложное требование обрабатывать исключение, которое на самом деле не может произойти:

ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
    someWidget.writeTo(out);
} catch (IOException e) {
    // can't happen (although we shouldn't ignore it if it does)
    throw new RuntimeException(e);
}

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

Но подождите, проверенные исключения на самом деле так раздражают, что они даже не позволят вам сделать обратное! Представьте, что вы в настоящее время улавливаете любое OutputStream , OutputStream вызовами write в OutputStream , но вы хотите изменить объявленный тип переменной на ByteArrayOutputStream , компилятор будет бить вас, пытаясь поймать проверенное исключение, которое, по его словам, не может быть выбрано.

Это правило вызывает некоторые абсурдные проблемы. Например, один из трех методов write OutputStream не переопределяется ByteArrayOutputStream . В частности, write(byte[] data) - метод удобства, который записывает полный массив, вызывая write(byte[] data, int offset, int length) со смещением 0 и длиной массива. ByteArrayOutputStream переопределяет метод с тремя аргументами, но наследует метод удобства с одним аргументом as-is. Унаследованный метод делает то же самое, но включает в себя предложение нежелательных throws . Возможно, это был надзор в дизайне ByteArrayOutputStream , но они никогда не могут исправить это, потому что это нарушит совместимость источника с любым кодом, который поймает исключение - исключение, которое никогда, никогда и никогда не будет выброшено!

Это правило раздражает во время редактирования и отладки. Например, иногда я буду временно вызывать вызов метода, и если бы он мог вызвать исключение проверки, компилятор теперь будет жаловаться на существование локальных блоков try и catch . Поэтому я тоже должен прокомментировать эти комментарии, и теперь, когда вы редактируете код внутри, среда IDE будет отступать до неправильного уровня, потому что { и } закомментированы. Г! Это небольшая жалоба, но, похоже, единственное, что проверяет исключения - это вызвать проблемы.

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

Вместо этого мы получаем следующую идиому, которая распространена как способ закрыть компилятор:

try {
    ...
} catch (SomeStupidExceptionOmgWhoCares e) {
    e.printStackTrace();
}

В графическом интерфейсе или автоматической программе печатное сообщение не будет видно. Хуже того, после исключения он сбрасывается с остальной частью кода. Является ли исключение на самом деле ошибкой? Затем не печатайте. В противном случае что-то еще взорвется через мгновение, и к тому времени исходный объект исключения исчезнет. Эта идиома не лучше, чем BASIC's On Error Resume Next или PHP error_reporting(0); ,

Вызов какого-то класса регистратора не намного лучше:

try {
    ...
} catch (SomethingWeird e) {
    logger.log(e);
}

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

Но ждать! Существует простой и универсальный способ поиска обработчика приложения. Это выше стека вызовов (или он задан как обработчик исключенных исключений Thread). Поэтому в большинстве случаев все, что вам нужно сделать, это бросить исключение выше стека . Например, throw e; , Проверенные исключения просто мешают.

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


Эта статья - лучший фрагмент текста по обработке исключений в Java, который я когда-либо читал.

Он предпочитает исключить проверенные исключения, но этот выбор объясняется очень подробно и основан на сильных аргументах.

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

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

Автор «ответы»:

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

И главная мысль или статья:

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

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

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


Короче:

Исключения - это вопрос проектирования API. -- Не больше, не меньше.

Аргумент для проверенных исключений:

Чтобы понять, почему проверенные исключения могут быть не очень удачными, давайте обратимся к вопросу и спросим: когда или почему проверяются исключения, привлекательные, то есть почему вы хотите, чтобы компилятор обеспечивал объявление исключений?

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

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

На самом деле, хотя слишком часто пакет com.acmeсоздает AcmeExceptionтолько определенные подклассы. Затем вызывающим абонентам необходимо обрабатывать, объявлять или переписывать AcmeExceptions, но все равно не может быть уверен, AcmeFileNotFoundErrorпроизошло ли это AcmePermissionDeniedError.

Поэтому, если вас интересует только вопрос AcmeFileNotFoundError, решение состоит в том, чтобы подать запрос функции с помощью программистов ACME и сообщить им, чтобы реализовать, объявить и документировать этот подкласс AcmeException.

Так зачем беспокоиться?

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

В результате, языки без проверенных исключений обычно не намного хуже. У программистов может возникнуть соблазн бросить неспецифические экземпляры общего Errorкласса, а не а AcmeException, но если они вообще не заботятся об их качестве API, они научатся внедрять в AcmeFileNotFoundErrorконце концов.

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

Если вы будете следовать этой линии рассуждений, должно быть очевидно, что «хлопоты» объявления, ловушки и повторного выброса исключений, которые так распространены на таких языках, как Java, часто не придают большого значения.

Также стоит отметить, что Java VM не имеет проверенных исключений - только компилятор Java проверяет их, а файлы классов с измененными объявлениями исключений совместимы во время выполнения. Безопасность Java VM не улучшена проверенными исключениями, только стиль кодирования.


Мы видели некоторые ссылки на главного архитектора C #.

Вот альтернативная точка зрения от парня Java о том, когда использовать проверенные исключения. Он признает многие негативы, о которых говорили другие: Эффективные исключения


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

Другая проблема с проверенными исключениями заключается в том, что они, как правило, неправильно используются. Прекрасным примером этого является метод java.sql.Connections close(). Он может выдать SQLException, даже если вы уже прямо заявили, что закончили с Connection. Какую информацию можно закрыть (), возможно, передать, что вам нужно?

Обычно, когда я закрываю () соединение *, оно выглядит примерно так:

try {
    conn.close();
} catch (SQLException ex) {
    // Do nothing
}

Кроме того, не заставляйте меня начинать с различных методов синтаксического анализа и NumberFormatException ... .NET TryParse, который не генерирует исключений, гораздо проще использовать, так как ему больно возвращаться к Java (мы используем как Java, так и C #, где я работаю).

*В качестве дополнительного комментария соединение Connection.close () PooledConnection даже не закрывает соединение, но вам все равно придется поймать SQLException из-за того, что он является проверенным исключением.


Хорошее доказывает, что Checked Exception не нужны:

  1. Множество фреймворков, которые делают некоторые работы для Java. Подобно Spring, который исключает исключение JDBC для исключенных исключений, бросает сообщения в журнал
  2. Множество языков, которые появились после java, даже сверху на платформе Java - они не используют их
  3. Проверяемые исключения, это доброе предсказание о том, как клиент будет использовать код, который генерирует исключение. Но разработчик, который пишет этот код, никогда не узнает о системе и бизнесе, в котором работает клиент кода. В качестве примера используются методы Interfcace, которые вынуждают исключить проверенное исключение. Существует 100 реализаций по системе, 50 или даже 90 реализаций не выбрасывают это исключение, но клиент все равно должен поймать это исключение, если он ссылается на этот интерфейс. Эти 50 или 90 реализаций, как правило, обрабатывают эти исключения внутри себя, помещая исключение в журнал (и это хорошее поведение для них). Что мы должны с этим делать? Мне бы лучше иметь некоторую фоновую логику, которая будет делать всю эту работу - отправку сообщения в журнал. И если я, как клиент кода, почувствовал бы, что мне нужно обработать исключение - я это сделаю.Я могу забыть об этом, правильно, но если я использую TDD, все мои шаги будут покрыты, и я знаю, чего хочу.
  4. Другой пример, когда я работаю с I / O в java, заставляет меня проверять все исключения, если файл не существует? что я должен с этим делать? Если он не существует, система не перейдет к следующему шагу. Клиент этого метода не получит ожидаемого контента из этого файла - он может обрабатывать Runtime Exception, иначе я должен сначала проверить Checked Exception, поместить сообщение в журнал, а затем исключить исключение из метода. Нет ... нет. Лучше сделайте это автоматически с помощью RuntimeEception, который автоматически сделает это / автоматически. Нет никакого смысла обрабатывать его вручную - я был бы счастлив, что увидел сообщение об ошибке в журнале (AOP может помочь с этим .. что-то, что исправляет java). Если, в конце концов, я нахожу, что система должна показывать всплывающее сообщение конечному пользователю - я покажу его, а не проблема.

Я был счастлив, если java предоставит мне выбор, что использовать, когда вы работаете с основными библиотеками, например I / O. Как и два экземпляра одного класса - один, завернутый в RuntimeEception. Затем мы можем сравнить, что люди будут использовать . На данный момент, однако, многие люди лучше подходят для некоторых фреймворков на Java или на разных языках. Как Скала, Юруби. Многие просто верят, что СОЛНЦЕ было прав.


Я много читал об обработке исключений, даже если (большую часть времени) я не могу сказать, что я доволен или грустен о существовании проверенных исключений, это мой прием: проверенные исключения в низкоуровневом коде (IO, network , ОС и т. Д.) И исключенные исключения на уровне API / уровне приложений высокого уровня.

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

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

В текущей версии (3-й) используются только исключенные исключения, и у нее есть глобальный обработчик исключений, который отвечает за обработку каких-либо неотображаемых. API предоставляет способ регистрации обработчиков исключений, которые решат, будет ли исключение считаться ошибкой (в большинстве случаев это так), что означает «log & notify somebody», или это может означать что-то еще - как это исключение, AbortException, что означает разрыв текущего потока выполнения и не регистрирует ошибку, потому что это не так. Конечно, чтобы выработать все пользовательские потоки, нужно обработать метод run () с помощью try {...} catch (all).

public void run () {

try {
     ... do something ...
} catch (Throwable throwable) {
     ApplicationContext.getExceptionService().handleException("Handle this exception", throwable);
}

}

Это не обязательно, если вы используете WorkerService для планирования заданий (Runnable, Callable, Worker), который обрабатывает все для вас.

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


Artima http://www.artima.com/intv/handcuffs.html с одним из архитекторов .NET, Андерсом Хейлсбергом, который остро освещает аргументы против проверенных исключений. Короткий дегустатор:

Предложение throws, по крайней мере, так, как оно реализовано на Java, не обязательно заставляет вас обрабатывать исключения, но если вы их не обрабатываете, это заставляет вас точно определить, какие исключения могут пройти. Это требует, чтобы вы либо ломали объявленные исключения, либо помещали их в свой собственный бросок. Чтобы обойти это требование, люди делают нелепые вещи. Например, они украшают каждый метод, «бросает исключение». Это просто полностью побеждает эту особенность, и вы просто заставили программиста написать более грязный мусор. Это никому не помогает.


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

Проверенные исключения в Java не решают последнюю проблему; C # и VB.NET выкидывают ребенка с водой для ванны.

В этом документе OOPSLA 2005 (или соответствующем техническом отчете ) описан хороший подход, который занимает среднюю дорогу .

Короче говоря, это позволяет вам сказать:, method g(x) throws like f(x)что означает, что g выбрасывает все исключения f throws. Voila, проверял исключения без проблемы с каскадными изменениями.

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


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

До сих пор мне было легче работать с базой кода с проверенными исключениями. Когда я использую чужой API, мне приятно видеть, какие именно условия ошибки я могу ожидать, когда я вызываю код и обрабатываю их правильно, либо путем регистрации, отображения или игнорирования (да, есть допустимые случаи для игнорирования исключения, такие как реализация ClassLoader). Это дает код, который я пишу, возможность восстановить. Все исключения во время выполнения распространяются до тех пор, пока они не будут кэшированы и обработаны с помощью некоторого общего кода обработки ошибок. Когда я нахожу проверенное исключение, которое я действительно не хочу обрабатывать на определенном уровне, или что я рассматриваю ошибку логики программирования, тогда я завершаю его в RuntimeException и позволяю ему пузыриться. Никогда, никогда не проглатывайте исключение без веской причины (и веские причины для этого довольно скудны)

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

Это, конечно же, вопрос предпочтений и навыков разработчика. Оба способа программирования и обработки ошибок могут быть одинаково эффективными (или неэффективными), поэтому я бы не сказал, что есть «Один путь».

В общем, мне легче работать с Checked Exceptions, особенно в крупных проектах с большим количеством разработчиков.


Моя запись на c2.com по-прежнему в основном неизменна от ее первоначальной формы: CheckedExceptionsAreIncompatibleWithVisitorPattern

В итоге:

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

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

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

Что касается основной проблемы:

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


Ну, это не об отображении stacktrace или молчании. Речь идет о возможности передачи ошибок между слоями.

Проблема с проверенными исключениями заключается в том, что они побуждают людей проглотить важные детали (а именно класс исключений). Если вы решите не проглатывать эту деталь, вам нужно продолжать добавлять объявления бросков по всему вашему приложению. Это означает, 1) что новый тип исключения будет влиять на множество сигнатур функций, и 2) вы можете пропустить конкретный экземпляр исключения, которое вы на самом деле хотите, чтобы поймать (скажем, вы открываете дополнительный файл для функции, которая записывает данные в файл. Дополнительный файл не является обязательным, поэтому вы можете игнорировать его ошибки, но из-за подписи throws IOExceptionлегко упустить это).

Сейчас я занимаюсь этой ситуацией в приложении. Мы переупаковывали почти исключения как AppSpecificException. Это сделало подписи действительно чистыми, и нам не пришлось беспокоиться о взрывах throwsв подписях.

Конечно, теперь нам нужно специализировать обработку ошибок на более высоких уровнях, реализуя логику повтора и т. Д. Однако все это AppSpecificException, поэтому мы не можем сказать: «Если исключение IOException выбрано, повторите попытку» или «Если ClassNotFound выбрано, полностью прекратите». У нас нет надежного способа получить реальное исключение, потому что вещи снова и снова переупаковываются, когда они проходят между нашим кодом и сторонним кодом.

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

Я нашел, снова и снова, и в течение всего проекта, о котором я упоминал, обработка исключений относится к трем категориям:

  1. Поймать и обработать конкретное исключение. Например, для реализации логики повтора.
  2. Поймать и реконструировать другие исключения. Все, что происходит здесь, обычно регистрируется, и обычно это банальное сообщение типа «Невозможно открыть $ filename». Это ошибки, о которых вы ничего не можете сделать; только более высокий уровень знает достаточно, чтобы справиться с этим.
  3. Поймайте все и отобразите сообщение об ошибке. Обычно это находится в самом корне диспетчера, и все это делает это, чтобы он мог сообщить об ошибке вызывающей стороне через механизм без исключения (всплывающий диалог, маршалинг объекта ошибки RPC и т. Д.).

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

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

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


Статья Эффективные исключения Java прекрасно объясняют, когда использовать непроверенные и когда использовать проверенные исключения. Вот некоторые цитаты из этой статьи, чтобы выделить основные моменты:

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

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

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

непредвиденные обстоятельства

  • Считается, что: часть дизайна
  • Ожидается, что произойдет: Регулярно, но редко
  • Кому это важно: код вверх, вызывающий метод
  • Примеры: альтернативные режимы возврата
  • Лучшее сопоставление: проверенное исключение

придираться

  • Считается, что: неприятный сюрприз
  • Ожидается, что: Никогда
  • Кому это нужно: люди, которые должны решить проблему
  • Примеры: ошибки программирования, аппаратные сбои, ошибки конфигурации, отсутствующие файлы, недоступные серверы
  • Лучшее сопоставление: исключение исключение

Это не аргумент против чистой концепции проверенных исключений, но иерархия классов, которую использует Java для них, - это уродское шоу. Мы всегда называем вещи просто «исключениями» - это правильно, потому что спецификация языка называет их тоже - но как исключение называется и представлено в системе типов?

По классу Exceptionодин воображает? Ну нет, потому что Exceptions - исключения, а также исключения - это Exceptions, за исключением тех исключений, которые не являются Exceptions, поскольку другие исключения фактически являются Errors, которые являются другим видом исключения, своего рода исключительным исключительным исключением, которое никогда не должно происходить, за исключением когда это происходит, и которые вы никогда не должны ловить, за исключением того, что иногда вам нужно. Кроме того, это еще не все, потому что вы также можете определить другие исключения, которые не являются ни Exceptions, ни Errors, а просто Throwableисключения.

Какие из них являются «проверенными» исключениями? Throwables проверяются исключения, за исключением тех случаев, когда они также являются Errors, которые являются необъявленными исключениями, а затем есть Exceptions, которые также являются Throwables и являются основным типом проверяемого исключения, за исключением того, что есть одно исключение из этого, также являются RuntimeExceptions, потому что это другой вид неконтролируемого исключения.

Для чего RuntimeException? Ну, точно так же, как следует из названия, они являются исключениями, как и все Exceptions, и они происходят во время выполнения, как и все исключения, за исключением того, что RuntimeExceptions являются исключительными по сравнению с другими run-time Exceptions, потому что они не должны произойти, кроме когда вы делаете какую-то глупую ошибку, хотя RuntimeExceptions никогда не Errorс, поэтому они предназначены для вещей, которые являются исключительно ошибочными, но которые на самом деле не являются Error. Кроме того RuntimeErrorException, что действительно RuntimeExceptionдля Errors. Но разве все исключения не должны представлять собой ошибочные обстоятельства? Да, все они. За ThreadDeathисключением исключительного исключительного исключения, поскольку в документации объясняется, что это «нормальное явление»,почему они сделали это типомError ,

В любом случае, поскольку мы делим все исключения на середину на Errors (которые являются исключениями исключительного исполнения, поэтому не Exceptionотмечены ) и s (которые для менее исключительных ошибок выполнения, поэтому проверены, за исключением случаев, когда они не являются), нам теперь нужны два различные виды каждого из нескольких исключений. Так что нам нужно , IllegalAccessErrorи IllegalAccessException, и , InstantiationErrorи InstantiationException, и , NoSuchFieldErrorи NoSuchFieldException, и , NoSuchMethodErrorи NoSuchMethodException, и ZipErrorи ZipException.

Кроме того, даже если исключение проверено, всегда есть (довольно простые) способы обмануть компилятор и выбросить его без проверки. Если вы делаете то, что вы можете получить UndeclaredThrowableException, за исключением других случаев, когда оно может быть выбрано как UnexpectedExceptionили UnknownException(которое не связано с тем UnknownError, что относится только к «серьезным исключениям»), или ExecutionException, или InvocationTargetException, или, или ExceptionInInitializerError.

О, и мы не должны забывать о запутанном новом Java 8 UncheckedIOException, который является RuntimeExceptionисключением, предназначенным для того, чтобы вы могли исключить концепцию проверки исключений из окна, обернув проверенные IOExceptionисключения из-за ошибок ввода-вывода (которые не вызывают IOErrorисключения, хотя это существует тоже), которые чрезвычайно трудно обрабатывать, и поэтому вам нужно, чтобы они не проверялись.

Спасибо Java!


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

С другой стороны, исключение из ядра Java API должны вообще быть проверены , если они могли бы когда - либо обращаться. Возьмите FileNotFoundExceptionили (мой любимый) InterruptedException. Эти условия должны почти всегда обрабатываться конкретно (т. Е. Ваша реакция на InterruptedExceptionне совпадает с вашей реакцией на IllegalArgumentException). Тот факт, что ваши исключения проверены, заставляет разработчиков задуматься о том, является ли состояние обработанным или нет. (Тем не менее, я редко видел InterruptedExceptionобработанные правильно!)

Еще одна вещь - RuntimeExceptionне всегда «где разработчик получил что-то не так». Незаконное исключение аргумента отбрасывается , когда вы попробуете и создать с enumиспользованием valueOfи там нет enumэтого имени. Это не обязательно ошибка разработчика!





checked-exceptions