Я неодноразово наштовхувався на це питання та хотів зробити більш вичерпну відповідь. Я думаю, що найкращий спосіб подумати над цим - як повернути помилки абоненту, і що ви повернетеся.
Як
Існує 3 способи повернення інформації з функції:
- Повернене значення
- Зовнішні аргументи
- Поза діапазоном, що включає не локальні goto (setjmp / longjmp), файлові або глобальні змінні області, файлову систему тощо.
Повернене значення
Ви можете повернути значення лише одного об'єкта, однак це може бути довільним комплексом. Ось приклад функції повернення помилок:
enum error hold_my_beer();
Одна з переваг повернених значень полягає в тому, що вона дозволяє ланцюжок викликів для менш нав'язливої обробки помилок:
!hold_my_beer() &&
!hold_my_cigarette() &&
!hold_my_pants() ||
abort();
Це не лише щодо читабельності, але може також дозволити рівномірно обробляти масив таких покажчиків функцій.
Зовнішні аргументи
Ви можете повернути більше через більш ніж один об’єкт за допомогою аргументів, але найкраща практика пропонує зберегти загальну кількість аргументів низькою (скажімо, <= 4):
void look_ma(enum error *e, char *what_broke);
enum error e;
look_ma(e);
if(e == FURNITURE) {
reorder(what_broke);
} else if(e == SELF) {
tell_doctor(what_broke);
}
Поза зоною
За допомогою setjmp () ви визначаєте місце та спосіб обробляти значення int, а також керуєте цим місцем через longjmp (). Див Практичне використання setjmp і longjmp в C .
Що
- Індикатор
- Код
- Об'єкт
- Зворотний виклик
Індикатор
Індикатор помилки говорить лише про те, що існує проблема, але нічого про характер вказаної проблеми:
struct foo *f = foo_init();
if(!f) {
/// handle the absence of foo
}
Це найменш потужний спосіб для повідомлення про стан помилок, однак ідеальний, якщо абонент не може відповісти на помилку в будь-якому разі градуйованим чином.
Код
Код помилки повідомляє абоненту про характер проблеми та може передбачати відповідну відповідь (з вищезазначеного). Це може бути повернене значення, або як приклад look_ma () над аргументом помилки.
Об'єкт
При об'єкті помилки абонент може бути поінформований про довільні складні питання. Наприклад, код помилки та відповідне читабельне для людини повідомлення. Він також може повідомити абоненту, що кілька речей пішли не так, або помилка на предмет під час обробки колекції:
struct collection friends;
enum error *e = malloc(c.size * sizeof(enum error));
...
ask_for_favor(friends, reason);
for(int i = 0; i < c.size; i++) {
if(reason[i] == NOT_FOUND) find(friends[i]);
}
Замість того, щоб попередньо виділити масив помилок, ви також можете (пере) розподілити його динамічно, як потрібно.
Зворотний виклик
Зворотний виклик - це найпотужніший спосіб вирішення помилок, оскільки ви можете сказати функції, яку поведінку ви хотіли б бачити, коли трапляється щось не так. Аргумент зворотного виклику може бути доданий до кожної функції або якщо для налаштування потрібен лише екземпляр такої структури:
struct foo {
...
void (error_handler)(char *);
};
void default_error_handler(char *message) {
assert(f);
printf("%s", message);
}
void foo_set_error_handler(struct foo *f, void (*eh)(char *)) {
assert(f);
f->error_handler = eh;
}
struct foo *foo_init() {
struct foo *f = malloc(sizeof(struct foo));
foo_set_error_handler(f, default_error_handler);
return f;
}
struct foo *f = foo_init();
foo_something();
Однією цікавою перевагою зворотного дзвінка є те, що його можна викликати кілька разів або взагалі жодного за відсутності помилок, у яких на щасливому шляху немає накладних витрат.
Однак існує інверсія контролю. Код виклику не знає, чи було викликано зворотний дзвінок. Як такий, може бути доцільним також використовувати індикатор.