将 Perl 移植到 Python
我首先要引用 Damian Conway在 Perl Best Practices 中的论述:“Perl 的‘面向对象’方法有点太 Perl 了:有太多实现方法......可能的实现、结果和语义组合太多,很难发现两个不相关的类层级使用完全相同的 Perl OO 样式。”
毫无疑问,Perl 语言设计中的这种内在灵活性导致了一些 Perl 代码的自然积累,这些代码在技术上可以运行,但不适应变化,且很难理解。更糟的是,原始开发人员可能找不到了,调到其他项目或别的公司了。除了遗留代码负担,您的生产要求可能发生了变化,或者,新的供应商 API 只提供 Python 版。此时,将 Perl 移植到 Python 这项“丰功伟业”就开始了。
要进行这个决策,需要选择最佳策略来解决问题。如果您足够幸运,拥有一个编写良好、面向对象的 Perl 代码基,那么可能只需将单元测试从 Perl 移植到 Python,然后编写适当的 Python 代码来通过新移植的 Python 单元测试。尽管有许多天才 Perl 程序员,他们编写可靠和可读的代码,但他们并不常见。最有可能的情况是,您将发现自己处于这样一种情况:您对 Perl 代码的确切工作方式知之甚少,只是注意到代码的用途。这就是将 Perl 移植到 Python 的困难之处。
不要这样做:从新 Python 代码调用 Perl 代码
在深入推荐的迁移方法之前,我们先看看不能做的事情。遇到困难时,人类的本性是选择阻碍最小的道路。将十年来自然增长、未经测试的 Perl 代码移植到 Python 是一个难题,因此最明显的解决方案是找到一种方法以重写所有 Perl 代码。这种思维模式将把您带到一个名为 perlmodule 的模块,它支持在 Python 中嵌入一个 Perl 解释器。这样,事情看起来就简单了,只需从新的 Python 调用旧的 Perl 代码,问题就会迎刃而解。
但这的确是个坏主意,因为您现在的问题甚至比刚开始时更大!您拥有不理解的遗留代码,并且,您拥有调用您不理解的代码的新代码。这就像用从一张信用卡获取的现金来偿还另一张信用卡的借款—您只是在延长不可避免的事情的发生时间并增加您的技术债务(参见参考资料中的链接了解关于技术债务的更多信息)。更糟的是,您将通过引入难以或不可能测试的细微 bugs,使您的新代码被“感染”。最后,加入这个项目的新开发人员将不得不处理这样一个代码基—一个令人恐惧的未经测试的 Perl 和测试不充分的 Python 的混合物。
使用 nose 创建一个新规范,对遗留代码进行功能测试
在 Working Effectively With Legacy Code 一书中,作者 Michael Feathers 指出:“几乎每个人都注意到一件事是,当他们尝试为现有代码编写测试时,他们会发现那些代码是多么不适合测试啊!当您首先想到将未经测试的遗留 Perl 移植到 Python 时,您有可能会注意到同样的事。
一个重要的心理和技术步骤可能是创建一个功能测试,该测试能够准确捕获要移植的 Perl 代码的最终结果。例如,您正在移植一个 Perl 脚本,该脚本解析一个大型日志文件并生成一个逗号分隔值报告,您可以编写一个简单的失败功能测试来检查这的确发生在您正在编写的新代码中。
要跟随下一个示例,您需要安装 nose。如果您已经安装 Python easy_install 工具,则只需发出命令:easy_install nose。否则,您可以按照 setuptools 安装说明先安装 setuptools。
一切就绪后,请看下面这个 nose 测试:
#!/usr/bin/env python
"""First pass at porting Perl to Python"""
import os
def test_script_exists():
"""This test intentionally fails"""
assert os.path.exists("myreport.csv")
如果您继续实际运行这个测试,测试结果应该如下所示:
linux% /usr/local/bin/nosetests
F
======================================================================
FAIL: test_failing_functional.test_script_exists
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/local/Python/2.5/site-packages/nose-0.10.4-py2.5.egg/nose/case.py",
line 182, in runTest
self.test(*self.arg)
File "/usr/home/ngift/tests/test_failing_functional.py", line 7, in test_script_exists
assert os.path.exists("myreport.csv")
AssertionError
----------------------------------------------------------------------
Ran 1 test in 0.037s
FAILED (failures=1)
这个失败测试显示,断言失败,原因是我们根本没有创建这个文件。尽管这一开始看起来似乎漫无目的,但这是将我们的遗留代码黑幕中的秘密尽可能地展现出来的过程的一个步骤。
一旦尽可能多地充实前面的代码的功能规范的功能测试编写出来之后,值得做的事是检查您是否可以识别任何模块化、可测试和编写良好的 Perl 片段,以便为其创建失败单元测试。可以为那些代码片段编写更多的失败测试,直到一个合理的规范开始成形。
最后一步,实际上也是最困难的一步,是编写通过您创建的那些测试的 Python 代码。遗憾的是,没有“银子弹”。移植未经测试的遗留 Perl 或其他语言代码的确非常困难,但编写失败测试可能会帮上大忙,是一个合理的策略。
结束语
最后,我将引用 Guido Van Rossum 在他的文章“Strong Versus Weak Typing”中的论断:“您不可能完全消除 bug。使代码便于阅读和编写,使其对那些将审查源代码的读者更透明,这也许有价值得多......”
总之,毫不夸张地说,创建可阅读和可测试的代码是将遗留代码移植到 Python 这样的新语言的一个主要目标。接受这种理念将有助于消除移植过程中的一些恐惧和痛苦。祝您好运!