File Coverage

blib/lib/Class/Sniff.pm
Criterion Covered Total %
statement 379 388 97.6
branch 87 120 72.5
condition 29 39 74.3
subroutine 59 59 100.0
pod 24 24 100.0
total 578 630 91.7


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