Я пытаюсь убедить руководство своей команды разрешить использование исключений в C ++ вместо возврата bool isSuccessful
или enum с кодом ошибки. Однако я не могу противостоять его критике.
Рассмотрим эту библиотеку:
class OpenFileException() : public std::runtime_error {
}
void B();
void C();
/** Does blah and blah. */
void B() {
// The developer of B() either forgot to handle C()'s exception or
// chooses not to handle it and let it go up the stack.
C();
};
/** Does blah blah.
*
* @raise OpenFileException When we failed to open the file. */
void C() {
throw new OpenFileException();
};
Рассмотрим разработчика, вызывающего
B()
функцию. Он проверяет документацию и видит, что она не возвращает никаких исключений, поэтому он не пытается ничего поймать. Этот код может привести к сбою программы в процессе производства.Рассмотрим разработчика, вызывающего
C()
функцию. Он не проверяет документацию, поэтому не ловит никаких исключений. Вызов небезопасен и может привести к аварийному завершению работы программы.
Но если мы проверим ошибки таким образом:
void old_C(myenum &return_code);
Разработчик, использующий эту функцию, будет предупрежден компилятором, если он не предоставит этот аргумент, и он скажет: «Ага, это возвращает код ошибки, который я должен проверить».
Как я могу безопасно использовать исключения, чтобы был какой-то контракт?
источник
Either
/Result
monad для возврата ошибки в безопасном для компоновки видеОтветы:
Это законная критика исключений. Они часто менее заметны, чем простая обработка ошибок, такая как возврат кода. И нет простого способа обеспечить соблюдение «контракта». Отчасти дело в том, чтобы позволить вам перехватывать исключения на более высоком уровне (если вам нужно перехватывать каждое исключение на каждом уровне, насколько оно отличается от возврата кода ошибки, в любом случае?). А это значит, что ваш код может быть вызван другим кодом, который не обрабатывает его должным образом.
Исключения имеют свои недостатки; Вы должны сделать случай, основанный на экономической выгоде.
Я нашел эти две статьи полезными: необходимость исключений и все неправильно с исключениями. Кроме того, эта запись блога предлагает мнения многих экспертов об исключениях с упором на C ++ . Хотя мнение эксперта, похоже, склоняется в пользу исключений, оно далеко от четкого консенсуса.
Что касается убеждения лидера вашей команды, это не может быть правильным выбором. Особенно с устаревшим кодом. Как отмечено во второй ссылке выше:
Добавление небольшого количества кода, который использует исключения для проекта, который в основном не делает, вероятно, не будет улучшением. Не использовать исключения в хорошо написанном коде - это далеко не катастрофическая проблема; это может не быть проблемой, в зависимости от приложения и специалиста, которого вы спрашиваете. Вы должны выбрать свои сражения.
Вероятно, это не тот аргумент, на который я бы потратил усилия - по крайней мере, до тех пор, пока не начнется новый проект. И даже если у вас есть новый проект, будет ли он использоваться или использоваться любым устаревшим кодом?
источник
На эту тему написаны буквально целые книги, поэтому любой ответ в лучшем случае будет кратким. Вот некоторые из важных моментов, которые, на мой взгляд, стоит сделать, основываясь на вашем вопросе. Это не исчерпывающий список.
Исключения не предназначены для повсеместного распространения.
Пока в основном цикле есть общий обработчик исключений - в зависимости от типа приложения (веб-сервер, локальный сервис, утилита командной строки ...) - у вас обычно есть все необходимые обработчики исключений.
В моем коде есть только несколько операторов catch - если они вообще есть - вне основного цикла. И это похоже на общий подход в современном C ++.
Исключения и коды возврата не являются взаимоисключающими.
Вы не должны делать это «все или ничего». Исключения следует использовать для исключительных ситуаций. Такие вещи, как «Config file not found», «Disk Full» или что-либо еще, что не может быть обработано локально.
Распространенные ошибки, такие как проверка правильности имени файла, предоставленного пользователем, не являются прецедентом для исключений; Вместо этого используйте возвращаемое значение в этих случаях.
Как видно из приведенных выше примеров, «файл не найден» может быть либо исключением, либо кодом возврата, в зависимости от варианта использования: «является частью установки», а «пользователь может сделать опечатку».
Так что нет абсолютного правила. Грубый принцип: если он может обрабатываться локально, сделать его возвращаемым значением; если вы не можете обработать это локально, выведите исключение.
Статическая проверка исключений бесполезна.
Так как исключения все равно не должны обрабатываться локально, обычно не важно, какие исключения могут быть выброшены. Единственная полезная информация - может ли быть выдано какое-либо исключение.
В Java есть статическая проверка, но обычно это считается неудачным экспериментом, и большинство языков, особенно C #, не имеют такого типа статической проверки. Это хорошее прочтение о причинах, почему в C # его нет.
По этим причинам C ++ устарел
throw(exceptionA, exceptionB)
в пользуnoexcept(true)
. По умолчанию функция может выдавать, поэтому программисты должны ожидать этого, если в документации явно не указано иное.Написание безопасного кода исключений не имеет ничего общего с написанием обработчиков исключений.
Я бы скорее сказал, что написание безопасного кода исключений - это все о том, как избежать написания обработчиков исключений!
Большинство лучших практик предназначено для уменьшения числа обработчиков исключений. Написание кода один раз и автоматический вызов его - например, через RAII - приводит к меньшему количеству ошибок, чем копирование-вставка одного и того же кода повсюду.
источник
IOException
s тоже . Проверенное деление произвольно и бесполезно.Программисты C ++ не ищут спецификации исключений. Они ищут гарантии исключений.
Предположим, что фрагмент кода выдал исключение. Какие предположения может сделать программист, которые все еще будут действительны? Что написано в коде, что гарантирует код после исключения?
Или это возможно, что определенный кусок кода может гарантировать, что никогда не выбросит (то есть, ничего, кроме процесса ОС будет прекращено)?
Слово «откат» часто встречается в дискуссиях об исключениях. Возможность возврата к допустимому состоянию (которое явно задокументировано) является примером гарантии исключения. Если нет гарантии исключения, программа должна завершиться на месте, потому что даже не гарантируется, что любой код, который она выполнит после этого, будет работать как задумано - скажем, если память была повреждена, любая дальнейшая операция является технически неопределенным поведением.
Различные методы программирования на C ++ обеспечивают гарантии исключений. RAII (управление ресурсами на основе области действия) предоставляет механизм для выполнения кода очистки и обеспечивает высвобождение ресурсов как в обычных, так и в исключительных случаях. Создание копии данных перед выполнением изменений на объектах позволяет восстановить состояние этого объекта в случае сбоя операции. И так далее.
Ответы на этот вопрос StackOverflow дают представление о том, как долго программисты на C ++ разбираются во всех возможных режимах сбоев, которые могут случиться с их кодом, и пытаются защитить достоверность состояния программы, несмотря на сбои. Строковый анализ кода C ++ становится привычкой.
При разработке на C ++ (для производственного использования) никто не может позволить себе замаскировать детали. Кроме того, двоичный двоичный объект (не с открытым исходным кодом) - проклятие программистов на C ++. Если мне нужно вызвать какой-нибудь двоичный двоичный объект, и этот двоичный объект не работает, то обратный инжиниринг - это то, что программист C ++ сделает дальше.
Ссылка: http://en.cppreference.com/w/cpp/language/exceptions#Exception_safety - см. В разделе «Безопасность исключений».
В C ++ не удалось реализовать спецификации исключений. Более поздний анализ на других языках говорит, что спецификации исключений просто не практичны.
Почему это неудачная попытка: для строгого соблюдения она должна быть частью системы типов. Но это не так. Компилятор не проверяет спецификацию исключений.
Почему C ++ выбрал это, и почему опыт из других языков (Java) доказывает, что спецификация исключений является спорным: Как один изменить реализацию функции (например, он должен сделать вызов другой функции, которая может бросить новый вид исключение), строгое соблюдение спецификации исключений означает, что вы также должны обновить эту спецификацию. Это распространяется - вам может понадобиться обновить спецификации исключений для десятков или сотен функций, что является простым изменением. Ситуация ухудшается для абстрактных базовых классов (эквивалент интерфейсов C ++). Если спецификация исключений применяется к интерфейсам, реализациям интерфейсов не разрешается вызывать функции, которые генерируют различные типы исключений.
Ссылка: http://www.gotw.ca/publications/mill22.htm
Начиная с C ++ 17, этот
[[nodiscard]]
атрибут можно использовать для возвращаемых значений функции (см .: https://stackoverflow.com/questions/39327028/can-ac-function-be-declared-such-that-the-return-value не может быть проигнорировано ).Так что, если я сделаю изменение кода, и это вводит новый тип условия отказа (то есть новый тип исключения), будет ли это критическое изменение? Должен ли он был заставить вызывающего абонента обновить код или хотя бы получить предупреждение об изменении?
Если вы принимаете аргументы, что программисты на C ++ ищут гарантии исключений вместо спецификаций исключений, тогда ответ таков: если условие сбоя нового типа не нарушает какие-либо исключения, гарантирует код, обещанный ранее, это не является критическим изменением.
источник
std::exception
будут выброшены стандартные исключения ( ) и ваше собственное базовое исключение. Но когда применяется ко всему проекту, это просто означает, что каждая функция будет иметь такую же спецификацию: шум.catch
она основана на типе объекта исключения, когда во многих случаях важна не столько прямая причина исключения, сколько то, что оно подразумевает в отношении состояние системы. К сожалению, тип исключения, как правило, ничего не говорит о том, вызвало ли оно прерывистый выход кода из кода таким образом, чтобы объект оставался в частично обновленном (и, следовательно, недействительном) состоянии.Это абсолютно безопасно. Ему не нужно ловить исключения в каждом месте, он может просто сделать попытку / поймать в месте, где он действительно может сделать что-то полезное. Это небезопасно, только если он позволяет ему вытекать из потока, но обычно нетрудно предотвратить это.
источник
C()
и, следовательно, не защищает от кода после пропуска вызова. Если этот код требуется для гарантии того, что некоторая инвариантность остается верной, эта гарантия теряет силу при первом появлении исключенияC()
. Не существует такого понятия, как совершенно безопасное для исключений программное обеспечение, и, скорее всего, не там, где исключений никогда не ожидали.C()
возникнет исключение - большая ошибка. По умолчанию в C ++ любая функция может выдавать - для этого требуется явное указаниеnoexcept(true)
компилятору иначе. В отсутствие этого обещания программист должен предположить, что функция может сгенерировать.C()
функций, которые прерываются при наличии исключений. Это действительно очень неразумная вещь, она обречена на массу проблем.Если вы создаете критическую систему, подумайте о том, чтобы следовать советам руководителя группы и не использовать исключения. Это правило AV 208 в стандартах кодирования C ++ компании Lockheed Martin, разработанных компанией Joint Strike Fighter . С другой стороны, в руководствах MISRA C ++ есть очень конкретные правила, касающиеся того, когда исключения могут и не могут использоваться, если вы создаете MISRA-совместимую программную систему.
Если вы создаете критические системы, скорее всего, вы также используете инструменты статического анализа. Многие инструменты статического анализа будут предупреждать, если вы не проверяете возвращаемое значение метода, что делает очевидным случаи пропущенной обработки ошибок. Насколько мне известно, подобная поддержка инструментов для обнаружения правильной обработки исключений не так сильна.
В конечном счете, я бы сказал, что проектирование с помощью контрактного и оборонительного программирования в сочетании со статическим анализом является более безопасным для критических программных систем, чем исключения.
источник