【问题标题】:Refactoring parameter list of perl subroutines重构 perl 子程序的参数列表
【发布时间】:2013-07-26 08:26:59
【问题描述】:

我目前正在重构一些应用程序代码,我希望能够从子例程中删除一些参数。例如,假设我有以下[1]:

sub do_something {
    my ( $param1, $param2, $param3, $param4 ) = ( @_ );
    ....
}

但是,作为重构的一部分,我使参数 2 和 3 变得多余。更新这个方法签名很容易,但是有没有一种直接的方法来更新对它的所有调用?

我一直在做一些定制的 grep/sed/perl 来做这件事,但是对 sub 的一些调用是多行的,这让我很痛苦,每次我在一个项目上这样做这是定制的。是否有适合进行这种特定重构的工具?

[1] - 不是实际参数或子程序名称,我向你保证!

【问题讨论】:

  • 我不知道有任何快捷方式,但您的问题是找到可靠的方法调用,还是编辑它们以符合新界面?
  • 找到它们很容易。所有实例都在一个包中,可以使用 grep 找到。这是编辑的问题。我可以编写一些脚本来分解调用参数列表并将其重新组合在一起,但这很可能是定制的。也许是时候为自己编写我自己的重构模块了。
  • 您说调用有时分布在多行上,因此我假设生成这些参数需要进行非平凡的计算。所以我希望在许多调用站点上手动清理是必要的,因为仅仅删除参数可能只会删除一些提到的计算。例如,在调用之前可能会有一些行也将变得多余。
  • 通话前肯定会有多余的线路。我的计划是删除参数,然后使用 git diff 的奇迹来检查哪些变量现在是多余的,并删除它们。

标签: perl refactoring


【解决方案1】:

Padre有一些重构的功能,但是不知道能不能达到你想要的效果。

更改您的界面以接受散列而不是位置列表,这将使未来的更改比现在更少工作。

sub do_something {
    my (%param) = (@_);
    ...
}

do_something(foo => 23, bar => 42);

【讨论】:

  • 对参数使用散列似乎有些错误(不知道为什么,可能只是因为我目前不这样做!),但我认为一旦子例程有更多,这可能是要走的路比几个参数。我也会给 Padre 一个好的。
  • 你不应该有这种感觉。这是best practice, see chapter 9.4
  • 啊哈!另一本书要添加到我的购买清单中。谢谢!
  • 我要补充一点,以这种方式使用哈希并不能免除您对参数的详细处理,包括清理方法中不必要的输入。根据经验,我可以告诉你 Perl 代码堆栈在很少或没有验证的情况下向上和向下传递哈希引用(例如,%param)的抽象层可能会花费大量时间来维护和调试。但是,如果您很好地命名键,daxim 的答案将帮助您找到传递冗余参数的位置。
【解决方案2】:

如果您有一个代码覆盖率接近 100% 的测试套件,您可以使用它来查找所有调用站点。

给定调用堆栈中位置的参数,caller 内置函数返回

  1. 调用者的包名
  2. 文件名
  3. 行号
  4. 完全限定的子名称
  5. ……还有更多

我们现在可以添加一些记录调用位置的代码。我们可以将结果放入某种数据结构中进行自动处理,也可以将报告写入日志文件。例如

sub this_logs {
  {
    # seperate scope to not pollute your sub
    state $log_fh //= do {
      open my $fh, ">", "record_callsites.log"; # assuming autodie;
      $fh;
    };
    state $seen = {};
    my (undef,    undef, undef, $sub) = caller(1);
    my ($package, $file, $line,     ) = caller(0);
    my $site = $sub ? "$sub()" : "pkg $package";
    unless ($seen->{$file}{$line}++) {
      say {$log_fh} "CALL from $site at $file line $line";
    }
  }
  my ($param1, $param2) = @_;
  # etc
}

假设你所有的代码都是

this_logs(1, 2, 3);         # direct call
foo();                      # call from same package
my $sub = "this_" . "logs"; 
baz($sub);                  # call by name
Foo::bar();                 # call from different package
foo();                      # duplicate call

sub foo {
  return this_logs(5, 6, 7);
}
sub baz {
  shift()->(1, 2, 3);  # no strict refs for this, please
};

package Foo;
sub bar {
  main::this_logs();
}

这将产生日志文件

CALL from pkg main at - line 20
CALL from main::foo() at - line 28
CALL from main::baz() at - line 31
CALL from Foo::bar() at - line 3

(文件名-表示标准输入)

因此,给定一个合适的测试套件,它能够找到无法被 grep 的调用站点。

如果您有一个非白痴编辑器,您还可以发出一个脚本,依次打开每个文件并将光标定位在正确的行上:

say "kate -l $line $file";
say "vim +$line $file";

【讨论】:

  • 有趣的是,这是一个测试套件内部的函数,所以我非常希望它有 100% 的覆盖率!这看起来非常有用。我会尽快对其进行测试。
猜你喜欢
  • 2013-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-09
  • 1970-01-01
  • 1970-01-01
  • 2011-03-26
  • 1970-01-01
相关资源
最近更新 更多