Monkey patching как инструмент партизанского программирования

14 Фев
2008

злой абизянВ некоторых языках программирования (Python, Ruby, JavaScript и
некоторые другие) есть возможность переопределять атрибуты и методы
классов во время исполнения программы. На этой возможности была
построена целая техника программирования, она называется Monkey
Patching
.

Откуда взялось такое название?

Изначально термин назывался guerilla patch (партизанский патч): разработчик незаметно добирался до чужого кода, изменял на лету поведение этого кода, не заботясь о соблюдении каких-либо правил, установленных создателем, а потом так же незаметно исчезал, оставив создателю порцию говна, в виде кода не работающего, как надо, без видимых на то причин. Поскольку, слова Guerilla и Gorilla звучат очень похоже — вскоре эту технику стали называть «горилла патч», что в конце концов трансформировалось в monkey patch. Именно этот термин прижился, поскольку лишен негативной окраски предшественников.

Практическое применение

Думаю, главное практическое применение техники обезьяних патчей — расширение функционала чужих продуктов (фреймворков, библиотек) без непосредственного вмешательства в их код, а также, т.н. security patches. Хотя, как инструмент террора, эта техника не менее хороша.

Пример из жизни

Существует замечательный, но не лишенный недостатков, фреймворк для веб-приложений — Django. В нем уже существует модель пользователя (User), используемая для авторизации, разграничения прав доступ, хранения самих пользователей... Естественно, эту модель нужно использовать. На практике оказывается, что у пользователя могут быть дополнительные атрибуты (не предусмотренные фреймворком), например, пол или телефон. Первое, что хочет сделать здравомыслящий разработчик, — написать новый класс пользователя, унаследовав его от джанговского. Увы, один из главных минусов django — его ORM не поддерживает наследование моделей, то есть модель то создастся... но она всего лишь продублирует все поля родителя в новую таблицу базы данных, создав тем самым избыточность.
В нашем случае можно сделать дополнительный класс UserProfile для дополнительных полей и связать его с классом User отношением один-к-одном,. но признайтесь, это, явно, не самое красивое решение, тем более, когда-нибудь в django доработают ORM. Патчить исходные коды django — вариант еще хуже, так как все нормальные разработчики пользуются его версией из репозитория (SVN), которая обновляется почти каждый день... выход один — monkey patching!
В любом файле (желательно в моделях) пишем слудующее:

from django.contrib.auth.models import User
GENDER_CHOICES = (
        ('M', 'Male'),
        ('F', 'Female'),
)
User.add_to_class('gender', models.CharField(max_length=1, choices=GENDER_CHOICES))
#Вцелом, этого уже достаточно, но для самой админки django добавим следующее:
User._meta.admin.fields += (
('Additional', {'fields': ('gender',)}),
)
User._meta.admin.list_display = User._meta.admin.list_display + ('gender', )

Все, отныне, никаких бесполых пользователей!Точно так же можно расширять функционал встроенного в django модуля FlatPages, представляющего из себя простейшую CMS-систему и многое-многое другое.

Меры предосторожности

Не стоит забывать, что monkey patching — это именно хак, а не нормальное поведение программы и применять его стоит только в тех случаях, когда иначе ситуацию решить нельзя. Будьте уверены, если такой патч, в будущем, вызовет ошибки в коде — наверняка, после многочасовых поисков причины, кто-нибудь захочет вас больно побить. Избежать этого довольно просто: всякий раз, когда применяете monkey patching, заботьтесь о том, чтобы этот код был покрыт автоматическими тестами.


  • Kirill Panshin

    Спасибо, было очень полезно. Пытался как-то наследовать от User () но не получилось, в итоге использовал related UserProfile (), что конечно дает дополнительные queries.

    Тесты — наше все:)

Наверх