[ABE.pm] perl 5.10 rules! - assertions

Ricardo SIGNES rjbs-perl-abe at lists.manxome.org
Wed May 30 05:25:02 PDT 2007


What else is new in 5.10?  Assertions!

Assertions are defined in various ways by various people, but they're generally
something like this:

  An assertion is a block of code written to ensure that everything is OK
  during the execution of a program during testing.  When the code is put into
  production, assertions can be disabled to improve performance.

I use assertions a lot, but mine usually work like this:

  sub change_email {
    my ($self, $address) = @_;

    RJBS::Assertions->assert_valid_addr($address);

    $self->{email} = $address;
  }

The assert_valid_addr method, then, does something like:

  sub assert_valid_addr {
    my ($self, $address) = @_;

    unless (Email::Valid->address($address)) {
      Exception::BadValue->raise(
        domain => 'RFC822::Addr',
        value  => $address,
        error  => "fails validation: " . Email::Valid->details . " test",
      )
    }
  }

In other words, I use them as reusable validate-or-raise-exception code units.
They always run, though, which can be expensive.  (It can also be useful!  I'm
not advocating turning off all your validation!)

With toggleable assertions, it's easy to turn off your assertions when they're
expensive and generally more for diagnostics than for critical functions.
Now, this example might look a little wordy, but keep in mind how useful this
kind of framework can be for an "enterprise" application:

  sub change_email {
    my ($self, $address) = @_;

    {
      use assertions 1; # always run
      _assert_valid_addr($address);
    }

    $self->{email} = $address;
  }

  sub _assert_valid_addr : assertion {
    my ($address) = @_;

    {
      use assertions 'testing';
      _assert_test_addr($address);
    }

    unless (Email::Valid->address($address)) {
      Exception::BadValue->raise(
        domain => 'RFC822::Addr',
        value  => $address,
        error  => "fails validation: " . Email::Valid->details . " test",
      )
    }
  }

  sub _assert_test_addr : assertion {
    my ($address) = @_;

    Carp::confess "you must not set the address to a non-test address!"
      if $address !~ /\@example\.com/;
  }

The assertion code (the subs marked ": assertion") is only run if the
assertions indicated by their scope's "use assertion" are on.  "1" is always
on, so _assert_valid_addr is always called.  _assert_test_addr is only called
if the testing assertions are on.  You turn assertions on by running perl with
the -A switch, meaning that if our test files started with:

  #!perl -A=testing

...then we'd run those assertions.  Otherwise, we wouldn't.  The call would be
optimized away when the program was compiled.  That's the benefit of assertions
over, say, "if ($ENV{TESTING})" -- they optimize away the whole call at compile
time, rather than checking the environment variable over and over at run time.
This works because you can change %ENV (for example) at run time, but you can't
change which assertions are active.

So, back to _assert_valid_addr -- why bother making it an assertion if we're
going to always run it?  Well, we're just setting ourselves up for future
maintenance.  If we later move some other validating code in front of this
"change_email" we might only want to _assert_valid_addr when testing some
bizarre bug, and we can then change "use assertions 1" to, say, "use assertions
'paranoid'"

I'll be honest: there are a bunch of things I don't love about the new
assertions interface.  I think it's a little clumsy and, as you may have
noticed, it forces you to use sub calls instead of method calls.  Some of this
may yet be changed or fixed by the final 5.10 release, and if it gets awesomer,
I will let you all know.

Still, optimize-away-able assertions will help remove obstacles to writing more
error- and sanity-checking code, which means more maintainable code -- and that
means a happy Ricardo.

-- 
rjbs


More information about the ABE-pm mailing list