我多次遇到这个问答,并想提出一个更全面的答案。我想思考的最佳方式是如何错误返回主调,和什么你回来。
怎么样
有3种方法可以从函数返回信息:
- 返回值
- 争论不休
- 带外,包括非本地goto(setjmp / longjmp),文件或全局范围的变量,文件系统等。
返回值
您只能将value返回为单个对象,但是,它可以是任意复杂的对象。这是一个错误返回函数的示例:
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()将控制权转移到该位置。请参阅C中setjmp和longjmp的实际用法。
什么
- 指示符
- 码
- 目的
- 打回来
指示符
错误指示符仅告诉您存在问题,而与上述问题的性质无关:
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]);
}
当然,除了预先分配错误数组外,您还可以根据需要动态(重新)分配它。
打回来
回调是处理错误的最强大方法,因为您可以告诉函数在出现问题时希望看到什么行为。可以将回调参数添加到每个函数,或者仅对于每个结构实例都需要自定义ui,如下所示:
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();
回调的一个有趣的好处是,可以多次调用它,或者在没有错误的情况下完全调用一次,而在错误情况下,快乐路径上没有开销。
但是,存在控制反转。调用代码不知道是否调用了回调。这样,也可以使用指示器。