A piggy bank of commands, fixes, succinct reviews, some mini articles and technical opinions from a (mostly) Perl developer.

Jump to

Quick reference

Showing posts with label subroutines. Show all posts
Showing posts with label subroutines. Show all posts

Reasons to split up a very long subroutine into several more subroutines

  • Repetition
    • If the same or very similar code is being called several times, it should be put into a subroutine to avoid code duplication.
    • This is the most obvious reason.
  • Clarity
    • You should be able to read the code like English
    • Reading the code in a top level subroutine or script should give an overview of what it does, without going into detail
    • Each subroutine should be able to be easily described in one or two sentences
    • Comments are not sufficient for this, as you would still have to scan through many pages to gain an understanding. 
    • This reason is controversial; Some people say repetition is the only valid reason (see 'dangers' below)
  • Testing
    • You should be able to test each conceptual part of the system separately.
    • It should be obvious to which part of the system a unit test applies.


Dangers of the second two reasons:
- Too much abstraction, difficult to understand and follow code flow
- Code is slow because of overhead of creating and managing subroutines

Perl: Fix DBI::db->disconnect invalidates 1 active statement handle

Add this subroutine to the module:

sub DESTROY {
    my ($self) = @_;
    # Finish off our statement handle, so we don't get warnings like:
    # DBI::db->disconnect invalidates 1 active statement handle
    my $sth = $self->{data_handle};
    if (blessed($sth) && $sth->can('finish')) {
        $sth->finish;
    }
}

Trace the execution of subroutines under mod_perl

1) First try one of these to trace every line (doesn't actually capture subroutine names):

Debug::TraceDebug::LTraceDevel::Trace, or others.

2) Then if you want to filter the list, try:

perl PERL5DB='sub DB::DB {my @c=caller;return if $c[1] =~ m|/opt/foo/bar| || $c[1] =~ m|/qux/| || $c[1] =~ m|Useless|; print STDERR qq|@c[1,2] ${"::_<$c[1]"}[$c[2]]|}' perl -d path/to/test/file.t

(thanks Brian)

3) Here's one I came up with using Moose:

use Moose;
use Scalar::Util;
use Data::Dumper;
for my $func qw(list all the subroutine names here) {
    around $func => sub {
        my $orig = shift;
        my $self = shift;
        warn "Running __PACKAGE__::${func} with:\n";
        my $i = 0;
        foreach my $param (@_) {
            $i++;
            # don't display class it$self
            next if blessed $param and $param->isa(__PACKAGE__);
            if (blessed $param) {
                # try not to dump out huge objects
                warn "\t$i = $param\n";
            } else {
                warn "\t$i = ".Dumper($param);
            }
        }
        # call the original sub
        $self->$orig(@_);
    }
}

4) Simple way to log all parameters:

my $r=\@_; $logger->debug('entering ', sub { $logger->dump(args => $r) });

5) Simple way to log method name only, without potentially verbose parameters:

$logger->info("entering ".( caller(0) )[3]);

6) Log4perl configuration

Use %M to log the method name, then just log anything, e.g. "entering".



Perl sub name in vi status line

" Thanks Ovid!
" http://blogs.perl.org/users/ovid/2011/01/show-perl-subname-in-vim-statusline.html


:set laststatus=2


if ! exists("g:did_perl_statusline")
    setlocal statusline+=%(\ %{StatusLineIndexLine()}%)
    setlocal statusline+=%=
    setlocal statusline+=%f\ 
    setlocal statusline+=%P
    let g:did_perl_statusline = 1
endif


if has( 'perl' )
perl << EOP
    use strict;
    sub current_sub {

        my $curwin = $main::curwin;
        my $curbuf = $main::curbuf;


        my @document = map { $curbuf->Get($_) } 0 .. $curbuf->Count;
        my ( $line_number, $column  ) = $curwin->Cursor;


        my $sub_name;
        # for modules, display the current sub name
        for my $i ( reverse ( 1 .. $line_number  -1 ) ) {
            my $line = $document[$i];
            if ( $line =~ /^\s*sub\s+(\w+)\b/ ) {
                $sub_name = $1;
                last;
            }
        }
        # for templates, display the current block starting line
        if (not $sub_name) {
            for my $i ( reverse ( 1 .. $line_number  -1 ) ) {
                my $line = $document[$i];
                # if ($input_state eq 'next_disc_at_station') {
                if ( $line =~ /^}/ ) {
                    # we're below a block
                    last;
                }
                elsif ( $line =~ /^(\S.+{)\s*$/ ) {
                    $sub_name = $1;
                    $sub_name =~ s/'/''/g; #' # escape single quotes for vim function
                    last;
                }
            }
        }
        # TODO:
        # * Reset sub name if we're out of the sub (check for closing bracket: })
        # * Search upwards vertically in the same column for nested blocks
        $sub_name ||= '..';
        VIM::DoCommand "let subName='$line_number: $sub_name'";
    }
EOP


function! StatusLineIndexLine()
  perl current_sub()
  return subName
endfunction
endif

Perl subroutine parameters

Call subs like this:
sub_name(
      param1 => 'value1',
      param2 => 'value2',
);

Define subs like this:
sub sub_name {
    my $p1      = {@_}->{param1};
    my $p2      = {@_}->{param2};
    # etc
}