Java - где и как следует использовать исключения? - PullRequest
10 голосов
/ 04 октября 2009

Я читал кое-что об обработке исключений в Java, чтобы иметь возможность писать лучший код. ОК, я признаю, я виновен; Я использовал слишком много блоков try-catch {}, я использовал ex.printStackTrace() в перехвате, даже не используя надлежащий регистратор (на самом деле System.out и System.err были перенаправлены на PrintWriter, поэтому журнал был создан). Однако после нескольких часов чтения я нахожусь в странном месте: неизвестном. Если исключения предназначены для передачи информации об аномальных состояниях потока, как узнать, ГДЕ это подходящий уровень, чтобы что-то делать с этой информацией?

Например, когда возникает ошибка базы данных, следует ли возвращать нулевое значение или код ошибки или выдавать исключение? Если выброшено, ГДЕ должно обрабатываться это исключение? Я понимаю, что бесполезно даже регистрировать исключение, если вы ничего не можете с этим поделать. Тем не менее, в приложениях с графическим интерфейсом, которые могут легко убить ваш графический интерфейс (я использую SWT, и я видел это слишком часто), даже в случае метода menuShown() (исключение ArrayIndexOutOfBounds закроет приложение, если нет обрабатываются). Пример может продолжаться вечно, но вот краткое изложение вопросов:

  1. Влияет ли чрезмерное использование try-catch () на производительность негативно?
  2. Лучше использовать определенные типы исключений? Что делать, если я пропустил ловить один из возможных X типов исключений, которые могут возникнуть?
    Честно говоря, я слышал и использую всего 10%, я думаю о стандартных исключениях Java, через 2-3 года. Да, кто-то сказал, что если вызывающий не знает, как обращаться с выданными исключениями, он НЕ ДОЛЖЕН ПРАВО вызывать метод throwing. Это правильно?
  3. Я читал эту статью Андерс Хейлсберг , говоря, что проверенные исключения - это плохо. Должно ли это указывать на то, что в некоторых случаях рекомендуется удобное проглатывание исключений?
  4. Картинка стоит 1000 слов; Я думаю, что некоторые примеры очень помогут здесь.

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

Ответы [ 9 ]

7 голосов
/ 04 октября 2009

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

  1. Нет, чрезмерное использование try / catch не повлияет на производительность
  2. Использование наиболее конкретного типа исключения, которое вы можете. Например, вы вообще не должны бросать Exception, если можете избежать этого. Выбрасывая определенный тип, вы даете пользователю знать, что может пойти не так. Тем не менее, вы можете перебросить его как нечто более общее, так что вызывающим абонентам, которые не имеют отношения к конкретному исключению, не нужно знать об этом (например, GUI не будет заботиться, если это IOException против ArrayIndexOutOFBoundsException).
  3. Вы найдете людей, которым больше нравятся проверенные исключения, и вы найдете людей, которым больше нравится непроверенные. В общем, я стараюсь использовать непроверенные исключения, потому что, как правило, с большинством проверенных исключений вы мало что можете сделать, и вы все равно можете обрабатывать непроверенные исключения, просто не нужно. Я часто перебрасываю проверенные исключения, так как не могу с ними ничего поделать (другая стратегия - перехватить проверенное исключение и перебросить его как непроверенное, поэтому классам, находящимся выше в цепочке, не нужно его перехватывать, если они не хотят ).

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

Чтобы привести несколько примеров:

try {
   // some database operation
}
catch (IOException ex) {
   // retry the database operation. then if an IO exception occurs rethrow it. this shows an example doing something other than just catch, logging and/or rethrowing.       
}

Я буду рад углубиться в любую часть этого, если хотите.

3 голосов
/ 04 октября 2009

Много хороших ответов, позвольте мне добавить пару моментов, которые не были упомянуты.

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

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

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

Простое правило: если у вас есть два или более исключений, которые ВСЕГДА обрабатываются с идентичным дублирующимся кодом, объедините их в одно исключение. Если ваш блок catch выполняет дополнительную проверку, чтобы выяснить, что это за ошибка, это должно быть два (или более) класса исключений. Я видел код, который выполняет exception.getMessage, а затем ищет ключевые слова в сообщении, чтобы выяснить, в чем проблема. Это безобразно Сделайте несколько исключений и сделайте это чисто.

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

