Так как периодически сталкиваюсь с этой темой решил высказать свое мнение и заодно поспорить на тему “Насколько легко программисту выучить новый язык и писать на нем”.

Я неоднократно слышал утверждения о том, что хорошему программисту выучить новый язык и писать на нем качественный софт/код не составит труда. Со своей стороны, я уже второй раз поменял основной язык, на котором пишу/зарабатываю на хлеб с маслом. Сначала был C#/.NET, как правило, это был веб, ASP.NET/ASP.NET MVC. Часто приходилось писать на JavaScript, потом практически полностью ушел от серверной части и писал Front-end на JS. Переход на JavaScript был для меня безболезненным, даже не смотря на поддержку IE6 :). Тем временем познакомился с Python и через какое-то время перешел полностью на него. Исходя из такого, пусть и небольшого, программерского опыта утверждаю что переход на другой язык программирования - это почти всегда обучение с нуля, переход, грубо говоря, от Senior (которым я себя никогда не считал) к Junior.

Почему так? Почему нельзя “научиться писать на другом языке за две недели/месяц/два месяца”? Потому что не все так просто. Даже если взять похожие, на первый взгляд Java и C# то трудностей будет много. Да, синтаксис во много похожий, но знаний только его не достаточно для разработки ПО. Ведь если кто-то выучит 100-200-1000 слов другого языка нельзя же сказать, что человек теперь знает другой язык и может свободно писать/читать/говорить на нем? Почему тогда отношения к программистам и языкам программирования другое? Кроме синтаксиса, есть еще понимание платформы, фреймворков, технологического стека и т.д. Да, синтаксис похож, да, веб-фреймворки, реализующие, например, MVC паттерн чем-то похожи по своей структуре и принципам работы, но все-таки отличия есть. 

Смотря на свой код, который писал после прочтения книги что-то вроде “Django для чайников” мне хочется плакать, выкинуть его и никогда не вспоминать. А ведь код-то рабочий. И делает то, что от него нужно. Но... но стиль мышления у меня тогда был как у asp.net разработчика и многие вещи из python/django мне были не понятны. Тогда я знал синтаксис, но не знал python. IMHO, тут есть очень важный психологический барьер, который не всегда бывает просто преодолеть. Когда нужно признаться себе что ты снова ничего не знаешь и нужно учиться заново, проходить путь от “hello world” до каких-то сложных приложений. И только когда снова появятся знания и понимания того языка/платформы/фреймворка, на котором пишете, тогда можно будет сказать что вы что-то знаете, а не лезете каждый раз в документацию или ищете похожие примеры.

Разработку программного обеспечения часто сравнивают со строительством домов. Тут же более удачный, на мой взгляд пример, это категории водителей. Если у вас есть категория В и вы можете водить легковую машину, то насколько легко будет сеть за руль трактора или мотоцикла? А если пустить такого водителя к штурвалу самолета? Никто не пустит... так почему с программистами все иначе? Не знаю, вряд ли на этот риторический вопрос есть достойный ответ...

 


 

Мне всегда казалось, что для небольших проектов использование buildout - это как с пушки по воробьям: долго, сложно и неудобно. Хотелось чего-то более простого и понятного. И таким оказалась связка virtualenv + pip.

Про virtualenv я уже писал тут. pip - это замена easy_install, менеджер пакетов для python, который позволяет быстро и удобно устанавливать, обновлять и удалять пакеты. Более подробно можно почитать на официальном сайте. Сейчас же меня интересует возможность быстрого развертывания необходимого окружения для запуска проекта. В двух словах, задача выглядит так:

Нужно запустить проект, который для работы требует следующие пакеты:

 

  • Django;
  • SQL Alchemy;
  • pep8 & pylint для проверки кода.

 

Стандартный алгоритм решения - создать новое окружение с помощью virtualenv и установить нужные пакеты. Но ведь не делать же это каждый раз каждому разработчику руками? А на build-сервере? Пришедшая мне первая мысль (написание нужного bash-скрипта) к счастью, оказалась не совсем правильной. Т.е. для полной автоматизации небольшой bash-скрипт будет не лишним, но вот устанавливать пакеты проще и правильнее через pip. Итак, как же нам установить все необходимые пакеты?

 

Шаг 1. Создаем virtual environment

$ virtualenv ./pip-test/ --no-site-packages

Ключ --no-site-packages указывает на то, что в нашем окружении не будет доступа к пакетам, установленных в операционной системе. Такая себе чистая, ничем не испачканная песочница.

 

Шаг 2. Устанавливаем нужные пакеты

