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

Error Reporting — система для сообщения об ошибках.

Краткое описание

#include <glib.h> GError; GError* g_error_new (GQuark domain, gint code, const gchar *format, ...); GError* g_error_new_literal (GQuark domain, gint code, const gchar *message); void g_error_free (GError *error); GError* g_error_copy (const GError *error); gboolean g_error_matches (const GError *error, GQuark domain, gint code); void g_set_error (GError **err, GQuark domain, gint code, const gchar *format, ...); void g_propagate_error (GError **dest, GError *src); void g_clear_error (GError **err);

Описание

GLib обеспечивает стандартный метод сообщения об ошибках из вызванной функции в вызываемом коде. (Это также решает проблему исключительной ситуации в других языках). Важно понять что это метод - data type (GError объект) и набора правил. Если вы используете GError неправильно, то ваш код не будет правильно взаимодействовать с другим кодом использующим GError, а пользователи вашего API вероятно запутаются.

Прежде всего: GError должен использоваться только для сообщения о восстанавливаемых ошибках в процессе выполнения. Обычно вы должны использовать g_warning(), g_return_if_fail(), g_assert(), g_error(), или похожее средство. (помните что функция g_error() должна использоваться только для программных ошибок, она не должна использоваться для печати любой ошибки о которой сообщено через GError.)

Примером восстанавливаемых ошибок во время выполнения программы являются "file not found" (файл не найден) или "failed to parse input" (не удаётся проанализировать ввод). Примером программных ошибок являются "NULL passed to strcmp()" или "attempted to free the same pointer twice". Эти два вида ошибок существенно отличаются: ошибки во время выполнения должны обрабатываться или о них должно быть сообщено пользователю, програмные ошибки должны ликвидироваться исправлением в программе. Поэтому большинство функций в GLib и GTK+ не используют GError.

Функции которые могут завершиться неудачно принимают в качестве последнего параметра место расположения возвращаемой GError. Например:

gboolean g_file_get_contents (const gchar *filename, gchar **contents, gsize *length, GError **error);

Если вы поместили не-NULL значение в параметр error, он должен указывать на место куда может быть помещена ошибка. Например:

gchar *contents; GError *err = NULL; g_file_get_contents ("foo.txt", &contents, NULL, &err); g_assert ((contents == NULL && err != NULL) || (contents != NULL && err == NULL)); if (err != NULL) { /* Сообщаем об ошибке пользователю, и освобождаем ошибку */ g_assert (contents == NULL); fprintf (stderr, "Unable to read file: %s\n", err->message); g_error_free (err); } else { /* Используем содержимое файла */ g_assert (contents != NULL); }

Помните что err != NULL в этом примере надежно проверяет не закончилась ли неудачей g_file_get_contents(). Дополнительно, g_file_get_contents() возвращает логическое которое указывает была ли она завершена удачно.

Поскольку g_file_get_contents() возвращает FALSE при неудаче, если вы интересуетесь только статусом завершения и не нуждаетесь в отображении сообщения об ошибке, вы можете поместить NULL в параметр error:

if (g_file_get_contents ("foo.txt", &contents, NULL, NULL)) /* игнорирование ошибок */ /* нет произошедших ошибок */ ; else /* ошибка */ ;

Объект GError содержит три поля: domain указывает функцию сообщения об ошибках, code указывает произошедшую специфическую ошибку, а message читабельное для пользователя сообщение которое более детально описывает ошибку насколько это возможно. Некоторые функции предназначены для связи с ошибкой полученной из вызываемой функции: g_error_matches() возвращает TRUE если ошибка соответствует данной domain и code, g_propagate_error() копирует ошибку в место хранения ошибки (таким образом вызываемая функция получает её), а g_clear_error() очищает место расположения ошибки освобождая ошибку и переустанавливая место хранения ошибки в NULL. Для отображения ошибки пользователю, просто отобразите error->message, возможно наряду с дополнительным контекстом известным только вызываемой функции (открываемый файл, или любой -- хотя в случае g_file_get_contents(), error->message уже содержит имя файла).

При реализации функций способных сообщать об ошибках основной инструмент g_set_error(). Обычно, если происходит фатальная ошибка вам необходима g_set_error(), затем немедленное возвращение. g_set_error() ничего не делает если расположение ошибки помещённое в неё NULL. Например:

