Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Kernigan_B__Payk_R_Praktika_programmirovania.pdf
Скачиваний:
76
Добавлен:
18.03.2016
Размер:
2.53 Mб
Скачать

Abort, Retry, Fail?

В предыдущих главах мы использовали для обработки ошибок функции вроде eprintf и estrdup — просто выводили некие сообщения перед тем, как прервать выполнение программы. Например, функция eprintf ведет себя так же, как fprintf (stderr, . . .), но после вывода сообщения выходит из программы с некоторым статусом ошибки. Она использует заголовочный файл <stdarg. h> и библиотечную функцию vfprintf для вывода аргументов, представленных в прототипе многоточием (...). Использование библиотеки stdarg должно быть начато вызовом va_start и завершено вызовом va__end. Мы еще вернемся к этому интерфейсу в главе 9.

#Include <stdarg.h>

#ifinclude <string.h>

#Ifinclude <errno. h>

/* eprintf: печать сообщения об ошибке и выход */ void eprintf(char *fmt, ...)

{

va_list args; fflush(stdout);

if (progname() != NULL) fprintf(stderr, "%s: ", prognameO);

va_start(args, fmt); vfpnntf(stderr, fmt, args); va_end(args);

if (fmt[0] != ДО' && fmt[strlen(fmt)-1] == ':') fprintf (stderr, " %s", strerror(errno)); fprintf(stderr, "\n");

exit(2); /* общепринятое значение

*/ /* для ненормального завершения работы */

}

Если аргумент формата оканчивается двоеточием (:), то eprintf вызывает стандартную функцию st re г го г, которая возвращает строку, содержащую всю доступную дополнительную системную информацию об ошибке. Мы написали еще функцию weprintf, сходную с eprintf, которая выводит предупреждение, но не завершает программу. Интерфейс, схожий с printf, удобен для создания строк, которые могут быть напечатаны или выданы в окне диалога.

Сходным образом работает est rdup: она пытается создать копию строки и, если памяти для этого не хватает, завершает программу с сообщением об ошибке (с

помощью eprintf):

/* estrdup: дублирует строку; */

/* при возникновении ошибки сообщает об этом */ char *estrdup(char *s)

{

char *t;

t = (char *) malloc(strlen(s)+1); if (t == NULL)

eprintf("estrdup(\"%.20s\") failed:", s); strcpy(t, s);

return t;

}

Функция emalloc предоставляет аналогичные возможности для вызова malloc:

/* emalloc: выполняет malloc; */

/* при возникновении ошибки сообщает об этом */ void *emalloc(size__t n)

{

void *p;

p = malloc(n); if (p == NULL) epnntf("malloc of %u bytes failed:", n); return p;

}

Эти функции описаны в заголовочном файле eprintf. h:

/* eprintf.h: функции, сообщающие об ошибках */ extern void eprintf(char *, ...);

extern void weprintf(char *, ...); extern char *estrdup(char *); extern void *emalloc(size_t); extern void *erealloc(void *', size_t); extern char *progname(void); extern void setprogname(char *);

Он включается в любой файл, вызывающий одну из функций, которые сообщают об ошибке. Каждое сообщение об ошибке содержит имя программы, определенное вызывающим кодом, — оно устанавливается и извлекается простейшими функциями set prog name и prog name, описанными в том же заголовочном файле и определенными в исходном файле вместе с eprintf:

static char *name = tfllLL; /* имя программы для сообщений */ /* setprogname: устанавливает хранимое имя программы */ void setprogname(char *str)

{

name = estrdup(str);

}

/* progname: возвращает хранимое имя программы */ char *progname(void)

{

return name;

}

Типичный пример использования выглядит примерно так:

int main(int argc, char *argv[])

{

setprogname("markov"); f = fopen(argv[i], "г");

if (f == NULL)

eprintf("can't open %s:", argv[i]);

}

что приводит к появлению сообщений вроде

markov: can't open psalm.txt: No such file or directory

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

Представим теперь, что вместо создания функций для собственного использования нам надо разработать библиотеку, с которой будут работать другие программисты. Что должна делать функция из этой библиотеки при возникновении ошибки? Те функции, что мы только что написали, выводят сообщение и умирают. Для многих программ, особенно для небольших самостоятельных утилит, такое поведение может'быть вполне приемлемым. Для других же программ простой выход не годится, поскольку при этом другие части программы лишаются возможности хотя бы попытаться вернуться в нормальное состояние; характерным примером являются текстовые редакторы, — в них стоит приложить максимум усилий для сохранения редактируемого документа. В некоторых ситуациях библиотечные функции не должны даже выдавать никакого сообщения, поскольку существуют системы, где такое сообщение будет мешать отображению полезной информации или же, наоборот, просто сгинет бесследно. Для подобных случаев полезно записывать сообщения в некий отдельный журнальный файл (log file), который можно просматривать независимо.

Обнаруживайте ошибки на низком уровне, обрабатывайте на высоком. Существует общий принцип: ошибки должны обнаруживаться на самом низком уровне, какой только возможен; обрабатывать же их надо на высоком уровне. В большинстве случаев определять способ обработки ошибки должен вызывающий код, а не вызываемый. Библиотечные функции могут помочь в этом, обеспечивая приемлемую реакцию при сбоях, — например, при получении несуществующего поля в качестве аргумента не прерывать работу всей программы, а возвращать NULL. Или, как в csvgetline, возвращать NULL вне зависимости от того, сколько раз эта функция была вызвана после достижения конца файла.

Не всегда очевидно, какие же значения должны возвращаться при ошибках; мы уже сталкивались с проблемой возвращаемого значения у функции csvgetline. Хотелось бы, конечно, возвращать как можно более содержательную информацию, но при этом в такой форме, чтобы остальная часть программы могла использовать ее без труда. В С, C++ и Java это значит, что информация должна возвращаться в качестве результата функции и, возможно, в значениях параметров-ссылок (указателей). Многие библиотечные функции умеют различать обычные значения и специальные значения ошибок. Функции ввода типа getcha r возвращают значение, конвертируемое в char для нормальных данных, и некоторое неконвертируемое в char значение, например EOF, для обозначения конца файла или ошибки.

Этот механизм, однако, не работает, если функция может возвращать любые значения из возможного диапазона. Например, математические функции вроде log могут возвращать любое число с плавающей точкой. В стандарте IEEE для чисел с плавающей точкой предусмотрено специальное значение NaN ("not a number" — не число), означающее ошибку, — это значение и возвращается функциями в случае ошибки.

Некоторые языки, такие как Perl и Tel, предоставляют несложный способ группировки двух и 0олее значений в кортеж (tuple). В таких языках значение функции и код

ошибки можно без проблем передавать совместно. В C++ STL имеется тип данных pai r, который можно использовать таким же образом.

Хотелось бы, по возможности, уметь различать исключительные значения типа конца файла или кода ошибок, а не запихивать их все в какое-то одно значение. Если значения нельзя разделить сразу же, можно поступить таким образом:

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

Именно такой подход используется в Unix и стандартной библиотеке С: многие системные вызовы и библиотечные функции возвращают в случае ошибки -1 и при этом устанавливают глобальную переменную errno; функция strerror возвращает строку, соответствующую номеру ошибки. В нашей системе программа

#include <stdio.h>

#((include <string. h>

#include <errno.h>

#include <math.h>

/* errno main: тестирование библиотеки */ int main(void)

; \ double f; errno = 0;

/* очищаем переменную кода ошибки */ f = log(:-;1.23);

printf("%f %d %s\n", f, errno, strerror(errno));

return 0;

}

напечатает

nаnОхЮОООООО 33 Domain error

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

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

Рассмотрим такой фрагмент, написанный на Java:

String fname = "someFileName"; try { FilelnputStream in = new FilelnputStream (fname);