Decoupling A Monolithic Server Application

25 апреля 2015

Написали статью по результатам недавних переделок Decoupling A Monolithic Server Application

Интерпретатор своими руками с помощью Graph-talk

25 ноября 2014

Хочу рассказать о том, как сделать интерпретатор с помощью библиотеки Graph-talk. В качестве примера будет использоваться замечательный язык Brainfuck.

Грамматика языка

Всего в языке имеется 8 команд:

  • «>» — переход к следующей ячейке памяти;
  • «<» — переход к предыдущей ячейке памяти;
  • «+» — увеличить значение в текущей ячейке на 1;
  • «-» — уменьшить значение в текущей ячейке на 1;
  • «.» — вывести значение текущей ячейки;
  • «,» — ввести значение извне в текущую ячейку
  • «[» — начало цикла, выполнить содержимое внутри цикла, если в текущей ячейке не 0;
  • «]» — конец цикла, вернуться на начало с учетом вложенности.

Собственно, это все. Изначально имеется 30 000 ячеек памяти. «Hello, World!» на этом милейшем языке выглядит так:

++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++
.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.
——.———.>+.>.

 

Основные понятия graph-talk

Graph-talk изначально проектировалась как библиотека для операций разбиения на лексемы, парсинга и интерпретации текстов, написанных на формальных языках.

Формальные языки и процессы обработки в ней представляются в виде графов, которые, в свою очередь, состоят из вершин (понятий) и дуг (связей или отношений между ними).

Есть 4 типа понятий:

  1. Простое понятие — лист дерева с некоторым названием (класс Notion);
  2. Активное понятие — простое понятие, которое вызывает некоторую функцию при его обработке (ActionNotion);
  3. Составное понятие — содержит в себе несколько других понятий в указанном порядке (ComplexNotion);
  4. Выборочное понятие — аналог оператора «select», составное понятие, которое содержит в себе одно из нескольких понятий (SelectiveNotion).

Понятия связаны между собой отношениями 4-х типов:

  1. Простая связь — соединяет вместе два понятия, одно из которых называется Subject, а другое — Object (класс Relation);
  2. Направленная связь — позволяет переходить от одного понятия к другому, если выполняется заданное условие (NextRelation);
  3. Активная связь — направленная связь, которая вызывает функцию при ее обработке (ActionRelation);
  4. Распознающая связь — направленная связь, которая «поглощает» обработанный текст, если он совпадает с ее условием (ParsingRelation);
  5. Циклическая связь — направленная связь, которая повторяет выбранное понятие указанное число раз (LoopRelation);

Для обработки используется несколько типов процессов (Process) с разными возможностями. Для данного примера нам будет нужен процесс распознавания (ParsingProcess). ParsingProcess получает на вход текст и стартовую вершину графа для обработки. Затем он начинает обход графа, передавая имеющуюся у него информацию о тексте элементам графа. В зависимости от типа элемента он получает «ответы» с указаниями о дальнейших действиях, например: перейти к следующему элементу, выполнить функцию, обработать кусок текста и так далее. В конечном итоге весь входной текст оказывается обработан при помощи распознающих связей.

Моделирование процесса интерпретации

Программа представляет собой набор команд. С помощью графа можно представить это следующим образом:
Программа на Brainfuck
Программа — это составное понятие, которое будет состоять из нескольких активных понятий в том порядке, в котором они встречаются в программе.

Для того чтобы реализовать цикл, его тело помещается внутрь соответствующего составного понятия, которое будет повторять содержимое до тех пор, пока значение текущей ячейки будет не 0. Это реализуется с помощью циклической связи (LoopRelation). На рисунке внизу показан цикл, который будет печатать содержимое текущей ячейки до тех пор, пока оно больше нуля:

Цикл

Для непосредственного выполнения команд реализуются соответствующие функции, которые будут затем исполняться виртуальной машиной Brainfuck. После распознавания команды вызов функции помещается в граф программы как активное понятие. Когда входной текст полностью обработан, процесс обхода переходит на граф программы и начинает его выполнение. Вот как будет выглядеть граф программы «,[-.]» — ввода и печати содержимого ячейки, пока оно больше 0:

Граф интерпретатора

Как видно из рисунка, сам процесс интерпретации («Interpreter») состоит из обработки исходного кода («Source», будет одинаковым для всех программ на языке Brainfuck) и результирующей программы («Program»).

