Действительно ли исключения для исключительных ошибок?
Насколько я понимаю, здравый смысл подсказывает использовать исключения только для действительно исключительных условий (на самом деле, я видел это утверждение здесь в SO несколько раз).
Однако Кшиштоф Квалина говорит:
Одно из самых больших заблуждений об исключениях заключается в том, что они предназначены для "исключительных условий". Реальность такова, что они предназначены для сообщения об ошибках. С точки зрения дизайна каркаса не существует такого понятия, как "исключительное условие". Является ли условие исключительным или нет, зависит от контекста использования, но повторно используемые библиотеки редко знают, как они будут использоваться. Например, OutOfMemoryException может быть исключительным для простого приложения ввода данных; это не так уж и необычно для приложений, выполняющих собственное управление памятью (например, SQL-сервер). Другими словами, исключительное состояние одного человека является хроническим состоянием другого человека.
Затем он также говорит, что исключения должны использоваться для:
- Ошибки использования
- Программные ошибки
- Системные сбои
Учитывая, что Кшиштоф Квалина - руководитель группы CLR в MS, я спрашиваю: что вы думаете о его заявлении?
15 ответов
Это звучит слишком упрощенно, но я думаю, что имеет смысл просто использовать исключения там, где они уместны. В таких языках, как Java и Python, исключения очень распространены, особенно в определенных ситуациях. Исключения подходят для типа ошибок, которые вы хотите всплывать через путь кода и заставлять разработчика явно ловить. В моем собственном кодировании я считаю подходящим моментом добавить исключение, когда ошибка либо не может быть проигнорирована, либо просто более элегантно генерировать исключение вместо возврата значения ошибки при вызове функции и т. Д.
Некоторые из наиболее подходящих мест для исключений, которые я могу придумать не по месту:
- NotImplementedException - очень подходящий способ указать, что определенный метод или функция недоступен, вместо простого возврата без каких-либо действий.
- Исключения OutOfMemory - сложно представить лучший способ обработки этого типа ошибок, поскольку он представляет собой ошибку выделения памяти для всего процесса или ОС. Это важно иметь дело, конечно!
- NullPointerException - Доступ к нулевой переменной - ошибка программиста, и IMO - это еще одно хорошее место, чтобы заставить ошибку всплыть на поверхность
- ArrayIndexException - В неумолимом языке как C переполнение буфера катастрофически. Более хорошие языки могут возвращать нулевое значение некоторого типа или, в некоторых реализациях, даже обтекать массив. На мой взгляд, исключение - гораздо более элегантный ответ.
Это ни в коем случае не исчерпывающий список, но, надеюсь, он иллюстрирует это. Используйте исключения, где они элегантны и логичны. Как всегда в программировании, правильный инструмент для правильной работы - хороший совет. Нет смысла сходить с ума - безумно даром, но столь же неразумно полностью игнорировать мощный и элегантный инструмент в вашем распоряжении.
Для людей, которые пишут фреймворки, возможно, это интересно.
Для остальных из нас это сбивает с толку (и, возможно, бесполезно). Для обычных приложений исключения должны быть выделены как "исключительные" ситуации. Исключения прерывают обычное последовательное представление вашей программы.
Вы должны быть осторожны с нарушением обычной последовательной последовательной обработки вашей программы. Обработка исключений - намеренно - трудна для чтения. Поэтому зарезервируйте исключения для вещей, которые находятся за пределами стандартных сценариев.
Пример: не используйте исключения для проверки ввода пользователя. Люди постоянно вводят ошибки. Это не исключение, поэтому мы пишем программное обеспечение. Вот для чего нужны if-заявления.
Когда ваше приложение получает исключение OutOfMemory, нет смысла его перехватывать. Это исключительное. Предположение о "последовательном выполнении" находится вне окна. Ваше приложение обречено, просто рухните и надейтесь, что ваша транзакция СУБД завершится до вашего сбоя.
Действительно трудно понять, что именно означает "исключительное условие", которое оправдывает использование исключения в программе.
Один пример, который очень полезен для сообщения о причине ошибок. Как говорит цитата Кшиштофа Квалины:
Одно из самых больших заблуждений об исключениях заключается в том, что они предназначены для "исключительных условий". Реальность такова, что они предназначены для сообщения об ошибках.
Чтобы привести конкретный пример, скажем, у нас есть getHeader(File f)
метод, который читает заголовок из файла и возвращает FileHeader
объект.
Может быть несколько проблем, которые могут возникнуть при попытке чтения данных с диска. Возможно, указанный файл не существует, файл содержит данные, которые не могут быть прочитаны, непредвиденные ошибки доступа к диску, нехватка памяти и т. Д. Наличие нескольких средств сбоя означает, что должно быть несколько способов сообщить о том, что пошло не так.
Если исключения не использовались, но необходимо было сообщить, какая ошибка произошла, с помощью сигнатуры текущего метода, лучшее, что мы можем сделать, это вернуть null
, С получением null
не очень информативен, лучшее сообщение, которое мы получаем из этого результата, это то, что "произошла какая-то ошибка, поэтому мы не могли продолжить, извините". - Это не сообщает причину ошибки.
(Или, в качестве альтернативы, у нас могут быть константы класса для объектов FileHeader, которые указывают условия FileNotFound и т. Д., Эмулируя коды ошибок, но это действительно пахнет наличием логического типа с TRUE
, FALSE
, FILE_NOT_FOUND
.)
Если бы мы получили FileNotFound
или же DeviceNotReady
исключение (гипотетическое), по крайней мере, мы знаем, каков был источник ошибки, и если бы это было приложение конечного пользователя, мы могли бы обработать ошибку способами, чтобы решить проблему.
Использование механизма исключений дает средство связи, которое не требует возврата к использованию кодов ошибок для уведомления о состояниях, которые не входят в обычный поток выполнения.
Однако это не означает, что все должно обрабатываться исключениями. Как отметил С. Лотт:
Например, не используйте исключения для проверки ввода пользователя. Люди постоянно делают ошибки. Вот для чего нужны if-заявления.
Это одна вещь, которая не может быть подчеркнута достаточно. Одной из опасностей незнания того, когда именно следует использовать исключения, является склонность к тому, чтобы быть счастливыми; используя исключения, где будет достаточно проверки ввода.
Там действительно нет смысла определять и бросать InvalidUserInput
исключение, когда все, что требуется для решения такой ситуации, - это уведомление пользователя о том, что ожидается в качестве ввода.
Кроме того, следует отметить, что пользовательский ввод, как ожидается, будет иметь ошибочный ввод в некоторый момент. Это защитная мера для проверки ввода перед передачей ввода из внешнего мира внутренним частям программы.
Немного трудно решить, что является исключительным, а что нет.
Поскольку я обычно программирую на Python, и на этом языке исключения встречаются повсюду, для меня исключение может представлять что-либо от системной ошибки до вполне допустимого условия.
Например, "pythonic" способ проверить, содержит ли строка целое число, состоит в том, чтобы попробовать int (theString) и посмотреть, вызывает ли оно исключение. Это "исключительная ошибка"?
Опять же, в Python цикл for всегда рассматривается как действующий на итераторе, и итератор должен вызвать исключение "StopIteration", когда завершает свою работу (цикл for перехватывает это исключение). Это "исключение" каким-либо образом?
Если вы практикуете "говорите, не спрашивайте", то исключение - это просто то, как программа говорит "я не могу этого сделать". Это "исключительный" в том смысле, что вы говорите "делать X", и он не может делать X. Простая ситуация обработки ошибок. В некоторых языках довольно распространено работать таким образом, в Java и C++ люди имеют другие мнения, потому что исключения становятся довольно дорогостоящими.
Общее: исключение просто означает "я не могу"
Прагматично: ... если вы можете позволить себе так работать на своем языке.
Гражданство: ... и ваша команда это позволяет.
Вот определение для исключения: Исключением является событие, которое происходит во время выполнения программы, которое нарушает нормальный поток инструкций программы.
Поэтому ответить на ваш вопрос нет. Исключением являются разрушительные события, которые могут быть или не быть исключительными. Мне нравится это определение, оно простое и работает каждый раз - если вы покупаете исключения, как я. Например, пользователь отправляет неверный un/pw, или у вас неверный аргумент / неверный ввод пользователя. Исключение здесь является самым простым способом решения этих проблем, которые являются разрушительными, но не исключительными и даже непредвиденными.
Их, вероятно, следовало бы назвать разрушениями, но эта лодка отплыла.
Я думаю, что чем ближе вы к земле, тем менее уместны исключения в качестве средства сообщения об ошибках. При более высокой абстракции, такой как в Java или.net, исключение может сделать элегантный способ передачи сообщений об ошибках вызывающим абонентам. Это, однако, не относится к C. Это также является концептуальным решением против разработки API.
Я думаю, что есть несколько веских причин, по которым исключения должны использоваться для выявления неожиданных проблем.
Во-первых, они создают объект для инкапсуляции исключения, что по определению должно сделать его намного дороже, чем обработка простого оператора if. В качестве примера Java вы должны вызывать File.exists(), а не ожидать и обрабатывать исключение FileNotFoundException.
Во-вторых, исключения, которые перехватываются за пределами текущего метода (или, возможно, даже класса), значительно затрудняют чтение кода, чем если бы в одном методе была вся обработка.
Сказав это, я лично люблю исключения. Они освобождают вас от необходимости явной обработки всех тех ошибок типа "может случиться, но, вероятно, никогда не будет", что заставляет вас многократно писать "print-an-error-and-abort-on-non-zero-return-return-" обработка кода каждого вызова метода.
Суть в том, что... если вы можете разумно ожидать, что это произойдет, то это часть вашего приложения, и вы должны написать для нее код. Все остальное является исключением.
Я сам задавался вопросом об этом. Что мы подразумеваем под "исключительным"? Может быть, нет строгого определения, но есть ли какие-то практические правила, которые мы можем использовать, чтобы решить, что является исключительным в данном контексте?
Например, было бы справедливо сказать, что "исключительное" условие - это условие, нарушающее контракт функции?
Я думаю, он прав. Посмотрите на разбор числа в Java. Вы не можете даже проверить входную строку перед анализом. Вы вынуждены анализировать и извлекать NFE, если что-то пошло не так. Ошибка разбора - это что-то исключительное? Я думаю нет.
То, к чему это сводится, - то, что инструмент необходим, чтобы сделать работу.
Исключения - очень мощный инструмент. Перед их использованием спросите, нужна ли вам эта сила и сложность, которая с ней связана.
Исключения могут показаться простыми, потому что вы знаете, что при попадании в строку с исключением все останавливается. Что здесь происходит, хотя?
Произойдет ли необъяснимое исключение?
Будет ли исключение поймано глобальной обработкой ошибок?
Будет ли исключение обрабатываться более детальной и детальной обработкой ошибок?
Вы должны знать все в стеке, чтобы знать, что будет делать это исключение. Это нарушает концепцию независимости. Этот метод теперь зависит от обработки ошибок, чтобы делать то, что вы ожидаете.
Если у меня есть метод, мне все равно, что находится за пределами этого метода. Меня должно волновать только то, что представляет собой ввод, как его обрабатывать и как вернуть ответ.
Когда вы используете исключение, которое, по сути, говорите, мне все равно, что здесь произойдет, что-то пошло не так, и я не хочу, чтобы оно стало хуже, сделайте все, что нужно, чтобы смягчить проблему.
Теперь, если вам небезразлично, как обработать ошибку, вы должны подумать и встроить ее в интерфейс метода, например, если вы пытаетесь найти какой-либо объект, возможно, вернуть объект по умолчанию для этого объекта, если он не может быть найден, а чем выбрасывание какого-то исключения типа "Объект не найден".
Когда вы встраиваете обработку ошибок в интерфейс ваших методов, подпись этого метода не только более наглядно описывает, что он может делать, но и возлагает ответственность за обработку ошибки на вызывающую функцию. Вызывающий метод может быть в состоянии работать через него или нет, и он будет сообщать снова вверх по цепочке, если нет. В конце концов вы достигнете точки входа приложения. Теперь было бы целесообразно создать исключение, поскольку вы лучше понимаете, как будут обрабатываться исключения, если вы работаете с открытым интерфейсом приложений.
Позвольте мне привести пример обработки ошибок для веб-службы.
Уровень 1. Глобальная обработка ошибок в global.asax - это защитная сеть для предотвращения необработанных исключений. Этого никогда не следует намеренно достигать.
Уровень 2. Метод веб-службы. Обернутый в try/catch, чтобы гарантировать, что он всегда будет соответствовать своему интерфейсу json.
Уровень 3. Рабочие методы - они получают данные, обрабатывают их и возвращают их в метод веб-службы.
В рабочих методах не следует создавать исключение. Да, у меня есть встроенная обработка ошибок метода веб-службы, но этот метод можно использовать в других местах, где он может не существовать.
Вместо этого, если рабочий метод используется для получения записи и запись не может быть найдена, он просто возвращает ноль. Метод веб-службы проверяет ответ и, когда он находит ноль, он знает, что не может продолжить. Метод веб-службы знает, что он имеет обработку ошибок для возврата json, поэтому создание исключения просто вернет подробности в json о том, что произошло. С точки зрения клиента замечательно, что он упакован в json, который легко анализируется.
Вы видите, что каждый кусок просто знает, что ему нужно делать, и делает это. Когда вы добавляете исключение в микс, вы захватываете поток приложений. Это не только приводит к тому, что коду трудно следовать, но и ответом на злоупотребление исключениями является попытка / улов. Теперь у вас больше шансов злоупотребить другим очень мощным инструментом.
Слишком часто я вижу, что try/catch ловит все в середине приложения, потому что разработчик испугался, что метод, который они используют, более сложен, чем кажется.
Утверждение, что исключения должны использоваться для исключительных обстоятельств, используется в "Effective Java Second Edition": одной из лучших книг по Java.
Беда в том, что это вырвано из контекста. Когда автор заявляет, что исключения должны быть исключительными, он только что показал пример использования исключений для завершения цикла while - неправильное использование исключений. Цитировать:
исключения, как следует из их названия, должны использоваться только для исключительных условий; они никогда не должны использоваться для обычного потока управления.
Так что все зависит от вашего определения "условия исключения". Вне контекста вы можете подразумевать, что его следует использовать очень редко.
Использование исключений вместо возврата кодов ошибок - это хорошо, тогда как использование их для реализации "умного" или "более быстрого" метода не годится. Обычно это означает "исключительное состояние".
Проверенное исключение - незначительные ошибки, которые не являются ошибками и не должны останавливать выполнение. ех. IO или синтаксический анализ файла Исключение непроверенное - программная "ошибка", которая не подчиняется контракту метода - напр. OutOfBoundsException. ИЛИ ошибка, которая делает продолжение выполнения очень плохой идеей - например, IO или разбор файла очень важного файла. Возможно, файл конфигурации.
Квалина имеет точку зрения. Будет хорошо определить случаи, когда код не будет работать (до предела)
Я согласен с С.Лоттом, что иногда валидация лучше, чем бросание исключения. Сказав это, OutOfMemory - это не то, что вы могли бы ожидать в своем приложении (если только оно не выделяет большую память и не нуждается в памяти для дальнейшей работы).
Я думаю, это зависит от домена приложения.
Я, конечно, считаю, что исключения должны использоваться только в том случае, если у вас есть исключительное условие.
Беда в определении "исключительный". Вот мой:
Условие является исключительным, если оно находится за пределами предполагаемого нормального поведения той части системы, которая вызывает исключение.
Это имеет некоторые последствия:
- Исключительно зависит от ваших предположений. Если функция предполагает, что ей переданы допустимые параметры, тогда выбрасывание исключения IllegalArgumentException - ОК. Однако, если контракт функции говорит, что она каким-то образом исправит ошибки ввода при вводе, то это использование "нормальное", и оно не должно вызывать исключения при ошибке ввода.
- Исключительно зависит от расслоения подсистемы. Функция сетевого ввода-вывода, безусловно, может вызвать исключение, если сеть разомкнута, поскольку она предполагает допустимое соединение. Однако следует ожидать, что брокер сообщений на основе ESB будет обрабатывать сброшенные соединения, поэтому, если он использует такую функцию сетевого ввода-вывода внутри себя, ему необходимо будет соответствующим образом перехватить и обработать ошибку. В случае, если это не очевидно, try / catch фактически эквивалентен подсистеме, говорящей "условие, которое является исключительным для одного из моих компонентов, на самом деле считается мной нормальным, поэтому я должен его обработать".
Заявление Кшиштофа Квалины немного вводит в заблуждение. Первоначальное утверждение относится к "исключительным условиям", для меня естественно, что я тот, кто определяет, что является исключительным или нет. Тем не менее, я думаю, что сообщение прошло через ОК, так как я думаю, что мы все говорим об исключениях "разработчика".
Исключения хороши для общения, но с небольшой иерархической структурой они также хороши для некоторого разделения проблем, особенно между уровнями (DAO, Business и т. Д.). Конечно, это полезно, только если вы по-разному относитесь к этим исключениям.
Хорошим примером иерархии является иерархия исключений доступа к данным Spring.