使用Modulinos拯救遗留代码

随着企业的发展,它们会遇到未曾预料的情况,并经常遇到其他企业渴望拥有的问题。在快速增长的情况下,它们的代码库很难跟上。我所见到的糟糕代码赚钱的例子比我所见到的优秀代码赚钱的例子要多,这是一个令人兴奋的修复机会。Modulinos是我将独立程序转化为可测试和可管理的类似CPAN分布的妙招。

Modulinos不是我想出的主意,但我使它流行起来。我最初是从Mark Jason Dominus的演讲和他撰写的diagnostics模块中得到的灵感,该模块由Tom Christiansen于1995年编写。在这篇文章中,我将简要谈谈这个技巧,但更多地谈谈为什么以及如何使用它。

这个技巧涉及使用caller来决定Perl文件应该如何根据其加载方式执行。当从命令行运行时,它像程序一样执行,但当作为模块加载时,它不会执行任何操作,同时仍然使其子程序和包可用。在Mastering Perl的第二版中,我对其进行了一些扩展,以检查测试框架的存在,以便可以运行以test_开头的方法,这是我喜欢的Python功能。

您可以在Modulino::Test中看到基本结构,它是Modulino::Demo分布的一部分。

package Modulino::Test;
use utf8;
use strict;
use warnings;

use v5.10;

our $VERSION = '0.10_01';

sub _running_under_tester { !! $ENV{CPANTEST} }

sub _running_as_app { ! defined scalar caller(1) }

sub _loaded_as_module { defined scalar caller(1); }

my $method = do {
        if( _running_under_tester()   ) { 'test' }
    elsif( _loaded_as_module()       ) { undef  }
    elsif( _running_as_app()            ) { 'run'  }
    else                                { undef }
    };

__PACKAGE__->$method(@ARGV) if defined $method;

sub test { ... }
sub run  { ... }

我最初在Perlmonks的如何将脚本变为模块上关于modulinos的文章,我第一次使用了这个术语。我可能还在创建那个帖子时创造了它。我还为将脚本作为模块对《The Perl Journal》进行了扩展(现在被《Dr. Dobbs Journal》吞并)。

当时,我正在做大量工作,将遗留代码库翻译成更易于管理的样子。我不需要重写一切,而是创建了通往更好行为的路径,并立即看到了结果。这个路径的一部分是测试现有的代码库,这样我就可以在下一部分中重新创建它,包括错误和粗糙的边缘。将独立脚本移动到库或模块是这一部分的重要组成部分;我必须维护程序的行为,但我想对其内部进行单元测试。

组织杂乱无章且(以前)未管理的代码库很有趣。一点工作就能带来很大的改变,并获得快速的收益。从这里开始,添加测试就很容易了。这是scriptdist的动机之一,我在使用scriptdist自动化分发一文中对其进行了描述。给定一个独立程序,我使用该工具在其周围构建一个分发,并包含测试文件。程序文件保持不变,但一旦用分发的优点包装起来,我就可以开始转换。即使这个代码永远不会进入CPAN,我仍然可以通过使其看起来像一个CPAN分发来使用所有CPAN工具。

将脚本转换为Modulino

假设我从一个脚本开始。这是一个简短的例子

#!/usr/bin/perl

print "Hello World!\n";

即使这个简单的程序也有问题(我们永远不会在程序中找不到错误;在某些地方,这几乎是一种血腥运动!)!我无法改变输出位置,并且它被硬编码为使用英语。

我的第一步是使这个程序具有相同的行为但具有不同的结构。Larry设计了Perl来消除许多其他语言所需的main子程序,但我又把它带回来了

#!/usr/bin/perl

__PACKAGE__->run();

sub run {
    print "Hello World!\n";
    }

《__PACKAGE__》标记是一个编译器指令,它指向当前包。它调用 run 子例程,其操作方式与引入新的作用域相同。一些黑魔法和奇怪的习语可能会出问题,但就大部分而言,这应该(一个危险的词!)运行相同。在这个时候,我可能也在将这个遗留代码库引入源代码控制,因此一个没有新行为的小改动可以作为新分支的良好首次补丁。

这个程序现在主要是一个模块,它具有允许我测试的分布结构。我可以开始创建验收测试(端到端,或其他标签),因为我还没有办法进入代码本身。这些构成了我可以用来检查新代码与原始代码之间的回归测试的基础。

当我确信新代码工作正常时,我可以进行更多更改。这就是 modulino 概念出现的地方。我想在不自动执行 run 中的代码的情况下测试代码。我可以使用 caller 技巧;如果调用栈中有更高级别(程序位于顶部),则不执行代码。

#!/usr/bin/perl

__PACKAGE__->run() unless caller;

sub run {
    print "Hello World!\n";
    }

这是实际代码中的另一个小改动,但却是行为上的重大变化。我可以访问测试程序中的代码

use Test::More;

subtest 'load program' => sub {
    require_ok( 'scripts/program.pl' );
    };

subtest 'test innards' => sub {
    ok( defined &run, 'Run subroutine is defined' );
    };

done_testing();

从那里,前进的道路更加清晰。我可以在程序中添加一个包声明,并开始重构 run,使用我了解的测试最佳实践。很快,开发就会转变为模块维护,它作为一个独立程序的历史不再重要。在通过这个过程,我也为最终维护者设定了正确的道路。

封面图片版权所有 © Andréia Bohner,图片已进行数字修改。


本文最初发布在 PerlTricks.com

标签

brian d foy

brian d foy 是一名 Perl 训练师和作家,也是 Perl.com 的高级编辑。他是《精通 Perl》、《Mojolicious 网络客户端》、《Learning Perl 练习》的作者,以及《Programming Perl》、《Learning Perl》、《Intermediate Perl》和《Effective Perl Programming》的合著者。

浏览他们的文章

反馈

这篇文章有什么问题吗?请在 GitHub 上打开一个问题或拉取请求以帮助我们。