Первый раз все-таки будет необходимо установить все пакеты вручную, т.к. список нужных пакетов на данном этапе бывает далеко не всегда.

$ pip install Django
$ pip install SQLAlchemy
$ pip install pep8
$ pip install pylint

 

Шаг 3. Автоматизируем установку пакетов

После установки нужных пакетов можно выполнить команду pip freeze -l, которая выведет на экран список установленных пакетов с их версиями. В моем случае, это выглядит так:

$ pip freeze -l
Django==1.3.1
SQLAlchemy==0.7.4
logilab-astng==0.23.1
logilab-common==0.57.1
pep8==0.6.1
pylint==0.25.1

 

Ключ -l выводит пакеты установленный только внутри virtualenv, что при создании окружения с ключем --no-site-packages теряет всякий смысл.

Далее этот список нужно сохранить:

$ pip freeze -l > pip-requirements

Тепреь в файле pip-requirements лежит список всех необходимых для запуска пакетов. Этот нужно положить в вашу source control и при необходимости обновлять.

Чтобы установить все необходимые пакеты, необходимо выполнить команду:

$ pip install -r pip-requirements

 

Несколько рускоязычных статей про buildout можно найту тут: http://www.vurt.ru/tag/buildout

 


Все сказанное ниже является личным мнением автора и не является объективной точкой зрения и/или истиной последней инстанции.

Я всегда считал и продолжаю это делать, что Django - не очень-то и модульный фреймворк. Он расширяемый, но не модульный, IMHO. В моем понимании модульный фреймворк, это фреймворк, который состоит из ядра (core), и каких-то модулей, которые можно к нему подключать при желании/необходимости. Но и без них будет доступна минимальная функциональность. В случае же Django - выкинуть из него некоторые модули (e.g. models, template engine) достаточно сложно. Что-бы сделать что-то подобное, необходимо самостоятельно удалять ненужные куски кода, что тянет за собой проверку работоспособности оставшихся частей и прочие неприятности в виде сложной поддержки полученного кода и обновление фреймворка.

Django - легко расширяемый фреймворк. Это видно по django.contrib и многочисленных сторонних дополнений и приложений. Но при добавлении нового, все-равно есть возможность пользоваться старыми стандартными модулями Django.

Т.к. этот вопрос интересует меня давно, я его задавал одному из Django Core Developers, Andrew Goodwin’уб на прошедшем в октябре UA Pycon. Далее в вольном пересказе пишу его слова так, как я их понял:

“Мы знаем об этой проблеме и у нас есть в планах заняться этой задачей. Но есть еще масса более важных задач, которые нужно делать. Поэтому я не могу сказать, когда мы начнем это реализовывать. Также по идеологии Django нужно совместимость версий: код, написанный для текущей версии, должен без изменений работать на следующей.

И есть еще одна проблема: community хочет не только модульность самого Django, но и возможность использование его компонентов, например models, вне Django. В данной это невозможно, но мы хотим когда-нибудь это сделать”

Несколько ссылок по теме из википедии:



 

Небольшой список того, что хотелось бы сделать в этом году:

  • дочитать книги Learning Python & Prorgamming Python;
  • опрделиться до конца с форматом блога;
  • вывести со статуса beta notacash.com и доделать там всё, что планировал;
  • разобраться с CQRS и дописать брошенное django-cqrs;
  • ещё больше перейти на лицензионный контент (в первую очередь софт, книги, музыка);
  • прочитать всё из  http://www.etnogenez.ru/;
  • сделать регулярными встречи KharkivPy;
  • закончить начатое в 2011-м.
Как-то так.

С Новым годом!

 


 

Никогда не знаешь, где упадет OpenStack(c) 
Я, в процессе очередного дебагга.

Те, кто читает мой твиттер (@e0ne), должны знать, что в последнее время я работаю с OpenStack’ом, а именно занимаюсь(конечно, не один я) попытками его запуска на Red Hat Enterprise Linux (RHEL), CentOS, Scientific Linux, etc. Т.к. все это построено на базе полной и непросветной enterpise в виде RHEL, то сборка нового дистрибутива, как правило, у меня начинается со сборки именно под эту ОС. 

Вот я и хочу поделиться своими впечатлениями от сборки последней версии OpenStack’а под RHEL. Началось все с попытки запустить чуть менее чем полностью поломанную версию essex-1. Потом все продолжилось с версией essex-2. Основные моменты, которые мешали мне радоваться жизни - переделки в glance, связанные и security, которые на время поломали работоспособность EC2 API. 

