针对 Python,有一个名为 importchecker 的脚本,它可以告诉您是否存在不必要的 import
语句。
那么,Perl 中是否有类似的工具用来检查 use
和 require
语句?
针对 Python,有一个名为 importchecker 的脚本,它可以告诉您是否存在不必要的 import
语句。
那么,Perl 中是否有类似的工具用来检查 use
和 require
语句?
可以看一下Devel::TraceUse,它可能可以满足你的需求。
有多种方法可以加载包并导入符号(或不导入)。我不知道有一个工具可以单独直接检查这些符号是否被使用。
但是对于明确给出导入列表的情况,
use Module qw(func1 func2 ...);
有一个Perl::Critic策略 TooMuchCode::ProhibitUnusedImport 可以帮助解决很多相关问题。
其中一个可以在命令行中运行。
perlcritic --single-policy TooMuchCode::ProhibitUnusedImport program.pl
程序会被检查。或者使用不带--single-policy
标志的方式运行完成检查,并在输出中寻找严重程度1
违规,这个就是。
以一个程序为例:
use warnings;
use strict;
use feature 'say';
use Path::Tiny; # a class; but it imports 'path'
use Data::Dumper; # imports 'Dumper'
use Data::Dump qw(dd pp); # imports 'dd' and 'pp'
use Cwd qw(cwd); # imports only 'cwd'
use Carp qw(carp verbose); # imports 'carp'; 'verbose' isn't a symbol
use Term::ANSIColor qw(:constants); # imports a lot of symbols
sub a_func {
say "\tSome data: ", pp [ 2..5 ];
carp "\tA warning";
}
say "Current working directory: ", cwd;
a_func();
运行上述perlcritic
命令会输出:
Unused import: dd at line 7, column 5. A token is imported but not used in the same code. (Severity: 1) Unused import: verbose at line 9, column 5. A token is imported but not used in the same code. (Severity: 1)
我们发现dd
被捕捉到了,而来自同一包的pp
没有被标记,因为它被使用了(在子例程中),carp
和cwd
也没有被报告,因为它们也被使用了。这是该策略的目标。
但请注意:
使用:constants
标签附带的内容未被找到
单词verbose
不是函数(并且是隐式使用的),被报告为未使用
如果a_func()
未被调用,则其中的pp
和carp
仍然未被报告,即使它们未被使用。这可能是可以接受的,因为它们在代码中存在,但值得注意
(此错误列表可能不全面。)
请记住,导入列表被传递给一个import子例程,它可能希望并使用模块的设计认为重要的任何内容;这些不一定只是函数名。显式导入只有函数名称的模块是良好实践,而此策略覆盖的是重要的用例。
此外,根据明确规定的策略使用,Dumper
(由Data::Dumper
导入)和来自Path::Tiny
的path
都未找到。该策略处理了一些奇怪的Moose
技巧。
如何做更多?一个有用的工具是Devel::Symdump,它收集符号表。它捕捉到上面程序中导入的所有符号(当然,如果使用了,则不能看到Path::Tiny
方法)。包括不存在的“符号”verbose
。添加:
use Devel::Symdump;
my $syms = Devel::Symdump->new;
say for $syms->functions;
针对上面的例子,为了处理运行时require
的库,我们需要在代码中它们已被加载之后的某个位置进行操作,这个位置可以在程序的任何地方。然后最好放在一个END
块中,例如:
END {
my $ds = Devel::Symdump->new;
say for $ds->functions;
};
接下来我们需要检查哪些是未被使用的。目前我所知道最好的工具是PPI,可以参考完整示例。另一种选择是使用像Devel::NYTProf这样的分析器。
另一个选项需要进行一些准备†,那就是编译器的后端工具B::Xref,它几乎包含了程序中使用到的一切信息。使用方式如下:
perl -MO=Xref,-oreport_Xref.txt find_unused.pl
输出的结果保存在文件report_Xref.txt
中。
输出有每个涉及文件的部分,其中包括子例程和其包的子部分。输出的最后一部分对于当前目的直接有用。
对于上面使用的示例程序,我得到的输出文件如下:
文件 /.../perl5/lib/perl5//Data/Dump.pm ... (大约3,000行) ... 文件 find_unused.pl --> 这是我们要找的程序的文件 子例程(定义) ... 几十行 ... 子例程(main) 包 main &a_func &43 &cwd &27 子例程 a_func 包 ? @?? 14 包 main &carp &15 &pp &14
因此,我们可以看到cwd
在子例程a_func
中被调用(在第27行),并且carp
和pp
也在该子例程中被调用。因此,在导入的符号中,dd
和path
没有被使用(使用其他方式找到的所有导入符号,例如Devel::Symdump
)。这很容易解析。
但是,虽然在使用path
时会报告其使用情况,但如果使用new
(也是Path::Tiny
中的传统构造函数)则不会在这个最后一部分中报告,其他方法也不会报告。
因此,原则上可以使用这种方法找到Devel::Symdump
报告存在的哪些符号(用于函数)已被程序使用。
*这里的示例简单易懂,易于处理,但我不知道在考虑到各种奇怪的导入子程序使用方式时,这是否完整或难以解析。
这里有一个我写的脚本,可以尝试解决此问题。它非常简单,不能为您自动化任何操作,但可以作为入门参考。
#!/usr/bin/perl
use strict;
use v5.14;
use PPI::Document;
use PPI::Dumper;
use PPI::Find;
use Data::Dumper;
my %import;
my $doc = PPI::Document->new($ARGV[0]);
my $use = $doc->find( sub { $_[1]->isa('PPI::Statement::Include') } );
foreach my $u (@$use) {
my $node = $u->find_first('PPI::Token::QuoteLike::Words');
next unless $node;
$import{$u->module} //= [];
push $import{$u->module}, $node->literal;
}
my $words = $doc->find( sub { $_[1]->isa('PPI::Token::Word') } );
my @words = map { $_->content } @$words;
my %words;
@words{ @words } = 1;
foreach my $u (keys %import) {
say $u;
foreach my $w (@{$import{$u}}) {
if (exists $words{$w}) {
say "\t- Found $w";
}
else {
say "\t- Can't find $w";
}
}
}
use Module;
可能会产生虚阴性结果。use strict qw( refs );
可能会产生错误阳性结果。use Module qw( :ALL );
可能会同时产生虚阴性和错误阳性结果。 - ikegamiuse Mod;
可能会导入符号 - 检查所有这些吗? (2)还是只针对在use Mod qw(...);
中列出的那些? (3)use Module qw();
怎么样(代码中没有使用Mod
子例程)? 这是关于可以完全删除的use
语句,还是与特定(未使用的)符号相关的特定use
语句?(我只知道(2)的perlcritic策略。) - zdimB::Xref
的注释。 - zdim