Determine where Moose attributes and methods were inherited from?
Asked Answered
S

2

5

I often work on a huge, not-very-well-documented, object-oriented Perl repo at my place of employment. While maintaining the code, I frequently need to trace things that are inherited from other classes so that I can understand what they're doing. For example, I need to figure out what $self->mystery is and what it's doing:

package Foo::Bar;
use Moose;
use Method::Signatures;
use Foo::Bar::Element;
use Foo::Bar::Function;
use base qw (Baz::Foo::Bar);

method do_stuff ($some_arg) {
    # mystery is not defined in Foo::Bar
    my $mystery = $self->mystery;
    $mystery->another_mystery($some_arg);
}

I usually find myself spending way too much time tracing through parent classes. So my question is, is there an easy way for me to figure out where $self->mystery comes from? Or in other words, I need to find where mystery is declared.

And by "easy way", I don't mean using ack or grep to string search through files. I'm hoping there's some sort of debugging module I can install and use which could help give me some insight.

Thank you.

Shiflett answered 6/3, 2015 at 0:42 Comment(9)
Seriously, though, unless you have a ton of methods in different classes all called mystery, ack '^sub mystery' really is the best way to find where that method is defined.Selfinterest
You don't make it clear how you want to use this information. I would want it to be available in a programming IDE, but since you ask for "some sort of debugging module", does the standard debugger do what you want? You can simply use s to step into the method call and see where it takes you.Drugget
@jcast - no, not exactly. mystery can be an Moose attribute. Or use For this repo, it can either be a sub or method, depending on who wrote it. Or it can be defined as a handler method: has foo => (is => 'ro', handles => [ qw(bar mystery baz) ] Shiflett
@Drugget I'm sorry, I should have been more clear. When I said some sort of debugging module, I meant something I can download from CPAN or where have you, that has a magic sub that could maybe do this for me: my $place_to_look = Magic::Module->($mystery) Not necessarily what I'm looking for, but it would probably be most helpful.Shiflett
I was looking into something called ctags, which probably looks most promising, but I don't know yet.Shiflett
@jcast - Due to the nature of Moose, I'd need to potentially use four different regular expressions each time I wanted to find a mystery from somewhere. This is what I want to avoid.Shiflett
@2rare2die - Um, the point of a regular expression is that they are expressions: ack '(?:sub|has|method|before|after|around) foo'. I put the formula in a script to re-use it.Selfinterest
OK, that works. But again, I don't want to use regex. This is more an issue with multiple inheritance and tracing things and their dependencies to their respective origin classes. For example, I'll run the regex you provided, and find that mystery is defined as method in some class. I'll look at the method and notice $self->another_mystery($arg). So now I need to regex again to find another_mystery. This gets tedious and complicated after 3 or 4 iterations, especially if mystery is defined in more than one place. I just want a quicker way. Hence my wording "is there a better way?"Shiflett
If you're familiar with Java and working in the Eclipse IDE, then you may have noticed how easy it is to trace methods back to their origin classes, just by hovering your mouse over a piece of text in the source file. But just to be clear, I'm not necessarily looking for an IDE, just anything that potentially makes this easier.Shiflett
G
6

Thanks to Standard Perl . . . the comes_from Method!

You don’t need to download any special tool or module this, let alone some giant IDE because your undocumented class structure has gotten too complicated for mere humans ever to understand without a hulking IDE.

Why not? Simple: Standard Perl contains everything you need to get the answer you’re looking for. The easy way to find out where something comes from is to use the very useful comes_from method:

$origin        = $self->comes_from("mystery");
$secret_origin = $self->comes_from("another_mystery");
$birthplace    = Some::Class->comes_from("method_name");

That will return the original name of the subroutine which that method would resolve to. As you see, comes_from works as both an object method and a class method, just like can and isa.

Note that when I say the name of the subroutine it resolves to, I mean where that subroutine was originally created, back before any importing or inheritance. For example, this code:

use v5.10.1;
use Path::Router;
my($what, $method) = qw(Path::Router dump);
say "$what->$method is really ", $what->comes_from($method);

prints out:

Path::Router->dump is really Moose::Object::dump

Similar calls would also reveal things like:

Net::SMTP->mail     is really Net::SMTP::mail
Net::SMTP->status   is really Net::Cmd::status
Net::SMTP->error    is really IO::Handle::error

It works just fine on plain ole subroutines, too:

SQL::Translator::Parser::Storable->normalize_name 
 is really SQL::Translator::Utils::normalize_name

The lovely comes_from method isn’t quite built in though it requires nothing outside of Standard Perl. To make it accessible to you and all your classes and objects and more, just add this bit of code somewhere — anywhere you please really :)

sub UNIVERSAL::comes_from($$) {
    require B;
    my($invocant, $invoke) = @_;
    my $coderef  = $invocant->can($invoke) || return;
    my $cv       = B::svref_2object($coderef);
    return unless $cv->isa("B::CV");            
    my $gv       = $cv->GV;
    return if $gv->isa("B::SPECIAL");
    my $subname  = $gv->NAME;
    my $packname = $gv->STASH->NAME;
    return $packname . "::" . $subname;
}

By declaring that as a UNIVERSAL sub, now everybody who’s anybody gets to play with it, just like they do with can and isa. Enjoy!

Glanville answered 21/4, 2015 at 0:36 Comment(1)
Thank you, this is perfect. So simple, exactly what I was looking for.Shiflett
R
2

Are you sure you don't want an IDE? It seems to be what you are asking about. Padre, Eclipse EPIC, Emacs , and vim and many other editors offer some variation on the features you mention - probably simpler than you seem to want. If you have big project to navigate ctags can help - it's usually easy to integrate into an editor and you are allowed to hack on your configuration file (with regexes BTW) to get it to recognize bits of a complicated set of source files.

There is a related PERL FAQ entry about IDEs and a SO question: What's a good development environment for Perl?. There are also a host of CPAN modules you will want to use when developing that let you look into your code programmatically:

You can see an example of a script that looks for methods in classes in the SO node: Get all methods and/or properties in a given Perl class or module.

You might be able to get tools like these to help you hop around in your source in a way you find useful from a shell or from inside the debugger. Trepan has a good short summary of debugging tools as part of its documentation. Generally though you can be very productive combining Data::Dumper the B:: modules (B::Xref , B::Deparse, etc., etc.) with the debugger and ack.

Rosner answered 11/3, 2015 at 20:51 Comment(3)
Thank you for all of the resources you listed. This looks great, I'm going to try these out.Shiflett
@2rare2die you might want to check out MooseX::amine and mex as well. I will add them to my response.Rosner
Once a program’s class structure becomes too complicated for mere humans to understand without the aid of an IDE, it has passed the point of no return, and you might as well be using Java not Perl. :( For some reason, that sort of inscrutable spooky magic at a distance doesn’t bother Moose users. For the rest of us, thankfully Standard Perl suffices and we aren’t forced into Javaland against our consent.Glanville

© 2022 - 2024 — McMap. All rights reserved.