How to reduce swelling of C code from error handling and debugging

Consider this function:

int get_result(int *result) { int err = 0; int number = 0; if (result == NULL) { printf("error: null input\n"); return -1; } err = get_number(&number); if (err != 0) { printf("error calling get_number: err = %d\n", err); return err; } err = calculate_result(number, result); if (err != 0) { printf("error calling get_result: err = %d\n", err); return err; } return err; } 

The real work in this function requires only 3 lines (declare a numeric variable, call get_number (), and then call calculate_result ()). However, error checking / handling code inflates this function to 17 lines (give or take, depending on how you count the lines).

On a larger scale, with many challenges and several error checks, we completely inflate this function and make it unreadable and very difficult to understand.

What are some ways around this bloat in C code and maintain the readability of the main function operation (without sacrificing the necessary error handling code)?

+6
source share
4 answers

Welcome to the world of product quality code. There are several macros and factoring tricks that can make error checking less detailed. Exceptions are another mechanism. But the main adjustment is the point of view. You think of β€œreal work” as something separate from error handling. Not this. Working with all possible conditions is the essence of software development. To get it out of your system, explain the β€œmain job” in the comments, then write a real algorithm with external state processing in front and in the center.

+3
source

This is the main reason for exceptions, but I have to admit that I am not a friend representing exceptions in the language using explicit memory management, so this answer may be biased. However, there are several common strategies for branching business logic from error handling in c .

  • Use exceptions - see longjmp() / setjmp() for their implementation in c . I would advise doing this.
  • If there is a need to clean up in case of an error, put it at the end of the function and goto there. (Yes indeed!)
  • To check the return value and print the message to stderr , try to separate common cases and define macros, for example, for example. in your case:

     #define CHECK_RETVAL(val, action) do { \ if (val < 0) { \ fprintf(stderr, "error calling " action ": %s\n", strerror((val))); \ goto error_cleanup; \ }} while (0) 
+1
source

If the length of the string bothers you a bit, you can write the following function:

 int checkforError(int errorCode, const char* message) { if (errorCode != 0) { printf("%s: err = %d\n", err", message, errorCode); return 0; } return 1; } 

And use it to shorten the last two ifs as follows:

 checkforError(err = get_number(&number), "error calling get_number") && checkforError(err = calculate_result(number, result), "error calling get_result"); return err; 

Since the first if has little to do with other cases, there is no reason to place a checkforError to it.

A short circuit has a guaranteed evaluation order, so this is not an undefined behavior. See Logical comparisons: is a rating guaranteed from left to right?

0
source

The only situation where an error handling code is not needed is redundant. Avoiding redundant error handling code is exactly the same as avoiding redundant code altogether. The only question is what might be the area for error handling code. Typically, there is so much common behavior in the greatest possible area.

For example, malloc failing is usually fatal, so instead of checking every return value, you can wrap the function ...

 void* fmalloc(size_t n) { void* const m = malloc(n); if (!m) on_fatal("out of memory"); return m; } 

If the behavior can only be limited by the calling function, you can use goto ...

 int on_file(fd, rm, wm) { if (read(fd, rm, 8) < 0) goto err; if (write(fd, wm, 8) < 0) goto err; return 0; err: on_error("on_file error"); return -1; } 

For things that can be parameterized, parameterize them.

An example that I am using is used here. In general, shortening error handling code is no different from simply grouping common behavior.

0
source

All Articles