glib имеет хороший набор макросов, которые вы можете использовать для увеличения инвариантов и предусловий в вашем коде. Они исчезнут после того, как вы определите "G_DISABLE_CHECKS" или "G_DISABLE_ASSERT", поэтому уменьшения производительности в реальном коде не будет. Рекомендуется широко использовать это. Тогда вы сможете находить ошибки намного быстрее. Вы можете даже добавлять условия и проверки всякий раз, когда находите ошибку, чтобы быть уверенными, что ошибка не появится в будущих версиях -- это дополняет регрессивный набор. Проверки особенно полезны, когда код, который вы пишете будет использоваться как черный ящик другими программистами; пользователи будут сразу же знать когда и как они неправильно использовали ваш код.
Естественно, вы должны быть уверены, что ваш код функционирует правильно не только в режиме отладки. Операторы, которые присутствуют в финальном коде никогда не должны иметь побочных эффектов.
g_return_if_fail(condition)
g_return_val_if_fail(condition, retval)
Список макросов 2..3 показывает проверки предусловий glib. "g_return_if_fail()" выводит предупреждение и выходит из текущей функции если условие condition ложно. "g_return_val_if_fail()" действует аналогично, но позволяет вам вернуть retval. Эти макросы невероятно полезны -- если вы их широко используете, особенно в комбинации с проверками Gtk+ периода исполнения, вы съэкономите время при нахождении плохих указателей или ошибок типов.
Использовать эти функции легко, ниже идет пример из реализации хэш-таблицы в
glib:
void g_hash_table_foreach(GHashTable *hash_table, GHFunc func,
gpointer user_data)
{
GHashNode *node;
gint i;
g_return_if_fail(hash_table != NULL);
g_return_if_fail(func != NULL);
for (i=0; i<hash_table->size; i++)
for (node=hash_table->nodes[i]; node; node=node->next)
(* func) (node->key, node->value, user_data);
}
Без проверок, передача NULL в качестве параметра в эту
функцию вызовет загадочный аварийный выход. Человек, использующий библиотеку
узнает, где случилась ошибка с помощью отладчика, и, возможно, посмотрит в
код glib чтобы узнать, что было неправильно. С проверками, они
получат красивое сообщение об ошибке, говорящее, что аргументы со значением
NULL недопустимы.
g_assert(condition)
g_assert_not_reached()
В glib также есть традиционные макросы условий, показанные в списке макросов 2..4. "g_assert()" в основном аналогичен "assert()", но реагирует на "G_DISABLE_ASSERT" и ведет себя одинаково на всех платформах. Есть также и "g_assert_not_reached()"; это условие, которое всегда ложно. Проверки условий вызывают "abort()" для выхода из программы и (если ваша среда это позволяет) записывает дамп памяти для отладочных целей.
Фатальные условия должны использоваться для проверки внутренней целостности функции или библиотеки, в то время как "g_return_if_fail()" предназначен для того, чтобы гарантировать приемлемые значения, которые передаются общим интерфейсам программного модуля. То есть, если условие не выполнится, вы, скорее всего будете искать ошибку в модуле, содержащем условие; если проверка "g_return_if_fail()" не пройдет, вы, скорее всего, будете искать ошибку в коде, который вызывает модуль.
Этот код, взятый из календарных вычислений glib показывает разницу:
GDate *g_date_new_dmy(GDateDay day, GDateMonth m, GDateYear y)
{
GDate *d;
g_return_val_if_fail(g_date_valid_dmy(day, m, y), NULL);
d = g_new(GDate, 1);
d->julian = FALSE;
d->dmy = TRUE;
d->month = m;
d->day = day;
d->year = y;
g_assert(g_date_valid(d));
return d;
}
Проверка предусловия в начале гарантирует то, что пользователь передал осмысленные аргументы для числа, месяца и года; условие в конце дает гарантию того, что glib создал приемлемый объект с данными нормальными значениями.
"g_assert_not_reached()" должен быть использован для отметки
невозможных ситуаций; обычное использование -- обнаружение
операторов в блоке switch, которые не обрабатывают все возможные
значения перечисления:
switch (val)
{
case FOO_ONE:
break;
case FOO_TWO:
break;
default:
" /* Неправильное значение перечисления */"
g_assert_not_reached();
break;
}
Все отладочные макросы печатают предупреждения, используя "g_log()" из glib, что означает, что предупреждение включает имя порождающей его программы или библиотеки, и вы можете дополнительно установить замену для процедуры печати предупреждений. Например, вы можете посылать все предупреждения в диалоговое окно или лог-файл, вместо печати их на консоль.