How to reduce code bloat in C from error handling & debugs [duplicate]
Asked Answered
R

4

7

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 number variable, call get_number(), then call calculate_result()). However, the error checking/handling code bloats this function to 17 lines (give or take, depending on how you count the lines).

At a larger scale, with many calls, and multiple error checks, we bloat the function entirely and make it unreadable and very hard to understand.

What are ways to get around this bloating in C code and maintain readability of the core operation of a function (without sacrificing essential error handling code)?

Reins answered 22/8, 2015 at 16:43 Comment(12)
Why are you using errno as a typename?Velamen
well. if you don't print to stderr, and cut the braces, you save 4-6 lines. c is like this. actually all languages are like this, you can argue in java you expand a easy call to a try-catch block too. so, you code what you need.Reptant
you can, actually using macro you save some work, say #define SAFE_CALL(expr) { errno err = expr; if (err != 0) return err; }. this wouldn't be too bad if you restrict such usage in your code within a reasonable scope.Reptant
Believe it or not goto might help. See https://mcmap.net/q/110326/-good-c-style-when-checking-lots-of-return-valuesPhotoconductivity
@EOF Should have been errno_t. Changed to int to avoid confusion.Reins
@Reins is errno_t provided or not? if not, you shouldn't name your type in *_t form since all of them are reserved according to standard.Reptant
@HuStmpHrrr: Not according to the C standard. Only the POSIX standard (which may or may not apply here).Subheading
@Subheading hence replaced it with int to avoid detracting from the purpose of the question.Reins
if the code works then you'll probably have better answers on codereview.stackexchange.comElvinelvina
@Akeel: Agreed, it's just a red herring people like to nitpick.Subheading
after all, it's a good question from the view of a software architect. Using int for error codes is common practice in c, so glad you fixed that.Relucent
The error conditions in your example could easily be assertions, e.g. assert(result != NULL).Caundra
S
3

Welcome to the world of production quality code. There are some macro and factoring tricks that can make error checking less verbose. Exceptions are another mechanism. But the main adjustment to make is point of view. You are thinking of "the real work" as something separate from error handling. It's not. Dealing with all possible conditions is the essence of software engineering. To get it out of your system, explain "the main work" in comments, then write the real algorithm with externally imposed condition handling front and center.

Sidky answered 22/8, 2015 at 17:19 Comment(0)
R
0

This is the main reason for exceptions, but I have to admit I'm not a friend of introducing exceptions in a language using explicit memory management, so this answer might be biased. That said, there are a few common strategies for separating business logic from error handling in .

  1. Use exceptions -- have a look at longjmp()/setjmp() for implementing them yourself in . I would advise against it.
  2. If there is cleanup to do in case of an error, put it at the end of the function and goto there. (yes, really!)
  3. For checking a return value and printing a message to stderr, try to factor out common cases and define macros like e.g. 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)
    
Relucent answered 22/8, 2015 at 16:59 Comment(0)
W
0

If line's length is little concern to you, 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 shrink two last ifs like this:

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 in common with other cases, there is no reason to accommodate checkforError to it.

Short circuiting has guaranteed order of evaluation, so it's not undefined behaviour. See Logical comparisons: Is left-to-right evaluation guaranteed?

Wakashan answered 22/8, 2015 at 16:59 Comment(0)
S
0

The only situation where error handling code is unnecessary is when it's redundant. Avoiding redundant error handling code is exactly the same as avoiding redundant code in general. The only question is what the scope for the error handling code can be. Generally factor as much common behavior into as large a scope as possible.

For example, malloc failing is generally 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 scoped to the calling function, you can use a 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.

Here's some example code I use. In general, reducing error handling code is no different than just grouping common behavior.

Sagittate answered 22/8, 2015 at 18:18 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.