Decoupling A Monolithic Server Application
Написали статью по результатам недавних переделок Decoupling A Monolithic Server Application
Написали статью по результатам недавних переделок Decoupling A Monolithic Server Application
Хочу рассказать о том, как сделать интерпретатор с помощью библиотеки Graph-talk. В качестве примера будет использоваться замечательный язык Brainfuck.
Всего в языке имеется 8 команд:
Собственно, это все. Изначально имеется 30 000 ячеек памяти. «Hello, World!» на этом милейшем языке выглядит так:
++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++
.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.
——.———.>+.>.
Graph-talk изначально проектировалась как библиотека для операций разбиения на лексемы, парсинга и интерпретации текстов, написанных на формальных языках.
Формальные языки и процессы обработки в ней представляются в виде графов, которые, в свою очередь, состоят из вершин (понятий) и дуг (связей или отношений между ними).
Есть 4 типа понятий:
Понятия связаны между собой отношениями 4-х типов:
Для обработки используется несколько типов процессов (Process) с разными возможностями. Для данного примера нам будет нужен процесс распознавания (ParsingProcess). ParsingProcess получает на вход текст и стартовую вершину графа для обработки. Затем он начинает обход графа, передавая имеющуюся у него информацию о тексте элементам графа. В зависимости от типа элемента он получает «ответы» с указаниями о дальнейших действиях, например: перейти к следующему элементу, выполнить функцию, обработать кусок текста и так далее. В конечном итоге весь входной текст оказывается обработан при помощи распознающих связей.
Программа представляет собой набор команд. С помощью графа можно представить это следующим образом:
Программа — это составное понятие, которое будет состоять из нескольких активных понятий в том порядке, в котором они встречаются в программе.
Для того чтобы реализовать цикл, его тело помещается внутрь соответствующего составного понятия, которое будет повторять содержимое до тех пор, пока значение текущей ячейки будет не 0. Это реализуется с помощью циклической связи (LoopRelation). На рисунке внизу показан цикл, который будет печатать содержимое текущей ячейки до тех пор, пока оно больше нуля:
Для непосредственного выполнения команд реализуются соответствующие функции, которые будут затем исполняться виртуальной машиной Brainfuck. После распознавания команды вызов функции помещается в граф программы как активное понятие. Когда входной текст полностью обработан, процесс обхода переходит на граф программы и начинает его выполнение. Вот как будет выглядеть граф программы «,[-.]» — ввода и печати содержимого ячейки, пока оно больше 0:
Как видно из рисунка, сам процесс интерпретации («Interpreter») состоит из обработки исходного кода («Source», будет одинаковым для всех программ на языке Brainfuck) и результирующей программы («Program»).
Исходный код состоит из команд («Commands»), которых может быть сколько угодно и которые мы будем продолжать искать до тех пор, пока текст не кончится. Каждая команда может быть одной из 8 команд языка, пробелом или чем-то другим (то есть ошибкой). Выбор команды осуществляется при помощи выборочного понятия («Command») и распознающих связей (ParsingRelation), каждая из которых «съедает» соответствующий текст при совпадении условия. Распознающие связи ведут к активным понятиям, функции которых помещают новые активные понятия, которые будут выполнять функции языка, в граф результирующей программы.
Несколько интересных моментов:
Полный код примера находится здесь.
Кроме интерпретатора, в нем приводится модель процесса преобразования кода на Brainfuck в код на Python, что значительно упрощает чтение текстов на этом эзотерическом языке.
Трудно сосчитать, сколько раз автоматическое тестирование выручало те проекты, в которых мне приходилось работать в последнее время. В текущем проекте, в отличие от прошлых, это не просто «полезно» или «неплохо», а «жизненно необходимо» — такова специфика задачи. У нас есть математическая модель, любое изменение которой может улучшить ситуацию в двух случаях и ухудшить — в десятке. Чтобы понять, хороши ли изменения, необходимо перепрогонять большие (несколько тысяч) объемы тестов.
Для себя я выделил 4 жирных плюса автоматических тестов:
Звучит вроде хорошо, но как это все сделать на практике? В «Концепторе» мы сначала ни о чем таком не думали — писали код с максимально возможной скоростью, чтобы как можно скорее получить хоть что-то рабочее. Но в определенный момент стало понятно, что кода уже слишком много, чтобы держать его в голове целиком, а проблемы могут проявиться не сразу. Тогда мы взяли в руки джанговские тесты и принялись доводить покрытие до разумного уровня. Получилось около 98% на важных файлах – возможно, перегиб, но после этого мы могли совершенно не волноваться о том, что какая-то смелая правка сломает, например, систему авторизации и регистрации. Для анализа покрытия мы пользовались Django coverage. Также рекомендую хорошую статью про Selenium WebDriver, которой пользуется Yandex для тестирования веб-интерфейса своей почты. В Java, на которой я программирую сейчас, мы используем JUnit. Кстати, PyCharm и IntelliJ Idea имеют встроенную поддержку просмотра покрытия кода тестами.
Но самое трудное — это, конечно, перейти из состояния «нифига нет, а нужно немеряно работы» в состояние «о, как у нас все круто» — обычно это происходит через состояние «ну, что-то уже есть». Легче, конечно, вздохнуть и отложить все в долгий ящик (испытанный способ похоронить хорошую идею). Потому что начальство кричит и все нужно вчера, как обычно. Сразу писать сотни тестов очень тяжело, хотя мы пошли именно по такому пути, и это заняло около двух с половиной недель работы на полной занятости (все-таки тесты пишутся довольно быстро). Можно начать постепенно, а самое лучшее — посадить за написание/обновление тестов нового участника команды. Два в одном — и в коде можно разобраться, и пользу принести. Можно начать потихоньку писать самому, в режиме «лучший отдых — смена деятельности». А руководству объяснить, что проще и дешевле устранять ошибки на самых ранних стадиях, чем выслушивать крики и угрозы пользователей.
Ну и, конечно, тесты нужно поддерживать в актуальном состоянии, но немного дисциплины очень полезно для программерского здоровья.
Меня часто спрашивают, а я ленюсь отвечать одно и то же. Поэтому хочу здесь дать несколько практических советов тем, кто начинает разбираться с DSA.
1. С чего начать?
Начинайте всегда с примера django-social-auth/example, а в нем — с простых авторизаций вроде OpenId. Потом можно заполнить local_settings и переходить к OAuth, но имейте в виду, что многие из них потребуют запуска на доменном имени вашего сайта, а не на localhost.
2. Не работает!
Когда я делаю merge больших изменений из основной ветки, то всегда проверяю, не сломалась ли авторизация (бывает, что ломается), и только потом выкладываю код на github. Это я как бы намекаю, что проблема, скорее всего, на вашей стороне. Проверьте настройки, URL’ы и импорты.
3. Используется ли в реальных проектах?
Да, в частности на http://conceptor.ru. Собственно, для этого проекта все и разрабатывалось, сайт можно использовать в качестве развернутого примера.
За время своей работы в IT я сталкивался с очень разным отношением к тестированию ПО. Хочу поделиться своими наблюдениями по этой теме.
Я встречал два подхода к тестированию: must have и «разработчики все сделают как надо и сами проверят». Почему-то среднего не наблюдалось.
Первый подход я наблюдал в двух компаниях, работающих в сфере телекоммуникаций, только одна разрабатывала собственно пользовательские мобильные устройства, а другая – сети для этих устройств. Тестовый процесс в них так же серьезен, как и разработческий: контроль того, что все требования покрыты тестами, несколько фаз тестирования – проверка работоспособности старых функций после интеграции нового кода (LegacyRegression/Sanity/Integration Test), проверка новой функциональности (System/Function Test), стресс-тестирование и так далее. Такой подход обусловлен следующими причинами:
То есть резон чисто экономический: выберите в проекте область, цена ошибки в которой стоит наибольших денег (потеря доверия пользователей, утечка или разрушение данных, потеря потенциальных покупателей, биллинг и т. п.), и айда тестировать.
Рома Ворушин, спасибо за цитату 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 для рабочих целей в проекте MoodBox и был поражен, какой это удобный способ хранения и обмена информацией. За два года я так на него «подсел», что теперь во всех новых проектах начинаю работу с вопроса: «Где у вас тут вики?» В случае ответа «чего?» следует его установка и внедрение в сознание участников проекта факта, какая это здоровская вещь.
Чем же он так хорош?