Я начну с цитаты из книги Дэмиана Конвея Передовые приемы работы с Perl: "Поддержка ООП в Perl реализована, пожалуй, чересчур в духе Perl: есть слишком много способов сделать это... Существует настолько много возможных комбинаций реализации, структуры и семантики, что в Perl сложно встретить две независимые иерархии классов, использующие в точности один и тот же стиль ООП".
Несомненно, заложенная в архитектуру языка Perl гибкость привела к естественному накоплению кода, который технически работает, однако очень хрупок при изменениях и труден для понимания. Проблема может усложняться тем, что авторы кода уже недоступны, так как перешли на работу в другой отдел или компанию. Помимо этого, требования к вашему продукту могли поменяться, либо оказалось, что новая версия используемого вами API доступна только на Python. В этот момент начинается эпохальная работа по переносу кода Perl на Python.
Если вы приняли такое решение, нужно выбрать оптимальную стратегию решения проблемы. Если вам повезло иметь в своем распоряжении хорошо написанный объектно-ориентированный код Perl, с полным покрытием кода тестами, то, вполне возможно, вам нужно будет просто перенести юнит-тесты с Perl на Python, а затем добиться того, чтобы соответствующий код Python проходил перенесенные на Python юнит-тесты. Хотя есть много талантливых Perl-программистов, которые пишут хорошо документированный и легко читаемый код, все же они встречаются не так часто, как хотелось бы. Скорее всего вы окажетесь в ситуации, когда вы видите, что ваш код Perl работает, однако не имеете понятия о том, как именно он работает. Именно здесь начинаются трудности переноса кода с Perl на Python.
Не следует вызывать код Perl из вашего нового кода Python
Прежде чем перейти к рекомендуемым подходам, давайте сначала посмотрим, чего не стоит делать. Человеку при столкновении с проблемой свойственно выбирать путь наименьшего сопротивления. Перенос долгое время органично развивавшегося и не покрытого тестам кода Perl на Python – это сложная задача, поэтому самым очевидным решением может показаться поиск способа избежать переписывания всего кода Perl. Такой ход мысли может в итоге привести вас к модулю perlmodule, который позволяет встроить интерпретатор Perl в Python, чтобы ограничиться вызовом старого кода Perl из нового кода Python.
Это плохая идея, так как в итоге вы будете иметь еще большую проблему, чем та, с которой вы начинали. У вас останется унаследованный код, который вы не понимаете, а также будет новый код, вызывающий код, который вы не понимаете. Это похоже на выплату платежа по одному кредиту за счет денег, полученных по другому кредиту – вы лишь отсрочиваете неизбежное и увеличиваете свой технический долг (см. подробную информацию о техническом долге по ссылке в разделе Ресурсы). Усугубляет ситуацию еще и то, что вы «инфицируете» свой новый код, включая в него трудноуловимые ошибки, которые сложно или невозможно покрыть тестами. В конечном счете новым разработчикам, которые потом придут в проект, придется работать с кодом, представляющим собой пугающую смесь непротестированного кода Perl и неадекватно протестированного кода Python.
Функциональное тестирование унаследованного кода с помощью nose для создания новой спецификации
В книге Working Effectively With Legacy Code автор Майкл Физерс пишет: "одна из вещей, которые замечают практически все, пытающиеся написать тесты для существующего кода, - это то, насколько плохо код подходит для тестирования". Возможно, вы тоже обратите на это внимание, впервые задумавшись о переносе унаследованного не покрытого тестами кода Perl на Python.
Важным психологическим и техническим шагом для переноса кода может являться создание функциональных тестов, проверяющих конечные результаты работы кода Perl. Например, допустим, вы переносите сценарий Perl, который анализирует большой файл журнала и генерирует по нему отчет в формате csv. Тогда вы можете написать простой функциональный тест, проверяющий, что ваш новый код тоже это делает.
Для выполнения следующего примера вам нужно будет установить пакет nose. Если у вас уже установлен инструмент easy_install для быстрой установки пакетов Python, можно просто выполнить команду easy_install nose. Если нет, вы можете сначала установить setuptools, следуя инструкциям по установке setuptools.
Вот пример простого теста nose:
Листинг 1. Заведомо неудачный тест nose
#!/usr/bin/env python
"""Первый этап переноса Perl на Python"""
import os
def test_script_exists():
"""Этот тест заведомо завершится неудачно"""
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)
|
Как видно из кода теста, тест проходит неудачно, так как мы еще ничего не делали для того, чтобы создать проверяемый файл. Хотя сначала это может показаться бессмысленным, но этот процесс является шагом вперед в фиксировании как можно большего объема функциональности унаследованного кода.
Когда вы напишете функциональные тесты, покрывающие как можно больше пунктов функциональной спецификации изначального кода, стоит просмотреть код еще раз и постараться выделить модульные, хорошо написанные фрагменты кода Perl, для которых можно создать выполняющиеся неудачно юнит-тесты. По мере написания таких тестов начнет формироваться внятная спецификация кода.
Последним и обычно самым сложным шагом является написание кода Python, проходящего созданные вами тесты. К сожалению, для данной задачи не существует серебряной пули. Перенос унаследованного не покрытого тестами кода, написанного на Perl или на любом другом языке, – это сложная задача. Написание неудачно выполняющихся тестов – это разумная стратегия, которая может оказаться очень полезной при решении этой задачи.
Я закончу цитатой из статьи Гвидо ван Россума "Strong Versus Weak Typing": "Вы никогда не выловите все ошибки. Сделать код легче для чтения и написания и понятнее для людей, которые будут его просматривать, может оказаться гораздо полезней...."
В конце концов создание читаемого, доступного для тестирования кода является одной из главных задач при переносе унаследованного кода на новый язык, такой как Python. Если вы поставите себе такую цель, вам будет гораздо легче выполнить задачу. Удачи!
Научиться
- Оригинал статьи (EN).
- Прочитайте обучающее руководство по nose An Extended
Introduction to the nose Unit Testing Framework.(EN)
- На сайте Nose
можно познакомиться с последними новостями проекта. (EN)
- В статье "Strong Versus Weak
Typing" можно познакомиться с мыслями Гвидо ван Россума о типизации. (EN)
- В CPAN можно найти подробную информацию о модуле perlmodule.(EN)
- В статье Википедии Technical debt рассказывается о термине, обозначающем стоимость непродуманной архитектуры и неудачных решений разработчиков.(EN)
- Познакомьтесь с книгой Working Effectively with Legacy Code.(EN)
- На сайте проекта Pythoscope можно более подробно познакомиться с этим генератором юнит-тестов для Python.(EN)
- Прочитайте книгу Дэмиана Конвея
Perl Best
Practices.(EN)
-
Следите за новостями в
разделе технических мероприятий и Web-трансляций developerWorks. (EN)
-
Посетите бесплатные брифинги developerWorks Live!, чтобы быстро познакомиться с продуктами и инструментами IBM, а также с современными тенденциями ИТ-отрасли. (EN)
-
Смотрите доступные по требованию демонстрации developerWorks,
посвященные разнообразным темам, - от установки и начала работы с продуктами IBM до продвинутой функциональности для опытных разработчиков. (EN)
-
Следите за developerWorks в Твиттереили подпишитесь на ленту Linux-твитов на developerWorks.(EN)
Получить продукты и технологии
-
Знакомьтесь с продуктами IBM
наиболее подходящим для вас способом: загрузите ознакомительную версию продукта, поработайте с продуктом в онлайновом режиме, используйте продукт в облачной среде или проведите несколько часов в
SOA Sandbox,
чтобы узнать об эффективном создании сервис-ориентированных архитектур.(EN)

Ной Гифт (Noah Gift) – соавтор книги "Python For Unix and Linux" издательства O'Reilly. Он также является автором, докладчиком, консультантом и лидером сообщества, пишущим публикации для IBM developerWorks, Red Hat Magazine, O'Reilly и MacTech. Адрес сайта его консалтинговой компании - www.giftcs.com, адрес его персонального сайта - www.noahgift.com. В настоящий момент Ной также поддерживает сайт www.pyatl.org – место общения пользователей, работающих с Python, из Атланты, Джорджия. Он окончил университет штата Калифорния, получив степень магистра в области информационных компьютерных систем, а также окончил политехнический университет штата Калифорния, расположенный в Сан-Луис-Обиспо, получив степень бакалавра в области пищевых наук. Он также является сертифицированным системным администратором Apple и LPI, имеет опыт работы в таких компаниях, как Caltech, Disney Feature Animation, Sony Imageworks и Turner Studios. Все свое свободное время он проводит со своей женой Лией (Leah) и их сыном Лиамом (Liam), играя на пианино и духовно совершенствуясь.