C Code Quality – Should Preconditions Be Asserted in Public API?

ccode-contractscode-qualitydesign-by-contractvalidation

I am writing a library for some data structures in C that will be used in embedded systems. I have had issues designing and coming up with a solid error handling plan. This API is only subject to logic errors which is why I am so conflicted. By this I mean the preconditions might be: "x != NULL" or "index < size of container".

Doing a lot of research on the forums here, it seems as though their are several approaches for error handling in C:

  1. Use errno. I believe the general sentiment is errno should be avoided at all costs, and its design philosophy is outdated, so this is a no-go.
  2. Use error codes. Every function could either return error codes, or the user could pass a pointer for the error codes to be assigned. I believe returning error codes is much more elegant than the latter, but some functions will feel clunky because the user will have to pass a pointer to get the "output" of the function. One of the functions is "get_index_of_object". You can see how this would be anything but convenient.
  3. Use asserts that will be disabled in production build. This is what I am leaning towards as of now, since these data structures will be used so often, the performance boost of having the asserts disabled in production build might be noticeable (although I have no data to support this as of now). As previously mentioned, this API can only be affected by logic errors (users not respecting the preconditions of the functions). To my understanding, asserting logic errors for debugging purposes is encouraged, but is it good practice to use asserts for validating function arguments in a public API? Especially when it will be used in embedded systems?
  4. Check the preconditions, and return early if they are violated. My biggest issue with this approach is debugging might be a nightmare for someone years later. Some functions could return, say "NULL", but others such as "get_index_of_object" can't return a value that will describe error.
  5. Use a combination of the above four error handling methods. I would like to avoid code bloat and keep things as convenient as possible, but this is an option.

I am curious as to what yall have to say.

Best Answer

Speaking as a developer for small embedded systems, I would take asserts over any of the other options.

If the space for your code is limited, then you can easily get into a situation where every byte counts. In that situation, asserts (especially the assert macro) has the advantage that you can disable the sanity checks to the point that they don't contribute to the code size.
Of course, that should only be done when you are confident that the calling code doesn't break the contracts of the API, but that can often be checked in a more space-lenient environment.

In most embedded development environments, the assert macro has the added benefit of immediately transferring you to a breakpoint in the debugger when the assertion fails, so you can immediately inspect why the assertion failed.

The general guidance for not using asserts in public API's is geared more towards situations where unsanitized user-input can reach the API (for example, a web-API) or where the implementation of the API isn't available to the programmer calling it (for example, a closed-source, binary-only library).
In the embedded world, even proprietary libraries generally come with the source code available because the variability in processors and tools makes it prohibitive to distribute binaries for all of them.

Related Topic