Исходный код состоит из команд («Commands»), которых может быть сколько угодно и которые мы будем продолжать искать до тех пор, пока текст не кончится. Каждая команда может быть одной из 8 команд языка, пробелом или чем-то другим (то есть ошибкой). Выбор команды осуществляется при помощи выборочного понятия («Command») и распознающих связей (ParsingRelation), каждая из которых «съедает» соответствующий текст при совпадении условия. Распознающие связи ведут к активным понятиям, функции которых помещают новые активные понятия, которые будут выполнять функции языка, в граф результирующей программы.

Несколько интересных моментов:

  1. Связь «пробел» в качестве условия использует регулярное выражение. При наличии символов пробела все они будут удалены из текста.
  2. Связь «ошибка» помечена как связь по умолчанию («default») для понятия «Command». Это означает, что процесс перейдет на нее только в том случае, если все другие связи не сработают.
  3. Для поддержки вложенности циклов необходимо «прикреплять» новые команды не к самому верхнему понятию программы, а к понятию «Top», которое зависит от уровня цикла. Для этого используется стек понятий «Top», при создании нового цикла в него помещается новый элемент, а при окончании цикла элемент извлекается и новые команды начинают подключаться на уровень выше.

Полный код примера находится здесь.

Кроме интерпретатора, в нем приводится модель процесса преобразования кода на Brainfuck в код на Python, что значительно упрощает чтение текстов на этом эзотерическом языке.

Абсолютное добро: автоматические тесты

31 марта 2013

Трудно сосчитать, сколько раз автоматическое тестирование выручало те проекты, в которых мне приходилось работать в последнее время. В текущем проекте, в отличие от прошлых, это не просто «полезно» или «неплохо», а «жизненно необходимо» — такова специфика задачи. У нас есть математическая модель, любое изменение которой может улучшить ситуацию в двух случаях и ухудшить — в десятке. Чтобы понять, хороши ли изменения, необходимо перепрогонять большие (несколько тысяч) объемы тестов.

Для себя я выделил 4 жирных плюса автоматических тестов:

  1. Функциональность, покрытая тестами, гарантированно работает,
  2. и это выясняется очень быстро, а попутно можно делать замеры производительности;
  3. Тестовые инженеры избавлены от рутинной работы и могут ловить действительно сложные ошибки;
  4. Достаточно просто смоделировать стресс-тестирование — просто увеличить количество/интенсивность тестов.

Звучит вроде хорошо, но как это все сделать на практике? В «Концепторе» мы сначала ни о чем таком не думали — писали код с максимально возможной скоростью, чтобы как можно скорее получить хоть что-то рабочее. Но в определенный момент стало понятно, что кода уже слишком много, чтобы держать его в голове целиком, а проблемы могут проявиться не сразу. Тогда мы взяли в руки джанговские тесты и принялись доводить покрытие до разумного уровня. Получилось около 98% на важных файлах – возможно, перегиб, но после этого мы могли совершенно не волноваться о том, что какая-то смелая правка сломает, например, систему авторизации и регистрации. Для анализа покрытия мы пользовались Django coverage. Также рекомендую хорошую статью про Selenium WebDriver, которой пользуется Yandex для тестирования веб-интерфейса своей почты. В Java, на которой я программирую сейчас, мы используем JUnit. Кстати, PyCharm и IntelliJ Idea имеют встроенную поддержку просмотра покрытия кода тестами.

Но самое трудное — это, конечно, перейти из состояния «нифига нет, а нужно немеряно работы» в состояние «о, как у нас все круто» — обычно это происходит через состояние «ну, что-то уже есть». Легче, конечно, вздохнуть и отложить все в долгий ящик (испытанный способ похоронить хорошую идею). Потому что начальство кричит и все нужно вчера, как обычно. Сразу писать сотни тестов очень тяжело, хотя мы пошли именно по такому пути, и это заняло около двух с половиной недель работы на полной занятости (все-таки тесты пишутся довольно быстро). Можно начать постепенно, а самое лучшее — посадить за написание/обновление тестов нового участника команды. Два в одном — и в коде можно разобраться, и пользу принести. Можно начать потихоньку писать самому, в режиме «лучший отдых — смена деятельности». А руководству объяснить, что проще и дешевле устранять ошибки на самых ранних стадиях, чем выслушивать крики и угрозы пользователей.

