В языках, поддерживающих исключения, именно они являются основным механизмом для передачи информации об ошибках. Java пытается заставить программиста обрабатывать ошибки, проверяя во время компиляции, что исключения обрабатываются (или, по крайней мере, ответственность за обработку исключения делегируется вызывающей программе). Однако есть исключения, которые могут возбуждаться в самых разных местах, поэтому Java не требует, чтобы они обрабатывались. Типичный пример – NullPointerException. Это печально, так как любое исключение – признак логической ошибки; если оно возникло, то довольно трудно восстановить нормальную работу программы, пусть даже вы его перехватываете.
Но и для тех исключений, которые Java заставляет обработать, компилятор не в состоянии проконтролировать, что вы делаете это сколько–нибудь разумным образом. Часто в этом случае просто завершают программу, даже не пытаясь восстановиться, а это все тот же отказ от обслуживания. Еще того хуже и, как это ни грустно, гораздо более распространена практика включать пустой обработчик исключения, в результате чего оно распространяется дальше. Но об этом позже.
Неправильная интерпретация ошибок
Некоторые функции, например recv() (для чтения из сокета), ведут себя просто странно. recv() может вернуть одно из трех значений. В случае успешного завершения возвращается длина сообщения в байтах. Если в буфере сокета ничего нет и удаленный хост выполнил аккуратное размыкание соединения (orderly shutdown), то recv() возвращает 0. В противном случае возвращается–1, а в переменную errno записывается код ошибки.
Бесполезные возвращаемые значения
Некоторые функции из стандартной библиотеки С попросту опасны, например strncpy не возвращает никакого уведомления об ошибке, а лишь указатель на целевой буфер независимо от того, как завершилась операция копирования. Если в результате вызова произошло переполнение буфера, то вы получите указатель на переполненный буфер! Если вам нужен был довод в пользу отказа от использования этих ужасных функций С, так вот он!
Обработка не тех исключений, что нужно
В языках, поддерживающих исключения, надо внимательно относиться к тому, какие именно исключения вы обрабатываете. Например, стандарт С++ гласит:
Функция выделения памяти извещает об ошибке, возбуждая исключение bad_alloc. В этом случае никакая инициализация не проведена.
Но так, к сожалению, бывает не всегда. Например, в библиотеке Microsoft Foundation Classes оператор new в случае ошибки может возбуждать исключение CMemoryException, а многие современные компиляторы С++ (в том числе Microsoft Visual С++ и gcc) позволяют использовать спецификацию std::nothrow, чтобы предотвратить возбуждение исключения оператором new. Поэтому если ваша программа готова обрабатывать исключения типа FooException, а код внутри блока try/catch возбуждает Bar Exception, то программа завершится аварийно, ибо перехватывать Bar Exception некому. Разумеется, можно было бы перехватить все исключения, но это тоже плохо, а почему, мы расскажем в следующем разделе.
Обработка всех исключений
Казалось бы, тема этого раздела – обработка всех исключений – прямо противоположна названию греха «Пренебрежение обработкой ошибок», но на самом деле то и другое тесно связано. Обрабатывать все исключения так же плохо, как вообще не обрабатывать ошибки. Тем самым программа «глотает» ошибки, о которых ничего не знает, которые не может обработать или – и это самое страшное–которые маскируют логические дефекты. Если вы притворяетесь, что никакой ошибки не произошло, то скрытые «баги», о которых вы ничего не знаете, рано или поздно проявятся и программа «умрет» от «непостижимой» причины, да так, что отладить ее будет очень непросто.
Греховность C/C++
В примере ниже автор проверяет неинформативное значение, возвращенное функцией, – strncpy просто возвращает указатель на начало целевого буфера. Эта информация бесполезна.
...
char dest[19];
char *p = strncpy(dest, szSomeLongDataFromAHax0r, 19);
if (p) {
// все сработало отлично, поинтересуемся значением dest или p
}
Переменная р указывает на начало dest вне зависимости от того, что произошло внутри strncpy. А между тем эта функция не завершает буфер нулем, если длина исходных данных больше или равна размеру буфера dest. При взгляде на этот код закрадывается подозрение, что автор просто не понимает, что именно возвращает strncpy, ожидая, что в случае ошибки получит NULL. Ох, грехи наши тяжкие!
Следующий код тоже часто встречается на практике. Да, здесь возвращаемое значение проверяется, но только внутри макроса assert, который исчезнет из программы, как только будет выключен отладочный режим. Кроме того, не проверяются аргументы функции, но это уже другая тема.
Читать дальше
Конец ознакомительного отрывка
Купить книгу