gint foo_open_file (GError **error) { gint fd; fd = open ("file.txt", O_RDONLY); if (fd < 0) { g_set_error (error, FOO_ERROR, /* область ошибки (error domain) */ FOO_ERROR_BLAH, /* код ошибки (error code) */ "Failed to open file: %s", /* форматированная строка сообщения об ошибке */ g_strerror (errno)); return -1; } else return fd; }

Немного сложнее если вы самостоятельно вызываете функцию которая может сообщить GError. Если подчинённая функция указывает на неисправимые ошибки некоторым способом отличающимся от сообщения GError, таким как возвращение TRUE при удачном исполнении, вы можете просто сделать следующее:

gboolean my_function_that_can_fail (GError **err) { g_return_val_if_fail (err == NULL || *err == NULL, FALSE); if (!sub_function_that_can_fail (err)) { /* предполагается что ошибка была установлена с помощью подчинённой функции (sub-function) */ g_assert (err == NULL || *err != NULL); return FALSE; } /* иначе продолжаем, ошибка не происходит */ g_assert (err == NULL || *err == NULL); }

Если подчинённая функция не указывает о других ошибках кроме как сообщения GError, вы должны создать временную GError та как помещаемая в неё может быть NULL. g_propagate_error() предназначена для использования в этом случае.

gboolean my_function_that_can_fail (GError **err) { GError *tmp_error; g_return_val_if_fail (err == NULL || *err == NULL, FALSE); tmp_error = NULL; sub_function_that_can_fail (&tmp_error); if (tmp_error != NULL) { /* сохраняем tmp_error в err, если err != NULL, * иначе вызываем g_error_free() для tmp_error */ g_propagate_error (err, tmp_error); return FALSE; } /* иначе продолжаем, ошибка не происходит */ }

Error pileups являются всегда ошибкой. Например, этот код неправильный:

gboolean my_function_that_can_fail (GError **err) { GError *tmp_error; g_return_val_if_fail (err == NULL || *err == NULL, FALSE); tmp_error = NULL; sub_function_that_can_fail (&tmp_error); other_function_that_can_fail (&tmp_error); if (tmp_error != NULL) { g_propagate_error (err, tmp_error); return FALSE; } }

tmp_error должна быть проверена немедленно после sub_function_that_can_fail(), и либо очищена либо распространена на верх. Правило: после каждой ошибки, вы должны либо обработать ошибку, либо вернуть её в вызываемую функцию. Помните что NULL для параметра расположения ошибки эквивалентно обработке ошибки ничего не делая с ней. Таким образом следующий код является хорошим примером, предполагается что ошибки в sub_function_that_can_fail() являются не фатальными для my_function_that_can_fail():

gboolean my_function_that_can_fail (GError **err) { GError *tmp_error; g_return_val_if_fail (err == NULL || *err == NULL, FALSE); sub_function_that_can_fail (NULL); /* игнорирование ошибок */ tmp_error = NULL; other_function_that_can_fail (&tmp_error); if (tmp_error != NULL) { g_propagate_error (err, tmp_error); return FALSE; } }

Помните что помещаемый NULL в параметр размещения ошибки означает игнорирование ошибок; это эквивалентно try { sub_function_that_can_fail(); } catch (...) {} в C++. Это не значит оставить ошибки не обработанными; это значит обработать ошибки ничего не делая.

Области и коды ошибки традиционно именуются следующим образом:

  • Область ошибки называют согласно шаблона <NAMESPACE>_<MODULE>_ERROR, например G_EXEC_ERROR или G_THREAD_ERROR.

  • Коды ошибки находятся в перечислении называемом согласно шаблона <Namespace><Module>Error; например, GThreadError или GSpawnError.

  • Элементы перечисления кодов ошибки называются согласно шаблона <NAMESPACE>_<MODULE>_ERROR_<CODE>, например G_SPAWN_ERROR_FORK или G_THREAD_ERROR_AGAIN.

  • Если есть "generic" или "unknown" коды ошибки для неисправимых ошибок нет смысла различать их другими специфическими кодами, они должны быть названы по шаблону <NAMESPACE>_<MODULE>_ERROR_FAILED, например G_SPAWN_ERROR_FAILED or G_THREAD_ERROR_FAILED.

