Perl を Python にポーティングする

テストされていないレガシーの Perl を Python にマイグレートするための手法

レガシーの Perl を Python へポーティングする作業は、尻込みしたくなるような作業です。この記事では、レガシー・コードを扱う際のセオリーを、やってはならないことを含め、いくつか紹介します。

Noah Gift , Senior Technical Director, GiftCS, LLC

Photo of Noah Gift

Noah Gift は O'Reilly から出版されている『Python For Unix and Linux System Administration』の共著者であり、また Manning から出版予定の『Google App Engine In Action』も執筆中です。彼は著作者、講演者、コンサルタント、そしてコミュニティーのリーダーとして、Red Hat MagazineO'ReillyMacTech などに記事を寄稿しています。彼のコンサルティング会社の Web サイトは http://www.giftcs.com であり、彼が寄稿した記事の大部分は http://noahgift.com で公開されています。Twitter で Noah をフォローすることもできます。

彼はカリフォルニア州立大学ロサンゼルス校でコンピューター情報システムの修士号を、カリフォルニア州立工科大学 San Luis Obispo 校で栄養科学の学位を取得しています。また、Apple と LPI 認定のシステム管理者であり、Caltech、Disney Feature Animation、Sony Imageworks、Turner Studios、Weta Digital などの企業に勤務経験があります。仕事以外の時間には彼の妻である Leah と息子の Liam と時間を過ごし、ピアノ曲を作曲し、マラソンをし、そして神に祈りを捧げています。



2010年 9月 01日

まず、Damian Conway が『Perl ベストプラクティス』の中で述べている言葉を引用することから始めます。彼によれば、「オブジェクト指向に対する Perl の手法は、過度に Perl 的になりすぎています。つまり、あまりにも方法が多すぎるのです。Perl の場合、実装、構造、セマンティクスには非常に多くの組み合わせがあり得るため、クラスの階層構造で使われているオブジェクト指向のスタイルがまったく同じであるクラスを見つけられることは極めて稀です。」

このように、Perl 言語での設計には柔軟性が付いてまわるため、表向きは正常に実行されているコードでも、変更するのが困難で、理解しにくい Perl コードが山のようにあるのは確かです。さらに問題を深刻にしているのは、当初開発を行った人たちが、他のプロジェクトや会社に移ってしまっていてつかまらない可能性があることです。レガシー・コードという重荷に加え、本番コードに対する要件が変更されている場合や、新しいベンダーの API が Python でしか利用できない場合があります。この時点で、Perl から Python へのポーティングという、途方もない作業が始まります。

ポーティングするという結論に達すると、この問題を解決するための最も適切な戦略を選択する必要があります。オブジェクト指向で適切に作成された Perl のコード・ベースがあり、テスト・カバレッジも完全という幸運な場合には、単純に単体テストを Perl から Python にポーティングし、新たにポーティングされた Python 単体テストをパスできる適切な Python コードを作成するだけでよいかもしれません。読みやすいコードを適切なドキュメントと共に作成する優秀な Perl プログラマーは数多くいますが、そうした人達がどこにでも普通にいるわけではありません。ほとんどの場合、皆さんは Perl コードによって実行される結果を観察する以外には何もできず、その Perl コードがどのように動作するのかを理解できない状況に置かれます。Perl から Python へのポーティングの困難な部分は、まさにここから始まるのです。

やってはならないこと: 新しい Python コードから Perl コードを呼び出す

Python のテストを自動生成する

Python プログラムを過信してはなりません。確かに、Python が非常に読みやすく設計されている言語だということに同意する人は多くいますが、テストされていないレガシーの Python コードには問題がある可能性があることに変わりはありません。結局のところ、Python は自らをテストしているわけではないからです。

テストされていないレガシーの Python を扱うために考えられる 1 つの方法は、Pythoscope というテスト生成ツールを使う方法です。Pythoscope のミッション・ステートメントには、「Python で作成されたレガシー・システムのための単体テストを自動的、または半自動的に生成する、容易にカスタマイズ可能で拡張可能なオープンソースのツールを作成すること」と書かれています。さらに Pythoscope ではエントリー・ポイントを定義すると、そのエントリー・ポイントを通じて自動的に関数を実行することで関数のテストをすることができます。この動作の詳細を示す例については「参考文献」を参照してください。

推奨の方法を詳しく説明する前に、やってはならないことを最初に説明しましょう。困難に直面した場合、最も苦労の少ない方法を選ぼうとするのは人情というものです。10 年も前に作成された、テストされていない Perl コードを Python にポーティングして適切に動作させるのは非常に難しい作業であるため、こうした Perl コードの全面的な作り直しを避ける手段を見つけることが、一番手っ取り早い方法のように思えるかもしれません。そのように考えると、次に思いつくのは Perl インタープリターを Python に組み込むための perlmodule というモジュールを使うことかもしれません。そして新しい Python コードから古い Perl コードを単純に呼び出せばすべて解決、と思えるかもしれません。

