將 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 測試:
"""First pass at porting Perl to Python"""
import os
def test_script_exists():
"""This test intentionally fails"""
assert os.path.exists("myreport.csv")
如果您繼續實際運行這個測試,測試結果應該如下所示:
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 這樣的新語言的一個主要目標。接受這種理念將有助于消除移植過程中的一些恐懼和痛苦。祝您好運!