Brian Phillips > MooseX-Contract > MooseX::Contract

Download:
MooseX-Contract-0.01.tar.gz

Dependencies

Annotate this POD

CPAN RT

Open  0
View/Report Bugs
Module Version: 0.01   Source  

NAME ^

MooseX::Contract - Helps you avoid Moose-stakes!

VERSION ^

Version 0.01

WARNING ^

This module should be considered EXPERIMENTAL and should not be used in critical applications unless you're willing to deal with all the typical bugs that young, under-tested software has to offer!

SYNOPSIS ^

This module provides "Design by Contract" functionality using Moose method hooks.

For example, in your Moose-built class:

        package MyEvenInt;

    use MooseX::Contract; # imports Moose for you!
        use Moose::Util::TypeConstraints;

        my $even_int = subtype 'Int', where { $_ % 2 == 0 };

        invariant assert { shift->{value} % 2 == 0 } '$self->{value} must be an even integer';

        has value => (
                is       => 'rw',
                isa      => $even_int,
                required => 1,
                default  => 0
        );

        contract 'add'
                => accepts [ $even_int ]
                => returns void,
                with_context(    # very contrived...
                        pre => sub {
                                my $self = shift;
                                my $add  = shift;
                                return [ $self->{value}, $add ];
                        },
                        post => assert {
                                my $pre = shift;
                                $pre->[0] + $pre->[1] == shift->{value};
                        }
                );
        sub add {
                my $self = shift;
                my $incr = shift;
                $self->{value} += $incr;
                return;
        }

        contract 'get_multiple'
                => accepts ['Int'],
                => returns [$even_int];
        sub get_multiple {
                return shift->{value} * shift;
        }

        no MooseX::Contract;

DESCRIPTION ^

The Design by Contract (DbC) method of programming could be seen as simply baking some simple unit test or assertions right into your regular code path. The set of assertions or tests for a given class is considered that class' contract - a guarantee of how any instance of that class will behave and appear. This implementation of DbC provides three types of assertions (referred to here as "contract clauses") when defining your class' contract:

pre clause

This clause is attached to a specific method and is executed before control is passed to the original method. Typically, these could be used to validate incoming parameters but one might also validate state of the object itself in this type of clause.

post clause

This clause is also attached to a specific method and is executed after the original method has been called. This type of DbC clause has the opportunity to validate return values (or lack thereof) as well as the state of the object following the method.

invariant clause

This is a special type of DbC clause that makes assertions about the ongoing state of the object. These clauses are invoked after each public method (subs that don't begin with an underscore) is called. Unlike post clauses, however, these clauses are only allowed to inspect the object's state (not the return values of the method).

The contract clauses are created using a declarative syntax as inspired by the Moose syntax.

One item worht noting: there's no guaranteed safe way to resume execution after a contract clause validation failure. For instance, if a method does something naughty and causes a post or invariant clause to fail, the object in question may be irreperably broken. Catching these errors and ignoring them (or in some cases, trying to handle them) is not advisable and makes the use of this module pointless. These contract errors should be allowed to die an ugly death. If you're concerned about the end user experience, you should disable all MooseX::Contract functionality in your production code and plan to have enough coverage in your development and test environments that you're comfortable with the checks not being in effect.

EXPORT ^

The following subs are exported by default and will be removed from the caller's namespace using no MooseX::Contract.

contract

This is the core method of the module. It sets up a contract clause for a specific method (or methods) and uses Moose's around hook to execute the pre and post clauses that are specified. Some of the "sugar" listed below help with building up the contract that you want to express.

The first argument to contract is always the method name. Following the method name, you must pass pairs of arguments (type => CodeRef). The type indicates the clause of the contract (pre or post) that the CodeRef should be applicable to. Another special invar type of clause is very similar to the post type except that it doesn't receive the return values to verify (demonstrated below).

Typically you will only want to use pre and post with the contract method.

For instance (using none of the sugar supplied below):

        contract 'some_method',
                pre => sub {
                        my($self,@params) = @_;
                        # do some validation here, dieing if validation fails
                }
                post => sub {
                        my($self, @return_values) = @_;
                        # do some validation here, dieing if validation fails
                };

You can provide as many pre and post hook but each of them must be preceded in the list by the lable (pre or post). They will be executed in the order they are listed and the first one that fails will result in the operation dieing.

As noted below in the PERFORMANCE section, you can short circuit all functionality provided by this module by setting the NO_MOOSEX_CONTRACT environment variable. That essentially makes the contract sub a no-op.

invariant

This is a special kind of contract clause that adds a post clause to all public method calls. Typically you would use this to assert a specific characteristic about the object itself.

check

This is pure sugar and simply returns the CodeRef that is passed in.

assert

This helper method creates a wrapper clause that will croak if the underlying anonymous sub does not return a true value.

        contract 'some_method'
                pre => assert { 
                        
                };

accepts

This helper method takes an ArrayRef of Moose type constraints and creates a pre clause that verifies the type of the value or values passed in to the method by the caller. Any extra arguments passed to the method that don't have explicit restrictions given to accepts will be passed without validation (this may change in the future)

        # method_a accepts at least two Int arguments
        contract method_a => accepts ['Int', 'Int'];

        # method_b accepts no arguments
        contract method_b => accepts void;

        # works with any type that Moose will recognize
        my $cheezey = subtype 'Str', where { m/cheese/ };
        contract method_c => accepts ['MyClass', 'ArrayRef[Str]', $cheezey];

returns

This helper method creates a post clause that looks at the value or values returned by the method it's affecting. PLEASE NOTE: these checks only have a chance to evaluate the values that are actually returned to the caller. If the caller is using scalar context, then this validation will get the value that is returned when in scalar context. More importantly (surprisingly?) if the caller is executing the statement in void context, these checks won't receive any return values to evaluate but may still validate the state of $self (the first argument received by the post hook).

void

A simple helper method that asserts zero items were passed (useful in specifying accepts and returns clauses).

with_context

This helper method wraps a pre and post clause with closures that allow a values to be compared between the two clauses. The SYNOPSIS above shows an example of how to use this functionality.

PERFORMANCE ^

As the saying goes, you never get something for nothing. That is definitely the case with this module (or indeed any usage of Moose's method hooks). At the time of this writing, Class::MOP claims that an around method hook is ~5x slower than a standard method invocation. This facter doesn't include any of the actual checks that are run as part of validating the contract so (short of doing actual profiling) I would guess using MooseX::Contract could slow your method calls down by up to 10x.

That is a pretty considerable drawback to using the features of this module. However, to mitigate this, MooseX::Contract allows you to turn off all method wrapping if it detects the NO_MOOSEX_CONTRACT environment variable. If you are about performance but wish to use some of the features of this module, you might want to enable these features only in your development or testing environment and let things run fast and free in production.

A WORD OF CAUTION ^

This module is by no means a comprehensive approach to DbC. I have very limited experience with this style of programming and wrote this module more as a learning project than anything.

SEE ALSO ^

Sub::Contract
Class::Contract

AUTHOR ^

Brian Phillips, <bphillips at cpan.org>

BUGS ^

Please report any bugs or feature requests to bug-moosex-contract at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=MooseX-Contract. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT ^

You can find documentation for this module with the perldoc command.

    perldoc MooseX::Contract

You can also look for information at:

ACKNOWLEDGEMENTS ^

COPYRIGHT & LICENSE ^

Copyright 2009 Brian Phillips

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

syntax highlighting: