问题
In C, is there a pattern that removes the need to continually check for errors in functions that call other functions?
e.g. If function foo() calls a(), b() and c() one after the other the return value from each has to be checked before continuing. if a(), b(), or c() also call other functions, which possibly also call other functions, this leaves a long sequence of error checking of possibly the same error...
int16_t a(x_t x) {return DO_SOME_WORK(x);}
int16_t b(y_t y) {return DO_OTHER_WORK(y);}
int16_t c(z_t z) {return DO_MORE_WORk(z);}
foo (x_t x, y_t y, z_t z) {
int16_t err = 0;
// I can handle errors returned by either:
err = a(x));
if (err) return err;
err = b(y);
if (err) return err;
err = c(z);
return err;
// Or
err = a(x);
if (!err) err = b(y);
if (!err) err = c(z);
return err;
}
I prefer the second method as there is a clear single exit point, but the other localises the handling and either method introduces a lot of ifs, and with a large program, that's lots of extra code, especially in functions that do nothing but call other functions and pass along the output between them, for example. Thanks
回答1:
In this case you could exploit short-circuit evaluation of &&
, where when the left hand operand is false
, the right-hand operand is not evaluated.
If you might want to perform other processing between calls to a()
, b()
or c()
, the following allows that:
bool foo (x_t x, y_t y, z_t z)
{
bool err = a( x );
err = err && b( y ) ;
err = err && c( z ) ;
return err;
}
whereas if the function is composed solely of calls a()
, b()
or c()
, then you can use the even more terse:
bool foo (x_t x, y_t y, z_t z)
{
return a( x ) &&
b( y ) &&
c( z ) ;
}
[added in response to comment]
Solving @kkrambo's point about any numeric value of the function return not being propagated to the caller can be solved but is increasingly less attractive as a solution:
int16_t foo (x_t x, y_t y, z_t z)
{
int16_t ret ;
bool err = (ret = a( x ));
err = err && (ret = b( y )) ;
err = err && (ret = c( z )) ;
return ret ;
}
or
int16_t foo (x_t x, y_t y, z_t z)
{
uint16_t ret ;
(ret = a( x )) &&
(ret = b( y )) &&
(ret = c( z )) ;
return ret ;
}
Increasing in horribleness you could even do:
int16_t foo (x_t x, y_t y, z_t z)
{
int16_t err ;
if( (err = a(x)) ) {}
else if( (err = b(y)) ) {}
else if( (err = c(z)) ) {}
return err ;
}
Simplifies to the following where err
is boolean:
bool foo (x_t x, y_t y, z_t z)
{
bool err = true ;
if( a(x) ) {}
else if( b(y) ) {}
else if( c(z) ) {}
else { err = false }
return err ;
}
回答2:
If each function call of a function needs error checking then every time you call that function you should check for errors. The reason is even though it is the same function the inputs are different and the global (and static local) variable are different changing the results. What this means is that if you want to check for errors you are not actually wasting the computational work.
Edit: I don't know if this is wise but you can use Macros to clean up the syntax of
err = a(x);
if(err) return err;
It would look something like
#define error_check(f, e) \
e = f; \
if(e) return e;
So in your code you can just write
error_check(a(x),err)
I am not expeirenced with macros but this should work.
回答3:
Since the goal it to return an error code and not simple a boolean, OP supplied 2 approaches are at least reasonable, if not preferable. Short circuit code like if (a(x) || b(x)) return true
fails to maintain the err
value derived from a()
and b()
.
It comes down to style.
If code simple consisted of a few lines, any approach would suffice. But given there may exist significant code interwoven between a(x)
, b(x)
and c(x)
, recommend against style 2 as it separates flow over many lines of code. I like style 3 over 1 as it is a bit more terse, but the differences between 1 & 3 is small.
To cope with style #2 ability to collect error return in one spot, consider a higher level function wrapper that unequivocally insures all prologue and epilogue activity occurs.
Best choice: go with group's style guidelines, else what work best for you to maintain, not write.
int16_t foo (x_t x, y_t y, z_t z) { // added matching return type
int16_t err = 0;
// OP style 1
err = a(x));
if (err) return err;
err = b(y);
if (err) return err;
err = c(z);
return err;
// OP style 2
err = a(x);
if (!err) err = b(y);
if (!err) err = c(z);
return err;
// style 3
// via enum or define, create an OK symbol
if ((err = a(x)) != OK) return err;
if ((err = b(x)) != OK) return err;
if ((err = c(x)) != OK) return err;
//
}
来源:https://stackoverflow.com/questions/24658258/pattern-to-prevent-continually-checking-error