Чтение онлайн

ЖАНРЫ

Программирование. Принципы и практика использования C++ Исправленное издание
Шрифт:

Проверка корректности аргументов в функцию

framed_area
не вызывает затруднений, а выдачу сообщения об ошибках можно по-прежнему поручить функции
error
.

int framed_area(int x, int y) // вычисляем площадь, ограниченную рамкой

{

const int frame_width = 2;

if (x–frame_width<=0 || y–frame_width<=0)

error("неположительный аргумент функции area \\

при вызове из функции framed_area");

return area(x–frame_width,y–frame_width);

}

Это

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

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

Итак, применим найденное решение к функции

area
.

int area(int length, int width) // вычисляем площадь прямоугольника

{

if (length<=0 || width <=0)

error("неположительный аргумент area");

return length*width;

}

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

area
, поэтому их теперь необязательно проверять в функции
framed_area
. Однако вы можете требовать большего — чтобы сообщение об ошибке было более конкретным.

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

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

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

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

Производительность.

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

Итак, что делать? Проверять аргументы в функции, если у вас нет веских причин поступать иначе.

После обсуждения некоторых тем, связанных с этим вопросом, мы вернемся к нему в разделе 5.10.

5.5.3. Сообщения об ошибках

Рассмотрим немного иной вопрос: что делать, если вы проверили набор аргументов и обнаружили ошибку? Иногда можно вернуть сообщение “Неправильное значение”. Рассмотрим пример.

// Попросим пользователя ввести да или нет;

// Символ 'b' означает неверный ответ (т.е. ни да ни нет)

char ask_user(string question)

{

cout << question << "? (да или нет)\n";

string answer = " ";

cin >> answer;

if (answer =="y" || answer=="yes") return 'y';

if (answer =="n" || answer=="no") return 'n';

return 'b'; // 'b', если "ответ неверный"

}

// Вычисляет площадь прямоугольника;

// возвращает –1, если аргумент неправильный

int area(int length, int width)

{

if (length<=0 || width <=0) return –1;

return length*width;

}

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

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

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

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

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

Поделиться с друзьями: