0

In a library, there could exist three types of functions. The first are those which are visible to the user i.e. their declarations are installed in the library's include directory. The third are those which do the core work of the function by e.g. getting into the numerics in a highly detailed fashion. The second are those which provide complex functionality by calling several functions of the third kind, and managing their outputs. I like to call these interface, bridge and implementation functions, respectively. If these are commonly referred to by other names, please tell me them.

Now an interface function could look like this (pseudo-code in C/C++):

void compute_something(const double* data, const std::size_t& size)
{
    bridge_function(data, size);
}

The above bridge function could look like this:

void bridge_function(const double* data, const std::size_t& size)
{
    const auto sum = impl::function1(data, size);
    impl::function2(data, size);
    impl::function3(data, size);
    // ...
    impl::functionN(data, size, sum);
}

Finally, the first of those above arbitrary implementation functions could look like this:

namespace impl
{
double function1(const double* data, const std::size_t& size)
{
    ret_val = double{};
    for (auto index = std::size_t{}; index < size; ++index)
    {
        ret_val += data[index];
    }
    return ret_val;
}
} // namespace impl

I've read often that every function should check its own preconditions. However, the above functions could share many — if not all — preconditions. In this simple example, it could be that the following are the only preconditions:

if (data == nullptr)
{
    // throw exception
}
if (size < 1)
{
    // throw exception
}

Now, here's the question: at which places should the perfect software developer check the preconditions? Possible answers: (a) only at the interface level (b) only at the implementation level (c) on all levels (d) at the interface level and within a debug section at the implementation level.

I look forward to hearing well supported/reasoned answers (also gladly outside of the above options) and appreciate your participation in this discussion.

2 Answers 2

1

There are basically two ways to deal with preconditions.

  1. The caller is fully responsible, and if the preconditions are not met, all warranties are void. This is essentially how the C standard library treats preconditions.
  2. The callee checks the preconditions and gives an error if they are not met.

From a viewpoint of defensive programming, the second is vastly preferred, as programming errors involving preconditions are way easier to catch.

On the other hand, checking preconditions in each and every function also incurs a cost in performing the same check over and over again, even if you can rigorously prove that the check never can fail.

In my opinion, the best compromise is that precondition checks are added to all functions, unless you have the processes and tools in place to prove beyond doubt that a function cannot be called in an incorrect way.
And another reason to skip the precondition check is if the check is prohibitively expensive to perform (for example, a check that a large array doesn't contain duplicated values).

This means that, mot likely, your interface functions need to check the preconditions. The bridge and implementation functions don't, if you can prove, by code reviews and tests, that those functions can't be called incorrectly.

0

Iron rule: No outside input must ever cause damage. Worst case it must be handled as if it never happened, possibly logged, possibly with an error message if the user might do something about it or would be surprised that nothing happened. Or it may be corrected, like a video player skipping over rubbish in its input stream.

There may be unusual things happening. Unusual means it shouldn’t normally happening, but you have code handling it. Like your configuration/ settings not being there, no internet connection, incorrect clock settings, no printer. You may log things, because they are unusual, so generally more logging because the code has less user testing. No asserts.

The rest is programming errors (wrong external input that hasn’t been handled at this point is a programming error). I have a set of macros to handle these. The closest to a C assert will log and fall into the debugger. AssertFatal is guaranteed to crash. AssertAlways always evaluates the condition tested. There’s a Boolean function that asserts, then reports success or failure and lets the app handle the outcome of the assertion. There is just no one size that fits all.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.