Switching code in or out

I tend to do a mix of Test Driven Development and Design By Contract.

I also tend to write code that chugs away for hours on a fast machine even when tightly optimized and all preconditions and invariants commented out.

Can anyone think of a convenient way of taking a class like....

class MyHairyClass
   def invariant
     raise "Invariant Failure: Bah" unless
        deep_expensive_check_on_the_invariants_of_the_class
   end

   def my_public_method( foo)
     invariant
     raise "Precondition failure: Blah" unless
       some_expression_involving_foo

     # DO STUFF

     raise "Postcondtion failure: Bloo" unless
       some_expression
     invariant
   end

end

and being able to selectively switch on and off the precondition and postcondition and pre and post invariant checks.

ie. I want all checks on maximum when I run the unit tests, I want them all switched off (compiled out) when I'm running the two hour long deep thought, and I want them selectively switched on when it crashes half way through the big run. eg. All the precond checks on.

The simplist would be...

if $debug
   def invariant
     # Do check
   end
else
   def invariant
   end
end

but that still does two method calls in some of my inner loops and doesn't handle the {pre,post}conditions nicely.

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.

Have you looked at the Ruby DbC package? It is not very robust solution, but
it is simple and sounds like it might suit your needs:

http://www.rubycentral.com/downloads/dbc.html

···

On Thursday 22 September 2005 8:18, John Carter wrote:

Can anyone think of a convenient way of taking a class like....

class MyHairyClass
   def invariant
     raise "Invariant Failure: Bah" unless
        deep_expensive_check_on_the_invariants_of_the_class
   end

   def my_public_method( foo)
     invariant
     raise "Precondition failure: Blah" unless
       some_expression_involving_foo

     # DO STUFF

     raise "Postcondtion failure: Bloo" unless
       some_expression
     invariant
   end

end

--
Jason Voegele
The little town that time forgot,
Where all the women are strong,
The men are good-looking,
And the children above-average.
  -- Prairie Home Companion

Would this suffice? Its syntax is limited compared to the 'raise' statement, but if YAGNI...

module Kernel
  def do_raise
    def maybe_assert(msg,&blk) raise msg unless blk.call end
  end
  def dont_raise
    def maybe_assert(msg) end
  end
  dont_raise
end

And replace use of 'raise ... unless ... ' with 'maybe_assert ... do ... end' in the code below.

I don't know what the performance hit of calling an empty method is, though....

Devin

John Carter wrote:

···

I tend to do a mix of Test Driven Development and Design By Contract.

I also tend to write code that chugs away for hours on a fast machine even when tightly optimized and all preconditions and invariants commented out.

Can anyone think of a convenient way of taking a class like....

class MyHairyClass
  def invariant
    raise "Invariant Failure: Bah" unless
       deep_expensive_check_on_the_invariants_of_the_class
  end

  def my_public_method( foo)
    invariant
    raise "Precondition failure: Blah" unless
      some_expression_involving_foo

    # DO STUFF

    raise "Postcondtion failure: Bloo" unless
      some_expression
    invariant
  end

end

and being able to selectively switch on and off the precondition and postcondition and pre and post invariant checks.

ie. I want all checks on maximum when I run the unit tests, I want them all switched off (compiled out) when I'm running the two hour long deep thought, and I want them selectively switched on when it crashes half way through the big run. eg. All the precond checks on.

The simplist would be...

if $debug
  def invariant
    # Do check
  end
else
  def invariant
  end
end

but that still does two method calls in some of my inner loops and doesn't handle the {pre,post}conditions nicely.

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.

Just in case you're not aware of this: there is an implementation of DBC already
http://www.rubycentral.com/downloads/dbc.html

I think Dave Thomas wrote it.

Kind regards

    robert

···

John Carter <john.carter@tait.co.nz> wrote:

I tend to do a mix of Test Driven Development and Design By Contract.

John Carter wrote:

I tend to do a mix of Test Driven Development and Design By Contract.

I'm exploring this with http://ruby-contract.rubyforge.org/ -- comments are very welcome.

Ok, so there seems to be three basic methods of doing this, the most hairy involves using class_eval to interpose an aspect on function call and return.

Cute.

Scary.

Anywhoo, the other two are basically to make the invariant method empty or to only invoke the invariant method if a global or const is true....

require 'benchmark'

DEBUG = false
$foo = false

class A

   attr_reader :inv

   alias_method :in_very_ant, :object_id

   def invariant
   end

   def very
     in_very_ant
   end

   def empty_method
     invariant
   end

   def if_const
     invariant if DEBUG
   end

   def if_global
     invariant if $foo
   end

   def attr_thing
     inv
   end

   def no_code
   end
end

n = 1000000
a = A.new

Benchmark.benchmark(" "*7 + Benchmark::CAPTION, 7, Benchmark::FMTSTR, ">total:", ">avg:") do |x|
   tf = x.report("empty_method:") { n.times do ; a.empty_method; end }
   tt = x.report("if_const:") { n.times do ; a.if_const ; end }
   tg = x.report("if_global:") { n.times do ; a.if_const ; end }
   tu = x.report("no_code:") { n.times do ; a.no_code ; end }
   tv = x.report("inv:") { n.times do ; a.attr_thing ; end }
   tw = x.report("very:") { n.times do ; a.very ; end }
   x.report("nothin:") { n.times do ; a ; end }
   [tf+tt+tg+tu+tv+tw, (tf+tt+tg+tu+tv+tw)/6]
end

The results are using Ruby 1.8.2...

ruby-1.8.2 binch.rb;ruby binch.rb
              user system total real
empty_method: 1.210000 0.000000 1.210000 ( 1.218951)
if_const: 1.010000 0.000000 1.010000 ( 1.016450)
if_global: 1.020000 0.000000 1.020000 ( 1.021371)
no_code: 0.580000 0.000000 0.580000 ( 0.584446)
inv: 0.850000 0.000000 0.850000 ( 0.852560)
very: 0.870000 0.000000 0.870000 ( 0.863922)
nothin: 0.260000 0.010000 0.270000 ( 0.261910)

total: 5.540000 0.000000 5.540000 ( 5.557700)
avg: 0.923333 0.000000 0.923333 ( 0.926283)

And a fairly recent version from CVS....
              user system total real
empty_method: 1.160000 0.000000 1.160000 ( 1.159409)
if_const: 0.950000 0.000000 0.950000 ( 0.953207)
if_global: 0.970000 0.000000 0.970000 ( 0.972160)
no_code: 0.570000 0.000000 0.570000 ( 0.569933)
inv: 0.790000 0.000000 0.790000 ( 0.789721)
very: 0.800000 0.000000 0.800000 ( 0.796247)
nothin: 0.260000 0.000000 0.260000 ( 0.266906)

total: 5.240000 0.000000 5.240000 ( 5.240677)
avg: 0.873333 0.000000 0.873333 ( 0.873446)

notes:

1) I was surprised to find the if const and if global so close. Obviously no constant folding is being done on branches.

2) Of course, no code is faster than no code. So the hairy class_eval tricks would be faster. Although not tremendously faster than the attr_reader trick.

3) Ruby 1.9 is noticably faster than 1.8.2

Thanks for all the responses and wild creativity.

4) Ruby-contract, Awesome and ruby-dbc are very very very scary modules.

I'm not sure I can cope with anything quite that clever near my code. It has all (and more, much more) the horror of the C preprocessor, without the gcc -E option to see the what it actually did.

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.