(a) Это позволяет избежать проблемы «магических» возвращаемых значений, например, ненулевая строка - это реальный ответ, но нулевой означает, что произошла ошибка. Или, что еще хуже, «NF» означает, что файл не найден, «NV» означает недопустимый формат, а все остальное - настоящий ответ. За исключением, исключение является исключением, а возвращаемое значение является возвращаемым значением.

(b) Исключения аккуратно пропускают основную строку кода. Обычно, когда есть ошибка, вы хотите пропустить целую кучу обработки, которая не имеет смысла без правильных данных, и перейти прямо к отображению сообщения об ошибке и выходу из системы, или к повторной попытке выполнить операцию, или к тому, что подходит. В старых дурных штампах мы писали "GOTO Panic-Abort". GOTO опасны по всем причинам, о которых много говорили. Исключения исключают то, что, возможно, было последней из оставшихся веских причин использовать GOTO.

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

1 голос
/ 04 октября 2009

Пожалуйста, не возвращайте ноль в случае нефатальных ошибок. Вместо этого верните NullObject.

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

1 голос
/ 04 октября 2009

Исключение есть, поэтому программисту Задачи не приходится решать проблему самостоятельно. (1): В случае, если проблема НЕ является ЛОГИЧЕСКОЙ для него, чтобы справиться в Задаче. Задача чтения строки из потока не должна обрабатывать ошибку диска, не так ли? Но это должно быть очень логично, если данные не содержат String.

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

(3): не существует логического (или управляемого) способа различать различные доходы. Если задача не может прочитать файл и вернуть ноль. А что, если файл в неправильном формате, тоже вернуть null? Как эти два могут отличаться? Исключения могут использоваться, чтобы отличать это. Вот почему это называется Исключением: -D.

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


interface DBAccess {
    public Result accessDB();
}

class DBOperation {
    static public void DoOperation(DBAccess pAccess) {
        try { return DBAccess.accessDB(); }
        catch(InvalidDBPasswordException IPE) {
             // Do anything about invalid password
        }
        catch(DBConnectionLostException DBCLE) {
             // Do anything about database connection lost
        }
        // Catch all possible DB problem
    }
}

...

private User[] ShowUserList_and_ReturnUsers() {
    // Find the used.

    // Show user list

    if (Users.count() == 0)
         return null;
    else return Users;

    // No need to handle DB connection problem here
}
private User[] GetUserProfile() {
    // Find the used and return
    // No need to handle DB connection problem here
}
...

/** An onClick event to show user list */ {
    DBOperation.DoOperation(new DBAccess() {
        public Result accessDB() {
            return ShowUserList_and_ReturnUsers();
        }
    });
}
/** An onClick event to show a user profile */ {
    DBOperation.DoOperation(new DBAccess() {
        public Result accessDB() {
            return GetUserProfile();
        }
    });
}
... Many more DB access

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



for(int i = 0; i < Users.length; i++) {
    User aUser = Users[i];
    // Do something with user
}

Replaced with

try {
  for(int i = 0; ; i++) {
      User aUser = Users[i];
      // Do something with user
  }
}
catch(ArrayOutOfBoundException AOBE) {}

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

<Ч />

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

(1). Использование чрезмерного try-catch () отрицательно влияет на производительность? Ответ: Согласно «Эффективной Java», насколько я помню, он имеет очень крошечный эффект (только не очень хороший в цикле) (сейчас у меня нет книги со мной).

(2). Использование определенных типов исключений лучше? Ответ: Для конкретного пользователя лучше избегать решения неправильной проблемы.

Что если я пропустил один из возможных типов исключений X, которые могут возникнуть? Честно говоря, я слышал и использую всего 10%, я думаю о стандартных исключениях Java, через 2-3 года. Ответ: Точно так же, как если вы обрабатываете ошибку без исключения, вы тоже можете ее пропустить. Вы просто добавляете это, когда узнаете это.

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