これは非常に不適切な考え方です。なぜなら、これでは作業を開始したときよりも大きな問題を抱えることになるからです。理解できないレガシー・コードがある上、理解できないコードを呼び出す新しいコードがあるのです。これはちょうど、1 枚のクレジットカードによる支払いを、別のクレジットカードのキャッシング・サービスを使って支払うようなものです。つまり、避けられないものを遅らせ、技術的な負債を増やしているにすぎません (技術的な負債については「参考文献」を参照)。さらに悪いことに、テストが困難な、あるいはテストが不可能な小さなバグを取り込んでしまうことで、新しいコードに問題が「伝染」してしまいます。最後に、後からプロジェクトに参加する新しい開発者は、テストされていない Perl と、不適切なテストしかされていない Python とが混在する、恐ろしいコード・ベースを扱う羽目になります。


nose を使ってレガシー・コードの機能テストを行い、新しい仕様を作成する

『レガシーコード改善ガイド』の中で、著者の Michael Feathers は次のように述べています。「既存のコードに対してテストを作成しようとする場合、ほとんど誰もが気付くことの 1 つは、そのコードがいかにテストに不向きのコードであるか、ということです。」おそらく皆さんも、テストされていないレガシーの Perl を Python にポーティングしようとする場合、同じことに気付くはずです。

心理的にも技術的に重要なステップとなり得るのは、ポーティングしようとしている Perl コードの最終的な実行結果を正確に捉える機能テストを作成することです。例えば、大規模なログ・ファイルを解析して CSV 形式のレポートを生成する Perl スクリプトをポーティングする場合、単純な、失敗する機能テストを作成し、作成中の新しいコードでも実際に解析やレポート作成が行われるかどうかをチェックする方法があります。

次に示す例を試すためには、nose をインストールする必要があります。Python の easy_install ツールを既にインストールしてある場合には、単純にコマンドとして easy_install nose を発行します。まだ easy_install をインストールしていない場合には、まず setuptools のインストール方法に従って setuptools をインストールします。

それを終えたら、以下のように nose テストを作成します。

リスト 1. 故意に失敗する、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")

このテストを実際に実行してみると、以下のような結果が得られるはずです。

リスト 2. テストの結果
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)

この失敗するテストからわかるように、アサーションに失敗していますが、それはこのファイルを作成するに当たって特に何もしていないからです。このテストは最初、的外れに思えるかもしれませんが、実際にはレガシー・コードのブラック・ボックスの中にあるものをできるだけ多く明らかにするプロセスの 1 つのステップなのです。

以前のコードの機能仕様を可能な限り満たす機能テストを作成したら、モジュールとしてテスト可能で、適切に作成された Perl コードのうち、失敗する単体テストを作成する対象となり得るコードがないかどうか、探してみる価値があります。仕様として妥当な形を取り始めるようになるまで、以前のコードに対し、失敗するテストをさらに作成していきます。

最後のステップとして、実はこのステップが最も困難なのですが、その段階までに作成したテストをパスする Python コードを作成します。残念ながら、手軽で万能な方法はありません。Perl であれ、他のどのような言語であれ、テストされていないレガシー・コードのポーティングは、明らかに大変な作業です。しかし、失敗するテストを作成する方法は非常に役立ち、また戦略として有効です。


まとめ

最後に、『Strong Versus Weak Typing』の中に書かれた、著者の Guido Van Rossum の言葉を引用しましょう。「バグをすべてなくすことはできません。コードを読みやすく、追加のコードを作成するのも簡単なようにし、ソース・コードをレビューするチームのメンバーにも明確にわかるようなコードを作成することは、非常に価値のあることです。」

突き詰めれば、理解しやすく、テスト可能なコードを作成することは、(控え目ながら) レガシー・コードを Python などの新しい言語にポーティングする上での主要な目標の 1 つです。こうした理想を抱くことで、ポーティング・プロセスにかかわる恐れや苦しみを多少なりとも取り除ける可能性があります。皆さんの幸運を祈っています。

参考文献

学ぶために

製品や技術を入手するために

  • 皆さんの目的に最適な方法で IBM 製品を評価してください。製品の試用版をダウンロードする方法、オンラインで製品を試す方法、クラウド環境で製品を使う方法、あるいは SOA Sandbox で数時間を費やし、サービス指向アーキテクチャーの効率的な実装方法を学ぶ方法などがあります。

議論するために

  • My developerWorks コミュニティーに加わってください。開発者向けのブログ、フォーラム、グループ、ウィキなど利用しながら、他の developerWorks ユーザーとやり取りしてください。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux, Open source
ArticleID=548933
ArticleTitle=Perl を Python にポーティングする
publish-date=09012010