File Coverage

blib/lib/Class/Sniff.pm
Criterion Covered Total %
statement 367 380 96.5
branch 82 116 70.6
condition 25 36 69.4
subroutine 58 58 100.0
pod 24 24 100.0
total 556 614 90.5


line stmt bran cond sub pod time code
1             package Class::Sniff;
2              
3 4     4   341769 use warnings;
  4         5  
  4         114  
4 4     4   13 use strict;
  4         5  
  4         63  
5              
6 4     4   2030 use B::Concise;
  4         24974  
  4         174  
7 4     4   20 use Carp ();
  4         4  
  4         53  
8 4     4   13 use Devel::Symdump;
  4         4  
  4         57  
9 4     4   12 use Digest::MD5;
  4         4  
  4         94  
10 4     4   2571 use Graph::Easy;
  4         313001  
  4         192  
11 4     4   1875 use List::MoreUtils ();
  4         28111  
  4         86  
12 4     4   1555 use Sub::Identify ();
  4         3069  
  4         67  
13 4     4   1470 use Text::SimpleTable;
  4         5370  
  4         217  
14              
15 4     4   22 use constant PSEUDO_PACKAGES => qr/::(?:SUPER|ISA::CACHE)$/;
  4         4  
  4         1189  
16              
17             =head1 NAME
18              
19             Class::Sniff - Look for class composition code smells
20              
21             =head1 VERSION
22              
23             Version 0.08_05
24              
25             =cut
26              
27             our $VERSION = '0.08_05';
28              
29             =head1 SYNOPSIS
30              
31             use Class::Sniff;
32             my $sniff = Class::Sniff->new({class => 'Some::class'});
33              
34             my $num_methods = $sniff->methods;
35             my $num_classes = $sniff->classes;
36             my @methods = $sniff->methods;
37             my @classes = $sniff->classes;
38              
39             my $graph = $sniff->graph; # Graph::Easy
40             my $graphviz = $graph->as_graphviz();
41             open my $DOT, '|dot -Tpng -o graph.png' or die("Cannot open pipe to dot: $!");
42             print $DOT $graphviz;
43              
44             print $sniff->to_string;
45             my @unreachable = $sniff->unreachable;
46             foreach my $method (@unreachable) {
47             print "$method\n";
48             }
49              
50             =head1 DESCRIPTION
51              
52             B code. You've been warned.
53              
54             The interface is rather ad-hoc at the moment and is likely to change. After
55             creating a new instance, calling the C method is your best option.
56             You can then visually examine it to look for potential problems:
57              
58             my $sniff = Class::Sniff->new({class => 'Some::Class'});
59             print $sniff->report;
60              
61             This module attempts to help programmers find 'code smells' in the
62             object-oriented code. If it reports something, it does not mean that your
63             code is wrong. It just means that you might want to look at your code a
64             little bit more closely to see if you have any problems.
65              
66             At the present time, we assume Perl's default left-most, depth-first search
67             order. We may alter this in the future (and there's a work-around with the
68             C method. More on this later).
69              
70             =head1 CLASS METHODS
71              
72             =head2 C
73              
74             my $sniff = Class::Sniff->new({
75             class => 'My::Class',
76             ignore => qr/^DBIx::Class/,
77             });
78              
79             The constructor accepts a hashref with the following parameters:
80              
81             =over 4
82              
83             =item * C
84              
85             Mandatory.
86              
87             The name of the class to sniff. If the class is not loaded into memory, the
88             constructor will still work, but nothing will get reported. You must ensure
89             that your class is already loaded!
90              
91             If you pass it an instance of a class instead, it will call 'ref' on the class
92             to determine what class to use.
93              
94             =item * C
95              
96             Optional.
97              
98             This should be a regex telling C what to ignore in class names.
99             This is useful if you're inheriting from a large framework and don't want to
100             report on it. Be careful with this, though. If you have a complicated
101             inheritance hierarchy and you try to ignore something other than the root, you
102             will likely get bad information returned.
103              
104             =item * universal
105              
106             Optional.
107              
108             If present and true, will attempt to include the C base class. If
109             a class hierarchy is pruned with C, C may not show up.
110              
111             =item * clean
112              
113             Optional.
114              
115             If present, will automatically ignore "pseudo-packages" such as those ending
116             in C<::SUPER> and C<::ISA::CACHE>. If you have legitimate packages with these
117             names, oops.
118              
119             =item * method_length
120              
121             Optional.
122              
123             If present, will set the "maximum length" of a method before it's reported as
124             a code smell. This feature is I experimental. See C
125             for details.
126              
127             =back
128              
129             =cut
130              
131             sub new {
132 10     10 1 2319 my ( $class, $arg_for ) = @_;
133             my $proto = $arg_for->{class}
134 10 50       33 or Carp::croak("'class' argument not supplied to 'new'");
135 10   66     43 my $target_class = ref $proto || $proto;
136 10 50 66     39 if ( exists $arg_for->{ignore} && 'Regexp' ne ref $arg_for->{ignore} ) {
137 0         0 Carp::croak("'ignore' requires a regex");
138             }
139             my $self = bless {
140             classes => {},
141             clean => $arg_for->{clean},
142             duplicates => {},
143             exported => {},
144             graph => undef,
145             ignore => $arg_for->{ignore},
146             list_classes => [$target_class],
147             long_methods => {},
148             method_length => ( $arg_for->{method_length} || 50 ),
149             methods => {},
150             paths => [ [$target_class] ],
151             target => $target_class,
152             universal => $arg_for->{universal},
153 10   100     125 } => $class;
154 10         30 $self->_initialize;
155 10         40 return $self;
156             }
157              
158             =head2 C
159              
160             B: This can be a very slow method as it needs to exhaustively walk
161             and analyze the symbol table.
162              
163             my @sniffs = Class::Sniff->new_from_namespace({
164             namespace => $some_root_namespace,
165             universal => 1,
166             });
167              
168             # Print reports for each class
169             foreach my $sniff (@sniffs) {
170             print $sniff->report;
171             }
172              
173             # Print out the full inheritance heirarchy.
174             my $sniff = pop @sniffs;
175             my $graph = $sniff->combine_graphs(@sniffs);
176              
177             my $graphviz = $graph->as_graphviz();
178             open my $DOT, '|dot -Tpng -o graph.png' or die("Cannot open pipe to dot: $!");
179             print $DOT $graphviz;
180              
181             Given a namespace, returns a list of C objects namespaces which
182             start with the C<$namespace> string. Requires a C argument.
183              
184             If you prefer, you can pass C a regex and it will simply return a
185             list of all namespaces matching that regex:
186              
187             my @sniffs = Class::Sniff->new_from_namespace({
188             namespace => qr/Result(?:Set|Source)/,
189             });
190              
191             You can also use this to slurp "everything":
192              
193             my @sniffs = Class::Sniff->new_from_namespace({
194             namespace => qr/./,
195             universal => 1,
196             });
197              
198             Note that because we still pull parents, it's possible that a parent class
199             will have a namespace not matching what you are expecting.
200              
201             use Class::Sniff;
202             use HTML::TokeParser::Simple;
203             my @sniffs = Class::Sniff->new_from_namespace({
204             namespace => qr/(?i:tag)/,
205             });
206             my $graph = $sniffs[0]->combine_graphs( @sniffs[ 1 .. $#sniffs ] );
207             print $graph->as_ascii;
208             __END__
209             +-------------------------------------------+
210             | HTML::TokeParser::Simple::Token |
211             +-------------------------------------------+
212             ^
213             |
214             |
215             +-------------------------------------------+ +---------------------------------------------+
216             | HTML::TokeParser::Simple::Token::Tag | <-- | HTML::TokeParser::Simple::Token::Tag::Start |
217             +-------------------------------------------+ +---------------------------------------------+
218             ^
219             |
220             |
221             +-------------------------------------------+
222             | HTML::TokeParser::Simple::Token::Tag::End |
223             +-------------------------------------------+
224              
225             All other arguments are passed to the C constructor.
226              
227             =cut
228              
229             sub new_from_namespace {
230 2     2 1 442 my ( $class, $arg_for ) = @_;
231             my $namespace = delete $arg_for->{namespace}
232 2 50       7 or Carp::croak("new_from_namespace requires a 'namespace' argument");
233 2         4 my $ignore = delete $arg_for->{ignore};
234              
235 2 50       7 $namespace = ('Regexp' eq ref $namespace)
236             ? $namespace
237             : qr/^$namespace/;
238              
239 2 50       5 if (defined $ignore) {
240 0 0       0 $ignore = ('Regexp' eq ref $ignore)
241             ? $ignore
242             : qr/^$ignore/;
243             }
244              
245 2         2 my @sniffs;
246             my %seen;
247             my $find_classes = sub {
248 685     685   646 my $symbol_name = shift;
249 4     4   17 no warnings 'numeric';
  4         5  
  4         10015  
250 685 50       1577 return if $seen{$symbol_name}++; # prevent infinite loops
251 685 100       1773 if ( $symbol_name =~ $namespace ) {
252 7 50 33     14 return if defined $ignore && $symbol_name =~ $ignore;
253 7         17 $symbol_name =~ s/::$//;
254 7         9 $arg_for->{class} = $symbol_name;
255 7 100       14 if ( not $class->_is_real_package($symbol_name) ) {
256             # we don't want to create a sniff, but we need to be able to
257             # descend into the namespace.
258 5         61 return 1;
259             }
260 2         5 push @sniffs => Class::Sniff->new($arg_for);
261             }
262 680         39681 return 1;
263 2         9 };
264 2         670 B::walksymtable( \%::, 'NAME', $find_classes );
265 2         148 return @sniffs;
266             }
267              
268             =head2 C
269              
270             my $graph = Class::Sniff->graph_from_namespace({
271             namespace => qr/^My::Namespace/,
272             });
273             print $graph->as_ascii;
274             my $graphviz = $graph->as_graphviz();
275             open my $DOT, '|dot -Tpng -o graph.png' or die("Cannot open pipe to dot: $!");
276             print $DOT $graphviz;
277              
278             Like C, but returns a single C object.
279              
280             =cut
281              
282             sub graph_from_namespace {
283 1     1 1 1 my ( $class, $arg_for ) = @_;
284 1         4 my @sniffs = $class->new_from_namespace($arg_for);
285 1         3 my $sniff = pop @sniffs;
286             return @sniffs
287 1 50       7 ? $sniff->combine_graphs(@sniffs)
288             : $sniff->graph;
289             }
290              
291             sub _initialize {
292 10     10   13 my $self = shift;
293 10         26 my $target_class = $self->target_class;
294 10         27 $self->width(72);
295 10         25 $self->_register_class($target_class);
296 10         18 $self->{classes}{$target_class}{count} = 1;
297 10         68 $self->{graph} = Graph::Easy->new;
298 10         944 $self->{graph}->set_attribute( 'graph', 'flow', 'up' );
299 10         1100 $self->_build_hierarchy($target_class);
300              
301 10         46 $self->_finalize;
302             }
303              
304             sub _finalize {
305 10     10   15 my $self = shift;
306 10         21 my @classes = $self->classes;
307 10         12 my $index = 0;
308 10         16 my %classes = map { $_ => $index++ } @classes;
  33         58  
309              
310             # sort in inheritance order
311 10         13 while ( my ( $method, $classes ) = each %{ $self->{methods} } ) {
  56         138  
312 46         72 @$classes = sort { $classes{$a} <=> $classes{$b} } @$classes;
  30         54  
313             }
314             }
315              
316             sub _register_class {
317 59     59   53 my ( $self, $class ) = @_;
318 59 100       145 return if exists $self->{classes}{$class};
319              
320             # Do I really want to throw this away?
321 33         1337 my $symdump = Devel::Symdump->new($class);
322 33         695 my @methods = map { s/^$class\:://; $_ } $symdump->functions;
  69         453  
  69         122  
323              
324 33         53 foreach my $method (@methods) {
325 69 50       436 my $coderef = $class->can($method)
326             or Carp::croak("Panic: $class->can($method) returned false!");
327 69         194 my $package = Sub::Identify::stash_name($coderef);
328 69 100       358 if ( $package ne $class ) {
329 2         6 $self->{exported}{$class}{$method} = $package;
330             }
331             else {
332              
333             # It's OK to throw away the exception. The B:: modules can be
334             # tricky and this is documented as experimental.
335 67         63 local $@;
336 67         78 eval {
337 67         421 my $line = B::svref_2object($coderef)->START->line;
338 66         292 my $length = B::svref_2object($coderef)->GV->LINE - $line;
339 66 100       123 if ( $length > $self->method_length ) {
340 1         4 $self->{long_methods}{"$class\::$method"} = $length;
341             }
342             };
343             }
344              
345 69         158 my $walker = B::Concise::compile( '-terse', $coderef ); # 1
346 69         2273 B::Concise::walk_output( \my $buffer );
347 69         4221 $walker->(); # 1 renders -terse
348 69         291970 $buffer =~ s/^.*//; # strip method name
349 69         1280 $buffer =~ s/\(0x[^)]+\)/(0xHEXNUMBER)/g; # normalize addresses
350 69         461 my $digest = Digest::MD5::md5_hex($buffer);
351 69   100     292 $self->{duplicates}{$digest} ||= [];
352 69         66 push @{ $self->{duplicates}{$digest} } => [ $class, $method ];
  69         1078  
353             }
354              
355 33         42 for my $method (@methods) {
356 69   100     195 $self->{methods}{$method} ||= [];
357 69         53 push @{ $self->{methods}{$method} } => $class;
  69         102  
358             }
359              
360 33         127 $self->{classes}{$class} = {
361             parents => [],
362             children => [],
363             methods => \@methods,
364             count => 0,
365             };
366 33         170 return $self;
367             }
368              
369             =head1 INSTANCE METHODS - CODE SMELLS
370              
371             =head2 C
372              
373             my $overridden = $sniff->overridden;
374              
375             This method returns a hash of arrays. Each key is a method in the hierarchy
376             which has been overridden and the arrays are lists of all classes the method
377             is defined in (not just which one's it's overridden in). The order of the
378             classes is in Perl's default inheritance search order.
379              
380             =head3 Code Smell: overridden methods
381              
382             Overridden methods are not necessarily a code smell, but you should check them
383             to find out if you've overridden something you didn't expect to override.
384             Accidental overriding of a method can be very hard to debug.
385              
386             This can also be a sign of bad responsibilities. If you have a long
387             inheritance chain and you override a method in five different levels with five
388             different behaviors, perhaps this behavior should be in its own class.
389              
390             =cut
391              
392             sub overridden {
393 5     5 1 5 my $self = shift;
394 5         6 my %methods;
395 5         5 while ( my ( $method, $classes ) = each %{ $self->{methods} } ) {
  30         50  
396 25 100       72 $methods{$method} = $classes if @$classes > 1;
397             }
398 5         10 return \%methods;
399             }
400              
401             =head2 C
402              
403             my $exported = $sniff->exported;
404              
405             Returns a hashref of all classes which have subroutines exported into them.
406             The structure is:
407              
408             {
409             $class1 => {
410             $sub1 => $exported_from1,
411             $sub2 => $exported_from2,
412             },
413             $class2 => { ... }
414             }
415              
416             Returns an empty hashref if no exported subs are found.
417              
418             =head3 Code Smell: exported subroutines
419              
420             Generally speaking, you should not be exporting subroutines into OO code.
421             Quite often this happens with things like C and other modules
422             which export "helper" functions. These functions may not behave like you
423             expect them to since they're generally not intended to be called as methods.
424              
425             =cut
426              
427 1     1 1 2 sub exported { $_[0]->{exported} }
428              
429             =head2 C
430              
431             my @unreachable = $sniff->unreachable;
432             for my $method (@unreachable) {
433             print "Cannot reach '$method'\n";
434             }
435              
436             Returns a list of fully qualified method names (e.g.,
437             'My::Customer::_short_change') which are unreachable by Perl's normal search
438             inheritance search order. It does this by searching the "paths" returned by
439             the C method.
440              
441             =head3 Code Smell: unreachable methods
442              
443             Pretty straight-forward here. If a method is unreachable, it's likely to be
444             dead code. However, you might have a reason for this and maybe you're calling
445             it directly.
446              
447             =cut
448              
449             sub unreachable {
450 3     3 1 6 my $self = shift;
451 3         7 my $overridden = $self->overridden;
452 3         10 my @paths = $self->paths;
453              
454             # If we only have one path through our code, we don't have any unreachable
455             # methods.
456 3 50       8 return if @paths == 1;
457              
458             # Algorithm: If we have overridden methods, then if we have multiple
459             # paths through the code, a method is unreachable if a *previous* path
460             # contains the method because Perl's default search order won't get to
461             # successive paths.
462 3         3 my @unreachable;
463 3         9 while ( my ( $method, $classes ) = each %$overridden ) {
464 5         4 my @classes;
465              
466             CLASS:
467 5         5 for my $class (@$classes) {
468 16         11 my $method_found = 0;
469 16         12 for my $path (@paths) {
470              
471             # method was found in a *previous* path.
472 21 100       27 if ($method_found) {
473 5         8 push @unreachable => "$class\::$method";
474 5         14 next CLASS;
475             }
476 16         8 for my $curr_class (@$path) {
477 37 100       50 next CLASS if $curr_class eq $class;
478 26 100 100     82 if ( not $method_found && $curr_class->can($method) ) {
479 13         11 $method_found = 1;
480             }
481             }
482             }
483             }
484             }
485 3         26 return @unreachable;
486             }
487              
488             =head2 C
489              
490             my @paths = $sniff->paths;
491              
492             for my $i (0 .. $#paths) {
493             my $path = join ' -> ' => @{ $paths[$i] };
494             printf "Path #%d is ($path)\n" => $i + 1;
495             }
496              
497             Returns a list of array references. Each array reference is a list of
498             classnames representing the path Perl will take to search for a method. For
499             example, if we have an abstract C class and we use diamond inheritance
500             to create an C class, we might have the following hierarchy:
501              
502             Animal
503             / \
504             Animal::Duck Animal::SpareParts
505             \ /
506             Animal::Platypus
507              
508             With Perl's normal left-most, depth-first search order, C will return:
509              
510             (
511             ['Animal::Platypus', 'Animal::Duck', 'Animal'],
512             ['Animal::Platypus', 'Animal::SpareParts', 'Animal'],
513             )
514              
515             If you are using a different MRO (Method Resolution Order) and you know your
516             search order is different, you can pass in a list of "correct" paths,
517             structured as above:
518              
519             # Look ma, one hand (er, path)!
520             $sniff->paths(
521             ['Animal::Platypus', 'Animal::Duck', 'Animal::SpareParts', 'Animal'],
522             );
523              
524             At the present time, we do I validation of what's passed in. It's just an
525             experimental (and untested) hack.
526              
527             =head3 Code Smell: paths
528              
529             Multiple inheritance paths are tricky to get right, make it easy to have
530             'unreachable' methods and have a greater cognitive load on the programmer.
531             For example, if C and C both define the same
532             method, C' method is likely unreachable. But what if
533             makes a required state change? You now have broken code.
534              
535             See L for a more in-depth
536             explanation.
537              
538             =cut
539              
540             sub paths {
541 47     47 1 41 my $self = shift;
542 47 100       85 return @{ $self->{paths} } unless @_;
  26         57  
543 21         39 $self->{paths} = [@_];
544 21         48 return $self;
545             }
546              
547             =head2 C
548              
549             my $num_classes = $sniff->multiple_inheritance;
550             my @classes = $sniff->multiple_inheritance;
551              
552             Returns a list of all classes which inherit from more than one class.
553              
554             =head3 Code Smell: multiple inheritance
555              
556             See the C section for C
557              
558             =cut
559              
560             sub multiple_inheritance {
561 3     3 1 5 my $self = shift;
562 3         5 return grep { $self->parents($_) > 1 } $self->classes;
  14         15  
563             }
564              
565             =head2 C
566              
567             B: This method is very experimental and requires the L
568             module.
569              
570             my $num_duplicates = $self->duplicate_methods;
571             my @duplicates = $self->duplicate_methods;
572              
573             Returns either the number of duplicate methods found a list of array refs.
574             Each arrayref contains a list of array references, each having a class name
575             and method name.
576              
577             B: We report duplicates based on identical op-trees. If the method
578             names are different or the variable names are different, that's OK. Any
579             change to the op-tree, however, will break this. The following two methods
580             are identical, even if they are in different packages.:
581              
582             sub inc {
583             my ( $self, $value ) = @_;
584             return $value + 1;
585             }
586              
587             sub increment {
588             my ( $proto, $number ) = @_;
589             return $number + 1;
590             }
591              
592             However, this will not match the above methods:
593              
594             sub increment {
595             my ( $proto, $number ) = @_;
596             return 1 + $number;
597             }
598              
599             =head3 Code Smell: duplicate methods
600              
601             This is frequently a sign of "cut and paste" code. The duplication should be
602             removed. You may feel OK with this if the duplicated methods are exported
603             "helper" subroutines such as "Carp::croak".
604              
605             =cut
606              
607             sub duplicate_methods {
608 1     1 1 2 my $self = shift;
609 1         1 my @duplicates;
610 1         1 foreach my $methods ( values %{ $self->{duplicates} } ) {
  1         3  
611 4 100       6 if ( @$methods > 1 ) {
612 2         3 push @duplicates => $methods;
613             }
614             }
615 1         3 return @duplicates;
616             }
617              
618             =head2 C (highly experimental)
619              
620             my $num_long_methods = $sniff->long_methods;
621             my %long_methods = $sniff->long_methods;
622              
623             Returns methods longer than C. This value defaults to 50 and
624             can be overridden in the constructor (but not later).
625              
626             =over 4
627              
628             =item * How to count the length of a method.
629              
630             my $start_line = B::svref_2object($coderef)->START->line;
631             my $end_line = B::svref_2object($coderef)->GV->LINE;
632             my $method_length = $end_line - $start_line;
633              
634             The C<$start_line> returns the line number of the I in the
635             subroutine, not the C declaration. The subroutine's
636             declaration actually ends at the ending curly brace, so the following method
637             would be considered 3 lines long, even though you might count it differently:
638              
639             sub new {
640             # this is our constructor
641             my ( $class, $arg_for ) = @_;
642             my $self = bless {} => $class;
643             return $self;
644             }
645              
646             =cut
647              
648 1     1 1 1 sub long_methods { %{ $_[0]->{long_methods} } }
  1         4  
649              
650             =item * Exported methods
651              
652             These are simply ignored because the C modules think they start and end in
653             different packages.
654              
655             =item * Where does it really start?
656              
657             If you've taken a reference to a method I to the declaration of the
658             reference being seen, Perl might report a negative length or simply blow up.
659             We trap that for you and you'll never see those.
660              
661             =back
662              
663             Let me know how it works out :)
664              
665             =head3 Code Smell: long methods
666              
667             Note that long methods may not be a code smell at all. The research in the
668             topic suggests that methods longer than many experienced programmers are
669             comfortable with are, nonetheless, easy to write, understand, and maintain.
670             Take this with a grain of salt. See the book "Code Complete 2" by Microsoft
671             Press for more information on the research. That being said ...
672              
673             Long methods might be doing to much and should be broken down into smaller
674             methods. They're harder to follow, harder to debug, and if they're doing more
675             than one thing, you might find that you need that functionality elsewhere, but
676             now it's tightly coupled to the long method's behavior. As always, use your
677             judgment.
678              
679             =head2 C
680              
681             # defaults to 'target_class'
682             my $num_parents = $sniff->parents;
683             my @parents = $sniff->parents;
684              
685             my $num_parents = $sniff->parents('Some::Class');
686             my @parents = $sniff->parents('Some::Class');
687              
688             In scalar context, lists the number of parents a class has.
689              
690             In list context, lists the parents a class has.
691              
692             =head3 Code Smell: multiple parens (multiple inheritance)
693              
694             If a class has more than one parent, you may have unreachable or conflicting
695             methods.
696              
697             =cut
698              
699             sub parents {
700 18     18 1 46 my ( $self, $class ) = @_;
701 18   66     29 $class ||= $self->target_class;
702 18 100       32 unless ( exists $self->{classes}{$class} ) {
703 1         18 Carp::croak("No such class '$class' found in hierarchy");
704             }
705 17         39 return @{ $self->{classes}{$class}{parents} };
  17         51  
706             }
707              
708             =head1 INSTANCE METHODS - REPORTING
709              
710             =head2 C
711              
712             print $sniff->report;
713              
714             Prints out a detailed, human readable report of C's analysis of
715             the class. Returns an empty string if no issues found. Sample:
716              
717             Report for class: Grandchild
718            
719             Overridden Methods
720             .--------+--------------------------------------------------------------------.
721             | Method | Class |
722             +--------+--------------------------------------------------------------------+
723             | bar | Grandchild |
724             | | Abstract |
725             | | Child2 |
726             | foo | Grandchild |
727             | | Child1 |
728             | | Abstract |
729             | | Child2 |
730             '--------+--------------------------------------------------------------------'
731             Unreachable Methods
732             .--------+--------------------------------------------------------------------.
733             | Method | Class |
734             +--------+--------------------------------------------------------------------+
735             | bar | Child2 |
736             | foo | Child2 |
737             '--------+--------------------------------------------------------------------'
738             Multiple Inheritance
739             .------------+----------------------------------------------------------------.
740             | Class | Parents |
741             +------------+----------------------------------------------------------------+
742             | Grandchild | Child1 |
743             | | Child2 |
744             '------------+----------------------------------------------------------------'
745              
746             =cut
747              
748             sub report {
749 1     1 1 394 my $self = shift;
750              
751 1         4 my $report = $self->_get_overridden_report;
752 1         4 $report .= $self->_get_unreachable_report;
753 1         4 $report .= $self->_get_multiple_inheritance_report;
754 1         4 $report .= $self->_get_exported_report;
755 1         4 $report .= $self->_get_duplicate_method_report;
756 1         3 $report .= $self->_get_long_method_report;
757              
758 1 50       3 if ($report) {
759 1         3 my $target = $self->target_class;
760 1         8 $report = "Report for class: $target\n\n$report";
761             }
762 1         8 return $report;
763             }
764              
765             sub _get_duplicate_method_report {
766 1     1   1 my $self = shift;
767              
768 1         5 my $report = '';
769 1         3 my @duplicate = $self->duplicate_methods;
770 1         1 my ( @methods, @duplicates );
771 1 50       2 if (@duplicate) {
772 1         2 foreach my $duplicate (@duplicate) {
773 2         2 push @methods => join '::' => @{ pop @$duplicate };
  2         7  
774 2         3 push @duplicates => join "\n" => map { join '::' => @$_ }
  7         13  
775             @$duplicate;
776             }
777 1         4 $report .= "Duplicate Methods (Experimental)\n"
778             . $self->_build_report( 'Method', 'Duplicated In',
779             \@methods, \@duplicates );
780             }
781 1         147 return $report;
782             }
783              
784             sub _get_overridden_report {
785 1     1   1 my $self = shift;
786              
787 1         2 my $report = '';
788 1         3 my $overridden = $self->overridden;
789 1 50       4 if (%$overridden) {
790 1         5 my @methods = sort keys %$overridden;
791 1         1 my @classes;
792 1         2 foreach my $method (@methods) {
793 2         2 push @classes => join "\n" => @{ $overridden->{$method} };
  2         6  
794             }
795 1         5 $report .= "Overridden Methods\n"
796             . $self->_build_report( 'Method', 'Class', \@methods, \@classes );
797             }
798 1         201 return $report;
799             }
800              
801             sub _get_unreachable_report {
802 1     1   2 my $self = shift;
803              
804 1         1 my $report = '';
805 1 50       4 if ( my @unreachable = $self->unreachable ) {
806 1         1 my ( @methods, @classes );
807 1         2 for my $fq_method (@unreachable) {
808 2         7 $fq_method =~ /^(.*)::(.*)$/; # time to rethink the API
809 2         4 push @methods => $2;
810 2         3 push @classes => $1;
811             }
812 1         4 $report .= "Unreachable Methods\n"
813             . $self->_build_report( 'Method', 'Class', \@methods, \@classes );
814             }
815 1         93 return $report;
816             }
817              
818             sub _get_multiple_inheritance_report {
819 1     1   2 my $self = shift;
820 1         1 my $report .= '';
821 1 50       3 if ( my @multis = $self->multiple_inheritance ) {
822 1         2 my @classes = map { join "\n" => $self->parents($_) } @multis;
  1         2  
823 1         3 $report .= "Multiple Inheritance\n"
824             . $self->_build_report( 'Class', 'Parents', \@multis, \@classes );
825             }
826 1         86 return $report;
827             }
828              
829             sub _get_exported_report {
830 1     1   1 my $self = shift;
831 1         3 my $exported = $self->exported;
832 1         2 my $report = '';
833 1 50       6 if ( my @classes = sort keys %$exported ) {
834 1         2 my ( $longest_c, $longest_m ) = ( length('Class'), length('Method') );
835 1         1 my ( @subs, @sources );
836 1         2 foreach my $class (@classes) {
837 1         1 my ( @temp_subs, @temp_sources );
838 1         1 foreach my $sub ( sort keys %{ $exported->{$class} } ) {
  1         3  
839 1         1 push @temp_subs => $sub;
840 1         2 push @temp_sources => $exported->{$class}{$sub};
841 1 50       2 $longest_c = length($class) if length($class) > $longest_c;
842 1 50       2 $longest_m = length($sub) if length($sub) > $longest_m;
843             }
844 1         3 push @subs => join "\n" => @temp_subs;
845 1         3 push @sources => join "\n" => @temp_sources;
846             }
847 1         2 my $width = $self->width - 3;
848 1         3 my $third = int( $width / 3 );
849 1 50       2 $longest_c = $third if $longest_c > $third;
850 1 50       5 $longest_m = $third if $longest_m > $third;
851 1         2 my $rest = $width - ( $longest_c + $longest_m );
852 1         5 my $text = Text::SimpleTable->new(
853             [ $longest_c, 'Class' ],
854             [ $longest_m, 'Method' ],
855             [ $rest, 'Exported From Package' ]
856             );
857 1         51 for my $i ( 0 .. $#classes ) {
858 1         3 $text->row( $classes[$i], $subs[$i], $sources[$i] );
859             }
860 1         47 $report .= "Exported Subroutines\n" . $text->draw;
861             }
862 1         97 return $report;
863             }
864              
865             sub _get_long_method_report {
866 1     1   2 my $self = shift;
867 1         2 my $report .= '';
868 1         2 my %long_methods = $self->long_methods;
869 1 50       5 if ( my @methods = sort keys %long_methods ) {
870 1         1 my @lengths;
871 1         2 foreach my $method (@methods) {
872 1         2 push @lengths => $long_methods{$method};
873             }
874 1         3 $report .= "Long Methods (experimental)\n"
875             . $self->_build_report( 'Method', 'Approximate Length',
876             \@methods, \@lengths );
877             }
878 1         73 return $report;
879             }
880              
881             sub _build_report {
882 5     5   7 my ( $self, $title1, $title2, $strings1, $strings2 ) = @_;
883 5 50       7 unless ( @$strings1 == @$strings2 ) {
884 0         0 Carp::croak("PANIC: Attempt to build unbalanced report");
885             }
886 5         10 my ( $width1, $width2 ) = $self->_get_widths( $title1, @$strings1 );
887 5         18 my $text =
888             Text::SimpleTable->new( [ $width1, $title1 ], [ $width2, $title2 ] );
889 5         211 for my $i ( 0 .. $#$strings1 ) {
890 8         145 $text->row( $strings1->[$i], $strings2->[$i] );
891             }
892 5         214 return $text->draw;
893             }
894              
895             sub _get_widths {
896 5     5   7 my ( $self, $title, @strings ) = @_;
897              
898 5         10 my $width = $self->width;
899 5         5 my $longest = length($title);
900 5         5 foreach my $string (@strings) {
901 8         6 my $length = length $string;
902 8 100       13 $longest = $length if $length > $longest;
903             }
904 5 50       10 $longest = int( $width / 2 ) if $longest > ( $width / 2 );
905 5         10 return ( $longest, $width - $longest );
906             }
907              
908             =head2 C
909              
910             $sniff->width(80);
911              
912             Set the width of the report. Defaults to 72.
913              
914             =cut
915              
916             sub width {
917 16     16 1 16 my $self = shift;
918 16 100       65 return $self->{width} unless @_;
919 10         12 my $number = shift;
920 10 50 33     78 unless ( $number =~ /^\d+$/ && $number >= 40 ) {
921 0         0 Carp::croak(
922             "Argument to 'width' must be a number >= than 40, not ($number)");
923             }
924 10         24 $self->{width} = $number;
925             }
926              
927             =head2 C
928              
929             print $sniff->to_string;
930              
931             For debugging, lets you print a string representation of your class hierarchy.
932             Internally this is created by L and I can't figure out how to
933             force it to respect the order in which classes are ordered. Thus, the
934             'left/right' ordering may be incorrect.
935              
936             =cut
937              
938 1     1 1 2 sub to_string { $_[0]->graph->as_ascii }
939              
940             =head2 C
941              
942             my $graph = $sniff->graph;
943              
944             Returns a C representation of the inheritance hierarchy. This is
945             exceptionally useful if you have C installed.
946              
947             my $graph = $sniff->graph; # Graph::Easy
948             my $graphviz = $graph->as_graphviz();
949             open my $DOT, '|dot -Tpng -o graph.png' or die("Cannot open pipe to dot: $!");
950             print $DOT $graphviz;
951              
952             Visual representations of complex hierarchies are worth their weight in gold.
953             See L.
954              
955             Because I cannot figure force it to respect the 'left/right' ordering of
956             classes, you may need to manually edit the C<$graphviz> data to get this
957             right.
958              
959             =cut
960              
961 32     32 1 101 sub graph { $_[0]->{graph} }
962              
963             =head2 C
964              
965             my $graph = $sniff->combine_graphs($sniff2, $sniff3);
966             print $graph->as_ascii;
967              
968             Allows you to create a large inheritance hierarchy graph by combining several
969             C instances together.
970              
971             Returns a L object.
972              
973             =cut
974              
975             sub combine_graphs {
976 1     1 1 2 my ( $self, @sniffs ) = @_;
977              
978 1         4 my $graph = $self->graph->copy;
979              
980 1         410 foreach my $sniff (@sniffs) {
981 1 50       9 unless ( $sniff->isa( ref $self ) ) {
982 0         0 my $bad_class = ref $sniff;
983 0         0 my $class = ref $self;
984 0         0 die
985             "Arguments to 'combine_graphs' must '$class' objects, not '$bad_class' objects";
986             }
987 1         2 my $next_graph = $sniff->graph;
988 1         5 foreach my $edge ( $next_graph->edges ) {
989 1         17 $graph->add_edge_once( $edge->from->name, $edge->to->name );
990             }
991             }
992 1         136 return $graph;
993             }
994              
995             =head2 C
996              
997             my $class = $sniff->target_class;
998              
999             This is the class you originally asked to sniff.
1000              
1001             =cut
1002              
1003 14     14 1 36 sub target_class { $_[0]->{target} }
1004              
1005             =head2 C
1006              
1007             my $method_length = $sniff->method_length;
1008              
1009             This is the maximum allowed length of a method before being reported as a code
1010             smell. See C in the constructor.
1011              
1012             =cut
1013              
1014 66     66 1 203 sub method_length { $_[0]->{method_length} }
1015              
1016             =head2 C
1017              
1018             my $ignore = $sniff->ignore;
1019              
1020             This is the regex provided (if any) to the constructor's C parameter.
1021              
1022             =cut
1023              
1024 125     125 1 206 sub ignore { $_[0]->{ignore} }
1025              
1026             =head2 C
1027              
1028             my $universal = $sniff->universal;
1029              
1030             This is the value provided (if any) to the 'universal' parameter in the
1031             constructor. If it's a true value, 'UNIVERSAL' will be added to the
1032             hierarchy. If the hierarchy is pruned via 'ignore' and we don't get down that
1033             far in the hierarchy, the 'UNIVERSAL' class will not be added.
1034              
1035             =cut
1036              
1037 87     87 1 220 sub universal { $_[0]->{universal} }
1038              
1039             =head2 C
1040              
1041             Returns true if user requested 'clean' classes. This attempts to remove
1042             spurious packages from the inheritance tree.
1043              
1044             =cut
1045              
1046 38     38 1 58 sub clean { $_[0]->{clean} }
1047              
1048             =head2 C
1049              
1050             my $num_classes = $sniff->classes;
1051             my @classes = $sniff->classes;
1052              
1053             In scalar context, lists the number of classes in the hierarchy.
1054              
1055             In list context, lists the classes in the hierarchy, in default search order.
1056              
1057             =cut
1058              
1059 16     16 1 21030 sub classes { @{ $_[0]->{list_classes} } }
  16         59  
1060              
1061             =head2 C
1062              
1063             # defaults to 'target_class'
1064             my $num_children = $sniff->children;
1065             my @children = $sniff->children;
1066              
1067             my $num_children = $sniff->children('Some::Class');
1068             my @children = $sniff->children('Some::Class');
1069              
1070             In scalar context, lists the number of children a class has.
1071              
1072             In list context, lists the children a class has.
1073              
1074             =cut
1075              
1076             sub children {
1077 4     4 1 22 my ( $self, $class ) = @_;
1078 4   66     10 $class ||= $self->target_class;
1079 4 100       10 unless ( exists $self->{classes}{$class} ) {
1080 1         10 Carp::croak("No such class '$class' found in hierarchy");
1081             }
1082 3         3 return @{ $self->{classes}{$class}{children} };
  3         14  
1083             }
1084              
1085             =head2 C
1086              
1087             # defaults to 'target_class'
1088             my $num_methods = $sniff->methods;
1089             my @methods = $sniff->methods;
1090              
1091             my $num_methods = $sniff->methods('Some::Class');
1092             my @methods = $sniff->methods('Some::Class');
1093              
1094             In scalar context, lists the number of methods a class has.
1095              
1096             In list context, lists the methods a class has.
1097              
1098             =cut
1099              
1100             sub methods {
1101 7     7 1 801 my ( $self, $class ) = @_;
1102 7   66     15 $class ||= $self->target_class;
1103 7 100       16 unless ( exists $self->{classes}{$class} ) {
1104 4         37 Carp::croak("No such class '$class' found in hierarchy");
1105             }
1106 3         3 return @{ $self->{classes}{$class}{methods} };
  3         22  
1107             }
1108              
1109             sub _get_parents {
1110 101     101   80 my ( $self, $class ) = @_;
1111 101 100 66     233 return if $class eq 'UNIVERSAL' or !$self->_is_real_package($class);
1112 4     4   23 no strict 'refs';
  4         4  
  4         422  
1113              
1114 87         87 my @parents = List::MoreUtils::uniq( @{"$class\::ISA"} );
  87         366  
1115 87 50 66     153 if ( $self->universal && not @parents ) {
1116 0         0 @parents = 'UNIVERSAL';
1117             }
1118 87 100       108 if ( my $ignore = $self->ignore ) {
1119 7         8 @parents = grep { !/$ignore/ } @parents;
  12         39  
1120             }
1121 87         189 return @parents;
1122             }
1123              
1124             sub _is_real_package {
1125 108     108   93 my ( $proto, $class ) = @_;
1126 4     4   25 no strict 'refs';
  4         8  
  4         100  
1127 4     4   13 no warnings 'uninitialized';
  4         4  
  4         1870  
1128 108 50       166 return 1 if 'UNIVERSAL' eq $class;
1129             return
1130 108 100       120 unless eval {
1131 108         68 defined *{ ${"${class}::"}{ISA} }{ARRAY}
  108         924  
1132 108 50       68 || scalar grep { defined *{$_}{CODE} } values %{"$class\::"};
  0         0  
  0         0  
  0         0  
1133             };
1134             }
1135              
1136             # This is the heart of where we set just about everything up.
1137             sub _build_hierarchy {
1138 38     38   59 my ( $self, @classes ) = @_;
1139 38         46 for my $class (@classes) {
1140 38 100       56 if ( my $ignore = $self->ignore ) {
1141 4 50       13 next if $class =~ $ignore;
1142             }
1143 38 100       81 if ( $self->clean ) {
1144 4 50       14 next if $class =~ PSEUDO_PACKAGES;
1145             }
1146 38 100       61 next unless my @parents = $self->_get_parents($class);
1147 21         47 $self->_register_class($_) foreach $class, @parents;
1148 21         55 $self->_add_children($class);
1149 21         44 $self->_build_paths($class);
1150 21         44 $self->_add_parents($class);
1151             }
1152             }
1153              
1154             # This method builds 'paths'. These are the paths the inheritance hierarchy
1155             # will take through the code to find a method. This is based on Perl's
1156             # default search order, not C3.
1157             sub _build_paths {
1158 21     21   27 my ( $self, $class ) = @_;
1159              
1160 21         32 my @parents = $self->_get_parents($class);
1161              
1162             # XXX strictly speaking, we can skip $do_chg, but if path() get's
1163             # expensive (such as testing for valid classes), then we
1164             # need it.
1165 21         20 my $do_chg;
1166             my @paths;
1167              
1168 21         49 foreach my $path ( $self->paths ) {
1169 33 100       58 if ( $path->[-1] eq $class ) {
1170 21         25 foreach my $parent (@parents) {
1171 28 50       32 if ( grep { $parent eq $_ } @$path ) {
  43         88  
1172 0         0 my $circular = join ' -> ' => @$path, $parent;
1173 0         0 Carp::croak("Circular path found in path ($circular)");
1174             }
1175             }
1176 21         26 ++$do_chg;
1177 21         27 push @paths => map { [ @$path, $_ ] } @parents;
  28         66  
1178             }
1179             else {
1180 12         15 push @paths => $path;
1181             }
1182             }
1183              
1184 21 50       72 $self->paths(@paths) if $do_chg;
1185             }
1186              
1187             sub _add_parents {
1188 21     21   23 my ( $self, $class ) = @_;
1189              
1190             # This algorithm will follow classes in Perl's default inheritance
1191             # order
1192 21         30 foreach my $parent ( $self->_get_parents($class) ) {
1193 23         33 push @{ $self->{list_classes} } => $parent
1194 28 100       29 unless grep { $_ eq $parent } @{ $self->{list_classes} };
  67         102  
  28         40  
1195 28         40 $self->{classes}{$parent}{count}++;
1196 28         56 $self->_build_hierarchy($parent);
1197             }
1198             }
1199              
1200             sub _add_children {
1201 21     21   31 my ( $self, $class ) = @_;
1202 21         37 my @parents = $self->_get_parents($class);
1203              
1204 21         37 $self->{classes}{$class}{parents} = \@parents;
1205              
1206 21         34 foreach my $parent (@parents) {
1207 28         918 $self->_add_child( $parent, $class );
1208 28         53 $self->graph->add_edge_once( $class, $parent );
1209             }
1210 21         1757 return $self;
1211             }
1212              
1213             sub _add_child {
1214 28     28   32 my ( $self, $class, $child ) = @_;
1215              
1216 28         36 my $children = $self->{classes}{$class}{children};
1217 28 100       56 unless ( grep { $child eq $_ } @$children ) {
  5         14  
1218 27         42 push @$children => $child;
1219             }
1220             }
1221              
1222             =head1 CAVEATS AND PLANS
1223              
1224             =over 4
1225              
1226             =item * Package Variables
1227              
1228             User-defined package variables in OO code are a code smell, but with versions
1229             of Perl < 5.10, any subroutine also creates a scalar glob entry of the same
1230             name, so I've not done a package variable check yet. This will happen in the
1231             future (there will be exceptions, such as with @ISA).
1232              
1233             =item * C3 Support
1234              
1235             I'd like support for alternate method resolution orders. If your classes use
1236             C3, you may get erroneous results. See L for a workaround.
1237              
1238             =back
1239              
1240             =head1 AUTHOR
1241              
1242             Curtis "Ovid" Poe, C<< >>
1243              
1244             =head1 BUGS
1245              
1246             Please report any bugs or feature requests to C
1247             rt.cpan.org>, or through the web interface at
1248             L. I will be
1249             notified, and then you'll automatically be notified of progress on your bug as
1250             I make changes.
1251              
1252             =head1 SUPPORT
1253              
1254             You can find documentation for this module with the perldoc command.
1255              
1256             perldoc Class::Sniff
1257              
1258             You can also look for information at:
1259              
1260             =over 4
1261              
1262             =item * RT: CPAN's request tracker
1263              
1264             L
1265              
1266             =item * AnnoCPAN: Annotated CPAN documentation
1267              
1268             L
1269              
1270             =item * CPAN Ratings
1271              
1272             L
1273              
1274             =item * Search CPAN
1275              
1276             L
1277              
1278             =back
1279              
1280             =head1 ACKNOWLEDGEMENTS
1281              
1282              
1283             =head1 COPYRIGHT & LICENSE
1284              
1285             Copyright 2009 Curtis "Ovid" Poe, all rights reserved.
1286              
1287             This program is free software; you can redistribute it and/or modify it
1288             under the same terms as Perl itself.
1289              
1290             =cut
1291              
1292             1; # End of Class::Sniff