Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

Ovid (2709)

Ovid
  (email not shown publicly)
http://publius-ovidius.livejournal.com/
AOL IM: ovidperl (Add Buddy, Send Message)

Stuff with the Perl Foundation. A couple of patches in the Perl core. A few CPAN modules. That about sums it up.

Journal of Ovid (2709)

Tuesday June 05, 2007
02:48 AM

Dead Subroutines

[ #33431 ]

When working with older code bases and doing heavy refactoring, it's amazing how many tools you write to analyze code. Sometimes these tools are hacks but they get the bulk of the work done and that's what counts when refactoring. I've fixed up some code I wrote to list which subroutines defined in the current file and expanded it to list how many times they are used in the current file. It skips POD and naively skips comments (the comment skipping is probably OK unless you have something which looks like a comment in a quoted string).

#!/usr/bin/perl

use strict;
use warnings;

use IO::Scalar;
use Pod::Stripper;

my $file = shift or die "usage: $0 perl_file";

open my $fh, '<', $file or die "Cannot open ($file) for reading: $!";
my @subs;

my $DOC = '';
my $fileh = IO::Scalar->new;

my $stripper = Pod::Stripper->new;
$stripper->parse_from_filehandle($fh, $fileh);
seek $fileh, 0, 0;
while (<$fileh>) {
    push @subs => $1 if /^\bsub\s+([[:word:]]+)/;
    $DOC .= $_ unless /^\s*#/;   # naively skip comments
}

my $longest_sub_name = 0;
@subs
  = map {
      $_->[0]
    . ( ' ' x ( $longest_sub_name + 2 - length $_->[0] ) )  # pad with spaces
    . $_->[1]
  }
  sort by_count_and_then_name
  map { decorate_sub_name($_) }
    @subs;
$" = "\n";
print "@subs";

sub by_count_and_then_name {
    return is_private($a->[0]) cmp is_private($b->[0])
      || $a->[1] <=> $b->[1]
      || $a->[0] cmp $b->[0];
}

sub decorate_sub_name {
    my $sub = shift;
    my $count = @{ [ $DOC =~ /\b(?<![\$\@\%])\Q$sub\E\b/g ] };
    $longest_sub_name += length $sub if length $sub > $longest_sub_name;
    return [ $sub, $count ];
}

sub is_private { '_' eq substr shift, 0, 1 }

It's not a brilliant piece of code (gotta love package-scoped lexicals, eh?), but more than once I've worked on some code only to find out that that some subs are defined and never used. It's a dead giveaway when it's a private sub that I have dead code. Of course, I use it so much that it's mapped to a key in vim:

noremap ,s  :!subs %<cr>

It's also really handy when you can't quite remember the name of the subroutine you're looking for.

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • ... of course you need a test suite for that :-)
    • Devel::Cover doesn't work here specifically because of the test suite problem. Our code coverage is up to about 36%, but some of the older code is virtually untestable due to how fragile tests become when I struggle to load some of these files. This became the 20% solution which delivers the 80% of results (see Pareto Rule [wikipedia.org], for those unfamiliar with this).

  • so you have private subs in your code!

    Good for you.

    The code I am maintaining has no _private functions. Actually it seems virtually every functions is called from at least 2 other modules. Preferably in a circular manner.

    --