(3). Я прочитал эту статью Андерса Хейлсберга, в которой говорится, что проверенные исключения - это плохо. Должно ли это указывать на то, что в некоторых случаях рекомендуется удобное проглатывание исключений? Ответ: Я думаю, что он говорит о «проверке исключения» как о функции для компилятора, чтобы гарантировать, что какое-то исключение должно быть обработано. Идея иметь исключение.

(4). Картинка стоит 1000 слов ... я думаю, что некоторые примеры очень помогут здесь. Ответ: код выше.

Я бегу сейчас ... Извините ... :-p (Будь там через минуту, дорогая !!)

1 голос
/ 04 октября 2009

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

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

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

Как я думаю, они сказали, что не имеет смысла объяснять пользователю, что такой кластер БД вышел из строя из-за недоступности DNS или из-за переполнения диска. На этом уровне произошло нечто, что не позволило завершить транзакцию, это все, что должен знать пользователь.

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

1 голос
/ 04 октября 2009

Возвращаемое значение против выдачи исключения

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

Влияние на производительность.

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

Особые исключения

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

проверено

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

Пример

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

for (int retries = 0;; retries++) {
    try {
        commandService.execute(command);
        return;
    } catch (Exception e}
        Log.error(e);
        if (retries < 3) {
            continue;
        } else {
            saveForAnalysis(command, e);
            alertOperator();
            return;
        }
    }
}

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

1 голос
/ 04 октября 2009
  • Использование чрезмерного try-catch () отрицательно влияет на производительность?

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

  • Использование определенных типов исключений лучше?Что если я пропустил один из возможных типов исключений X, которые могут возникнуть?Честно говоря, я слышал и использую всего 10%, я думаю о стандартных исключениях Java, через 2-3 года.Да, кто-то сказал, что если вызывающий не знает, как обращаться с отклоненными исключениями, он не должен иметь права вызывать метод броска.Это правда?

Я не уверен, что понял вопрос, но скажу: «Если вы не знаете, что делать с исключением, повторите его».

  • Я прочитал эту статью Андерса Хейлсберга, в которой говорится, что проверенные исключения - это плохо.Должно ли это указывать на то, что в некоторых случаях рекомендуется удобное проглатывание исключений?

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

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

Spring's DataAccessException - очень хороший пример.Проверьте главу 10.Поддержка DAO .

1 голос
/ 04 октября 2009
  1. Хотя у меня нет цифр, я не верю, что try-catch оказывает какое-либо существенное влияние на производительность (не то, что я видел). Я думаю, что если вы не столкнетесь со многими исключениями, влияние на производительность будет в основном ничем. Но, в любом случае, лучше сначала позаботиться о правильной реализации кода и о хорошей производительности во-вторых, гораздо проще выполнить вторую после того, как первая будет выполнена.

  2. Я думаю, что класс исключения должен быть конкретным относительно того, что на самом деле является исключением. Проблема с SQLExceptions в Java заключается в том, что они не дают вам никакой информации о том, что действительно пошло не так. Spring использует гораздо больше описательных исключений для баз данных (исключений взаимоблокировок, исключений целостности данных и т. Д.). Таким образом, вы можете определить, в чем на самом деле была проблема.

  3. Проверенные исключения могут раздражать, но я не думаю, что они всегда плохие. Например, Spring использует непроверенные исключения для ошибок базы данных, но я все еще проверяю их и либо 1) обрабатываю их прямо там, если это возможно, либо 2) заключаю в более общее исключение, которое показывает, что компонент не прошел. 1012 *

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

1 голос
/ 04 октября 2009

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

Например, у нас есть ValidationException для обработки ошибок валидации. У нас есть ApplicationException для обработки системных ошибок.

Вы хотите минимизировать количество попыток. В нашем случае у нас будет валидатор, который соберет ВСЕ проверки в объектах «InvalidValue», а затем сгенерирует единственное исключение ValidationException с информацией о недопустимом значении, включенном в него. Затем вы можете сообщить пользователю, какие поля были ошибочными и т. Д.

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

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

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