On Mon, 6 Mar 2006, chiaro scuro wrote: > Interesting. Could you expand on "recommends quite sanely that you > should only spin a class if and only if you have an invariant to > protect"? it feels right but I am not sure I get it completely. Don't forget he was talking C++ naturally, so he was basically saying if you need to protect an invariant use "class" otherwise use "struct". In Ruby/Perl this may translate to if you need to protect an invariant use "class", otherwise this is just an unrelated bag of "stuff" so maybe use a Hash. > Another thing.. one big problems that I have with assertions is > duplication. Do you assert any method? do you assert on class public > methods? It's very to sprinkle the same assertions all over the place. I tend to think of levels of sanity / maturity. The code in any one class tends to be equally buggy or unbuggy. Thus placing asserts within private methods tend to leave you wondering, is the public method that invoked this private method buggy? Or is it the assert in the private method wrong? Private methods, being private tend to be very fluid and change often, thus putting an "anchor", a "stake in the ground", an "assert" in a private method tends just to slow me down without much return. So doing Test Driven development the stages (for me) are thus... 1) Write Test Driving code for Class A (This test code is very unstable / immature / buggy). 2) Write class A API, put in a smattering of preconditions. These preconditions test the test. Run test (I run the tests at almost every step) 3) Fix test / preconds. 4) Write postcondition assertions in Unit Test of class A. 5) They fail, so start writing code for class A. 6) Usually I think of some more preconditions that are in important while writing so I might add one or two more. 7) Often the test still fails because I'm stupid. So I start printing things out. Ah. That value is wrong. How could it possibly be that? Start going back up the call chain and pushing in pre-cond asserts in the code and post-cond tests in the unit test until my mistake is obvious. 8) Tests all pass, the code is written. I understand what and why it does, I write an invariant check method and slap calls to it in a bunch of places (start and end of most public methods). 9) Start on a class B that uses class A, now test A and class A are reasonably mature, test B and class B are very immature. 10) Write Test Driving code for Class B (This test code is very unstable / immature / buggy). 11) Write class B API, put in a smattering of preconditions. These preconditions test the test. 12) Fix test B / preconds for B. 13) Write postcondition assertions in Unit Test B of class B. 14) They fail, so start writing code for class B. >>>--WAKE UP! THIS IS THE IMPORTANT BIT!<<< 15) _Already_ the precond assertions within class A are starting to tell me about bugs in class B! 16) Often the test still fails because I'm stupid. So I start printing things out. Ah. That value is wrong. How could it possibly be that? Start going back up the call chain and pushing in both pre-cond asserts in the code (in both classes) and post-cond asserts (in both tests) until my mistake is obvious. 17) I put in an invariant check method of class B, which invokes the invariant check method of class A. 18) By the time test B and class B is written and tests pass my pre-cond and post-cond coverage of class A is substantially better and several bugs / design issues have been ousted from class A. 19) Start write class C that re-uses class A, class A is _very_ mature now, very trustworthy, and right from the first invocation the pre-cond and invariant checks in class A catch bugs in test C and class C very early. 20) Code is a little kludgy, so refactor and clean up. Unit Test's and assertions catch breakage. If functional tests catch breakage undetected by unit tests & precond, insert test code / precond / unit test postcond until breakage is caught then fix it. 21) Code is feature complete, but slow. Too many checks. Run profile and weed out a few checks that are in inner loops. Code is now fast, safe clean and bug free. > > On 3/6/06, John Carter <john.carter / tait.co.nz> wrote: >> Unit Tests and DbC assertions are complementary, not exclusive. I always >> do both. >> >> I tend to put the "post condition" assertions in the Unit Tests and >> invariant and pre-condition assertions in the code. >> >> Why? >> >> 1) The pre-condition assertions are "tests of the tests". Test's are code >> too, therefore have bugs. >> >> 2) Post-conditions are effectively the asserts found in the unit test. ie. >> If you have strong test coverage, don't bother with post-condition >> asserts, refactor them into the unit test code. >> >> 2) Client code using my code has bugs and the precondition asserts catch a >> lot of them very rapidly and at the right point. A the client code Unit >> test postcondition assert says the client (or maybe the lower layer) >> code did vaguely the wrong thing somewhere someplace sometime. A >> Precondition assert says the client code violated a pre-condition >> RIGHT HERE. >> >> 3) Invariant code are Object Sanity Checks. Stroustrup (author of C++) >> recommends quite sanely that you should only spin a class if and only if >> you have an invariant to protect. Invariant checking method is a handy >> way of checking at run time that your class is doing what it was >> designed to do. >> >> 4) I always throw vanilla Exceptions on assertion failure. It meshes >> nicely with the stacktrace mechanism of Ruby, but so far I can't think of >> any good reason to create a new exception class for them. >> >> >> >> On Mon, 6 Mar 2006, chiaro scuro wrote: >> >>> I would get my assertions to raise exceptions too. They are just a >>> specific kind of exception, related to the domain of inputs and >>> outputs, different from exceptions generated from something that goes >>> wrong in the middle. >>> >>> What do other people think about the relationship between assertions >>> and exceptions? >>> >>> On 3/6/06, Pat Maddox <pergesu / gmail.com> wrote: >>>>> tests alone however are never exhaustive and cannot prove correctness. >>>>> assertions can catch some types of 'wrongness'. >>>> >>>> Isn't that what exceptions are for? >>>> >>>> def foo(a) >>>> raise 'Argument can not be negative' if a < 0 >>>> >>>> b = something_that_should_always_be_positive >>>> >>>> raise 'Something strange happened and b was negative' if b < 0 >>>> b >>>> end >>> >>> -- >>> -- Chiaroscuro -- >>> Liquid Development: http://feeds.feedburner.com/LiquidDevelopment >>> >> >> >> >> John Carter Phone : (64)(3) 358 6639 >> Tait Electronics Fax : (64)(3) 359 4632 >> PO Box 1645 Christchurch Email : john.carter / tait.co.nz >> New Zealand >> >> Carter's Clarification of Murphy's Law. >> >> "Things only ever go right so that they may go more spectacularly wrong later." >> >> From this principle, all of life and physics may be deduced. >> >> > > > -- > -- Chiaroscuro -- > Liquid Development: http://feeds.feedburner.com/LiquidDevelopment > John Carter Phone : (64)(3) 358 6639 Tait Electronics Fax : (64)(3) 359 4632 PO Box 1645 Christchurch Email : john.carter / tait.co.nz New Zealand Carter's Clarification of Murphy's Law. "Things only ever go right so that they may go more spectacularly wrong later." From this principle, all of life and physics may be deduced.