Dead Subroutines

Ovid on 2007-06-05T07:48:47

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(? $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 %

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


Erm... Devel::Cover?

Adrian on 2007-06-05T08:16:37

... of course you need a test suite for that :-)

Re:Erm... Devel::Cover?

Ovid on 2007-06-05T08:36:37

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, for those unfamiliar with this).

private subs, you must be kidding

gabor on 2007-06-05T20:15:42

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.