Ну и, конечно, тесты нужно поддерживать в актуальном состоянии, но немного дисциплины очень полезно для программерского здоровья.

Рекомендации для начинающих работать с Django Social Auth

18 октября 2011

Меня часто спрашивают, а я ленюсь отвечать одно и то же. Поэтому хочу здесь дать несколько практических советов тем, кто начинает разбираться с DSA.

1. С чего начать?
Начинайте всегда с примера django-social-auth/example, а в нем — с простых авторизаций вроде OpenId. Потом можно заполнить local_settings и переходить к OAuth, но имейте в виду, что многие из них потребуют запуска на доменном имени вашего сайта, а не на localhost.

2. Не работает!
Когда я делаю merge больших изменений из основной ветки, то всегда проверяю, не сломалась ли авторизация (бывает, что ломается), и только потом выкладываю код на github. Это я как бы намекаю, что проблема, скорее всего, на вашей стороне. Проверьте настройки, URL’ы и импорты.

3. Используется ли в реальных проектах?
Да, в частности на http://conceptor.ru. Собственно, для этого проекта все и разрабатывалось, сайт можно использовать в качестве развернутого примера.

Кое-что о тестировании

23 ноября 2010

За время своей работы в IT я сталкивался с очень разным отношением к тестированию ПО. Хочу поделиться своими наблюдениями по этой теме.

Цена ошибки

Я встречал два подхода к тестированию: must have и «разработчики все сделают как надо и сами проверят». Почему-то среднего не наблюдалось.
Первый подход я наблюдал в двух компаниях, работающих в сфере телекоммуникаций, только одна разрабатывала собственно пользовательские мобильные устройства, а другая – сети для этих устройств. Тестовый процесс в них так же серьезен, как и разработческий: контроль того, что все требования покрыты тестами, несколько фаз тестирования – проверка работоспособности старых функций после интеграции нового кода (LegacyRegression/Sanity/Integration Test), проверка новой функциональности (System/Function Test), стресс-тестирование и так далее. Такой подход обусловлен следующими причинами:

  • в случае производителя устройств: пользователь, к примеру, сотового телефона не имеет возможности легко и часто ставить обновления. А было время, когда флэш-память была настолько дорогой, что код прошивался насмерть и оставалась только специальная область памяти для патчей;
  • в случае производителя инфраструктуры: система работает под высокой нагрузкой, обновления ставить тоже непросто, а уж падение (про потерю данных лучше вообще промолчу) стоит тысячи долларов в лучшем случае.

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

Читать дальше…

О jQuery 1.4.3 и новых версиях

19 октября 2010

Рома Ворушин, спасибо за цитату Simon Willison про новый jQuery:

Once again, the thing that impresses me most about this jQuery release is how stable the core API is. Hardly any new methods added, but the existing methods are made faster, more flexible and more predictable. The same as been true for the past several releases as well. It just keeps getting more and more polished.

Интересно было сравнить Release Notes — то, что было переделано вообще, и изменения в API — то, что изменится для пользователей. Изменений в API довольно мало, 4 новых штуки, все остальное — по большей части изменения функций events, связанных с добавлением eventData.

Из новых вещей мне понравилась jQuery.type; что касается eventData — с его помощью можно указывать некую дополнительную информацию, которая будет доступна на момент вызова handler’a (подробное описание я нашел вот тут).

Возвращаясь к цитате :) Приятно было увидет подтверждение одного из откровений, которое посетило меня при работе над Рисоваськой. Оно формулировалось так: целью создания новой версии программы не является добавление нового функционала, а улучшение той главной функции, которую выполняет продукт. Если при этом появляется новый функционал — это нормально, но огульное добавление «ништяков» ведет к мутациям и ожирению.

О пользе хранения информации в wiki

3 октября 2010

Впервые я воспользовался wiki для рабочих целей в проекте MoodBox и был поражен, какой это удобный способ хранения и обмена информацией. За два года я так на него «подсел», что теперь во всех новых проектах начинаю работу с вопроса: «Где у вас тут вики?» В случае ответа «чего?» следует его установка и внедрение в сознание участников проекта факта, какая это здоровская вещь.

Чем же он так хорош?

Читать дальше…