Проект Glance - это услуги по отбору, регистрации и поиску виртуальных «machine images» (VMI). В рамках Glance используется RESTful API, что позволяет делать запрос метаданных VMI и выполнять поиск фактического образа (VMI). (http://openstack.ru/openstack_glance.html)

И тут я решил попробовать запустить все из последней версии исходников, с master’а...

После небольших усилий инстансы (виртуалки) начали запускаться, но были проблемы с сетью. Команда “killall dnsmasq”, подсказанная моим коллегой, решила часть проблем, а именно - выдачу IP адреса виртуальной машине. Далее в логах при загрузке неворуженым глазом были замечены такие проблемы:

 

Что означало, что запущенный инстанс не может получить данный от Metadata Server’а. Т.к. Metadata Server нормально работал в Diablo, то сразу вспомнились 2 вещи:

 

 

Далее по аналогии с Nova API был создан файл /etc/init.d/openstack-nova-api-metadata, который предназначался для запуске сервиса Metadata Server’а. Metadata Server, на первый взгляд, успешно запустился, то я попытался запустить снова инстанс, что мне привело к предыдущей ошибке. Логи Metadata Server’а немного испугали:

 

Мозг сразу начал представлять кошмары, связанные с выводом strace, linux kernel debugger’ом и так далее. Вовремя опомнившись, я полез в Google. Поиск по “exit code 134” ничего полезного не дал. А вот поиск по “iptables-restore buffer overflow” дал нужный результат в виде двух багов iptables: http://bugzilla.netfilter.org/show_bug.cgi?id=641 иhttps://bugzilla.redhat.com/show_bug.cgi?id=545600. Довольно-таки стандартное, на сколько мне известно, переполнение буфера, вызванное функцией strcpy, которую, к слову, не очень-то и рекомендуют использовать. Подробности в этом комментарии: https://bugzilla.redhat.com/show_bug.cgi?id=545600#c6

 

 

Т.к. обновление iptables в RHEL 6.1 - не самая приоритетная для меня задача, то я решил зайти с другой стороны - посмотреть что же делает OpenStack для получения такого результата.

 

 

https://github.com/openstack/nova/blob/master/bin/nova-api-metadata

Metadata Server запускается таким же способом, как и другие REST-сервисы Nova, поэтому проблему нужно было искать где-то дальше. Об этом же говорили и логи, и ошибка, связанная с iptables.

https://github.com/openstack/nova/blob/master/nova/network/linux_net.py

 

Метод metadata_accept() отрабатывает без ошибок и падает все в iptables_manager.apply().

 

Т.к. данный метод используется далеко не в одном месте

 

 

то ошибка где-то в передаваемых параметрах. В качестве параметров к iptables-restore у меня передавалось такое:

 ip-restore-full.jpg

 

 

Зная о баге в iptables и то, что падало все только при запуске Metadata Server’а, то получилось быстро найти нужную команду, которая все ломала:

iptables-restore <<EOF
*nat
:nova-api-metadata-POSTROUTING - [0:0]
-A POSTROUTING -j nova-api-metadata-POSTROUTING
COMMIT
EOF

len(‘nova-api-metadata-POSTROUTING’)==29, что вместе с символом конца строки в языке С давало нам 30 символов и при копировании их в массив из 29 символов давало нам переполнение буффера (см. ссылки на баги выше). Хорошо, проблема найдена, теперь нужно ее устранить. Для этого находим код, где у нас генерируется имя chain’а:

chain1.jpg, chain2.jpg

 

где:

binary_name = os.path.basename(inspect.stack()[-1][1])

Таким образом проблема была в имени исполняемого файла. Переименовав “/usr/bin/nova-api-metadata” в “/usr/bin/nova-metadata ” все заработало. Вопрос лишь в том, насколько долго оно будет работать с таким “фиксом”? 

Более правильное решение нужно делать исправляя код OpenStack’а и/или обновляя iptables. Также интересно как это себя ведет на других RHEL-based дистрибутивах(версия iptables) и ubuntu, но это уже проверю завтра, а пока поставил качаться нужные образы дистрибутивов...

[Update]

Баг с iptables проверен после установки чистой ОС после установки всех апдейтов с помощью команды "yum update" на следующих ОС:

  • RHEL 6.1 x86_64 - iptables v1.4.7, buffer overflow detected
  • RHEL 6.2 x86_64 - iptables v1.4.7, buffer overflow detected
  • CentOS 6.1 x86_64 - iptables v1.4.7 buffer overflow detected
  • CentOS 6.2 x86_64 - iptables v1.4.7 buffer overflow detected
  • Scientific Linux 6.1 x86_64 - iptables v1.4.7 buffer overflow detected
  • Fedora 15 x86_64 - testing in process
  • Fedora 16 x86_64 - testing in process

 


Как правило, адаптация сайтов под мобильные устройства заключается в выполнении одного или нескольких пунктов из следующего списка:

  • подключения специальной версии CSS;
  • подключения нужных JavaScript’ов;
  • создание мобильных шаблонов (templates) с версткой (html).

Сразу оговорюсь, что вопрос мобильной верстки сейчас затрагивать не буду.

Исходя из этого списка, шаблоны, которые предназначенные для мобильных устройст будут выглядеть, примерно, так:

{% if request.mobile %}
    Mobile
{% else %}
    Not mobile
{% endif %}

 

Или же наша view поменяет вид на такой:

def index(request):
    if not request.mobile:
        return render_to_response('index.html’)
    else:
        return render_to_response('mobile_index.html’)

Теперь дело за малым - сделать так, чтоб в объекте нашего запроса (request’а) появилось свойство mobile. Один из самых простых и достаточно эффективных способов - посмотреть какой USER_AGENT у браузера, который делает запрос. Для этих целей уже есть небольшой, но удобный компонент minidecector, который анализирует USER_AGENT из запроса и выставляет нужное значение свойства request.mobile.

minidetector можно подключать двумя способами:

  • добавление декоратора detect_mobile к нужной view;
  • добавление уже готового Middleware; в этом случае будут обрабатываться все запросы к нашему приложению.

Небольшой пример использования minidetector лежит на GitHub’e: https://github.com/e0ne/BlogSamples/tree/master/MobileTest

 

Другие ссылки по теме:

Напомню, что протестировать это на встроеном в Django веб-сервере не получится, т.к. он работает только локально и вы не зайдете на него со своего мобильного устройста. Настройка Django+Apache+mod_wsgi описана тут: https://code.djangoproject.com/wiki/django_apache_and_mod_wsgi


 

Рассмотрим задачу сравнения двух инстансов простого класса A в Python. Сам класс A выглядит так:

class A(object):
    def __init__(self, int_param):
        self.int_value = int_param

В данном случае, если мы выполним такой код:

a1 = A(1)
a2 = A(1)

то инстансы этого классы не будут равны между собой:

a1 == a2 - False
a1 is a2 - False

Чтобы понять как это все работает и почему получается такой результат рассмотрим функцию id(object). Функция id() возвращает идентификатор объекта, который будет уникальным и неизменным на протяжении времени жизни объекта. В CPython эта функция возвращает адрес объекта в памяти. В моем случае это:

id(a1)=4299743824
id(a2)=4299743888

Теперь, если мы посмотрим на документацию оператора is, то увидем, что он возвращает True, только в случае, если, в нашем случае a1 и a2, - один и тот же объект. В таком случае, если сделать a3=a1, то получим:

a3 == a1 - True
a3 is a1 - True

Оператор “==” сравнивает объекты с помощью метода __cmp__, который может возвращать одно из 3-х значений:

 

  • -1  - в случае, когда один объект “меньше” (<) другого;
  • 1  - в случае, когда один объект “больше” (>) другого;
  • 0  - объекты равны (==).

 

 

Самая простая реализация этого метода выглядит так:

class B(object):
    def __init__(self, int_param):
        self.int_value = int_param

    def __cmp__(self, other):
        if self.int_value < other:
            return -1
        elif self.int_value > other:
            return 1
        else:
            return 0

После этого получаем такой вывод:

b1 = B()
b2 = B()
b1 == b2 - True
b1 is b2 - False

Примеры кода, традиционно, на GitHub’e: https://github.com/e0ne/BlogSamples/tree/master/PythonEquals

 


Git: создаем branch из tag'а

Published 12/21/2011 by e0ne in Python
Tags: ,

 

Любая source control система (TFS, SVN, Git и т.д.) умеет работать с такими вещами, как branch (ветка) и tag (метка). Ветки нужны для разработки каких-то фич, исправления багов и т.д., что бы в это время не ломать уже работающий код. Тэги, в свою очередь, нужны для заморозки какой-то версии кода без возможности последующих исправлений. Грязные хаки вроде залезть в базу данных source control чтобы поменять файл с каким-то тэгом я не рассматриваю по понятным причинам.

В моем случае, изменения в код с каким-то тэгом было связано с задачей сборки новой версии Openstack essex-2 под Red Hat Enterprise Linux (RHEL). Алгоритм работы был, примерно такой:

 

  • забираем исходники Openstack’а: $ git clone https://github.com/openstack/nova.git
  • переключаемся на нужный тэг: $ git checkout essex-2
  • делаем необходимые изменения в коде и пытаемся запушить в новый бранч: $ git push myrepo essex-2.

 

После данных действий, в моем репозитории, к удивлению, вместо нового бранча с нужными изменениями появился тэг essex-2, который был идентичен такому тэгу с официального репозитория Openstack’а. Чтение Pro Git расставило все на свои места и стало ясно почему так случилось.

Далее, для выполнения нужных мне действий понадобилось выполнить команды: 

 

  • $ git checkout -b branchname tag
  • $ git push myrepo essex-2

 

Что эквивалентно такому:

 

  • $ git branch branchname tag
  • $ git checkout branchname
  • $ git push myrepo essex-2

 

Надеюсь что мой пост не помешает правильному использованию тэгов.

 


Качество кода

Published 12/12/2011 by e0ne in Offtopic

Качественного кода нельзя добиться в случае:

  • нет code review;
  • code review проводит один и только один человек;
  • нету guidelines;
  • проверка кода не происходит на Continius Integration сервере;
  • хотя бы один из членов команды не заинтересован в качественном коде;
  • время на уменьшение технического долга не закладывается при планировании следующей итерации.


 

Казалось бы, отправить (засабмитить) HTML форму на сервер - стандартная задача, которая не должна вызывать никаких проблем и/или вопросов. Но так, как мы не занимаемся сферическим программированием в вакууме, то время от времени появляются трудности, вопросы, проблемы.

Сегодня на повестки для такая задача:

Есть страница для бронирования мест в театре/кино и т.д. Выглядит она, примерно, так:

map

Можно выбрать несколько мест и нажать кнопку “Зарезервировать”. При этом данные на странице постоянно обновляются (ajax) для отображения актуальной информации о свободных местах. Еще одна проблема - процесс брони места является достаточно долгим, например, 5-10-15 секунд.

Понятно, что реализация такой задачи полностью зависит от выбранной технологи, но общих подхода тут, как мне кажется, два: делать async form submit (ajax) или обычную синхронную отправку формы на сервер. Ниже я приведу алгоритмы для каждого из случая, которые, на мой взгляд, являются оптимальными для данной задачи.

 

Синхронный сабмит формы:

+ Самый простой способ сделать это как на клиенте, так и обработать на сервере.
+ Понятный жизненный цикл страницы.
+ Легко отследить момент завершения запроса и получение ответа от сервера.
- При асинхронном обновлении страницы, необходимо это останавливать, чтобы в POST пошли только нужные данные.
- Необходимо учитывать, что при долгой обработке запроса весь UI “замирает”.
- При сабмите формы асинхронные запросы, посылаемые в этот момент, могут завершиться с ошибкой и есть шансы получить javascript error (при правильном подходе и использовании jQuery такое бывает не часто).
- Ну и напоследок, не модно как-то, не “вебдванольно” когда весь UI замирает на 10-15 секунд.

Плюсов и минусов в этом подходе одинаково (не считая последний минус), но обработка ситуаций, связанных с первыми двумя “-” может потратить много времени и нервов.  Описание алгоритма такого обработчика - не имеет смысла, т.к. все достаточно стандартно. Единственное, что стоит отметить, так это то, что долговременные операции можно выполнять в отдельном потоке (не забывая про ограничения среды выполнения и веб-сервера), а еще лучше - запускать это в каком-то application server/service, но тогда теряется вся простота реализации.

 

Асинхронный сабмит формы:

+ Весь UI остается, в общем случае, полностью работоспособен.
+ Пользователь не видит никаких тормозов страницы.
- Сложнее реализовывать, чем первый вариант.
- Баги, связанные с асинхронностью никто не отменял.

Стандартный алгоритм такого решения выглядит так (не рассматриваю ситуацию, когда пользователь уходит со страницы):

 

  • делается асинхронный(ajax) POST формы на сервер;
  • пока ждем ответа от сервера где-то на UI сообщаем пользователю что “запрос выполняется, подождите немного”;
  • в случае успешного запроса показываем пользователю сообщение с ссылкой на страницу с более детальной информацией;
  • в случае ошибки (500-я или тайм-аут) показываем сообщение и предлагаем попробовать еще раз

 

В этом подходе получается больше логики на клиентской стороне, но при этом в любой момент времени у нас остается полностью рабочий UI.

На этом все. В следующий раз я покажу на примерах, как реализовывать тот или иной способ.

 


e0ne's comments

A Web Developer's Blog!