On exceptions, errors, and contract violations

Bruno Medeiros via Digitalmars-d digitalmars-d at puremagic.com
Wed Oct 29 06:28:22 PDT 2014


On 03/10/2014 18:40, Sean Kelly wrote:
> I finally realized what's been bugging me about thew program logic
> error, airplane vs. server discussion, and rather than have it lost in
> the other thread I thought I'd start a new one.  The actual problem
> driving these discussions is that the vocabulary we're using to describe
> error conditions is too limited.  We currently have a binary condition.
> Either something is a temporary environmental condition, detected at
> run-time, which may disappear simply by retrying the operation, or it is
> a programming logic error which always indicates utter, irrecoverable
> failure.
>
> Setting aside exceptions for the moment, one thing I've realized about
> errors is that in most cases, an API has no idea how important its
> proper function is to the application writer.  If a programmer passes
> out of range arguments to a mathematical function, his logic may be
> faulty, but *we have no idea what this means to him*.  The confusion
> about whether the parameters to a library constitute user input is
> ultimately the result of this exact problem--since we have no idea of
> the importance of our library in each application, we cannot dictate how
> the application should react to failures within the library.
>

Spot on, I pretty much agree with everything above, but up to this point 
only.

After that, not so much.

> A contract has preconditions and postconditions to validate different
> types of errors.  Preconditions validate user input (caller error), and
> postconditions validate resulting state (callee error).  If nothing
> else, proper communication regarding which type of error occurred is
> crucial.  A precondition error suggests a logic error in the
> application, and a postcondition error suggests a logic error in the
> function.  The function writer is in the best position to know the
> implications of a postcondition failure, but has no idea what the
> implications of a precondition failure might be.  So it's reasonable to
> assert that not only the type of contract error is important to know
> what to fix, but also to know how to react to the problem.
>
> Another issue is what the error tells us about the locality of the
> failure.  A precondition indicates that the failure simply occurred
> sometime before the precondition was called, while a postcondition
> indicates that the failure occurred within the processing of the
> function.  Invariant failures might indicate either, which leads me to
> think that they are too coarse-grained to be of much use.  In general, I
> would rather know whether the data integrity problem was preexisting or
> whether it occurred as the result of my function.  Simply knowing that
> it exists at all is better than nothing, but since we already have the
> facility for a more detailed diagnosis, why use invariants?
>

"A precondition error suggests a logic error in the application, and a 
postcondition error suggests a logic error in the function."

Suggests yes, but it doesn't guarantee. A postcondition error (or 
invariant failure) in a component (imagine a library) could well be 
triggered not due to a bug in that component, but a bug in the use of 
that component.
Only if the component properly defines all the preconditions for all its 
functions that case should not happen. But in practice we know that 
software is not perfect, and a lot of preconditions may not be 
explicitly defined.

> Without running on too long, I think the proper response to this issue
> is to create a third subtype of Throwable to indicate contract
> violations, further differentiating between pre and postcondition
> failures.  So we use Exception to represent (environmental) errors which
> may disappear simply from retrying the operation (and weirdly, out of
> memory falls into this category, though @nothrow precludes
> recategorization), Error to represent, basically, the things we want to
> be allowable in @nothrow code, and ContractFailure (with children:
> PreconditionFailure, PostconditionFailure, and InvariantFailure) to
> indicate contract violations.  This gets them out from under the
> Exception umbrella in terms of having them accidentally discarded, and
> gives the programmer the facility to handle them explicitly.  I'm not
> entirely sure how they should operate with respect to @nothrow, but am
> leaning towards saying that they should be allowable just like Error.
>

Even if we could correctly differentiate between precondition failures 
and postcondition ones, what would that gives us of use?
I think in practice when some code hits an error in some component that 
it uses, knowing whether it is a precondition failure (bug in the code 
using the component), or a postcondition (bug in the used component 
itself), it may actually not tell us much about how much of the program 
has been affected, ie, which fault domain is broken.

I think the default behavior should be simply the clean throw of an 
Exception when an assertion fails. If there is a performance issue with 
this, and we want to crash the program immediately when an assertion 
fails, then that should be an option too. However this behavior should 
be configurable per library/component, not globally for the whole 
program, that is too coarse. Also, it should be configurable *in the 
code* itself, not at compile time. For some components it should even be 
possible to use the component with hard-stop assertion failure behavior 
in some places in the program, and with clean exceptions in other places 
of the *same program*.


-- 
Bruno Medeiros
https://twitter.com/brunodomedeiros


More information about the Digitalmars-d mailing list