Сводные правила использования GError:

  • Не сообщайте о программных ошибках через GError.

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

  • Вызывающий может помещать NULL для GError** если его не интересуют подробности произошедшей ошибки.

  • Если NULL помещён для GError** аргумента, тогда ошибка не должна возвращаться к вызывающей функции, но ваша функция всё же должна прерываться и возвращаться если произошла ошибка. Таким образом, на управление потоком не должно влиять получение или неполучение вызывающей функцией GError.

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

  • GError* должна быть инициализирована в значение NULL перед помещением её адреса в функцию которая может сообщать об ошибках.

  • "Накопление" ошибок - всегда ошибка. Таким образом, если вы назначили новую GError для GError* которая не-NULL, переписывая таким образом предыдущую ошибку, она указывает вам что нужно прервать операцию вместо продолжения. Вы можете продолжать если очистите предыдущую ошибку с помощью g_clear_error(). g_set_error() будет предупреждать если вы накапливаете ошибки.

  • В соответствие с соглашением, если вы возвращаете логическое значение, то TRUE означает успешное выполнение, а FALSE означает неудачу. Если возвращено значение FALSE, ошибка должна быть установлена в значение не-NULL.

  • Возвращённое значение NULL часто также означает что произошла ошибка. Вы должны разъяснить в вашей документации означает ли NULL допустимое возвращаемое значение в случае безошибочного выполнения; если NULL допустимое значение, то пользователи должны проверить была ли возвращена ошибка, чтобы увидеть выполнена ли функция успешно.

  • Когда реализуется функция которая может сообщать об ошибках, вы можете добавить проверку в начале вашей функции является ли место расположения ошибки значением NULL или используется ли значение NULL в качестве указателя ошибки (например g_return_if_fail (error == NULL || *error == NULL);).

Детали

GError

typedef struct { GQuark domain; gint code; gchar *message; } GError;

Структура GError содержит информацию о произошедшей ошибке.

GQuark domain; область ошибки, например G_FILE_ERROR.
gint code; код ошибки, например G_FILE_ERROR_NOENT.
gchar *message; удобное для чтения пользователем информационное сообщение об ошибке.

g_error_new ()

GError* g_error_new (GQuark domain, gint code, const gchar *format, ...);

Создаёт новую GError с полученными domain и code, а сообщение форматируется с помощью format.

domain : область ошибки
code : код ошибки
format : printf()-стиль форматирования для сообщения об ошибке
... : параметры для форматирования сообщения
Возвращает : новая GError

g_error_new_literal ()

GError* g_error_new_literal (GQuark domain, gint code, const gchar *message);

Создаёт новую GError; в отличие от g_error_new(), message является строкой не в printf()-стиле форматирования. Используйте эту функцию если message содержит текст не контролируемый вами, который мог бы включать printf() escape-последовательность.

domain : область ошибки
code : код ошибки
message : сообщение об ошибке
Возвращает : новая GError

g_error_free ()

void g_error_free (GError *error);

Освобождает GError и связанные с ней ресурсы.

error : GError

g_error_copy ()

GError* g_error_copy (const GError *error);

Создаёт копию error.

error : GError
Возвращает : новую GError

g_error_matches ()

gboolean g_error_matches (const GError *error, GQuark domain, gint code);

Возвращает TRUE если error соответствует domain и code, FALSE в любом другом случае.

error : GError
domain : область ошибки
code : код ошибки
Возвращает : соответствует ли error domain и code

g_set_error ()

void g_set_error (GError **err, GQuark domain, gint code, const gchar *format, ...);

Ничего не делает если err имеет значение NULL; если err не-NULL, то *err должна быть NULL. Новая GError создаётся привязанной к *err.

err : расположение для возвращаемой GError, или NULL
domain : область ошибки
code : код ошибки
format : printf()-стиль форматирования
... : аргументы для format

g_propagate_error ()

void g_propagate_error (GError **dest, GError *src);

Если dest имеет значение NULL, освобождает src; иначе, перемещает src в *dest. *dest должен быть NULL.

dest : расположение возвращаемой ошибки
src : ошибка для перемещения в расположение указанное параметром dest 

g_clear_error ()

void g_clear_error (GError **err);

Если err имеет значение NULL, ничего не делает. Если err не-NULL, вызывает g_error_free() для *err и устанавливает *err в значение NULL.

err : расположение возвращаемой GError