3

Could you please tell me what in Perl is the smartest way to validate function parameter?

Code-Snippet:

sub testfunction {
    my ($args) = @_;
    my $value = $args->{value} || die "no value set";
    # process value ...
}


testfunction({value => 'Hallo'});

testfunction({novalue => 'Hallo'});

Is the above way an good alternative to validate function parameter in perl or are there smarter ways? Many Thanks to everybody.

Sinan Ünür
  • 116,958
  • 15
  • 196
  • 339
  • 1
    Related [What Perl modules are useful for validating subroutine arguments?](https://stackoverflow.com/questions/1433070/what-perl-modules-are-useful-for-validating-subroutine-arguments) and [Perl subroutine arguments](https://stackoverflow.com/questions/19234209/perl-subroutine-arguments). – Sinan Ünür May 19 '17 at 13:27
  • See also [How to specify default values for optional subroutine arguments?](http://stackoverflow.com/q/29577085/2173773) and [Perl: Named parameters validation best practice](http://stackoverflow.com/q/19514694/2173773) – Håkon Hægland May 20 '17 at 09:07

2 Answers2

12

It depends a lot on what you're trying to accomplish, and whether it's user input, local functions or remote functions.

I mean, some parameters - it's far easier to 'validate' by trying to use it. A file name for example - rather than trying to trap all the edge cases by hand, just pass it to open .... or die $!

And sometimes - it's more sensible to 'default-and-override'. E.g.:

my ( $first_thing, $second_thing ) = @_; 
$first_thing //= "default value";
$second_thing //= 0; 

Note - use of // rather than || - it's similar in usage, but it tests for just being undefined - rather than being defined-but-false such as '' or 0.

And other times you want better still validation, by applying a set of validation regexes. Just be careful, some things - like email addresses or IP addresses - can be a lot more complicated to exhaustively validate than you think - so a simple edge case of 'was there a parameter at all' can work.

Of course, at that point you can also start looking at function prototypes. Which aren't anything like the prototyping you may think of from other languages:

#!/usr/bin/env perl

#says - must have a single scalar arg. 
sub testfunction($) {
   my ( $required_arg ) = @_; 
   print $required_arg;
}

testfunction(1);
testfunction;

The latter will fail with:

Not enough arguments for main::testfunction

Note - perl prototypes check type and number of arguments (e.g. is it scalar, is it array). But don't check values, and can have some slightly unexpected effects when it comes to passing in arrays.

I'd suggest that passing in hashes is quite well suited to a defaulting mechanism, because you can:

#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;

my %defaults = (
  "test" => 1,
);

sub with_default {
    my %args = (%defaults, @_); 
    print Dumper \%args; 
}

with_default;
with_default(test => 4 );

But to go back to your original case - if your function simply won't work without 'value' being set, then it's good to spell it out in code:

if ( not defined $args -> {value} 
  or not $args -> {value} =~ m/^\w+$/ ) {
   die 'value parameter must be supplied and match m/^\w+$/';
}

It's probably worth mentioning 'taint' mode at this point. It's a feature of perl that you don't see in many other languages.

It specifically tells the interpreter that 'user supplied' is 'tainted' and thus cannot be used without validation. And that includes insecure paths, environment vars etc.

See perlsec

But:

#!/usr/bin/env perl -T
use strict;
use warnings;

system "echo $ENV{'USERNAME'}";

Will fail - because system will not allow 'tainted' vars. (But print will). You need to pass it through a validation step (e.g. regex typically) before the 'taint' is removed.

Sobrique
  • 52,974
  • 7
  • 60
  • 101
  • Many Thanks to you. Great and very helpfull answer from you :) – FrankTheTank_12345 May 19 '17 at 09:26
  • 2
    You might want to use single quotes instead of doubles in your `die "value parameter ... match /^\w+$/"` as with double quotes,`$/` will be interpreted as a Perl variable. – Dada May 19 '17 at 09:36
3

The problem with this:

$args->{value} || die "no value set";

Is that it would fail when value is defined but set to zero.

testfunction({value => 0});  #Oops!

That may be ok if zero is not allowed for the value. But in general, a better design would be to use exists or // to check whether the hash key has been defined.

die "Value not defined!" if not exists $args->{value};

die "Value not defined!" unless exists $args->{value};

$args->{value} // die "Value not defined!";

For this simple statement I would tend to prefer exists because I think it reads a bit more clearly. But // allows nice things like substituting default values, as shown in Sobrique's answer. I agree with Sobrique's more general advice about approaches, as well.

  • 1
    You can also use `//` in lieu of `||` which solves the problem you outline. (this requires a slightly newer version of Perl than I've seen installed in some places) – Sobrique May 19 '17 at 09:21