what is the point in using assert() to chk for errors if it only works
in debug build?
--
Best Regards,
Mike Colasono
What's the point of having a release build if it doesn't differ
from a debug build?
Victor
>
> hi:
>
> what is the point in using assert() to chk for errors if it only works
> in debug build?
What's "debug build"? It is up to the programmer to decide whether or
not the assert() macro is active any time the program is built, unless
he/she decides to surrender control to some predefined settings
decided on by the project manager of an IDE.
What's the point of testing at all?
--
Jack Klein
Home: http://JK-Technology.Com
FAQs for
comp.lang.c http://www.eskimo.com/~scs/C-faq/top.html
comp.lang.c++ http://www.parashift.com/c++-faq-lite/
alt.comp.lang.learn.c-c++ ftp://snurse-l.org/pub/acllc-c++/faq
it will. there will be no debug info.
Best Regards,
Mike Colasono
you don't remove the _DEBUG def 'cause it affects other areas of the
build. you would remove the #ifdef from around the assert macro.
> What's the point of testing at all?
>
right. let it crash. the customer expects crap software and we must
maintain the highest standards of crap lest they expect more from us.
write your error messages so that not even you can understand them (make
them say the same bs thing) and bill xtra hours to move the problem to
another block of code thus ensuring a job in the future. blame th
problem on stoopid user tricks that you didn't have time to prevent
because you're so overworked.
>
i see alot of code using assert() and there's nary a hint that anything
was modified to allow assert() to work in a release build, thus my
question.
C++ doesn't define "release build" or "debug build". It does define
how the assert macro functions if the macro NDEBUG is defined, and how
it functions if that macro is not defined.
What makes up a "release build" or a "debug build" is something that
is up to the programmer or the IDE designed, if the programmer gives
up that control.
On the other hand, if defining NDEBUG causes a change in the program
other than in how the assert() macro operates from not defining it,
either your compiler is non-conforming or is being operated in a
non-conforming mode, or there is a bug in your code.
If you don't see the point, don't use it. But if you understand how
to control your compiler, you can make the assert() macro active in
any mode you want to.
> hi:
>
> what is the point in using assert() to chk for errors if it only works
> in debug build?
None whatsoever. assert() is a lazy tool for weak minds, those incapable
of writing proper error-handling code; if you *have* such error-handling
code, then production build or debug build doesn't matter; the
error-handling code does what it's supposed to do - handles errors. If
you have such code, you don't need assert; if you don't have it, well,
your app is pretty much screwed anyway.
assert() is not a substitute for error checking. For example, if you open a
file, checking that it opened at all should not be done with assert. Instead
your code, both debug and release builds should handle failure to open.
Where assert() is useful is checking class invariants and other types of
invariants. assert() is used to make sure you did not make a logical mistake
in class design.
For example you might have some private internal member function foobar(),
int someclass::foobar(int i)
{
:
}
and i is only supposed to 5 or greater. An assert will check this in a debug
build. If the assert ever fails, you know your class needs fixing.
Another example.
class someclass2
{
private:
int x, y, z;
};
At all times for this class, x + y +z must be 10. So that means if you call
a public member function
someclass2::somefunction()
At the start of someclass2::somefunction(), the total must be 10, during it,
it may change but on returning from the function, it must be 10. An assert()
at that top and bottom of someclass2::somefunction() can be used to check
that you designed someclass2 correctly.
An analogy:
All banks have security: vaults, steel doors, laser scans etc. The security
is like checking for errors. assert() is like tripwires inside the vault. If
ever they go off, you know that your security has been compromised and needs
fixing. The tripwires are not a substitute for security but there to let you
know when your secuirty needs looking at.
Stephen Howe
>
> hi:
>
> what is the point in using assert() to chk for errors if it only works
> in debug build?
>
The assert() macro only makes sense in "debug builds". It is a tool for
detecting logical errors and unintended side effects in the source, not for
handling runtime error conditions. Simply terminating a running program
would be a pretty poor way of handling such conditions anyway.
If you feel you need to have such assertions in your "release builds",
chances are you might need debugging symbols, too...
Regards
But your not answering his question. He did not ask how assert() works, or
how to make assert() work but why you would ever use it. And you have not
answered that.
Are you claiming that assert() is of no use at all?
If you are not, please give an example of when it would be use and why.
Thanks.
Stephen Howe
You gave your answer. We're all grateful for it. There is no
need to _demand_ anything from others in this newsgroup. There
is no need for _everybody_ to give the "correct" answer. Don't
you think?
Victor
--
Please remove capital A's from my address when replying by mail
Class creation
set parameter initialized = false
Initialization method
set parameter initialized = true
Another method
if not initialized, assert
That way, if someone else is using my class, they use it properly. They'll
find that out when they are still debugging. In order to use my class at
all, it will be used correctly, and by the time they make the release build,
the assert is not needed. It's for the programmers, not the final users.
-karen
That's a pretty bold assertion. It's also not correct. The assert function
is not and never was intended as "error-handling" code. It is for the
programmer's use in debugging logical errors in coding (such as passing an
incorrect index to an array-handling function), not for detecting run-time
problems (such as failure to open a file). You should not be writing
error-handling code to handle incorrect program design. Likewise, you
should not be writing assertions to handle run-time problems. They're two
different beasts. If you were to ever get an assertion failure in a release
build (assuming that assertions were in use then - which they should not
be), that would indicate a failure on your part as a programmer to do
complete testing of your software prior to releasing it.
Howard
>
> "Kelsey Bjarnason" <kel...@xxnospamyy.telus.net> wrote in message
> news:pan.2003.04.21....@xxnospamyy.telus.net...
>> On Mon, 21 Apr 2003 00:12:27 -0400, Mike C wrote:
>>
>>
>> > hi:
>> >
>> > what is the point in using assert() to chk for errors if it only works
>> > in debug build?
>>
>> None whatsoever. assert() is a lazy tool for weak minds, those incapable
>> of writing proper error-handling code; if you *have* such error-handling
>> code, then production build or debug build doesn't matter; the
>> error-handling code does what it's supposed to do - handles errors. If
>> you have such code, you don't need assert; if you don't have it, well,
>> your app is pretty much screwed anyway.
>>
>
> That's a pretty bold assertion. It's also not correct. The assert function
> is not and never was intended as "error-handling" code.
Nor did I say it was; I said that if you have error-handling code, assert
becomes instantly useless and if you lack error-handling code, your *app*
becomes instantly useless; thus in no case does having assert offer you
anything useful.
> It is for the
> programmer's use in debugging logical errors in coding (such as passing an
> incorrect index to an array-handling function),
Which error-handling code also does, thus rendering assert's existence
moot. Or are you suggesting that it is okay to ignore possible OOB
conditions in a production build? No? Fine, then you're checking for
them and handling errors, right? Therefore you don't need assert, right?
Right. Assert does nothing for you here that your error-handling code
doesn't do.
> problems (such as failure to open a file). You should not be writing
> error-handling code to handle incorrect program design.
No, you should be writing error-handling code to handle errors, which can
be anything from passing bogus NULLs around to being told to read 500
bytes from a 50-byte file to failure to allocate memory to inability to
write to a stream and on and on and on.
In none of these cases does assert offer anything your error-handling code
doesn't already offer - assuming you have error-handling code that's worth
having - and in the one possible case you mention - design error - assert
can still only trap on predictable failures, which the error-handling code
can do equally well but handle far more flexibly. Again, assert buys
nothing.
A simple problem for you: define any assert whatsoever. The only
requirements for it are that it must be something which cannot be handled
by a runtime error-handler with _at least as much flexibility_ as assert()
handles it.
assert( fp != NULL ); Nope; a simple if() would handle that and allow you
to decide what to do - dump the data, open a different file, copy to the
printer, whatever. assert? Bah; data's irrelevant, just crash and burn.
If your error-handling code is designed well, you never need assert; if
it's designed poorly, you have problems assert cannot help you with.
A to the notion of using assert as a design-time tool, allow me to suggest
that relying on assert, rather than on good and proper error-handling,
even at design time, is a very bad idea. For one thing, in any but the
most trivial applications, exhaustive testing of all data paths is
virtually impossible; thus while your asserts may survive whatever tests
you've bothered to run, it is virtually certain they have not been exposed
to every possible application state. As such, confidence in their ability
to detect faulty design is at best low.
Given this, you are faced with three choices: to simply assume that, having
not had any fired assertions, the application is now "correct", to
provide the application with a defense mechanism against faulty states -
which is to say, error-handling code, or to manually go through and
re-examine the app's code to assure that the condition can never occur.
Here, allow me to demonstrate:
void func( FILE *fP )
{
assert( fp != NULL );
/* worker code */
}
void func( FILE *fp )
{
if ( fp == NULL )
{
/* handle error */
}
/* worker code */
}
In the former, only those test cases in which fp ends up being set to NULL
prior to calling func() will trigger the assertion; unless you
exhaustively test every single possible data path in your app, you have no
confidence that the function will never be passed a NULL.
In the latter, however, you always test, so fp can _never_ be NULL when
handed to the worker code, regardless of whether you've tested the current
application state or not.
Of the two, only one offers anything approaching application safety; only
one offers the chance to actually cope with the user's data being at risk,
by noting the error and offering a way around the risk.
The assert? It does nothing for us. Nothing whatsoever. It's single
utility is to lull us into thinking our application is designed correctly
and thus we don't need to provide fancy error-handling code. Twaddle. We
do need such code; what we don't need is assert and its false assurances
that everything is okay.
mmm ... bcs release build software never has errors :)
I too recently started using asserts more often. I especially liked
Stephen's response to your original post. Security and trip-wires
seems to be a really good analogy. With limited assertiveness :) I
thought it might be worth offering up the following. These examples
are intended to be very simplistic so please don't read much into
them.
Often, there are things that a developer assumes. For instance, you
define
void foo (Bar bar)
{
...
}
If this is the only definition of foo, you can assume with some degree
of confidence that your compiler will not build code that passes
objects unrelated to type Bar into function foo. This seems obvious.
Well, what about
void foo (Bar * bar)
{
bar->blah ();
}
What are you assuming here?
Well, again, you're assuming the type "bar". Hopefully, bar is indeed
a Bar *, but what else? You're also assuming that bar actually points
to a valid Bar object. But you say, its cool - it runs fast, its even
virtual !! and people shouldn't be making calls to foo with null
pointers anyway ... and you document this rigorously.
So, is that really a valid assumption? I don't know. Is your software
a closed system? Do you control every call to this method? Can you
"test" your software in every possible scenario to be sure that
indeed, a valid Bar * object is always passed here? Or are you writing
a library for others to use? Are you sure they will always call this
correctly? Do you want to give them any help? or just document your
api so well that they can't go wrong.
Ok - so you start your program ... and get this ...
% ./a.out
segment fault
%
Sucks ... not very much info ... maybe its related to foo ... maybe
not? foo is not the only questionable function you've written ... you
could open a debugger and chase the error ... or maybe you don't feel
comfortable with gdb ... so, you decide to go the long route ... you
"printf" your code to death. Finally, you find that indeed ... its
breaking in foo. Yikes ... well, how big is your project? What if we
could have something automatically check things like this for us.
Maybe we can write
void foo (Bar * bar)
{
if (bar == 0)
return;
bar->blah ();
}
What about that?
mmm ... well, what if the user wasn't expecting that? foo's test fails
and returns -- but the user "assumes" foo finished successfully!! mmm
... ok, probably not a good idea.
void foo (Bar * bar)
{
if (bar == 0)
throw Bar_Is_Null_Exception ();
bar->blah ();
}
You could try this. At least the user could potentially recover from
this and try again ... but - ah, one drawback is that you pay for
this. Its a quick test, but its still a test. It includes some runtime
overhead. Overhead in DEBUG or RELEASE mode.
What about one other idea. And, what if we have something called a
DEBUG build mode. We agree that we will stringently test all our code
from a DEBUG build - and once we have pushed it though the wringer -
and tried to put it through every possible situation ... if our next
test hasn't failed, then maybe we can be reasonably certain that it
might be ready for RELEASE. If testing is exhaustive ... and it never
fails in testing, then we might be able to remove the overhead in our
RELEASE build. Well, along come asserts :)
void foo (Bar * bar)
{
assert (bar != 0);
bar->blah ();
}
You can compile with or without them. Compile with them when you're
testing. Compile without them when you're building for RELEASE.
Whatever you want.
WARNING:
asserts are NOT a substitute for proper error checking. IE:
int user_input (int age)
{
assert (age > 0);
return year_of_birth[age];
}
Why is this bad?
Bad bad because these errors are expected to happen all the time. This
is not an invariant error as much as it is an expected error. Using
asserts here, you'd never catch this problem in RELEASE builds, much
less, deal with it appropriately using asserts.
As Stephen implies, asserts tend to be good at checking things you
just always assume are correct. Invariants -- by design, rules that
should always hold. But, because design isn't everything and
commercial software is big and complex and not every Joe Shmo is as
smart or intelligent .. or as aware as you are. Someone may think that
by passing you a null pointer - you'll create one for them! May 2003,
CUJ, "An Idea for Dynamic C Strings" by Daniel Lawrence --- presents
just such a c string library. Its convenient ... and in his case,
might have some worth.
Rules of thumb are hard to lay down - I've looked for them. Its a
judgement call - and those with greater experience often see clearer
patterns and consequently use better judgement. I hope you notice that
the real question is not "what is the point in using assert()" but
rather, "when is it appropriate to use assert()?"
asserts are your friends ... and just one of the many tools you can
use to make your code bulletproof (I'd also suggest TDD ala Kent Beck
- but that's another discussion).
I apologize if in the length of my post, I've mis-spoken.
-Luther
I'd really like to build a preprocessor based TDD framework, Kent Beck
style, defining tests in and out of my debug/release implementations
.. :)
Well that's a rather long and well-spoken argument, but I am not quite
convinced. The idea of loading down my release build with countless checks
that the state of my internal universe is correct sounds like incredible
overkill. A function presents a contract to the outside world, and that
contract must be followed in order for it to be expected to behave as
promised. If you have to check the validity of every parameter passed into
a function, and every variable or resource used, then you'll spend more time
checking than working.
Of course I do not assume that my app is perfect just because I get no
assert failures. But I suggest that it certainly helps me target key places
where I might make a mistake when making use of a functon I've previously
written.
The kind of failures that assert is useful for are those that your code
should not be ALLOWING to occur in the first place. For isnteance, if
you're selecting an item from a menu and then passing the index of that
selection to a function to access the Nth item from some array, it is useful
to be sure that your code CANNOT produce an out-of-bounds condition when
doing so. Once your calling code is correct, the function to which you
passed that menu selection index should not need to constantly check that it
is receiving a valid index. You're done designing, and now you are in
production, and you have already made sure, at coding time, that such a
thing cannot happen.
Now granted, you can always use if's, and then remove or comment out
parameter checks like that if you don't want them any more, but it is simply
easier to use asserts. That is, after all, what they were designed for, and
why they dissappear in non-debug builds.
I am also sure that there are many other examples where you do not want or
need run-time checking for invalid conditions. And I submit that you simply
can't afford the time and resources to check everything.
You obviously disagree with me (and the designers of the language) on this
point, but that's my opinion, anyway. I could be wrong... :-)
Howard
> What is the point in using assert() to chk for errors
> if it only works in debug build?
The C preprocessor macro assert is used to help programmers
detect programming errors (bugs) and *not* exceptions.
Exceptions are expected but unpredictable events
which the programmer *cannot* prevent.
Bugs are unexpected programming errors
which programmers can *prevent*
by fixing the bugs when they are detected.
The canonical example is the difference between
vector member operator[] and function at().
reference operator[](size_t n) {
assert(n < this->size());
return *(begin() + n);
}
reference at(size_t n) {
if (n < this->size())
return *(begin() + n);
else
throw out_of_range("vector");
}
Member operator[] is *undefined* unless
0 <= n < this->size()
It is a programming *error* to pass n outside this range
to operator[].
It is *not* a programming error to pass n outside this range
to member function at(). It is an *exception*
and the behavior of at() is *well defined* --
it is supposed to "throw" and exception [object].
The C preprocessor macro assert in operator[]
helps programmers detect bugs in their programs.
Once the code has been thoroughly tested
and all of the bugs have been eliminated,
the assert macro can be disabled.
No. It is as he says.
If you had a simple string class, say
class String
{
private:
size_t allocated;
size_t length;
char *data;
};
where for performance purposes "length" is how many characters the string
holds, "allocated" is how many characters have been requested from new []
and data is the string data + a terminating '\0'.
then class invariants would be
length <= allocated
strlen(data) == length
Now what are you suggesting?
Are you suggesting we should check these class invariants hold, before/after
every call of String's public interface?
Because I would maintain that is a needless performance killer and they
don't fall into the criterion of error checking.
Nevertheless, they are useful as a "check of last resort" that a class is
logically correct in debug builds.
This is similar to Betrand Meyer's "Design by Contract". assert() is perfect
for pre and post conditions.
Stephen Howe
Let's exaggerate this a little ... I've written a new String wrapper
as part of this project. I use it in my project parser code. Or ...
forget that I wrote a new String, I'll just use the STL - which, by
your argument below, we know hasn't been tested against every possible
data case - and so, can't be completely verifiably safe in every
possible programming circumstance ... and maybe I'm just using it
incorrectly ... I don't know. In any case, lets go berserk.
class String<T>
{
public:
String (const T * c_str);
...
String & operator= (const String & str);
bool operator== (const String & str);
bool operator== (const T * str);
...
};
class Parser
{
public:
void do_something_useful (const char * input_parse_string)
{
assert (input_parser_string != 0);
String<char> str1 (input_parse_string);
assert (str1 == input_parser_string);
String<char> str2 (str1);
assert (str1 == str2);
String<char> str3 = (ENCRYPT_STRING<char> (str1));
assert (str3 != str1);
assert (str1 == str2);
// check side effects ENCRYPT_STRING contracts to do
assert (str3.length() < String<char>::max_length);
...
// I've only done 3 things so far
}
};
Wow ... in fact, I now realize that I could check every single side
effect that every single function I write could have. That might be 10
or 20 checks after every single call.
Here is what you propose:
class Parser
{
public:
void do_something_useful (const char * input_parse_string)
{
if (input_parse_string != 0)
{
// handle error
}
String<char> str1 (input_parse_string);
if (str1 == input_parser_string)
{
// handle error
}
String<char> str2 (str1);
if (str1 == str2)
{
// handle error
}
String<char> str3 = (ENCRYPT_STRING<char> (str1));
if (str3 != str1)
{
// handle error
}
if (str1 == str2)
{
// handle error
}
if (str3.length() < String<char>::max_length)
{
// handle error
}
...
// I've only done 3 things so far
}
};
Is that really what your code looks like?
mmmm ... I guess I don't really consider this "checking for errors" as
much as I am trying to make be positive I am using the class
correctly. Probably, a failed assertion here means I am using an
object incorrectly. Maybe I am unknowningly using auto_ptr with a
vector ... and I thought it would copy correctly. Personally, I'm not
explicitly tracking errors at all. I'm trying to validate String's
behavior.
As you so clearly state:
> A to the notion of using assert as a design-time tool, allow me to suggest
> that relying on assert, rather than on good and proper error-handling,
> even at design time, is a very bad idea. For one thing, in any but the
> most trivial applications, exhaustive testing of all data paths is
> virtually impossible; thus while your asserts may survive whatever tests
> you've bothered to run, it is virtually certain they have not been exposed
> to every possible application state. As such, confidence in their ability
> to detect faulty design is at best low.
Maybe its a perception issue, but in my example, I don't consider it
error handling ... One is free to use asserts to be absolutely sure
one understands what they operation just did and that String<char>
does indeed behave as I think it is supposed to. And with String or
operator= ... its easy to think you don't need to check ordinary
operations since type String and operator= have very well defined
semantics. Lets change it ... what if it was some other - extremely
benign object or some screwy operation. What if someone cast's away
constness and alters my const Obj parameter? I think I know how its
supposed to work - but I'm not sure. I didn't write it. Joe Shmo wrote
it and he's left the group. Or ... I downloaded it from Boost. Oh yea,
its a shared_ptr. mmmm ... how many times has that been updated
online? Well, I'd like to be able to download that latest update and
just test my TDD application ala Kent Beck with the latest shared_ptr
implementation.
And I know they're not perfect, but my tests are relevant to what I
do. My tests might assert a condition I "know" to exist and
furthermore, that I depend on to exist for handling later in my code.
In addition now, I'd like to make sure no "new" implementation breaks
these established invariants. I want to try a boost shared_ptr, a
qt_shared_ptr, a ubisoft_shared_ptr. I check that the invariant exists
for all my cases in DEBUG and then performance test in RELEASE. Hey -
lets go one step farther. Lets say I can be 99% sure that if a class
indeed acts as I think it should and the invariants I believe hold
true actually do - then my project will work perfectly. So -- I'd like
to check every invariant at every step of the game. Every side effect,
every possible change.
I bet my code would be one step closer to bulletproof.
Lets consider implementing all these tests as if statements. mmmm ...
I don't think so... all those test at runtime? mmm ... I bet I won't
write so many. I bet I won't test stuff I 'know' to be true. In fact,
I'll have fewer tests.
I don't know about you, but I sleep alot better at night knowing my
DEBUG build had 1000000000 asserts and never failed. I'd be pretty
comfortable going into production with a RELEASE build. And I don't
disagree with your idea of proper error checking code. Its essential.
Gotta have it. But 3rd party objects or api misunderstandings or bugs
in libs are almost orthogonal to application errors and might warrant
some other catch.
<core>
In a nutshell, you're correct, you can't test every possible case. So
- I doubt very much you'll try. Especially if you are writing your
tests with embedded if statements everywhere. I personally enjoy the
freedom of mind that comes from rigorously going berserk with asserts
in debug mode, and then magically losing all that extensive testing in
a RELEASE.
</core>
I still think you need explicit error code as well - I just agree with
Howard that there is some justification for asserts. And your point is
well taken - but to the extent that you say
> The assert? It does nothing for us. Nothing whatsoever. It's single
> utility is to lull us into thinking our application is designed correctly
> and thus we don't need to provide fancy error-handling code. Twaddle. We
> do need such code; what we don't need is assert and its false assurances
> that everything is okay.
is just maybe a bit impractical if not overbearing. At some point - I
imagine that even you stop testing "everything" and assume something
about the libs or code you are using or writing.
-Luther
<abusing-asserts>
I agree that people can abuse asserts - and use them fallibly for
error checking - which argument against assert proponents might hold
more water than simply asserting ;) that "asserts are evil."
</abusing-asserts>
Honestly, I don't assert everything in my code as this berserk example
portrays ... but its there when I need it.
That's a nice analogy, but it doesn't explain why switching off
asserts before delivering your software to the customer is a good
idea. Suppose you are a manufacturer of bank vaults. You have just
built the first of your new super-secure model. Before marketing it,
you decide to get the five best safe crackers and bank robbers in the
world to each put together a team to try and break into your vault.
Each team fails to break in so the tripwires never go off. You can't
afford any more time or money for R&D so you start selling vaults. Are
you going to sell them without tripwires in place? As you say,
tripwires won't prevent someone breaking in, but they will alert the
customer to the problem immediately, so you can get working on a fix
as quickly as possible.
Where that falls down is that assert tests have a runtime cost in
software (perhaps the analogy would be the customer having to spend 5
minutes arming and disarming the tripwires every time they went into
the vault). But unless that runtime cost makes the software unuseable
(sometimes it will do, sometimes it won't) I prefer to leave the test
in place. If the test ever fails, the program logs the failure and
exits. It is no use to the customer, but at least the bug, and a
starting point for a fix, have been identified quickly. That seems
preferable to a program which responds to a logic error by silently
doing the wrong thing until it crashes mysteriously three days later.
GJD
Well with most software you alpha & beta test your programs. Remember, these
asserts are there to catch logical errors in your programs. If they occur,
you fix the design. If the debug version has been running smoothly for 6
months with some clients as guinea pigs, no asserts are asserted, then it is
ready for release.
Remember I did say that assert() is not substitute for error checking? If
there are checks going on where assert() is playing the role of error
checking then that is the wrong use of assert(). It should be a error check
instead.
> Suppose you are a manufacturer of bank vaults. You have just
> built the first of your new super-secure model. Before marketing it,
> you decide to get the five best safe crackers and bank robbers in the
> world to each put together a team to try and break into your vault.
> Each team fails to break in so the tripwires never go off. You can't
> afford any more time or money for R&D so you start selling vaults. Are
> you going to sell them without tripwires in place? As you say,
> tripwires won't prevent someone breaking in, but they will alert the
> customer to the problem immediately, so you can get working on a fix
> as quickly as possible.
Yes but in this case here, you are using the tripwires as part of the
security which is not the purpose. If there are 6 entrances to the vault and
a tripwire covering one of the entrances goes off, then that tells you the
security guarding the entrance is flawed. The right response is to beef up
the security for the entrance.
> Where that falls down is that assert tests have a runtime cost in
> software (perhaps the analogy would be the customer having to spend 5
> minutes arming and disarming the tripwires every time they went into
> the vault). But unless that runtime cost makes the software unuseable
> (sometimes it will do, sometimes it won't) I prefer to leave the test
> in place. If the test ever fails, the program logs the failure and
> exits. It is no use to the customer, but at least the bug, and a
> starting point for a fix, have been identified quickly. That seems
> preferable to a program which responds to a logic error by silently
> doing the wrong thing until it crashes mysteriously three days later.
Right but as said that is where alpha and beta testing comes in. If I have
some asserts in a copy constructor, then I assume that alpha and beta
testing will exhaustively stress the copy constructor. If nothing occurs
then a non-debug version can be produced.
All of this testing can be exhaustive. Some software houses don't like
releasing software until all possible paths of execution have been checked
or the more stringent, all possible inputs (if possible to check) or doing
some form of memory stress test where new/malloc() fails, etc or limited
resource checking.
If test ing has been done, it is through, then producing a production
executable is the next step.
Stephen Howe
...and re-tested. ;-)
>
> All of this testing can be exhaustive. Some software houses don't like
> releasing software until all possible paths of execution have been checked
> or the more stringent, all possible inputs (if possible to check) or doing
> some form of memory stress test where new/malloc() fails, etc or limited
> resource checking.
>
> If test ing has been done, it is through, then producing a production
> executable is the next step.
That doesn't mean that it won't have internal "bugchecks", conventional
asserts aside for a moment.
regards,
alexander.
--
http://www.ibmlink.ibm.com/usalets&parms=H_203-002
(Technical Information... Note:...)
On Mon, 21 Apr 2003 23:02:15 -0700, Luther Baker wrote:
> Here is what you propose:
>
>
>
> class Parser
> {
>
> public:
>
> void do_something_useful (const char * input_parse_string)
> {
> if (input_parse_string != 0)
> {
> // handle error
> }
So far so good.
>
> String<char> str1 (input_parse_string);
> if (str1 == input_parser_string)
> {
> // handle error
> }
Is this actually an error? Okay, assume it is... good...
> String<char> str2 (str1);
> if (str1 == str2)
> {
> // handle error
> }
Likewise...
> String<char> str3 = (ENCRYPT_STRING<char> (str1));
> if (str3 != str1)
> {
> // handle error
> }
> if (str1 == str2)
> {
> // handle error
> }
> if (str3.length() < String<char>::max_length)
> {
> // handle error
> }
Umm... why would a string _less than_ the max length be an error? I think
you mean if str3.length() > max... but if so, it would seem that this is
not the place to be testing it; rather, the place to be testing it is
whethever the concatenation or other extension is being performed:
void cat(string& st1, const string& st2 )
{
if ( st1.length() + st2.length() > max_length )
{
// handle error
}
}
> Is that really what your code looks like?
Largely. Although I tend to prefer to use either exceptions or Error
types (eg a string type specifically used to indicate an error, rather
than data) or both. Anything short of this is pointless - why have error checking if
you're not going to check for errors? Design-time checking is likewise
useless, unless you can exhaustively examine every possible application
state.
> mmmm ... I guess I don't really consider this "checking for errors" as
> much as I am trying to make be positive I am using the class
> correctly.
Passing two strings to be catenated where the result is too large isn't an
error? Of course it is.
> Maybe its a perception issue, but in my example, I don't consider it
> error handling ... One is free to use asserts to be absolutely sure
> one understands what they operation just did and that String<char>
> does indeed behave as I think it is supposed to.
And it does, as long as your strings are only, say, 5,000 characters long.
Soon as you toss in some that are 25,000 characters long, the code fails
- but you didn't test that particular option, so the app merrily corrupts
memory, destroying the user's data. Whereas ignoring assert entirely and
providing sensible error handling would catch this instantly.
> semantics. Lets change it ... what if it was some other - extremely
> benign object or some screwy operation.
Define "benign object". Recall that even something as trivial as
bitshifting ints - both a trivial object and a trivial operation - can lead to
some less than pleasant results.
> What if someone cast's away
> constness and alters my const Obj parameter? I think I know how its
> supposed to work - but I'm not sure. I didn't write it. Joe Shmo wrote
> it and he's left the group. Or ... I downloaded it from Boost. Oh yea,
> its a shared_ptr. mmmm ... how many times has that been updated
> online? Well, I'd like to be able to download that latest update and
> just test my TDD application ala Kent Beck with the latest shared_ptr
> implementation.
Depends; are these things which can be tested? If so, then either the app
*can* encounter them and should take whatever steps are appropriate, even
if only to say "Oops, an application error has been encountered; should I
save your data?" then saving before barfing out. If they're not testable,
then assert can hardly factor in either. :)
> these established invariants. I want to try a boost shared_ptr, a
> qt_shared_ptr, a ubisoft_shared_ptr. I check that the invariant exists
> for all my cases in DEBUG and then performance test in RELEASE.
And blithely assume the libraries are completely bug-free in release mode?
:)
> Lets consider implementing all these tests as if statements. mmmm ...
> I don't think so... all those test at runtime? mmm ... I bet I won't
> write so many. I bet I won't test stuff I 'know' to be true. In fact,
> I'll have fewer tests.
The thing about _any_ such testing is to test where it makes sense to -
but to *always* test where it makes sense to. For example, the following
wouldn't make much sense:
int x = 3;
x++;
if ( x != 4 ) { // error! }
That is, we do place at least _some_ confidence in the compiler, the
libraries, etc. However, it makes perfect sense to test the following:
char *ptr;
ptr = malloc(100);
if ( ! ptr ) { // allocation failure... }
Given that it makes sense, we should test it - always, runtime, design
time, you name it. Same for, say, string catenation: if the result would
be too big, wouldn't it make sense to test for that?
> I don't know about you, but I sleep alot better at night knowing my
> DEBUG build had 1000000000 asserts and never failed.
I don't; all that would tell me is that the 1000000000 test cases thrown
at it thus far have passed; if the application has a billion times that
many possible states, all of which remain untested, then the assert has
done nothing but lull me into thinking my app works when in fact it's
simply a ticking bomb.
On the other hand, testing those points of failure, at runtime, assures me
that the app is _not_ such a ticking bomb, regardless of whether there are
any asserts or not, regardless of how many or few have ever fired.
> I'd be pretty
> comfortable going into production with a RELEASE build.
I wouldn't.
> Gotta have it. But 3rd party objects or api misunderstandings or bugs
> in libs are almost orthogonal to application errors and might warrant
> some other catch.
Not in my book. I tend to regard an error as an error. Whether it is a
design error, or an allocation failure or an out of bounds makes no
difference; either the data is protected from corruption or it isn't; if
it is, it's because there is properly defensive error handling in the app.
> In a nutshell, you're correct, you can't test every possible case. So
> - I doubt very much you'll try. Especially if you are writing your
> tests with embedded if statements everywhere.
No, but that's just it - don't write them "everywhere", write them where
they matter - but leave them in.
> I personally enjoy the
> freedom of mind that comes from rigorously going berserk with asserts
> in debug mode, and then magically losing all that extensive testing in
> a RELEASE.
Which simply tells you that the error that kills your app hasn't happened
_yet_ and leaves you without a way to cope with it in the production
build, since you're relying on asserts instead of error handling. This is
a good thing, is it?
> I still think you need explicit error code as well - I just agree with
> Howard that there is some justification for asserts. And your point is
> well taken - but to the extent that you say
>
>> The assert? It does nothing for us. Nothing whatsoever. It's single
>> utility is to lull us into thinking our application is designed correctly
>> and thus we don't need to provide fancy error-handling code. Twaddle. We
>> do need such code; what we don't need is assert and its false assurances
>> that everything is okay.
>
> is just maybe a bit impractical if not overbearing. At some point - I
> imagine that even you stop testing "everything" and assume something
> about the libs or code you are using or writing.
Sure; I assume that, say, strcat actually catenates strings, assuming it's
bbeen handed catenable strings of sufficient size and which aren't,
combined, beyond the reach of a size_t. Would I test that these
conditions are true just prior to a strcat call? Probably not except in
unusual situations; the right place would be in the code that got the
strings in the first place, likely. IME, well over 90% of error handling
is in validating input - both of data and of commands how to process the
data. If those are handled in a rock-solid manner, most of the rest of
what you might want to include as error handling can be done away with
completely.
Perhaps you've heard of the endless procession of buffer overflow errors
leading to security issues in various web servers? This is the sort of
thing which, for the life of me, I don't understand. I've written some
very basic server code which you can happily throw 10Mb of data at when it
only wants 100 bytes and the result is that it simply doesn't care: it
just discards the rest (or, optionally, leaves it to be subsequently eaten
in n-byte chunks). Unless the underlying stack itself is faulty, there is
simply no way to get more data into the server than the server expects.
No such thing as a buffer overflow. Doesn't happen.
Of course, since it doesn't happen, and it is in fact prevented from
happening by the input code, the rest of the code need not check for
buffer overflow conditions; they can simply proceed with doing what they
do - unless they happen to append to the buffer, in which case, they, too,
need to validate that they are not overflowing.
None of this would in any way be better served by assert, nor does it open
significant risk factors, nor does it necessitate excessive amounts of
runtime testing; decently-implemented error handling renders assert a
pointless waste, places little additional burden on the app yet should
catch virtually all cases of error which are sensible to test for in the
first place.
So tell me again how assert is anything but a tool intended to lull one
into a false sense of security?
>
> "Kelsey Bjarnason" <kel...@xxnospamyy.telus.net> wrote in message
>
> Well that's a rather long and well-spoken argument, but I am not quite
> convinced. The idea of loading down my release build with countless checks
> that the state of my internal universe is correct sounds like incredible
> overkill.
Only if poorly approached. If your error handling isn't as much a part of
the app's design as what the app is intended to do, then your design has
failed. If the error handling is designed in, well and properly, the
overhead should be minimal yet still catch every instance of failure which
it makes any sense to check for in the first place.
> A function presents a contract to the outside world, and that
> contract must be followed in order for it to be expected to behave as
> promised.
Yes, and? Oh, wait, I see. The contract says "don't pass me a NULL or
you'll get unpredictable behaviour". Well, fine, so we don't pass NULLs.
But how do we ensure we don't pass NULLs? Right; by checking our inputs
and our processing - that is, by error-checking.
> The kind of failures that assert is useful for are those that your code
> should not be ALLOWING to occur in the first place. For isnteance, if
> you're selecting an item from a menu and then passing the index of that
> selection to a function to access the Nth item from some array, it is useful
> to be sure that your code CANNOT produce an out-of-bounds condition when
> doing so.
Right. By checking the result against a range of legal values.
> Once your calling code is correct, the function to which you
> passed that menu selection index should not need to constantly check that it
> is receiving a valid index.
Oh, indeed. Why would you? This isn't even a source of _potential_
errors, though, so error-handling code here would be stupid. On the other
hand... why would you be using asserts?
Oh, right, because you haven't analyzed the code to ensure it doesn't
produce bogus results. So you rely on assert instead. Well, hell, 200
tests haven't fired an assert, it must work, recompile! Until the 201st
test where you do get the bogus result.
So, your asserts bought you nothing, did they? You still have to either
analyze the code to prove the bogus results cannot happen, or handle the
cases where they do happen - neither of which assert will help you with.
Remind me again why assert is supposedly useful?
> Now granted, you can always use if's, and then remove or comment out
> parameter checks like that if you don't want them any more, but it is simply
> easier to use asserts.
Only if you like bugs.
> That is, after all, what they were designed for, and
> why they dissappear in non-debug builds.
The fact that assert exists is of no more consequence than the fact that
gets() and atoi() exist; none of them belong in real code. Two cannot be
used safely and the third, while it can be used safely, doesn't actually
accomplish anything useful.
> You obviously disagree with me (and the designers of the language) on this
> point, but that's my opinion, anyway. I could be wrong... :-)
I don't know that the designers necessarily disagree, though they might.
Don't forget, they did include items such as gets, which nobody, including
them I am quite certain, would recommend ever using.
On Tue, 22 Apr 2003 00:56:20 +0100, Stephen Howe wrote:
> where for performance purposes "length" is how many characters the string
> holds, "allocated" is how many characters have been requested from new []
> and data is the string data + a terminating '\0'.
> then class invariants would be
>
> length <= allocated
> strlen(data) == length
>
> Now what are you suggesting?
Is this the sort of thing you would test with an assert? If so, then
there's a *reason* you check it with assert: presumably because it's not
guaranteed to hold true. If it's not guaranteed to hold true, then why
the hell wouldn't you be checking it at runtime?
This claim is simply untrue.
Assertions are important for detecting conditions that, in an ideal
world, would be incapable of happening. Such conditions are often due
to bugs elsewhere in the code, which implies that no meaningful error
recovery is possible. After all, what reason is there to believe that
any attempt at recovery would not contain similar bugs?
A variation on this idea is when the validity checking is too
expensive to do during production, which makes it important to be able
to turn it on only during testing.
In circumstances such as these, the most sensible thing the program
can do when it discovers the ``impossible'' situation is to halt
instantly, and preserve as much information as possible about the
state of the program at the time the problem was discovered.
Here are three examples:
1) A memory allocator that discovers that the internal data structures
that it uses to keep track of what memory is allocated have become
corrupted. At this point, it is no longer possible to tell what
memory is allocated and what isn't. Any attempt at recovery is
likely only to obscure the problem further.
2) A container class that uses a balanced binary tree as its
implementation. Every node of such a tree is required to meet certain
balance criteria. The code that deals with the tree is designed to
maintain the balance criteria at all times. If the program ever
discovers that the tree fails to meet the balance criteria, it means
that something is seriously broken -- and again, no recovery is
possible.
3) A program that maintains a table that is kept sorted. Values
are frequently sought in the table by binary search, which is why
keeping the table sorted is important. Every once in a while, a
value is inserted into the table, which is a logical time to verify
that the table is still sorted. It makes no sense to verify that
the table is sorted every time you search for an element, because
then there is no point in using binary searches at all.
Suppose you find that the table has somehow become unsorted. Then it
is useful as a debugging tool to be able to check the table's order
every time you search it. Again, the object of the game is to be able
to stop the program as soon as possible after the problem has been
discovered, and again no meaningful recovery is possible.
In all of these cases, there is a category of error beyond merely
passing an argument to a function that is outside the function's
domain. It is to forestall such problems that assertions are useful.
--
Andrew Koenig, a...@research.att.com, http://www.research.att.com/info/ark
>> length <= allocated
>> strlen(data) == length
>> Now what are you suggesting?
Kelsey> Is this the sort of thing you would test with an assert? If
Kelsey> so, then there's a *reason* you check it with assert:
Kelsey> presumably because it's not guaranteed to hold true. If it's
Kelsey> not guaranteed to hold true, then why the hell wouldn't you be
Kelsey> checking it at runtime?
Because it's too expensive.
> > A function presents a contract to the outside world, and that
> > contract must be followed in order for it to be expected to behave as
> > promised.
>
> Yes, and? Oh, wait, I see. The contract says "don't pass me a NULL or
> you'll get unpredictable behaviour". Well, fine, so we don't pass NULLs.
> But how do we ensure we don't pass NULLs? Right; by checking our inputs
> and our processing - that is, by error-checking.
You are still mistaking problems in run-time behavior with bugs in the code.
I am talking about a coder making use of a function that expects a certain
range of values to be passed to it (or whatever), not a case where reading
from a file or getting input from a user can generate undesired values. I'm
talking about detecting and stomping out *bugs*.
Surely you have heard of "Design by Contract"? This is not a paradgim I am
making up, but a widely accepted design concept. You write error-handling
code ("if" statements) to handle conditions that might be expected to
happen, but are nonetheless undesireable, in order to prevent the program
from doing things it should not be doing, and to allow for the possibility
to correct the problem before cotinuing or to allow the user to abort or
modify the current action.
You write asserts for a very different reason - to assist in coding and
testing, period. Well-placed asserts make sure that anyone using your code
(other coders, or yourself, NOT end-users) follow the rules. It is entirely
their responsiblilty to test the "end-point", or extreme, conditions. It is
on their shoulders to follow the terms of the contract. But you can assist
them greatly by screaming bloody murder (via an assert) if they violate the
contract, instead of making them go hunting for the bug because, for
example, a buffer got overwritten, causing a crash at some random later
time. Using an "if" statement always adds overhead at run-time, and is, in
my opinion, rather pessimistic - it assumes that those who called your code
do not have the sense or capability to design, code and test their code with
respect to this specific contract, and that therefore bad data will come in
at some undetermined future time.
>
> > The kind of failures that assert is useful for are those that your code
> > should not be ALLOWING to occur in the first place. For isnteance, if
> > you're selecting an item from a menu and then passing the index of that
> > selection to a function to access the Nth item from some array, it is
useful
> > to be sure that your code CANNOT produce an out-of-bounds condition when
> > doing so.
>
> Right. By checking the result against a range of legal values.
>
> > Once your calling code is correct, the function to which you
> > passed that menu selection index should not need to constantly check
that it
> > is receiving a valid index.
>
> Oh, indeed. Why would you? This isn't even a source of _potential_
> errors, though, so error-handling code here would be stupid. On the other
> hand... why would you be using asserts?
>
> Oh, right, because you haven't analyzed the code to ensure it doesn't
> produce bogus results. So you rely on assert instead. Well, hell, 200
> tests haven't fired an assert, it must work, recompile! Until the 201st
> test where you do get the bogus result.
>
But YOU are not neccessarily writing the calling code! You may have
absolutely no knowledge of what code will be calling yours. Using an
assert, however, provides those who DO contract with your code a method of
knowing exactly what terms of the contract were violated.
In any large application, NOBODY knows the entire design of the system, down
to its nuts and bolts. You MUST rely on others to do their work correctly,
and to run appropriate tests to ensure correctness to whatever degree is
feasible. It is not a matter of how many tests were thrown at it without an
assertion failure. It is a matter of trust in your coders and testers, a
good design, and a good test plan, and any help that your application can
provide in assuring the correctness of the design can only help.
I do not suggest that testing, even with asserts, will guarantee
correctness. However, I suggest that even the most detailed and well
thought-out designs and analyses ALSO do not guarantee correctness. You do
what you can, you do it as well as you can given the resource and time
constraints, and you use every tool in the toolbelt to help you do so.
Assertions are one of those tools.
If you have no need for assertions, then don't use them. What do we care?
I only chimed in because you made a blanket statement that they were totally
useless, which is entirely your opinion, not a fact. (Surely the OP is
entitled to know that?)
Ok, rant complete..I'm out of here... :-)
Howard
This particular case is demonstrates the insidious stupidity of most
assert use. It's quite possible that the debug mode build did have a
null at the string where the non-debug mode (which lacks a test when the
assert is removed) doesn't provide one.
Yes indeed, it's possible. But if the test is too expensive,
what do you propose to do about it?
Decouple the assert supression from the rest of the debug vs. optimized
compilation.
Well - thanks for the tone of this last post ... and not taking my
post out of context. I enjoyed reading your response - and for what
its worth, thanks.
I'm open minded - I'll try it.
-Luther
On Tue, 22 Apr 2003 20:16:26 +0000, Howard wrote:
> You are still mistaking problems in run-time behavior with bugs in the
code.
No, I'm not.
> I am talking about a coder making use of a function that expects a certain
> range of values to be passed to it (or whatever), not a case where reading
> from a file or getting input from a user can generate undesired values. I'm
> talking about detecting and stomping out *bugs*.
Error (n): any undesired result of a program.
Bug (n): anything which produces or permits an error.
> Surely you have heard of "Design by Contract"? This is not a paradgim I am
> making up, but a widely accepted design concept.
Yup. And like all others, without sufficient error handling, you're
hooped. So?
> You write error-handling
> code ("if" statements) to handle conditions that might be expected to
> happen
Why? That is, why limit it this way?
Sure, I *expect* that malloc failures will happen from time to time, so I
cope with them. I *don't* expect, say, that the user will enter "one"
when asked for a numerical input. However, I don't write my code on the
expectation that the user will always enter the right thing, do I? No; I
write defensive code that can cope with the unexpected instead of just the
expected.
> You write asserts for a very different reason - to assist in coding and
> testing, period.
Coding, perhaps. Not testing. Anything needing an assert to test it had
damned well better either be *proven* to work correctly - hence obviating
the need for the assert in the first place - or wrapped in sensible
error-handling code, thus rendering assert moot.
> Well-placed asserts make sure that anyone using your code
> (other coders, or yourself, NOT end-users) follow the rules.
No, it merely assures you that the particular values handed off during a
hatful of test runs happened to be valid.
> It is entirely
> their responsiblilty to test the "end-point", or extreme, conditions.
Off-by-one, max limit, OOB, invalid input... doesn't matter. Either
you're handling the errors, or your app deserves to be circular filed.
> It is
> on their shoulders to follow the terms of the contract. But you can assist
> them greatly by screaming bloody murder (via an assert) if they violate the
> contract, instead of making them go hunting for the bug because, for
> example, a buffer got overwritten, causing a crash at some random later
> time.
Or you can provide useful error-handling. "A software error has occurred
(see the error log for details) and your data is at risk. Shall I save it
before exiting?" Followed by a quick perusal of the error log to note
that line 197 of foo.c encountered an overflow condition.
Hey, look; we just "screamed bloody murder" 10 years after the app was
shipped, saved the users data *and* provided the information necessary to
find and fix the problem. Yes, and? This argues in favor of assert how,
exactly?
> Using an "if" statement always adds overhead at run-time,
There's an old maxim which goes something along the lines of "An
application can be made arbitrarily fast if incorrect results are
acceptable."
Yes, ensuring correct operation incurs overhead. *Not* ensuring correct
operation generally incurs far greater overhead. Starting with having the
programmer fired uncermoniously for gross negligence.
> and is, in
> my opinion, rather pessimistic - it assumes that those who called your code
> do not have the sense or capability to design, code and test their code with
> respect to this specific contract, and that therefore bad data will come in
> at some undetermined future time.
Yes, and? Look, you know - you KNOW - that at some point your code will
be called with bogus data. The question is, do you take steps to cope
with that, or do you simply sit back, smile, and tell your boss "Well, you
know that 20-million dollar contract we just lost because we trashed all
their data? Not my problem, see, here's the code contract. What? Error
handling? 'Course not, I don't do that. It's the other guy's fault for
not... hmm... well, for not providing error handling. The very thing I
can't be bothered to do."
>> Oh, right, because you haven't analyzed the code to ensure it doesn't
>> produce bogus results. So you rely on assert instead. Well, hell, 200
>> tests haven't fired an assert, it must work, recompile! Until the 201st
>> test where you do get the bogus result.
>>
>
> But YOU are not neccessarily writing the calling code!
So you damn well better make sure that *your* code, at least, behaves
sensibly. Like, say, evaluating the selected against the list of possible
valid results to make sure it's in there... and if not, then either
handling it, or at the very least, returning a predefined and documented
error code back to the caller.
"select option 1 to 7:" 8
You think returning 8 here is a _good_ thing, do you?
> You may have
> absolutely no knowledge of what code will be calling yours.
This matters?
> Using an
> assert, however, provides those who DO contract with your code a method of
> knowing exactly what terms of the contract were violated.
Assuming the violation happens *at test time*. Not six months after
shipping. Whereas doing the job right - by avoiding the pointless waste
of time that is assert and using sensible error handling - it doesn't
matter if it happens today, tomorrow or ten years from now.
> In any large application, NOBODY knows the entire design of the system, down
> to its nuts and bolts. You MUST rely on others to do their work
correctly,
Ah; I see. You work in one of those magic shops where bugs don't occur.
Must be nice.
See, in the universe I live in, bugs *do* happen, regardless of how much
care and attention is given. Knowing this, most coders in my universe
take steps to cope with such things.
> and to run appropriate tests to ensure correctness to whatever degree is
> feasible.
You've been arguing all along specifically to *not* test to "whatever
degree is feasible", but rather, to test many things only at development
and testing time, with almost certainly incomplete test suites. I, on the
other hand, have been arguing for ongoing testing under all conditions,
which certainly seems a lot more "whatever degree feasible" than your
offering is. Does this mean you're now changing your mind and now see the
error of relying on assert?
> It is not a matter of how many tests were thrown at it without an
> assertion failure. It is a matter of trust in your coders and testers
No, it's a matter of coding defensively, knowing ahead of time that
coders, testers and the like are not perfect. Knowing this, you take
steps to cope with the inevitable problems.
> , a
> good design, and a good test plan, and any help that your application can
> provide in assuring the correctness of the design can only help.
Sure, it helps - but it's at best a beginning. Thing is, properly
designed error handling encompasses everything assert has to offer,
provides greater opportunity to cope with what you didn't test for,
provides data protection opportunities which assert can't and, as a
result, renders assert completely irrelevant.
> I do not suggest that testing, even with asserts, will guarantee
> correctness. However, I suggest that even the most detailed and well
> thought-out designs and analyses ALSO do not guarantee correctness.
'Course not - so you provide sensible error-handling to cope with both the
cases you expect to happen *and* the cases you don't.
> If you have no need for assertions, then don't use them. What do we care?
> I only chimed in because you made a blanket statement that they were totally
> useless, which is entirely your opinion, not a fact. (Surely the OP is
> entitled to know that?)
My opinion, sure - yet so far nothing has been offered which even hints at
an actual value to assert. Given that the defenders of it cannot come up
with a reason for its existence which is not better served by other means,
it would seem that my opinion and the facts of the matter are in accord.
I am certainly willing to be shown wrong - do feel free to come up with a
use of assert which is not _at least as well_ served by sensible
error-handling code and I'll happily tip my hat to you; just don't expect
me to hold my breath waiting. :)
> Ok, rant complete..I'm out of here... :-)
Rant on, McDuff, and void main to he who cries first, "Nay, enough!"
<Uncle Willy should shoot me for that one...>
>>> where for performance purposes "length" is how many characters the
>>> string holds, "allocated" is how many characters have been requested
>>> from new [] and data is the string data + a terminating '\0'. then
>>> class invariants would be
>
>>> length <= allocated
>>> strlen(data) == length
>
>>> Now what are you suggesting?
>
> Kelsey> Is this the sort of thing you would test with an assert? If
> Kelsey> so, then there's a *reason* you check it with assert: Kelsey>
> presumably because it's not guaranteed to hold true. If it's Kelsey>
> not guaranteed to hold true, then why the hell wouldn't you be Kelsey>
> checking it at runtime?
>
> Because it's too expensive.
Which is more expensive: correct code which incurs a hatful of additional
cycles to ensure correctness? Or lost or trashed data?
Well, hell, if spending the extra cycles is too expensive, there's a
simple answer: for any file you write, any document you print, any graph
you display, just spew forth the products of rand(); if we don't care
about correctness, any output is acceptable, right?
Let's get serious here. We're talking software engineering, not
mickey-mouse and Tinker Toy.
On Tue, 22 Apr 2003 19:28:46 +0000, Andrew Koenig wrote:
> 1) A memory allocator that discovers that the internal data structures
> that it uses to keep track of what memory is allocated have become
> corrupted. At this point, it is no longer possible to tell what
> memory is allocated and what isn't. Any attempt at recovery is
> likely only to obscure the problem further.
Except that at this point, the user already has his 200-page doctoral
thesis in memory, and your answer is "Well, sorry, tough luck." As
opposed to attempting to salvage as much of it as possible, say by dumping
to a file before exiting.
Yes, I'm sure the user would be *thrilled*.
> 2) A container class that uses a balanced binary tree as its
> implementation. Every node of such a tree is required to meet certain
> balance criteria. The code that deals with the tree is designed to
> maintain the balance criteria at all times. If the program ever
> discovers that the tree fails to meet the balance criteria, it means
> that something is seriously broken -- and again, no recovery is
> possible.
Except for dumping the data preserving as much of the order is available,
perhaps indicating which sections belong to the unbalanced portion, thus
allowing manual recovery if nothing else, possibly allowing recovery in
hours or days instead of weeks or months or years.
> 3) A program that maintains a table that is kept sorted. Values
> are frequently sought in the table by binary search, which is why
> keeping the table sorted is important. Every once in a while, a
> value is inserted into the table, which is a logical time to verify
> that the table is still sorted. It makes no sense to verify that
> the table is sorted every time you search for an element, because
> then there is no point in using binary searches at all.
Indeed.
> Suppose you find that the table has somehow become unsorted. Then it
> is useful as a debugging tool to be able to check the table's order
> every time you search it. Again, the object of the game is to be able
> to stop the program as soon as possible after the problem has been
> discovered, and again no meaningful recovery is possible.
The only time I can see this being useful in the first place is if you're
doing occasional "order examinations", presumably because your
confidence in the ability of the guy in the next stall to write sorting
code is pretty low. However, if you're doing that _anyway_, you may not
be able to preserve the strict ordering of the table contents... but you
can at least preserve the _values_ of the table contents - which is to
say, the user's data - along with a record of what, when and where you
detected things going wrong. Oh, unless you use an assert, in which case,
sorry, game over.
> In all of these cases, there is a category of error beyond merely
> passing an argument to a function that is outside the function's
> domain. It is to forestall such problems that assertions are useful.
And in all three, better solutions are available than assert; in all
three, assert's total functionality is to say "Oops, problem here" while
ddiscarding all the work done up to that point. A saner solution would be
to preserve as much of the work done as possible, even if it does contain
errors as a result, _then_ crash and burn.
Okay, one more time: this is an argument for assert how, exactly?
Give me one - just one is all I ask - example of where assert buys you
*anything* which isn't more effectively done by sensible error handling
code. Just one. If I see one, I'll break down and admit assert has a
justification for existing. So far, the count is zero.
On Mon, 21 Apr 2003 14:19:25 +0100, Stephen Howe wrote:
> int someclass::foobar(int i)
> {
> :
> }
>
> and i is only supposed to 5 or greater. An assert will check this in a debug
> build. If the assert ever fails, you know your class needs fixing.
And if it never fires, you release your code and six weeks later, you find
that i now equals 4. Except you don't find it, because you relied on
assert instead of sensible error handling and you've now just trashed the
user's data to hell and gone.
Sorry, would you mind repeating the rationale for using assert in this
case?
Yes.
> If so, then there's a *reason* you check it with assert: presumably
because it's not
> guaranteed to hold true.
On the contrary. It is supposed to _ALWAYS_ true.
If it is ever false then it is failure in the class design.
The assert() is there to so that if it ever occurs, I need to sit down with
some coffee and work out what is wrong with my class design. It is a logical
fault of some kind, it should never occur.
> If it's not guaranteed to hold true, then why
> the hell wouldn't you be checking it at runtime?
It is always supposed to be true. We are not talking about the fact that 75%
it is true and 25% of the time it is false. It is supposed to be always 100%
true. Given that, why should production code check for an "impossible
error"? If you say to this, "why bother checking for something impossible?".
That is just it, it is supposed to be impossible. The assert() is there to
alert me to the fact that the "impossible" just occured if you run the debug
version. This is a logical error in class design not a run-time error.
At work, I have some program that stores compressed data. The public class
interface is such that for each member function a battery of tests occurs
that checks validity of the API call. But after that private internal
functions are called to do the real work. The private internal functions do
some error checking. So everytime a file open, read, seek, tell, write,
flush is done, the operation is checked to see that it succeeded and if not,
failure is communicated back. But these are not logical errors.
Anyway, there are a suite of internal functions that handle the indexing.
Some of them are such that they should "always work". There are a battery of
asserts() that check that certain pre and post conditions per function are
true. They are logical assumptions that are supposed to be always true. It
has been extensively tested.
Well in 4 years, I have had 2 "impossible" assert()'s fire. I have then had
to re-examine the logic as to why something that is technically impossible
should fail. And the logic has been fixed. Nothing has happened for the past
year. This makes me think that the logic is correct. Given that, why should
I be inserting code that is supposed to handle testing for "errors" in class
design? That, in this case, is expensive. By "expensive" I mean that if this
function is going to be 100000 times per day (real figures), it might error
once in 4 years, why should I be checking?
Stephen Howe
That is neither here nor there.
With most errors we want to handle them gracefully and recover gracefully.
We want to give users of our program a soft landing.
If on writing a file out, an error indicated that the disk is full, it is
possible we could give the user a different alternative and so they could
save their work elsewhere.
And if they saving to a floppy disk, they have forgotten to put a disk in,
you could prompt them to put a disk in and make a second attempt.
It is all called disaster recovery.
But I would contend that logical errors in program design are worse. Because
in the example that Andrew indicated, if some assert() indicated that there
was a flaw in an internal function or copy constructor etc in the memory
allocator, what could you trust? It may be that in order to save the
"200-page doctoral thesis", it has to allocate some memory. But if the
memory allocator is in an unstable state, all bets are off. How do you know
that it will save correctly? How do you know whether it will save but the
contents on disk are now garbled? How do you know, that it might not wipe
out slighltly older but possibly useful copies of the same thesis? You might
say, "I will save to a different filename". Well the existing filename and
an older filename might also be stored in the same memory allocator.
The only correct action is to find this logical bug and fix the class or
function design of the memory allocator.
And frankly people should not be using this program of yours to write
doctoral theses's if it is riddled with logical errors such that they cannot
tell if it is safe to save a document or not (or even if it saved it
correctly and it is not garbled). Early on in the software production
process, you should have eliminated logical errors from class/function
design in the alpha and beta testing so that the only errors are domain or
run-time errors or questions of what kind of nice functionality it should
have.
Nobody should be using the program if something as basic as a memory
allocator cannot be trusted.
Classes should be such that from the moment an instance of a class exists
via its constructor to the moment it is being destroyed by its destructor,
its internal data is in a known state. Each public member function,
operators etc that changes the instance should be such that its internal
data changes from one known state to another known state. assert()'s are
used to pick up inconsistencies. It should not be the case that the instance
of the class perpetually goes on the psychiatric couch asking, "am I in a
reasonable state? am I internally consistent?" . In a production program,
those questions on class design should have been answered a long time ago.
> > 2) A container class that uses a balanced binary tree as its
> > implementation. Every node of such a tree is required to meet certain
> > balance criteria. The code that deals with the tree is designed to
> > maintain the balance criteria at all times. If the program ever
> > discovers that the tree fails to meet the balance criteria, it means
> > that something is seriously broken -- and again, no recovery is
> > possible.
>
> Except for dumping the data...
1) How will you do that if you cannot guarantee that it is binary?
2) Suppose it is now no longer a binary tree (because the pointers have been
updated incorrectly on balancing) but a directed graph where a node may
pointed to by more than one node?
3) Given 2), how will you save information you are no longer sure you can
traverse the tree?
4) How do you know that the nodes left and right pointers point to other
valid nodes? Suppose they now point to invalid memory? Possibly freed
memory? You could have just erased a node but updated the pointers
incorrectly in rebalancing.
Logical errors are such that, once made, all bets are off. What can you
trust anymore?
> > 3) A program that maintains a table that is kept sorted. Values
> > are frequently sought in the table by binary search, which is why
> > keeping the table sorted is important. Every once in a while, a
> > value is inserted into the table, which is a logical time to verify
> > that the table is still sorted. It makes no sense to verify that
> > the table is sorted every time you search for an element, because
> > then there is no point in using binary searches at all.
>
> Indeed.
>
> > Suppose you find that the table has somehow become unsorted. Then it
> > is useful as a debugging tool to be able to check the table's order
> > every time you search it. Again, the object of the game is to be able
> > to stop the program as soon as possible after the problem has been
> > discovered, and again no meaningful recovery is possible.
>
> The only time I can see this being useful in the first place is if you're
> doing occasional "order examinations", presumably because your
> confidence in the ability of the guy in the next stall to write sorting
> code is pretty low. However, if you're doing that _anyway_, you may not
> be able to preserve the strict ordering of the table contents... but you
> can at least preserve the _values_ of the table contents - which is to
> say, the user's data - along with a record of what, when and where you
> detected things going wrong. Oh, unless you use an assert, in which case,
> sorry, game over.
As said, assert()'s are used to catch logical errors
> > In all of these cases, there is a category of error beyond merely
> > passing an argument to a function that is outside the function's
> > domain. It is to forestall such problems that assertions are useful.
>
> And in all three, better solutions are available than assert...
No there are not. I have pointed out above that there are deep problems once
you detect that a class is an incoherent state - precisely the conditions
where assert() is useful. If it is in an incoherent state, which parts of
the class data should you beleive? You might replace assert() with some type
of alternative assert() that dumps a class state to disk but it still
basically the same. You will be peering at the entrails of a class instance
to try and work out how to fix a logical error.
> ...three, assert's total functionality is to say "Oops, problem here"
while
> ddiscarding all the work done up to that point.
Only if with inconsistent class data, you believe it is possible to "save
work".
> A saner solution would be
> to preserve as much of the work done as possible, even if it does contain
> errors as a result, _then_ crash and burn.
How can it "saner" if the data it has work with is now "insane". Which part
of the data are you to believe?
> Give me one - just one is all I ask - example of where assert buys you
> *anything* which isn't more effectively done by sensible error handling
> code...
I have. I have raised questions you have not answered with Andrews examples
. Your unspoken assumption is that _ALL_ situations can be handled with
"sensible error handling code". I contend that that is not possible because
there will be some situations where you no longer be sure as which part of a
classes data is correct or believable. And if class in question happens to
be something like fstream or the guts of new then even "saving to disk"
might not be possible.
One last thing. I happen to know a flight engineer for a commercial plane.
He is not the pilot or co-pilot but the person responsible for console and
instruments. If a light flashes indicating that there is only 10 minutes of
fuel left, he has to decide if the light is correct, or whether the light
could be faulty. Some other console indicators might say that there is 6
hours fuel and that the first indicator is okay. Well that is inconsistent.
Bearing in mind that there are lives at stake and money as a secondary
consideration, what should he advise the pilot to do?
Stephen Howe
On Tue, 22 Apr 2003 15:15:19 -0700, Luther Baker wrote:
> Well - thanks for the tone of this last post ... and not taking my
> post out of context. I enjoyed reading your response - and for what
> its worth, thanks.
>
> I'm open minded - I'll try it.
Not sure if that's intended to be sarcastic or not. :) I tried to at
least deal with the issues brought up without going horridly out of
context; if I goofed, just let me know where.
On Wed, 23 Apr 2003 00:43:59 +0100, Stephen Howe wrote:
>> Is this the sort of thing you would test with an assert?
>
> Yes.
>
>> If so, then there's a *reason* you check it with assert: presumably
> because it's not
>> guaranteed to hold true.
>
> On the contrary. It is supposed to _ALWAYS_ true.
> If it is ever false then it is failure in the class design.
Then either the design is proven correct - or it isn't proven correct. If
it is, you don't need asserts; if it isn't, it may fail at runtime after
shipping - in which case you want proper error handling.
> The assert() is there to so that if it ever occurs, I need to sit down with
> some coffee and work out what is wrong with my class design. It is a logical
> fault of some kind, it should never occur.
See above.
>> If it's not guaranteed to hold true, then why
>> the hell wouldn't you be checking it at runtime?
>
> It is always supposed to be true.
Yes, and malloc, on a system with loads of virtual memory, should never
fail either - yet we test that religiously. So?
> We are not talking about the fact that 75%
> it is true and 25% of the time it is false. It is supposed to be always 100%
> true. Given that, why should production code check for an "impossible
> error"?
If it is impossible, you don't need assert. The fact you're using assert
tells me that it is not in fact impossible.
> If you say to this, "why bother checking for something impossible?".
> That is just it, it is supposed to be impossible.
Then assert isn't necessary.
> The assert() is there to
> alert me to the fact that the "impossible" just occured if you run the debug
> version. This is a logical error in class design not a run-time error.
No, it's unclear thinking. :) If the situation is impossible, then it
won't happen and there's no point in the assert. If it's not impossible,
then the assert _may_ or _may not_ catch it. If the assert doesn't catch
it and it makes it into production code, you're screwed.
Of course, decent error handling would catch this, given that it is in
fact not impossible to occur in the first place; if it really is
impossible, then there's no need for any testing, at any point.
> At work, I have some program that stores compressed data. The public class
> interface is such that for each member function a battery of tests occurs
> that checks validity of the API call. But after that private internal
> functions are called to do the real work. The private internal functions do
> some error checking. So everytime a file open, read, seek, tell, write,
> flush is done, the operation is checked to see that it succeeded and if not,
> failure is communicated back. But these are not logical errors.
Nope, but they are errors nonetheless.
> Anyway, there are a suite of internal functions that handle the indexing.
> Some of them are such that they should "always work".
And therefore there is no need to test them even with assert. Unless, of
course, they do _not_ "always work" in which case it's simply a matter of
time until one blows up your shipping app.
> There are a battery of
> asserts() that check that certain pre and post conditions per function are
> true. They are logical assumptions that are supposed to be always true. It
> has been extensively tested.
If it's been tested sufficiently to guarantee that the functions will, in
fact, always work in all cases, bar none, then there's no need for the
asserts, is there? If the functions haven't been tested to that extent,
then they can't be shipped with confidence, can they? Not without some
way to detect and handle the errors... which, again, renders assert moot.
> Well in 4 years, I have had 2 "impossible" assert()'s fire.
Then they weren't impossible, were they?
> I have then had
> to re-examine the logic as to why something that is technically impossible
> should fail. And the logic has been fixed.
A logic error, a processing error, an input error, a scheduling error;
doesn't really matter; if an error _can_ occur, it should be trapped,
dealt with, the data saved. That they happened tells you that they were
not "technically impossible"; they were in fact possible and they happened.
> Nothing has happened for the past
> year. This makes me think that the logic is correct.
Have you proven that formally? If not, then how do you know it's not
simply a matter of time until the next "technical impossibility" happens?
> Given that, why should
> I be inserting code that is supposed to handle testing for "errors" in class
> design?
Because as you yourself noted, the "impossible" happened. If it happens
all over a user's valuable data and you haven't bothered to provide decent
error handling, don't you think they might be a little upset? Even more
so when you assure them, with utmost confidence, that you couldn't be
bothered to check for such errors, because they're "technically
impossible" despite having just eaten the user's data?
> That, in this case, is expensive. By "expensive" I mean that if this
> function is going to be 100000 times per day (real figures), it might error
> once in 4 years, why should I be checking?
Depends on the situation, but I can think of one example: code used in,
say, modelling of drugs. 4CPU years isn't a terribly long time,
especially when multiple machines are involved. So you introduce one
bogus value out of a couple hundred billion, fine, great, marvellous - but
does that cause us to miss out on the cure for cancer? Or to spend
largish chunks of money on a wild goose chase that your app spewed forth
as a probable winner, all because you couldn't be bothered to check for
supposedly "impossible" errors which you, yourself, have seen happen?
Besides; 100,000 times per day is diddly. Let's assume for the moment
that the additional checking consumes, oh, 500 cpu cycles per iteration,
and that a 1Ghz machine can perform, on average, a billion cycles per
second. We can, therefore, perform some 200 million such tests per
second. I rather suspect that the overhead, run a paltry 100,000 times
per day, wouldn't even be noticed - yet the effects of _not_ performing
the tests may well be drastic.
Right. Every function should verify its input is correct. But suppose that
having done so, its input is verified to be non-NULL, the function
sub-delegates parts of its task to simpler internal functions (that cannot
be called from anywhere else, they are static to a module) also passing the
input as arguments. I ask you, "Should the internal functions re-check
input?". What if these internal functions sub-sub-delegate to other smaller
and simpler functions? Should we recheck input again? How many times do we
have to check that the input is not NULL? It could be that 4096 internal
functions are called - do we need 4096 checks? We checked it once on entry,
is that not enough?
The top-level function _does_ check for invalid input. If it get invalid
input it returns indicating that this situation occured. But now having
crossed that barrier, the internal functions don't bother to check - why
should they - the check has already been done. Why cripple the performance
of these internal functions with needless additional checks? What would be
the point?
But everybody has off days and it maybe that unintentionally an internal
function is called and somehow invalid input has got past the line of
defense of the top function. This is where assert() creeps in. I would do
assert() on the input parameter - but only for the internal functions. When
the assert activates, it is the top level function that needs fixing.
> Oh, indeed. Why would you? This isn't even a source of _potential_
> errors, though, so error-handling code here would be stupid. On the other
> hand... why would you be using asserts?
>
> Oh, right, because you haven't analyzed the code to ensure it doesn't
> produce bogus results. So you rely on assert instead. Well, hell, 200
> tests haven't fired an assert, it must work, recompile! Until the 201st
> test where you do get the bogus result.
Sorry I don't see this. I see these things as complementary.
What you are talking about is "static analysis" that the program is correct.
assert() is an example of "dynamic analysis" (run time analysis if you like)
that the program is correct.
But I don't want to be forced into an artificial choice of choosing "static
analysis" over "dynamic analysis".
I want "both-and" not "either-or". I see them as complementary.
Why should I be forced to use "static analysis" only (which I use) which is
what you seem to indicate?
Why not both?
But "static analysis" has its weaknesses. It is weak with complex classes
and complex code. Once the complexity of something increases (and there are
software metrics that measure that), what makes you so sure that all your
analysis will spot inconsistencies?
> So, your asserts bought you nothing, did they? You still have to either
> analyze the code to prove the bogus results cannot happen, or handle the
> cases where they do happen - neither of which assert will help you with.
No, assert() won't help here. What it will do is alert me to the fact that
there _IS_ a problem. So it _HAS_ bought me that at least to my attention.
Contrast that to your sole reliance on "static analysis" methods or error
checking everything. The former is such that for complex problems you
certainly won't spot all bugs with your eyes; the latter means that your
code will be unnecessarily slow due to constant rechecking of arguments.
(I have also had cases where the assert() itself is wrong. But in this
case, it has helped me understand that something I thought was invariant is
not. In cases like this, I remove the assert() and make some notes in the
documentation of the function/class - that some combination of data
properties that look impossible are actually possible).
Stephen Howe
Any time you choose to code something a certain way, you do so because you
believe the code will make your program behave in the way you intend--in
your own mind, you've "proven" the code is "correct." But the human mind is
fallible, especially when it tries to grasp complex systems, and very often
we make mistakes in our reasoning. So programmers generally aren't
satisfied with mere proof; they test their reasoning by testing their
programs, and look for evidence that they've made errors. The assert macro
is one tool used in such testing.
It sounds like you're saying that as long as we've constructed a deductive
proof that our program _should_ work, we don't need to verify that
conclusion by testing the program. Is that actually what you believe, or am
I misunderstanding you?
Regards,
Russell Hanneken
rhan...@pobox.com
No that does not occur. You don't seem be able to differentiate between
(i) logical inconsistency
(ii) the evaluation of parameter input and output
On point (ii), I would never use assert(), I use "sensible error handling".
On this and this alone, you and I agree. I assume from this point on, you
won't raise this again - we agree.
But on point (i), "sensible error handling" is of no use at all. If I cannot
tell which data is okay and which is not - how can I handle it sensibly?
That is when I use assert().
> and you've now just trashed the
> user's data to hell and gone.
Yes but if point (i) has just occured, please explain how I can avoid
trashing the user's data when I can longer tell which data I can rely on.
> Sorry, would you mind repeating the rationale for using assert in this
> case?
Just have :-)
Stephen Howe
On Wed, 23 Apr 2003 02:20:16 +0100, Stephen Howe wrote:
> It is all called disaster recovery.
>
> But I would contend that logical errors in program design are worse.
Perhaps; I tend to look at it from a user point of view: I don't care
*why* it failed, all I care about is *that* it failed. Logic errors, disk
failures, memory allocation issues, I don't give a hoot; what I care about
is my data.
> Because
> in the example that Andrew indicated, if some assert() indicated that there
> was a flaw in an internal function or copy constructor etc in the memory
> allocator, what could you trust? It may be that in order to save the
> "200-page doctoral thesis", it has to allocate some memory. But if the
> memory allocator is in an unstable state, all bets are off.
Sure, it _could_ be completely hooped. Or there may be enough available
to do the job. Or the underlying system may pre-allocate working buffers
and you'll get those.
Thing is, while it may not work, it may; the other approach has absolutely
zero chance of working. If a user's data is at stake, which do you think
he'd prefer: "Sorry, app's dead, so's your data, have a nice day" or
"Sorry, app's dead; we may not be able to save your data, but we'll at
least try"?
> How do you know
> that it will save correctly?
We don't; it may be partially or even wholly corrupted. What we do know
is that _attempting_ to save it has a vastly greater chance of preserving
at least some of the work than simply tossing up our hands in disgust and
discarding the data with no attempt to save it.
> out slighltly older but possibly useful copies of the same thesis? You might
> say, "I will save to a different filename". Well the existing filename and
> an older filename might also be stored in the same memory allocator.
Easy enough: ask the user for a name to save it under. If he overwrites
the original, it's his problem. :)
> The only correct action is to find this logical bug and fix the class or
> function design of the memory allocator.
After having saved whatever data could be saved, yes.
> And frankly people should not be using this program of yours to write
> doctoral theses's if it is riddled with logical errors such that they cannot
> tell if it is safe to save a document or not (or even if it saved it
> correctly and it is not garbled).
Not my app; note it has been uniformly _other_ people relying on assert to
tell them things are correct.
> Early on in the software production
> process, you should have eliminated logical errors from class/function
> design in the alpha and beta testing so that the only errors are domain or
> run-time errors or questions of what kind of nice functionality it should
> have.
"Should have", sure. Let's be realistic: how often does "should have"
equate to "did"? Find me a program without a single logic error in it
anywhere, one beyond the "Hello world" scope. They're few and far
between, IME. One can eliminate the obvious errors, but it's precisely
the non-obvious ones, the ones that show up only every millionth run or
what have you, that are the issue.
> used to pick up inconsistencies. It should not be the case that the instance
> of the class perpetually goes on the psychiatric couch asking, "am I in a
> reasonable state? am I internally consistent?" . In a production program,
> those questions on class design should have been answered a long time
ago.
Again; how often does "should have been answered" equate to "were
answered"?
The core argument here is the utility of assert versus production error
handling. I offer this observation in relation to your comments: the only
reason to use assert _at all_ is if you are unsure of the correctness of
your design. However, that correctness cannot be established by the use
of assert; testing does not ensure correctness, it only tells us that a
particular type of incorrectness _under a particular set of conditions_
has not occurred.
The only assurance of correctness is a formal proof of such; the utility
of assert in such a proof is zero. Lacking such a formal proof, there
remains the possibility of a failure of the system to cope with some state
which in theory it should handle, but doesn't. Unless your test cases
encompass _every possible state_ they cannot eliminate such a condition;
therefore it follows that such a state may well make it into production
code.
So; assert is useless in proving correctness; it is equally useless in
assuring that invalid states occur; worse, once the code has gone
production, assert ceases to have any utility whatsoever.
Note that properly implemented error handling provides _all_ the supposed
benefits of assert, while offering a way to cope with the situations that
lack formal proofs of correctness and managed to make it past testing.
>> Except for dumping the data...
>
> 1) How will you do that if you cannot guarantee that it is binary?
'Scuse? What, you mean it may be EBCDIC text? Okay, so what?
> 2) Suppose it is now no longer a binary tree (because the pointers have been
> updated incorrectly on balancing) but a directed graph where a node may
> pointed to by more than one node?
By which I assume you mean "How do I avoid an infinite loop while writing
out the data?" Good question. Best answer I can give off the top of the
cuff is this: *your* app hosed the user's data because *you* couldn't be
bothered to check for errors; *you* figure out how to save as much as
possible.
> Logical errors are such that, once made, all bets are off. What can you
> trust anymore?
Not a hell of a lot, obviously. However, if the choice is between simply
discarding the data because we couldn't be bothered to do error checking,
and at least attempting to save as much as we can, I'll take the attempt,
thank you very much.
>> The only time I can see this being useful in the first place is if you're
>> doing occasional "order examinations", presumably because your
>> confidence in the ability of the guy in the next stall to write sorting
>> code is pretty low. However, if you're doing that _anyway_, you may not
>> be able to preserve the strict ordering of the table contents... but you
>> can at least preserve the _values_ of the table contents - which is to
>> say, the user's data - along with a record of what, when and where you
>> detected things going wrong. Oh, unless you use an assert, in which case,
>> sorry, game over.
>
> As said, assert()'s are used to catch logical errors
As are sensible error handling routines; they simply add benefits which
assert doesn't, thus rendering assert at best a pointless waste of time.
> No there are not. I have pointed out above that there are deep problems once
> you detect that a class is an incoherent state - precisely the conditions
> where assert() is useful.
Not useful at all if the state happens in a production build, is it?
Right. Again; decent error handling copes, assert doesn't. Even if
"coping" simply means "Look, we're gonna bail before making an even bigger
mess of things."
With sensible error handling, you - or the user - has the _option_ of what
to do; with assert, you, the coder, have no option - it simply dies - and
the user doesn't even get that; the code just keeps on chugging, mucking
things up worse until things are so bad the system kills the app for
violating system integrity. Or whatever equivalent.
> of alternative assert() that dumps a class state to disk but it still
> basically the same.
Along with, if you choose, a dump of whatever variables, states, etc, may
be useful in tracking such things down, rather than just getting a call at
2AM from someone saying "An assertion failed at line 297 of foo.c". Or
more likely, given it's a production build, a phone call at 2AM saying
"the app ate my data for no apparent reason and didn't even leave me a
message as to what happened."
> Only if with inconsistent class data, you believe it is possible to "save
> work".
Absofragginglutely in many, many cases. Not all, certainly, nor
necesarily all the data. However, in many cases, some is better than none.
>> A saner solution would be
>> to preserve as much of the work done as possible, even if it does contain
>> errors as a result, _then_ crash and burn.
>
> How can it "saner" if the data it has work with is now "insane". Which part
> of the data are you to believe?
You aren't; that's up to the user. Tell you what: when your DB code barfs
and as a result of barfing mucks up my data, which do you think I'm going
to be happier about: that you tossed it _all_ or that you managed to save
60% of it? Or even 30%?
> I have. I have raised questions you have not answered with Andrews examples
> . Your unspoken assumption is that _ALL_ situations can be handled with
> "sensible error handling code".
Not quite; rather, that all situations can be handled _at least as well_
with sensible error handling code as with assert. Don't read more into it
than is there. The argument is not that error handling can be universally
effective; rather, the argument is that there is no case in which assert
is more useful than sensible error handling code.
I'm not some silly-ass neophyte who assumes computers are magic; I'm well
aware that not all cases can be recovered; in fact, I'd argue that the
majority of failure cases _cannot_ be recovered. However, that isn't and
has not been the issue here.
Rather, the issue here is the utility of assert. That is, specifically,
to provide a single instance in which assert offers _at least as much_
utility as decent error handling code.
As a simple example, assert often issues only a line number and a filename
as "diagnostic information" before aborting. Slightly more advanced error
handling could do that as well, but also offer other information:
displaying the state of the class, for example. The more information
about why the error occurred, the more likely it is to get fixed, no?
Fine; then assert is not the tool to use, as it offers less information.
> I contend that that is not possible because
> there will be some situations where you no longer be sure as which part of a
> classes data is correct or believable.
Obviously. However, you seem to be confusing the issue here by failing to
keep track of what is under discussion. A file write failure - perhaps as
a result of a full disk - may be recoverable (by writing to another disk,
say) while a corrupted heap may not; the issue isn't to provide universal
error recovery, but a simpler question: given any given error condition,
which is more likely to provide a _useful_ mechanism for coping, both in
terms of saving the data (where possible) and providing a richer context
for fixing the error - assert, or sensible error handling?
Obviously, of the two, assert is the _least_ flexible, the _least_ rich in
such information. As such, its utility is at most to provide a false
sense of security rather than an actual aid.
> One last thing. I happen to know a flight engineer for a commercial plane.
> He is not the pilot or co-pilot but the person responsible for console and
> instruments. If a light flashes indicating that there is only 10 minutes of
> fuel left, he has to decide if the light is correct, or whether the light
> could be faulty. Some other console indicators might say that there is 6
> hours fuel and that the first indicator is okay. Well that is inconsistent.
> Bearing in mind that there are lives at stake and money as a secondary
> consideration, what should he advise the pilot to do?
Depends. First, any indicator upon which lives or serious money rests
should exist in _at least_ triplicate on completely redundant systems; this
way a fault in one doesn't lead to erroneous conclusions; ponder an
indicator that says there are 6 hours of fuel left while it's sole
counterpart says otherwise; which do you believe?
Without knowing the design of the system, the "safe" conclusion is land,
now.
Odd, isn't it? If the airplane were built like the code discussed here
thus far, it wouldn't even _have_ the indicator lights; they'd have been
compiled out in the release build. Unless, of course, we're talking
about, say, sensible error handling code. :)
On Wed, 23 Apr 2003 03:51:51 +0100, Stephen Howe wrote:
> (i) logical inconsistency
> (ii) the evaluation of parameter input and output
>
> On point (ii), I would never use assert(), I use "sensible error handling".
> On this and this alone, you and I agree. I assume from this point on, you
> won't raise this again - we agree.
>
> But on point (i), "sensible error handling" is of no use at all. If I cannot
> tell which data is okay and which is not - how can I handle it sensibly?
> That is when I use assert().
Umm... if you can't tell what is okay and what isn't, you can't assert
anything meaningful, can you? Assert is only potentially useful for
determining that something is (or isn't) correct - which requires that you
know what the correct thing is in the first place. Thus, by your argument
here, you in fact cannot use assert at all.
>> and you've now just trashed the
>> user's data to hell and gone.
>
> Yes but if point (i) has just occured, please explain how I can avoid
> trashing the user's data when I can longer tell which data I can rely
on.
Perhaps you can't. However, even in cases where you cannot, error
handling, rather than assert, offers two things: one, it can cause the app
to stop before doing _more_ damage, and two, it can provide a richer
context for determining why the app failed. Far richer considering that
the assert won't even exist in the production code where the error
occurred in the first place. ;)
Why? That is not 100% guaranteed. What if x is located on volatile memory,
is some hardware register etc.
Lets get down to basics. How does
#include <stdio.h>
int main()
{
printf("Hello World\n");
return 0;
}
look to you?
Should I be testing the return value of printf()?
What happens if it returns -1, how should I handle that?
These are not 100% impossible, "cannot occur" situations.
Stephen Howe
Kelsey> Which is more expensive: correct code which incurs a hatful of
Kelsey> additional cycles to ensure correctness? Or lost or trashed
Kelsey> data?
Kelsey> Well, hell, if spending the extra cycles is too expensive,
Kelsey> there's a simple answer: for any file you write, any document
Kelsey> you print, any graph you display, just spew forth the products
Kelsey> of rand(); if we don't care about correctness, any output is
Kelsey> acceptable, right?
Kelsey> Let's get serious here. We're talking software engineering, not
Kelsey> mickey-mouse and Tinker Toy.
This is an example of a bogus rhetorical technique that is common on
Usenet: Restate someone's opinion--incorrectly-- as a ridiculous
extreme, then dismiss it because it is ridiculous.
Because your argument, as stated, is bogus, it does not deserve a
reply beyond pointing out that it is bogus.
Kelsey> Except that at this point, the user already has his 200-page
Kelsey> doctoral thesis in memory, and your answer is "Well, sorry,
Kelsey> tough luck." As opposed to attempting to salvage as much of
Kelsey> it as possible, say by dumping to a file before exiting.
Kelsey> Yes, I'm sure the user would be *thrilled*.
This argument is a diversionary tactic. At no point did I even suggest
what the proper reponse should be to a failure of this sort. If the
application in qustion is a text editor, or something like it, an
integral part of its design should be figuring out *in advance* how
to recover from otherwise unexpected failures. One way might be to
checkpoint the text from time to time--at a point when it can be verified
to be correct--so that if a disaster does occur, a reasonably recent
copy can be recovered.
Such a strategy is completely unrelated to what to do--or not to
do--when memory has become corrupted to the point where further
execution is impossible.
>> 2) A container class that uses a balanced binary tree as its
>> implementation. Every node of such a tree is required to meet
>> certain balance criteria. The code that deals with the tree is
>> designed to maintain the balance criteria at all times. If the
>> program ever discovers that the tree fails to meet the balance
>> criteria, it means that something is seriously broken -- and again,
>> no recovery is possible.
Kelsey> Except for dumping the data preserving as much of the order is
Kelsey> available, perhaps indicating which sections belong to the
Kelsey> unbalanced portion, thus allowing manual recovery if nothing
Kelsey> else, possibly allowing recovery in hours or days instead of
Kelsey> weeks or months or years.
Again, this argument is a diversionary tactic. If the nature of the
program is such that a single corrupted data structure will require
weeks or months or years to recover, then it is necessary to deal with
that risk through other means. Again, those means are not the subject
of this discussion.
>> 3) A program that maintains a table that is kept sorted. Values
>> are frequently sought in the table by binary search, which is why
>> keeping the table sorted is important. Every once in a while, a
>> value is inserted into the table, which is a logical time to verify
>> that the table is still sorted. It makes no sense to verify that
>> the table is sorted every time you search for an element, because
>> then there is no point in using binary searches at all.
Kelsey> Indeed.
>> Suppose you find that the table has somehow become unsorted. Then it
>> is useful as a debugging tool to be able to check the table's order
>> every time you search it. Again, the object of the game is to be able
>> to stop the program as soon as possible after the problem has been
>> discovered, and again no meaningful recovery is possible.
Kelsey> The only time I can see this being useful in the first place
Kelsey> is if you're doing occasional "order examinations", presumably
Kelsey> because your confidence in the ability of the guy in the next
Kelsey> stall to write sorting code is pretty low. However, if you're
Kelsey> doing that _anyway_, you may not be able to preserve the
Kelsey> strict ordering of the table contents... but you can at least
Kelsey> preserve the _values_ of the table contents - which is to say,
Kelsey> the user's data - along with a record of what, when and where
Kelsey> you detected things going wrong. Oh, unless you use an
Kelsey> assert, in which case, sorry, game over.
I understand that such situations are the only time you can see such a
strategy as being useful. I believe that this fact stems from a
failure of vision on your part.
>> In all of these cases, there is a category of error beyond merely
>> passing an argument to a function that is outside the function's
>> domain. It is to forestall such problems that assertions are useful.
Kelsey> And in all three, better solutions are available than assert;
Kelsey> in all three, assert's total functionality is to say "Oops,
Kelsey> problem here" while ddiscarding all the work done up to that
Kelsey> point. A saner solution would be to preserve as much of the
Kelsey> work done as possible, even if it does contain errors as a
Kelsey> result, _then_ crash and burn.
No, it is not a saner solution. Because if your strategy is to preserve
as much of the work as possible, then somehow you have to verify that
the code that you think does that preservation actually does so.
If the failure case against which you're trying to defend is unlikely,
it may not even be possible to test the recovery code--in which case
it might make the system less reliable, not more.
Kelsey> Okay, one more time: this is an argument for assert how, exactly?
Kelsey> Give me one - just one is all I ask - example of where assert
Kelsey> buys you *anything* which isn't more effectively done by
Kelsey> sensible error handling code. Just one. If I see one, I'll
Kelsey> break down and admit assert has a justification for existing.
Kelsey> So far, the count is zero.
I stand by the examples I've already given. Nothing you have said has
invalidated them.
Another fallacy.
You seem to believe that if a design is proven correct, it is correct.
Are you really naive enough to believe that proofs never contain errors?
This is comp.lang.c++. In C++, it is not always possible for a
function to verify that its input is correct. [I'm not convinced
that it is always possible in other languages, either; but that's
a discussion for a different newsgroup]
>> > A function presents a contract to the outside world, and that
>> > contract must be followed in order for it to be expected to behave as
>> > promised.
>>
>> Yes, and? Oh, wait, I see. The contract says "don't pass me a NULL or
>> you'll get unpredictable behaviour". Well, fine, so we don't pass NULLs.
>> But how do we ensure we don't pass NULLs? Right; by checking our inputs
>> and our processing - that is, by error-checking.
>
> Right. Every function should verify its input is correct. But suppose that
> having done so, its input is verified to be non-NULL, the function
> sub-delegates parts of its task to simpler internal functions (that cannot
> be called from anywhere else, they are static to a module) also passing the
> input as arguments. I ask you, "Should the internal functions re-check
> input?".
Depends on what the caller does, and how it does it. In many cases,
obviously, doing so would be a waste, as the sub-functions are guaranteed
(barring hardware failure or some other bizarre happenstance) to have good
data passed to them.
I'll point out (since it seems to be less than clear) that the issue is
not "check everything everywhere", or even "universal data recovery and
saving is possible" - both of which seem to have crept in as
misunderstandings of my position.
Rather, the issue is the comparative utility of assert versus error
handling code.
I maintain, simply, that in _no_ case does assert provide any benefit over
properly-written error handling code, for a variety of reasons, which can
include the ability to save the data (if possible) and the ability to
provide richer context for future debugging, or even simply preventing
further damage to whatever data has been processed.
There may be other benefits as well, but those three strike me as the most
obvious, if somewhat trivial, examples: assert helps either not at all or
not as much as error handling code would and thus offers no real benefit
whatsoever. Worse, it can lead to a false sense of security as in "well,
the asserts never fired during testing, so compile release" thus removing
_any_ chance of the asserts preventing further damage by firing.
>What if these internal functions sub-sub-delegate to other smaller
> and simpler functions? Should we recheck input again? How many times do we
> have to check that the input is not NULL? It could be that 4096 internal
> functions are called - do we need 4096 checks? We checked it once on entry,
> is that not enough?
Absolutely; why would you think otherwise?
> But everybody has off days and it maybe that unintentionally an internal
> function is called and somehow invalid input has got past the line of
> defense of the top function. This is where assert() creeps in. I would do
> assert() on the input parameter - but only for the internal functions. When
> the assert activates, it is the top level function that needs fixing.
Whoops, now you've pooched it. If your TLF isn't doing the right thing,
your app is hooped; either it _happens_ to fire an assertion at testing or
it doesn't, you pass it, and the user's data is toast.
I don't see the benefit of assert here. Either the things that need to be
proven correct are and the things which can cause errors have error
handling, or they aren't or don't. If they aren't or don't, then all
assert does is tell you, at test time, that the particular data sets
handed to it _didn't_ cause an assertion; not that subsequent data sets
_won't_ cause an error.
> Sorry I don't see this. I see these things as complementary.
> What you are talking about is "static analysis" that the program is correct.
> assert() is an example of "dynamic analysis" (run time analysis if you like)
> that the program is correct.
Actually, I'm differentiating between design and error management. One
happens even before the code is written, the other continues for the life
of the application. Neither, as far as I can tell, benefit one iota from
assert, at least when compared to more flexible error handling.
> I want "both-and" not "either-or". I see them as complementary.
> Why should I be forced to use "static analysis" only (which I use) which is
> what you seem to indicate?
On the contrary; I *do* suggest using both. We simply differ in where the
distinction is drawn; static analysis is of design (and the produced code)
by inspection; dynamic analysis is runtime checking, from now until
doomsday.
> But "static analysis" has its weaknesses. It is weak with complex classes
> and complex code. Once the complexity of something increases (and there are
> software metrics that measure that), what makes you so sure that all your
> analysis will spot inconsistencies?
Nothing whatsoever - which is exactly why assert is so evil; by the time
of a release build, it is *gone*, totally unable to help. Whereas
completely ignoring assert's existence forces one to pay attention where
it matters: design and inspection (the static side) and production runtime
(the dynamic phase).
> No, assert() won't help here. What it will do is alert me to the fact that
> there _IS_ a problem.
Which error handling does until the app goes out of use, 20 years from
now, while providing (should you be interested in implementing it) much
richer details on exactly _what_ went wrong, plus, in at least some cases,
the ability to recover and save the user's data. assert does what? Oh,
right; not a damned thing, since it's been compiled out.
> Contrast that to your sole reliance on "static analysis" methods or error
> checking everything.
You have a category error happening there. I'm for full, all-around
analysis and error handling on every front. What I'm against is the use,
nay, the very existence of assert, as IMO it is actively, wholly, totally
and completely contrary to good, solid coding.
On Tue, 22 Apr 2003 19:35:14 -0700, Russell Hanneken wrote:
> Any time you choose to code something a certain way, you do so because you
> believe the code will make your program behave in the way you intend--in
> your own mind, you've "proven" the code is "correct."
This isn't "warm fuzzies 101"; opinions don't matter. Either you _have_
proven the code to be correct, or you haven't; if you haven't, you can -
nay, must - expect errors. The question is how to cope with them.
Perhaps you cannot save the data; you can, at the very least, very likely
provide far richer record of _why_ the app pooched than you can with
asserts which weren't even compiled in thanks to a production build.
> But the human mind is
> fallible, especially when it tries to grasp complex systems, and very often
> we make mistakes in our reasoning.
Which is why I said "proven correct", not "got the warm fuzzies that the
code is in a happy state."
> So programmers generally aren't
> satisfied with mere proof;
Umm... not sure what flavour of programmer you're dealing with, but anyone
with the proof in hand and the wits to comprehend it won't bother writing
test cases for it.
> It sounds like you're saying that as long as we've constructed a deductive
> proof that our program _should_ work
No; not at all. I'm saying that the only way to demonstrate that a
program _is_ correct is to sit down, pencil and paper in hand, walk
through every single decision path, every single input, every single
output, indeed, every single item which has a predictable point of failure
(by which I mean fopen, malloc, etc, rather than i++ not equalling one
morethan the previous value of i, ignoring for the moment overflow issues
and the like) and demonstrating, absolutely, that no failure at any point
is left unmanaged, no value can go out of range, no memory is lost, no
invalid assumptions are made, and so forth.
Barring this - and let's be honest, in many cases this is simply not
feasible - the alternative is to use error handling; prove the simple
cases, such as overall program flow, handling of fopen and the like, are
all dealt with, then add in the error code for the rest, the bits you
cannot prove are correct, that may fail, at least in manners which are
testable - i.e. any manner in which assert would be used, but at runtime
in the production build.
> , we don't need to verify that
> conclusion by testing the program. Is that actually what you believe, or am
> I misunderstanding you?
Almost completely.
> Kelsey> Then either the design is proven correct - or it isn't proven
> Kelsey> correct. If it is, you don't need asserts; if it isn't, it
> Kelsey> may fail at runtime after shipping - in which case you want
> Kelsey> proper error handling.
>
> Another fallacy.
>
> You seem to believe that if a design is proven correct, it is correct.
> Are you really naive enough to believe that proofs never contain errors?
If a proof contains an error, it isn't a proof, is it? I think you miss
the point of what I was saying; I'm not arguing that "proving" an app is
the way to go, not at all; rather I was responding to the notion that
something which is supposedly "proven correct" needs testing. If it _is_
proven correct, it _doesn't_ need testing; if it does need testing, it is
precisely because it is _not_ proven correct.
Further, if you've paid *any* attention at all to what's going on, you'll
note that I have been arguing _against_ the assumption that "just cuz my
asserts didn't fire, my code's right" - i.e. that the whole notion of
"correctness" offered by analysis and testing is at _best_ a starting
point, not an end point, and that precisely because such methods rarely,
if ever, cover all cases, run-time checking is an absolute necessity.
>>> Because it's too expensive.
>
> Kelsey> Which is more expensive: correct code which incurs a hatful of
> Kelsey> additional cycles to ensure correctness? Or lost or trashed
> Kelsey> data?
>
> Kelsey> Well, hell, if spending the extra cycles is too expensive,
> Kelsey> there's a simple answer: for any file you write, any document
> Kelsey> you print, any graph you display, just spew forth the products
> Kelsey> of rand(); if we don't care about correctness, any output is
> Kelsey> acceptable, right?
>
> Kelsey> Let's get serious here. We're talking software engineering, not
> Kelsey> mickey-mouse and Tinker Toy.
>
> This is an example of a bogus rhetorical technique that is common on
> Usenet: Restate someone's opinion--incorrectly-- as a ridiculous
> extreme, then dismiss it because it is ridiculous.
Your argument is "it is too expensive to ensure correct operation"; how
else is one to interpret this *but* to mean that incorrect operation is
perfectly acceptable? Spewing forth values generated by rand() would, in
all but a few cases, qualify as "incorrect operation", which you have
deemed acceptable; how, then, is my examination of your position bogus?
Either you *do* care about correct operation, in which case the "too
expensive" doesn't apply, or you don't, in which case any result is
acceptable. Not so?
On Wed, 23 Apr 2003 04:25:09 +0000, Andrew Koenig wrote:
> This argument is a diversionary tactic.
"This argument" being the one you offer; yes. Given that the discussion
is of the relative merits of assert over other error handling, yet you
persist in examining instead the limits of error handling capability,
rather than the subject at hand, your entire argument, start to finish,
top to bottom, is one gigantic diversion.
Since you seem to dislike such tactics, perhaps you'd kindly stop?
One more time for the reading impaired:
assert is evil. Why? Because it lulls one into a false sense of security
that the application, having not fired the assertions, is somehow "valid";
further, run-time error handling provides the capability to to everything
assert does, provide a richer context, and _in some cases at least_ save
data which would otherwise be lost.
No magic bullets, no mystical "we can save everthing"; simply that assert
is an evil entity which should be banished to the nether hells.
>> int x = 3;
>> x++;
>>
>> if ( x != 4 ) { // error! }
>>
>> That is, we do place at least _some_ confidence in the compiler, the
>> libraries, etc.
>
> Why? That is not 100% guaranteed. What if x is located on volatile memory,
> is some hardware register etc.
Then you either told the compiler that, or your compiler is borked. :)
> Lets get down to basics. How does
>
> #include <stdio.h>
>
> int main()
> {
> printf("Hello World\n");
> return 0;
> }
>
> look to you?
>
> Should I be testing the return value of printf()?
Depends; do you _care_ whether the output succeeded in this case? If so,
then probably yes.
> What happens if it returns -1, how should I handle that?
> These are not 100% impossible, "cannot occur" situations.
'Course not. Couple of possibilities suggest themselves: opening a log
file, writing the results, including the failure condition, there.
Dumping to stderr. Alerting the user by some implementation-specific
means that something is wrong, fix it if possible.
And it may come to pass that, in the end, you *cannot* recover from this.
fine, great, so be it. Back to the question at hand: in what way would
using assert here be a *benefit* over using sensible error handling?
> [snips]
>
> On Wed, 23 Apr 2003 03:51:51 +0100, Stephen Howe wrote:
>
>> (i) logical inconsistency
>> (ii) the evaluation of parameter input and output
>>
>> On point (ii), I would never use assert(), I use "sensible error
>> handling". On this and this alone, you and I agree. I assume from
>> this point on, you won't raise this again - we agree.
>>
>> But on point (i), "sensible error handling" is of no use at all. If I
>> cannot tell which data is okay and which is not - how can I handle it
>> sensibly? That is when I use assert().
>
> Umm... if you can't tell what is okay and what isn't, you can't assert
> anything meaningful, can you? Assert is only potentially useful for
> determining that something is (or isn't) correct - which requires that
> you know what the correct thing is in the first place. Thus, by your
> argument here, you in fact cannot use assert at all.
Huh? If the assert is that A == B, and the assert fails, which is the
"correct" value, A or B?
No sarcasm intended ... I thought you stated your case well. You've
got me thinking :) I'll continue to watch the conversation evolve ...
Thanks,
-Luther
A proof is either sound, or it is unsound. That is a fact of reality,
independent of your opinions on the matter.
On the other hand, the fact of whether a proof is sound or unsound doesn't
simply reveal itself to you. You have to follow the proof, and your own
judgment about the proof forms the basis of your actions. So yes, your
opinion does matter.
Unfortunately, we know from experience that we can make mistakes in our
reasoning. We can study a proof and become convinced it is correct, when in
fact it is not correct. We are fallible, and this is a problem. One
strategy we have for dealing with our fallibility is testing: we make
predictions based on the conclusions we derive by reason, and then look to
experience to see if those predictions are borne out.
> if you haven't, you can -
> nay, must - expect errors. The question is how to cope with them.
> Perhaps you cannot save the data; you can, at the very least, very likely
> provide far richer record of _why_ the app pooched than you can with
> asserts which weren't even compiled in thanks to a production build.
The purpose of assert is not to provide error-handling. Anyone who uses
assert that way is abusing it. The purpose of assert is to test our beliefs
about the way our code works.
> > So programmers generally aren't
> > satisfied with mere proof;
>
> Umm... not sure what flavour of programmer you're dealing with, but anyone
> with the proof in hand and the wits to comprehend it won't bother writing
> test cases for it.
Sometimes a proof is complex enough that we can't be sure we've followed it
without missing something. In those cases, testing is called for.
> > It sounds like you're saying that as long as we've constructed a
> > deductive proof that our program _should_ work
>
> No; not at all. I'm saying that the only way to demonstrate that a
> program _is_ correct is to sit down, pencil and paper in hand, walk
> through every single decision path, every single input, every single
> output, indeed, every single item which has a predictable point of failure
> (by which I mean fopen, malloc, etc, rather than i++ not equalling one
> morethan the previous value of i, ignoring for the moment overflow issues
> and the like) and demonstrating, absolutely, that no failure at any point
> is left unmanaged, no value can go out of range, no memory is lost, no
> invalid assumptions are made, and so forth.
The process you're describing sounds like what I'd call a deductive proof
that your program works.
> Barring this - and let's be honest, in many cases this is simply not
> feasible
I would say that this is usually not feasible with any program of even
modest complexity.
> - the alternative is to use error handling; prove the simple
> cases, such as overall program flow, handling of fopen and the like, are
> all dealt with, then add in the error code for the rest, the bits you
> cannot prove are correct, that may fail, at least in manners which are
> testable - i.e. any manner in which assert would be used, but at runtime
> in the production build.
Error-handling isn't a substitute for testing. You write error-handling
code to take care of problems that you know could occur at runtime, but
can't prevent. You write assert statements to help you discover problems
that you inadvertently created in your own code.
Regards,
Russell Hanneken
rhan...@pobox.com
This is perfectly clear. I think Performance is a significant factor
when balancing asserts and error checking code.
>
> Here are three examples:
>
> 1) A memory allocator that discovers that the internal data structures
> that it uses to keep track of what memory is allocated have become
> corrupted. At this point, it is no longer possible to tell what
> memory is allocated and what isn't. Any attempt at recovery is
> likely only to obscure the problem further.
>
> 2) A container class that uses a balanced binary tree as its
> implementation. Every node of such a tree is required to meet certain
> balance criteria. The code that deals with the tree is designed to
> maintain the balance criteria at all times. If the program ever
> discovers that the tree fails to meet the balance criteria, it means
> that something is seriously broken -- and again, no recovery is
> possible.
>
> 3) A program that maintains a table that is kept sorted. Values
> are frequently sought in the table by binary search, which is why
> keeping the table sorted is important. Every once in a while, a
> value is inserted into the table, which is a logical time to verify
> that the table is still sorted. It makes no sense to verify that
> the table is sorted every time you search for an element, because
> then there is no point in using binary searches at all.
>
> Suppose you find that the table has somehow become unsorted. Then it
> is useful as a debugging tool to be able to check the table's order
> every time you search it. Again, the object of the game is to be able
> to stop the program as soon as possible after the problem has been
> discovered, and again no meaningful recovery is possible.
>
>
> In all of these cases, there is a category of error beyond merely
> passing an argument to a function that is outside the function's
> domain. It is to forestall such problems that assertions are useful.
I don't mean to be a door knob ..
When I think of an assertion, I think of a DEBUG only test. If that's
not part of the definition of an assertion, then, under such a
definition, an assertion is really, a nifty little macro you're using
for error control. Granted, it doesn't recover from an error, but in
these cases, you've decided not to. You've described 3 cases where you
don't want the application to recover. To me, that seems like perfect
error control logic. Detect and decide. You just decided to halt.
After your post, it seems like Kelsey got caught up making a case why
you should do more than just halt, but it seems to me that in a way,
you are making his original case. It sounds like these problems need
to be detected in production release code. In fact, its probably more
critical to handle this type of error in a release build - when its
most important not to randomly overwrite someone's data. You are
saying that an assert does this nicely. But if you've thought it
through and decided you wish to halt after an unrecoverable problem,
and you know that the "semantics" of an assert do this nicely, and
therefore, you decide to build your release with asserts - and
consequently call that a reason to use asserts ... I'd contend that
its actually, well thought out error handling, you've just given it a
different label.
I think thats a major part of Kelsey's argument. The fact that asserts
disappear when the code goes into production. You're agreeing with him
here ... stating that indeed, we need to catch this error and do
something in production. Namely, quit.
So ... assume its agreed: "a particular failure is unrecoverable - and
in addition, we want to halt the program to stop any more malicious
things from happening."
<core>
So, why are assertions better suited for handling your examples - than
say - a normal test that consequently halts the program?
</core>
If I can clarify even farther. Are you presenting a case that
<1>
some errors are unrecoverable ... and that Kelsey stop trying to
actually recover? consequently, justifying the value for (detection
==> halt) behavior? I didn't think Kelsey was explicitly against
(detection ==> halt) behavior. I thought it was the DEBUG/RELEASE
issue that asserts disappear when code is RELEASED. Furthermore, I
think Kelsey's point was that asserts lull the programmer into a false
sense of security. I tend to think if Kelsey was convinced that an
error was unrecoverable, he would see this as viable:
if (unrecoverable_error)
{
try_and_gracefully_halt ();
}
</1>
<2>
Or that specifically, asserts are better suited to handle such errors
- than other error detection/handling schemes which could, feasibly,
decide to do nothing - if not, gracefully _attempt_ to close the
application?
</2>
Thanks - and my apologies if I've mis-spoken. I was Ruminating :)
-Luther
It's called "straw man".
> Your argument is "it is too expensive to ensure correct operation"; how
> else is one to interpret this *but* to mean that incorrect operation is
> perfectly acceptable?
<snip>
You're taking the words out of context.
It is often too expensive to check the state of things at every point
during a normal run. How would you like to tell a customer they have to
spend 10 times as much on hardware, or must use a much larger battery in
their mobile hardware, to cover all your run-time checks? Hopefully one
writes unit tests and system tests that check the behaviour of the
program and its components more thoroughly than is practical to do during
every run.
You still don't get it. Ok, one more try.
assert()/bugcheck()/abort() is just "a proper way to blow away the
current mess"... leaving a nice core-dump/crash-dump/CEEDUMP/whatever-
you-call-it for the service-/change- team [development phase aside for
a moment]... AND with subsequent recovery driven by some higher level
"recovery system". (that can "optionally" even handle *disasters* like
the-entire-site blowups as well -- I mean things like geographically
dispersed parallel sysplexes, etc.)
regards,
alexander.
--
http://groups.google.com/groups?selm=3CC53D11.B02D12D%40web.de
http://groups.google.com/groups?selm=3DB6DBD7.34704050%40web.de
Just because none of your asserts fired during the last six months,
this does not prove that your code is absolutely 100% free of program
logic errors. I would only remove a test for logic errors if the cost
of leaving it in was prohibitive. As with all performance issues, the
overhead has to be measured. Only if that overhead is such that the
software is incapable of satisfying the customer's requirements (in
terms of speed or memory usage or whatever the requirements are) would
I remove the logic error test. Sometimes that will be the case,
sometimes it won't.
GJD
We have a class that creates a thread
class CSomeThreadOrOther
{
public:
CSomeThreadOrOther();
virtual ~CSomeThreadOrOther();
bool Init();
bool IsInited() const;
bool DeInit();
};
Look OK so far? No asserts yet. The thread is nice and handy and does
something really useful, that alot of my colleagues reuse. They call
Init and DeInit at will. The functions look a bit like this
bool CSomeThreadOrOther::Init()
{
/* start the thread and return wether we succeeded */
}
bool CSomeThreadOrOther::DeInit()
{
if ( IsInited() )
{
/* signal the thread to stop */
/* wait for the thread to stop */
}
}
with me so far? Still no asserts and the error handling is all present
and correct (insofar as it can be in the pseudo code)
Now, one of my colleagues, who is not as familiar with Win32 as I am
calls init in her dll's PROCESS_ATTACH message, and calls deinit in
her dll's PROCESS_DETATCH message. Now any Win32 coder whos done this
will know it is wrong. If you wait for a thread inside DllMain you'll
wait forever, which leaves us with an error, which will always be hit.
I can put comments in the class definition saying "Don't call this in
DllMain", and cover the million other places your not allowed to call
it, but that doesn't guarantee she'll read it, so lets say she runs
this code and finds that her app deadlocks. I could handle the error
properly, but what is the correct course of action. Do I leave the
thread running, and risk a difficult to trace access violation
(because if checking the validitiy of memory is hard with one thread,
it is alot harder with two!) when the other thread frees required
resources, or do I leave the deadlock in and cause my colleagues to
spend ages trying to figure out why their app is deadlocks.
Or, and heres the point, do I put an assert in DeInit that checks the
state before trying to terminate the thread. That way, when my
colleage runs the code, she knows straight away that shes made a
mistake because the assert has a big comment on it saying what is
wrong, and she can correct it, without spending hours poring over
stack traces, core dumps etc, trying to find either an horrible
multithreaded access violation or a deadlock. The assert may not be
perfect, but at least you can use it to catch common, understandable
usage errors before they cause your colleagues (or yourself for that
matter, how often have you forgotten how to use your own code - be
honest!) unnecesary heartache and debugging.
It is precisely in the cases where code fails after apparently being
proved correct that assertions are the most useful.
If a piece of code has a corresponding correctness proof, and there is
nothing wrong with the proof, then in theory the code is correct.
However, in practice, the code will still not give correct results
when run, for any number of reasons:
There might be a bug in the implementation of the language(s)
in which the program is (are) written.
There might be a bug in the underlying operating system or
hardware.
There might be an error in the proof that escaped inspection
(if the proof was checked by hand), or that was not revealed
due to a bug in the program that checked the proof.
The original claims that were proved about the program are
not sufficient to guarantee the correct behavior.
The proof fails to take into account one or more aspects of
the actual environment in which the program runs, such as
limited resources.
In all cases, the implication of the apparent proof not being a proof
at all is that if the program fails, the conceptual model used to create
it is wrong--which means that any attempt to recover may well be just
as wrong as the alleged proof.
Kelsey> Further, if you've paid *any* attention at all to what's going
Kelsey> on, you'll note that I have been arguing _against_ the
Kelsey> assumption that "just cuz my asserts didn't fire, my code's
Kelsey> right" - i.e. that the whole notion of "correctness" offered
Kelsey> by analysis and testing is at _best_ a starting point, not an
Kelsey> end point, and that precisely because such methods rarely, if
Kelsey> ever, cover all cases, run-time checking is an absolute
Kelsey> necessity.
If you'd paid *any* attention at all to what I've said, you would
realize that the paragraph above is irrelevant to this discussion.
Kelsey> Your argument is "it is too expensive to ensure correct
Kelsey> operation"; how else is one to interpret this *but* to mean
Kelsey> that incorrect operation is perfectly acceptable? Spewing
Kelsey> forth values generated by rand() would, in all but a few
Kelsey> cases, qualify as "incorrect operation", which you have deemed
Kelsey> acceptable; how, then, is my examination of your position
Kelsey> bogus?
My argument is not "it is too expensive to ensure correct operation."
Kelsey> Either you *do* care about correct operation, in which case
Kelsey> the "too expensive" doesn't apply, or you don't, in which case
Kelsey> any result is acceptable. Not so?
Not so. You are making the assumption that it is always possible to
determine absolutely whether a program is correct. This claim is
nonsense, for if it were true, it would provide a solution to the
halting problem.
Kelsey> Depends; do you _care_ whether the output succeeded in this
Kelsey> case? If so, then probably yes.
>> What happens if it returns -1, how should I handle that?
>> These are not 100% impossible, "cannot occur" situations.
Kelsey> 'Course not. Couple of possibilities suggest themselves:
Kelsey> opening a log file, writing the results, including the failure
Kelsey> condition, there. Dumping to stderr. Alerting the user by
Kelsey> some implementation-specific means that something is wrong,
Kelsey> fix it if possible.
Kelsey> And it may come to pass that, in the end, you *cannot* recover
Kelsey> from this. fine, great, so be it. Back to the question at
Kelsey> hand: in what way would using assert here be a *benefit* over
Kelsey> using sensible error handling?
Show us the code, please.
That is: You admit that printf might return -1 in this context.
You claim that the program ought to check for it. So show us a
complete program that does what you call ``sensible error handling,''
and explain why that program is better than, say, this variation:
#include <stdio.h>
int main()
{
int rc;
rc = printf("Hello World\n");
assert (rc >= 0);
return 0;
Kelsey> No magic bullets, no mystical "we can save everthing"; simply
Kelsey> that assert is an evil entity which should be banished to the
Kelsey> nether hells.
If assertions lull you into a false sense of security, I can see that
you might want to avoid them. But I think the problem is really your
false sense of security.
Right. And in one of those cases, namely the third, the test makes
the entire program dramatically slower--so much so that it is right to
omit the test entirely once satisfied that the condition that you thought
could never occur actually doesn't occur.
Luther> After your post, it seems like Kelsey got caught up making a
Luther> case why you should do more than just halt, but it seems to me
Luther> that in a way, you are making his original case. It sounds
Luther> like these problems need to be detected in production release
Luther> code. In fact, its probably more critical to handle this type
Luther> of error in a release build - when its most important not to
Luther> randomly overwrite someone's data. You are saying that an
Luther> assert does this nicely. But if you've thought it through and
Luther> decided you wish to halt after an unrecoverable problem, and
Luther> you know that the "semantics" of an assert do this nicely, and
Luther> therefore, you decide to build your release with asserts - and
Luther> consequently call that a reason to use asserts ... I'd contend
Luther> that its actually, well thought out error handling, you've
Luther> just given it a different label.
Luther> I think thats a major part of Kelsey's argument. The fact that
Luther> asserts disappear when the code goes into production. You're
Luther> agreeing with him here ... stating that indeed, we need to
Luther> catch this error and do something in production. Namely, quit.
What I am actually saying is that it is nice to catch this particular kind
of error *if it is feasible*, but it may be too expensive to do so in
production.
Luther> So ... assume its agreed: "a particular failure is
Luther> unrecoverable - and in addition, we want to halt the program
Luther> to stop any more malicious things from happening."
Or we want to halt the program because we don't have a clue as to
what to do at this point. Or we want to halt the program because we
believe that in theory this particular failure is impossible--so if
it does happen, our only sensible recourse is to reexamine the entire
program and see where our conceptual model was mistaken.
Luther> <core>
Luther> So, why are assertions better suited for handling your examples - than
Luther> say - a normal test that consequently halts the program?
Luther> </core>
Luther> If I can clarify even farther. Are you presenting a case that
Luther> <1> some errors are unrecoverable ... and that Kelsey stop
Luther> trying to actually recover? consequently, justifying the value
Luther> for (detection ==> halt) behavior? I didn't think Kelsey was
Luther> explicitly against (detection ==> halt) behavior. I thought it
Luther> was the DEBUG/RELEASE issue that asserts disappear when code
Luther> is RELEASED. Furthermore, I think Kelsey's point was that
Luther> asserts lull the programmer into a false sense of security. I
Luther> tend to think if Kelsey was convinced that an error was
Luther> unrecoverable, he would see this as viable:
Luther> if (unrecoverable_error)
Luther> {
Luther> try_and_gracefully_halt ();
Luther> }
Luther> </1>
Luther> <2>
Luther> Or that specifically, asserts are better suited to handle such errors
Luther> - than other error detection/handling schemes which could, feasibly,
Luther> decide to do nothing - if not, gracefully _attempt_ to close the
Luther> application?
Luther> </2>
Here's a concrete example that I hope will clarify the discussion.
Suppose that x is a value of type T and v is a vector<T>. Now
consider the following code.
v.push_back(x);
sort(v.begin(), v.end());
vector<T>::iterator it = find(v.begin(), v.end(), x);
assert(x != v.end());
I'm appending a value to a vector, sorting the vector, and searching for the
value I appended. I am not verifying that I found the value.
It's nearly trivial to prove that the value will, indeed, be found.
In that sense, there is no need for the assert to be there.
So what do you think of it, and why? If you think it shouldn't be
there at all, why not? If you think I should have done something else
instead, what should it be? Actual code, please--not a generic claim
such as ``handle the error'' or ``attempt to close the application.''
In particular, please assume that this code is part of a generic library,
so I do not know what applications might be using it.
No I don't think. Despite the arguments that are raging about the merits and
non-merits of assert() in this thread, if the OP is on-topic, his/her
question should be answered.
The OP wanted to know about the value of assert() if it only works in debug
builds, perfectly straightforward question. Jack did not answer that.
Instead we had this bullshit. The advice in the FAQ, Scott Meyers books,
Steve Dewhurst's "C++ Gotchas" and to a less extent Herb Sutter's books is
basically moral advice on how to write sound, robust C++ programs. Why
couldn't he give a reply along the same lines?
To Jack's soliloquy of
"What's the point of testing at all?"
I would respond
"What's the point of writing any program?"
"What's the point of living?"
Stephen Howe
I agree.
One of my examples would have turned an O(log n) algorithm into an
O(n) one. I consider such a change to be prohibitive, because if it
weren't, there was no need to use the O(log n) algorithm in the first
place.
Gavin> As with all performance issues, the overhead has to be
Gavin> measured. Only if that overhead is such that the software is
Gavin> incapable of satisfying the customer's requirements (in terms
Gavin> of speed or memory usage or whatever the requirements are)
Gavin> would I remove the logic error test. Sometimes that will be the
Gavin> case, sometimes it won't.
I agree with you in principle, but if your application is a generic
library, it may not be possible to determine in advance whether
the overhead renders the software incapable of satisfying the
customer's requirements. The problem is that you don't know yet
who all of the customers are.
That's what I was afraid of. You seem to have some beef with Jack.
Why don't you take it off-line or at least out of the newsgroup?
Any answer that doesn't sound like yours or doesn't satisfy you is
not a reason enough to come down on the answerer like a ton of bricks.
Lighten up. Have a breather. It's not the end of the world. Yet.
You are still totally misunderstanding the concept of testing prior to
release. No one has ever claimed here that using asserts provides any sort
of guarantee that an entire product is correct. Rather, using asserts
appropriately provides extra assurance that specific cases are being handled
correctly.
I cannot understand why you fail to see that there may be cases where
error-checking can be too costly. A small, yet highly used piece of code
may execute millions of times in a short period, and the error-checking, in
production use, will simply be too expensive, perhaps even changing the
order of magnitude of time for an operation to complete. Do you not agree
that that is possible? (If not, no sense reading further, I gesss.)
Given that, in some of those cases where a check of the internal state is
prohibitive, it may _also_ be true that any code written to use this small
piece of logic is _required_ to meet certain preconditions prior to making
use of it. Is that not also possible?
How then would you guarantee that those conditions are met? (Remember, I am
absolutely _not_ talking about conditions based upon user input or data read
from a disk, but rather the internal state of the system as written by
someone making use of this small piece of important, time-critical code.)
In this case, an assert will alert the person using and testing their code
against yours that they have _screwed up_! Again, I am not talking about a
one-in-a-trillion possible combination of user data that throws my code for
a loop, causing it to somhow erase their master's thesis. I'm talking about
the design of the code that makes use of my code being faulty.
I am not in control of that design work. And please do not tell me that
they need to "prove" their code with pencil and paper. Proof is impossible.
You can only assure yourself to some finite degree that your code is valid.
You _must_ then test it. And if you test it and I have not put an assert in
my important little piece of code, you may never know that there is a
problem until someone in the field complains that the software has a
problem. (It may be as simple as wrong output somewhere down the line. It
does not have to be a hosed computer ot trashed data as has been thrown
about here.)
But if I _did_ put that assert in there, then this other coder gets
_immediate_ feedback that they are doing things wrong.
Of _course_ this does not assure them (or me) that _everything_ is right.
It only assures me that this one very specific requirement has been met by
the design of _any_ code making use of mine.
I know that this will not convince you, however. But it absolutely _is_
true that sometimes error checking is too expensive. It is also true that
sometimes you need to have verification that what is being done does not
violate specific assumptions. And if you can provide a simple method for
detecting at least some of those cases where those assumptions are being
violated, then what's wrong with making use of that tool?
-Howard
Well, my take on this:
#include <cassert> // The C++ stuff
#include <stdio.h> // The POSIX one
#include "errbutt" // That's my own
int main() {
int rc;
rc = printf("Hello World\n");
assert(rc);
if (rc < 0 || (rc = fclose(stdout)) == EOF) {
rc = errno;
assert(rc);
}
else {
assert(!rc);
}
return rc ? report_error(rc) : 0;
}
regards,
alexander. < ;-) >
--
http://groups.google.com/groups?selm=3E734A5F.8A7F5B56%40web.de
No, I have no personal beef with Jack at all. I take it on a case-by-case
basis.
And contrary to what you claim, I have never desired that everybodies answer
should match mine. Like everybody else I learn from others.
It is just that in this it is out-of-order to gripe at the OP when they
asked a reasonable question, well within the topicality of the newsgroup.
What is wrong with the OP's request?
Cheers
Stephen Howe
I am not going into another discussion about what makes certain posts
walk the edge between being topical and simply a question and whatever
that causes others to answer in a manner you don't like. The only
thing I am still feeling strange about is why you, having answered
the same question in the same thread, wanted to confront somebody else
about their way of answering the question. You gave your answer. It
is, to the best of your knowledge and ability, the answer the OP needed
and deserved. Somebody else answered differently. So what?
Now, re-read the question and Jack's answer. Try to abstract from the
opinion you have already formed about all this. What do you see?
"What's the point in using a certain feature of the language if it's
not active is certain situations anyway?" Sounds like a normal
question? Doesn't to me. Still, Jack didn't say it was off-topic
or something that didn't deserve an answer. Nobody did. However,
given the tone of the question and some premises on which it was set,
the answers Jack gave were on the money and therefore valid.
You started arguing with Jack after his second reply to the apparent
"yeah right" attitude of the OP. You seemed to have let it by without
a note. This all sounds like you either (a) have a personal beef with
Jack or (b) have targeted a particular post without really seeing
the bigger picture.
Now, do I really want to continue this pointless exercise in typing?
No, not really. Sorry I bothered you.
I stand by my example (and it is just one):
Class creation
set parameter initialized = false
Initialization method (parameters...)
set parameter initialized = true
perhaps set user ID or other parameters that MUST be initialized before
using the class (this might be more useful in GUI programming, where
something needs to be set before the window starts to draw. But I'm sure
that's not the only time initializations are needed.)
Another method
if not initialized, assert
check that the user ID is valid and other error testing
Sure, I could just check for the valid user ID, but it's quicker if it
immediately dies on the programmer attempting to use my code. I can
efficently communicate with other programmers how I expect them to use my
class. They will be using it correctly (or as I have envisioned it) before
it ever gets a release build. The error checking will be used to catch
everything else that might go wrong, in debug and release build.
Asserts are for programmers only. Error checking is for programmers and
users. Therefore, having an assert go away in a release build is acceptable
and recommended.
-karen
What deductive nonsense. assert() does not detect "correctness" here, just
inconsistency. assert() is not going to tell you what is correct and in that
you are right. But it will tell you if the expression, often boolean, is
false. Further investigation will be required to work what needs correction.
That is all I am asking assert() to do. So it _IS_ meaningful and useful.
If I have
int var1;
int var2 = var1;
:
:
assert(var1 == var2);
and the assert fails, it could be
(i) that the value of var1 is incorrect
(ii) that the value of var2 is incorrect
(iii) that the value of var1 and var2 are both incorrect
(iv) that the assumption that at this point in the program that the
"correct" behaviour is for them to be equal is itself wrong. So the assert
needs removing. Note on this point, if you were working from required
specification, the program conformed to the specification (and to date, you
have never specified how you would "prove" such a thing - everybody is
thread would love to see some detail on your part) and the specification
claimed that these 2 variables would be equal then assert() has found an
inconsistency in the specification.
Without further analysis, it is impossible to say what is at fault. But at
least assert() brought to your attention there is inconsistency in the
program that needs resolving.
One further thing. In some of your arguments, you imply that if you know
something is correct, assert() is not needed because you know. Well for some
debug versions of C librarys I see implementations of standard functions
like strlen():
size_t strlen(const char *s)
{
assert(s != NULL);
// calculate string length
}
Why? Becasue there is nothing in the ISO C99 standard that says that the
argument to strlen() is checked to see if it is non-NULL. The standard
expects users of the library to supply non-NULL arguments. So the assert()
is useful to alert you to the fact that you are using your compiler vendors
C library incorrectly. And if "correctness" was so easily determinable, why
do the vendors bother having an assert there at all?
> > Yes but if point (i) has just occured, please explain how I can avoid
> > trashing the user's data when I can longer tell which data I can rely
> on.
>
> Perhaps you can't. However, even in cases where you cannot, error
> handling, rather than assert, offers two things: one, it can cause the app
> to stop before doing _more_ damage...
That is your claim. I expect you to present evidence (and so far you have
presented none - just made assertions). Given that data is inconsistent as
mentioned in (i), how do you know that any movement is any direction is
valid?
If my class consists of 10 member boolean variables, I detect inconsistency
then there are 1024 possible states to handle. Your saying, without any
knowledge of which of the 10 (could be all) is suspect that you know how to
prevent "_more_ damage"?
Not really believable at all.
>..., and two, it can provide a richer
> context for determining why the app failed.
That is your claim. Could I ask how you are going to do this?
Dump the class state to screen?
Dump the class state to file?
Those options are not bad in themselves. It would be easy to work up a
class-specific custom form of assert that did do this prior to terminating
the program. Note also, this is a dynamic option.
> Far richer considering that
> the assert won't even exist in the production code where the error
> occurred in the first place. ;)
Circular reasoning. For the reasons outlined above I contend that these
class of errors (inconsistencies) are not amenable to "sensible error
handling" which you have admitted above by saying "Perhaps you can't." and
also as Andrew Koenig outlines in his 3 examples. You don't even know that
you could get anything "Far richer" once inconsistency has been hit. In my
case they would have trapped in early alpha and best testing.
Stephen Howe
I think its a very good example :)
> If you think it shouldn't be there at all, why not?
> If you think I should have done something else
> instead, what should it be? Actual code, please--not a generic claim
> such as ``handle the error'' or ``attempt to close the application.''
I think its a very good example of what I consider an opportune place
for an assert. In an earlier post to Kelsey, I suggested similar tests
where I felt extensive error handling code would be inappropriate.
Namely:
> class Parser
> {
>
> public:
>
> void do_something_useful (const char * input_parse_string)
> {
> assert (input_parser_string != 0);
>
> String<char> str1 (input_parse_string);
> assert (str1 == input_parser_string);
>
> String<char> str2 (str1);
> assert (str1 == str2);
>
> String<char> str3 = (ENCRYPT_STRING<char> (str1));
> assert (str3 != str1);
> assert (str1 == str2);
>
> // check side effects ENCRYPT_STRING contracts to do
> assert (str3.length() < String<char>::max_length);
> ...
>
> // I've only done 3 things so far
>
> }
> };
>
>
>
> Wow ... in fact, I now realize that I could check every single side
> effect that every single function I write could have. That might be 10
> or 20 checks after every single call.
>
> Here is what you propose:
>
>
> class Parser
> {
>
> public:
>
> void do_something_useful (const char * input_parse_string)
> {
> if (input_parse_string != 0)
> {
> // handle error
> }
>
> String<char> str1 (input_parse_string);
> if (str1 == input_parser_string)
> {
> // handle error
> }
>
> String<char> str2 (str1);
> if (str1 == str2)
> {
> // handle error
> }
>
> String<char> str3 = (ENCRYPT_STRING<char> (str1));
> if (str3 != str1)
> {
> // handle error
> }
> if (str1 == str2)
> {
> // handle error
> }
> if (str3.length() < String<char>::max_length)
> {
> // handle error
> }
>
> ...
>
> // I've only done 3 things so far
>
> }
> };
>
>
> Is that really what your code looks like?
[please ignore the obvious logical errors above ...]
Conceptually, this version creates too much trivial overhead for a
final build. (I say "conceptually" since practically, "too much" is a
relative term. Code written with extensive error handling as described
above will still probably run faster than similar Java based apps. 25
seconds after "double clicking" TogetherJ, I get a titleplate. At 90
seconds, the application window displays, by 130 seconds, the previous
project is loaded and I'm ready to start :)
Back to your current example - I really can't make a case for
extensive release code error handling, since I don't feel its
necessary.
Thanks,
-Luther
You have already done so. You did not have to respond at all.
> Now, re-read the question and Jack's answer. Try to abstract from the
> opinion you have already formed about all this. What do you see?
> "What's the point in using a certain feature of the language if it's
> not active is certain situations anyway?" Sounds like a normal
> question? Doesn't to me.
Does to me. After all that is the behaviour of assert() that the standard
mandates. The OP has asked why it does this. That is a normal question. Two
points:
(i) Remember that for the original C standard (and I beleive there is a
draft for the C++ standard) there was a rationale as to why the language and
library were the way they were. That was there so that if people questioned
"why was this so?" So obviously the commitee felt it was normal that these
type of questions would be asked. See
http://std.dkuug.dk/JTC1/SC22/WG14/www/docs/n802.pdf
(ii) That the Annotated Reference Manual by Stroustrup and Ellis in the very
useful annotations explained why some of the strange rules on the C++
language were so.
> Still, Jack didn't say it was off-topic
> or something that didn't deserve an answer. Nobody did. However,
> given the tone...
What tone? The OP only went over the top in his 2nd post to JK. I had to
find that. Nothing wrong with the OP's 1st post. Nothing wrong with the OP's
response to you either. Perfectly reasonable question.
> You started arguing with Jack after his second reply to the apparent
> "yeah right" attitude of the OP. You seemed to have let it by without
> a note.
Yes, well I responded to the original post not the 2nd post by the OP. And
Florian, Ron, Luther and Robert all give different responses. And nothing
personal as you imply.
> Now, do I really want to continue this pointless exercise in typing?
Well I did not start this, you did.
Stephen Howe
heeheee .... no harm intended ... but you'd have trouble proving that
statement via any discrete mathematics model I've heard of.
I don't even think I can draw it correctly:
a: asserts
b: error checking
c: debug build (programmer)
d: release build (user)
Since (a -> c) and (b -> (c || d)) >>>>>> therefore, (d -> (!a))
:)
Ok, let's assume what you say might be true. Your statement still
doesn't prove that asserts are the _right_ thing to do. Your statement
only says "having an assert go away in a release build is acceptable
and recommended."
... and I totally agree :)
-Luther
Not at all. In some of my spare time I look after embedded ROM programmers.
With a linker/locator it would dead easy to make a mistake and accidentally
position the stack on some volatile memory. Nothing wrong with the C++
compiler, it did its job. The result is the above would fail and it would
bad use of a linker/locator that would cause this.
> > Should I be testing the return value of printf()?
>
> Depends; do you _care_ whether the output succeeded in this case? If so,
> then probably yes.
I am asking you. Your the one who claims that either
(i) the program is "proved correct" (whatever you mean by that and whatever
constitutes "proof" to you which, so far, you have never explained) it needs
no asserts.
(ii) the program is not "proved correct" and therefore requires some
"sensible error checking" and therefore also needs no asserts.
Now which of these 2 possibilities do you claim that the "Hello World"
program falls into?
It is only a 7 line program, should be easy to decide.
Put your money where your mouth is :-)
> > What happens if it returns -1, how should I handle that?
> > These are not 100% impossible, "cannot occur" situations.
>
> 'Course not. Couple of possibilities suggest themselves: opening a log
> file, writing the results, including the failure condition, there.
The printf() could fail because stdout is redirected to a file and the disk
is full. Not impossible, in fact it happened on my PC last month, at work,
on leaving an export program running overnight. It never completed 6 months
of exports.
Now what happens if "writing the results" also can't be done because the
same disk is full?
> Dumping to stderr.
Which can also be redirected by most shells these days. That also could fail
to dump.
> And it may come to pass that, in the end, you *cannot* recover from this.
> fine, great, so be it. Back to the question at hand: in what way would
> using assert here be a *benefit* over using sensible error handling?
It would not. Because failure of printf() is not a logical error, it is just
run-time failure that is all. Notice how tough it is to apply "sensible
error handling" to a 7 line program. That makes me less confident that
"sensible error handling" applied to multi-module program that is 250,000
lines of source will cattch all errors. I would rather have as many tools as
possible in arsenal in oder to be sure that the program is of preofessional
quality.
But don't worry, I will put a realistic example where assert() is guarding
the possibility of logical errors and you can criticise my example.
Thanks
Stephen Howe
I don't see that at all. Certainly there's no decision procedure that
can determine whether an arbitrary program meets an arbitrary formal
specification (except perhaps in a very restricted language). However
that isn't necessary or even desirable - we only want correct programs
and since we're writing the programs we can develop the proofs along
with them - if we have the time!
Ben> I don't see that at all. Certainly there's no decision procedure
Ben> that can determine whether an arbitrary program meets an
Ben> arbitrary formal specification (except perhaps in a very
Ben> restricted language). However that isn't necessary or even
Ben> desirable - we only want correct programs and since we're writing
Ben> the programs we can develop the proofs along with them - if we
Ben> have the time!
Even if you have developed a proof along with your program, it is
still possible for the program to produce incorrect results, because
one or more of the assumptions on which the proof relies might be
false.
For example, I had a program fail once because on the implementation
I was using, unsigned integer multiplication sometimes gave the wrong
result. No proof is going to detect that kind of problem.
I realise this, but Kelsey's ideas have nothing to do with solving the
halting problem. The idea that we must either prove programs correct
or handle every conceivable error condition is wrong because, as you
say, there are sources of error that we can't handle and can't prove
anything about.
I agree with you that it should be possible in principle to prove
correctness for every program that you right.
Where the hanting problem comes in is when you have to prove
correctness for programs that you didn't write, but must rely on
nevertheless.