in reply to Re: Documenting Methods/Subs
in thread Documenting Methods/Subs

This is actually a good reason to use a module like Params::Validate, it helps provide documentation for a subroutine/method.

For example:

 sub do_the_thing
 {
     my %p = validate( @_, { foo => { isa => 'Foo::Big' },
                             size => { type => SCALAR, default => 10 },
                             ...
                           } );
     ...
 }

This way all the possible named arguments are documented, as well as their types/classes, defaults, etc. Along with good names, this really does completely document what parameters are accepted, _and_ as a bonus does actual validation on them.

Replies are listed 'Best First'.
Re: Re: Re: Documenting Methods/Subs
by pdcawley (Hermit) on Jan 12, 2003 at 16:45 UTC
    I'm not actually that much of a fan of enforcing object types in method signatures to be honest because it can lead to a certain amount of pain at testing time, especially if you're using the very lovely and worthwhile Test::Class. The 'Self Shunt' testing pattern is remarkably handy, that's when you do:
    sub test_the_thing : Test { my $self = shift; ... $object_under_test->do_the_thing( foo => $self, ... ) ... } sub called_from_do_the_thing { my $self = shift; ok array_eq(\@_, [qw/foo bar baz/]; }
    and similar tricks. If you're enforcing the object type you have to draft in Test::MockObject, which is great, but is also substantially more complex.

      If you're enforcing the object type you have to draft in Test::MockObject, which is great, but is also substantially more complex.

      You can still use the self-shunt unit testing pattern with enforced object types and Test::Class if you take advantage of perl's run-time method dispatch.

      For example, consider a class that takes an optional logging object to allow custom logging on an object by object basis.

        Cute trick. It's actually something I'd considered but rejected (but I would say that wouldn't I?), but your wrapper is very neat.

        The problem with it, as I see it is that, with optimisitic typing (ie, just passing in $self), you get an immediate failure if the object under test calls an unexpected method simply because you haven't implemented it. With a localized @ISA, Logger's methods becomes callable and an unexpected method call could get dispatched to real code (and you can't be sure that that would throw an exception.

        If Params::Validate does the right thing ($obj->isa('foo'), not UNIVERSAL::isa($obj, 'foo')) then you could always do

        sub _do_as (&$) { my($block, $fake_class) = @_; local *Object::Test::isa = sub { my($self, $target_class) = @_; return $target_class eq $fake_class || $self->SUPER::isa($target_class); }; $block->(); }
        If the validater calls UNIVERSAL::isa, you could override that instead, but it would be rather more awkward. And it's all a good deal more awkward than just chucking the test object in without adornment.

        This really comes down to how much value you see in the strict typing of variables. Personally I am unconvinced by it. I'd prefer to see a good test suite (and Smalltalk style method selectors, but they're rather harder to come by in Perl. Smalltalk method selectors are great though. They give you lots more naming opportunities, your 'new' method would become

        newWithLogger: aLogStream ^ self new setLogger: aLogStream
        which isn't necessarily the most convincing of arguments, but believe me it's great with more complex method signatures...)

      I'm not actually that much of a fan of enforcing object types in method signatures to be honest because it can lead to a certain amount of pain at testing time, ...

      my %p = validate( @_, { bar => { can => 'do_that_thang' } } );
        That's certainly a possibility. However (and I'm aware that this may sound like me attempting to move the goalposts), in well factored code the method is likely to be short enough that anyone reading the code will be able to see at a glance that bar will be expected to do_that_thang so the validator declaration is unlikely to introduce any real clarification.

        Actually, my whole argument for not having much in the way of code documentation is predicated on the belief that well factored code doesn't need much in the way of code documentation and, more strongly, that code that does need substantial explanatory documentation probably isn't well factored enough.