On 03/25/2011 08:21 PM, Jonathan M Davis wrote:
So, there really is no good answer.
- Jonathan M Davis

So why do you need to differentiate between assert and enforce if you can't
choose, which of them should be used?

We can't really turn off both of them, and if we really want performance
and no checks, we would want to turn off both of them, so they should work
in the same way, shouldn't they?

assert and enforce serve _very_ different purposes. assert is for checking the
logic of your code. It can go away, so you can't rely on it. It's simply for
additional checks in your code to ensure that it's of high quality. You can't
rely on it. It's also _not_ for error handling. When an assertion fails, there
is a bug in your program.

Exceptions - and therefore enforce - are for error handling. They are supposed
to _always_ be there. Performance has nothing to do with them (other than the
fact that they can obviously harm performance, which may cause you to refactor
your code so that they're not necessary). Typically, exceptions - and
therefore enforce - are used when validating input. That input is often
completely dependent on the particular run of the program, and bad input isn't
necessarily a bug in the program at all. When enforce fails, that does _not_
necessarily indicate a bug in your program, and it should _not_ be used for
finding bugs.

Input from the user is obviously always input, and you're going to have to
check that and throw an exception on failure rather than use assertions. Input
to a function which is completely local to your code is not in any API
anywhere and whose input is completely controlled by your code should use
assertions. At that point, if the function gets bad input, it's a bug in your
code. Also, out blocks, invariants, and checks in the middle of functions
typically have _nothing_ to do with input and should be assertions. If they
fail it's a logic bug.

The problem is when a function could be both used internally and used on user
input. For instance, iota is typically given hard-coded values - iota(5, 100,
2) - but you could pass it value which was given to main - iota(5, 100,
to!int(args[1]). With hard-coded values, assert is the correct solution. But
with user input, enforce would be. So, which do you do? assert or enforce?

In the case of iota, since it is almost always used with hard-coded values and
even when it isn't, it's likely used with computed values rather than user
input, so if it's wrong, it's a bug in the code rather than bad user input.
The application can check (with enforce or with an if and throwing an
exception or whatever) that the input is good before passing it to iota if
that's what it's doing.

With other functions though, it's less clear. And with every such function, a
choice must be made. Should it treat its input as user input or as values
local to the program? If it's user input, then it needs to use exceptions. If
it's local to the program (at which point a bad value would be a bug in the
program), then assert should be used. And when you're dealing with a library,
it's not as clear what the best solution should be in. The fact that
assertions are almost certainly going to be compiled out might make it so that
you want to treat input to the library's API as user input rather than local
to the program when you would choose to have those same functions be treated
as local to the program if they weren't in another library (though of course,
there are plenty of cases where API functions should just plain be treating
input as user input regardless).

So, there is a clear and distinct difference between the intended uses of
assert and exceptions (and therefore enforce). They have very different roles.
The question then is not what their roles are but what you need a particular
function to do - e.g. treat it's input as user input or treat it as local to
the program (and therefore a bug if it's wrong).

This logic certainly looks sensible, but I cannot understand how it should work in practice. Say I'm implementing a little set of operations on decimals. Among those, some (division, square root...) will necessarily have to check their input. According to the rationale you expose, I should use assertions, since operand will nearly never be (direct) user input, instead be products of the app's logic. Then, what happens when div gets a null denominator on a release build? In practice, the issue is not that serious since I will certainly delegate to a lower-level func which itself throws. But I could also (in theory) implement it in assembly or whatnot. My point of view is if a func has preconditions on its args, then checkings simply cannot go away.

Such considerations lead me to wonder whether we should not instead use exceptions/enforce everywhere for actual func arg checking, and use asserts in unittests only. Or use them also for /temporary/ additional checkings during development (similar to unittests in fact).

A special case may be about checkings that control logical values or ranges which do not prevent the func to run. Say, a.length should logically be in 1..9 -- but the func can run fine anyway.

Denis
--
_________________
vita es estrany
spir.wikidot.com

Reply via email to