solovyov.net

Clojure Cup 2013

11 min read · life, programming, clojure

Недели три назад мне пришла в голову идея, что было бы прикольно поучаствовать в Clojure Cup и написать что-то прикольное на кложуре (сорри, я буду называть еë кложура, а не кложа, как делаю и в реальной жизни, потому что я так вижу ;)). Я вообще никогда особенно не испытывал большого желания участвовать в подобных соревнованиях (и не очень подобных, типа ICFPC) - мне всегда думалось, что если я захочу что-то написать, я и так напишу, и мне не нужны для этого соревнования.

Но тут сложилось то, что мне никак (из-за лени, и приоритезации проектов попроще/попрактичнее) не удавалось начать писать что-то хотя бы относительно серьëзное на кложуре, плюс наличие цельной идеи, у которой чëтко были видны рамки - Risk’a. Мы (чуть забегая вперëд) играли хотя бы несколько раз в Conquer Club, который вполне неплох в смысле коммьюнити, карт и разумности правил, при этом у него абсолютно ужасный интерфейс, который не меняется годами. Альтернативы тоже довольно паршивые, на флеше и сильверлайте, бррр. Вообще, Риск казался мне довольно амбициозной целью, но разве мы не шарящие чуваки, а?

Мы - это я, Сева, Сергей и Рома. Рома немножко посомневался, сможет ли он участвовать, и стоит ли, но в конце-концов таки решился.

До

Недели за полторы я начал попинывать чуваков на тему “учаснеги, вы всë уже забыли, раздупляйтесь с кложурой”, на что они вяло отвечали “да, надо бы…”, и я тоже не проявлял какой-то особенной активности (ну, хотя бы заставил всех завести флоудок, гг). Они немного посидели над 4clojure.com, чего, конечно, было мало, и еще за неделю до события мы втроëм (без Ромы) собрались у Севы и минимально обсудили планы и разные подробности. Обсуждение вышло на мой вкус не очень продуктивным - по крайней мере оно ничего не изменило в моей голове (впрочем, не скажу о головах других, но всë же). Всем было несколько лень напрячься, представления о том, как это всë будет, мне кажется, были довольно смутные.

К среде мы уже больше начали напрягаться, все заджойнились во Flowdock, который нам дали на шару на 3 месяца, и вечером устроили еще одно обсуждение, уже более продуктивное. В процессе обсуждения родилась наконец идея, как делать фронт-энд.

Фронт-энд

Техническое отступление. Горячая тема последних месяцев в кложуре - это core.async. Дэвид пилит впечатляющие демки, все с ним носятся и пишут туториалы и гайды, устраивают срывы покровов и вообще творят вакханалию. Не могу сказать, что меня обошла эта вакханалия, но одно смущало: чуваки предлагают кор.асинк для управления потоками данных, а к интерфейсу приклеиваются по старинке, селекторами. Это меня резко отвращает, селекторы всегда всë делают противным. В то же время, есть концептуально красивый hoplon, с которым я ношусь последние полгода. Но с ним другая проблема - он банально тормозит в файрфоксе. У фф (по словам Алана, одного из авторов хоплона) есть определëнные траблы с оптимизацией рекурсии и вложенных стеков, и вот результат. Короче, меня порядочно плющило и я никак не мог решить, умный я или красивый.

И тут у нас родилась прекрасная идея! Надо взять React - который, между прочим, я выбрал для текущего проекта на работе, большей частью из-за концептуальной близости к хоплону, чуть смещëнной в сторону прагматичности и императивной привычности. Так вот, реакт, и сделать для него обëртку на кложурскрипте, заодно заменив синтаксис определения DOM-дерева на синтаксис темплейтов Hiccup.

Если вы не в курсе, то идея Реакта в том, что он из метода render должен вернуть “виртуальное” дерево DOM (из обычных жс-объектов), и потом специальный механизм согласует реальный DOM с этим виртуальным. Работает это довольно быстро (быстрее, чем Angular или Ember, как минимум) и, что главное - декларативно. Главное уметь отрисовать состояние полностью, а заботиться о том, как изменять DOM - не нужно. Минимальный компонент выглядит как-то так:

var Comp = React.createClass({
    render: function() {
        return <div>Hello</div>;
    }
});

Мы выдумали вот такое:

(defr Comp []
    [:div "Hello])

Т.е. весь хтмл вместо какого-нибудь специального синтаксиса описывается структурами данных на кложуре, которые можно тасовать и преобразовывать стандартным образом. Как сгенерировать список элементов? [:ul (map (fn [x] [:li x]) ["a" "b" "c"])], и всë (если вам кажется, что выглядит страшно, вам кажется - см. твит Ромы).

Так вот, мы наконец решили, как всë надо делать, четверг был занят, а в пятницу после обеда Сева сел разбираться и с реактом, и с кложурскриптом, и к тому моменту, как я пришëл домой, уже имел парочку рабочих компонентов (как всегда - не стоит недооценивать незнакомости языка и его окружения). Я приступил к работе (копированию и переделыванию частей hiccup’a и crate’а под наши нужды), потом мы всë это вместе как-то слили и пошли спать к 12 ночи с незаконченной либой.

Суббота

Первые часа 3-4 субботы мы с Севой потратили на допиливание pump‘a до рабочего состояния (куча всякой неочевидно фигни всплыла, чуть расслаблюсь и буду сочинять письмо в рассылку реакта, хочется как-то плотнее интегрироваться), а Сергей с Ромой на раздупление с экосистемой, эксперименты с архитектурой и мокапы интерфейса.

К двум часам дня мы рестартовали репозиторий начисто и понеслась! Сергей занялся бэкендом, я занялся архитектурой клиента, Рома занялся мордой и Сева занялся картой. Распределились в этот момент мы реально очень удачно, потому что мы друг другу не мешали и независимо по всем направлениям достигли успехов - сервер чо-та там работал, принимал и слал, клиент показывал рожу, карта обретала очертания и данные, и я таки заставил (часа полтора угробил!) работать кроссоверы в cljsbuild - шаринг кода между клиентом и сервером кложуры (я потом был нереально счастлив, что не бросил это дело на полпути).

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

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

Воскресенье

Поднялся как-то довольно рано, и пока все раздуплялись, проапгрейдил макро в pump’e, чтоб компоненты стало писать еще проще, чем раньше, и забег начался опять.

Я занялся запиливанием мелочей и красоты (типа граватара, редактирования профиля, улучшениями мелкими внутренних вещей), Сева замутил нам лого (еее, теперь мы совсем взрослые!), Серëга продолжал штамповать код на сервере (списки игр, всякие там переходы состояний в игре), а Рома разбирался с Персоной, которая не хотела ни держать юзера залогиненным, ни вылогинивать - оказалось, что мы еë децл неправильно поняли.

Logo

Энивей, к 3 часам дня у нас уже были лого, список игр, мы могли в игры заходить, логин работал безупречно (кто мешал с персоной разобраться на неделе? )), карта ресайзилась под экран и вообще всë начинало выглядеть так, как будто у нас есть шанс успеть - я оптимист, но под конец субботы темп никак не выглядел таким, как будто что-то выйдет.

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

К 10 вечера уже был весь интерфейс, который мы хотели сделать, исключая собственно игру, логику которой начал делать Сергей - заполнение карты юнитами, распределение провинций по людям, атаку и передвижение войск после неë. Я тоже немножечко помог с этим - сделал логику получения солдат в начале хода, которая может задеплоить 7.666667 солдат. ;))

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

Примерно же в это время мы словили дикий баг с тем, что (js->clj data :keywordize-keys true) преобразовывает в кейворды не только то, что было кейвордами, а вообще абсолютно всë, и наш {:player-state {1 ...}}, где 1 - это ид пользователя, становился {:player-state {:1 ...}}! Мы это очень долго ловили и фиксили в двух местах, а потом оказалось, что (keyword 1) и (keyword "1") возвращают разные вещи, и у меня вообще в голове очень смутно помнится, как мы это обнаружили (и я до сих пор прозреваю, что таки обнаружили)! NB: Нужно зарепортить баг.

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

К 3 часам у нас была карта, можно было из интерфейса задеплоить войска, атаковать и закончить атаку. Где-то в 2:50 я задеплоил джарник и оказалось, что на сервере карта не показывается! В 2:54 виден коммит, когда я попытался пофиксить проблему - и он почти бы заработал, потому что там я написал:

(def map-data (ClassLoader/getSystemResource "resources/static/map-classic.json"))

Всего-то не надо было писать строку resources/, и всë бы работало! Очень неприятно, особенно учитывая что за 6 минут я таки мог бы догадаться и починить… Но незнание JVM и общая усталось (3 часа ночи, я обычно уже часа 3 как сплю!)… Щит, неприятно. Я еще пытался метаться, что заметил и Никита (там есть скрин моего твита в посте), но уже всë. Закруглились и поехали по домам.

Ощущения

То, что не подготовились хоть сколько-нибудь - очень плохо. У нас, мне кажется, были серьëзные шансы - и довольно серьëзная идея, и отличный логотип, и название относительно цепляющее (хотя при чëм тут магниты, не понимаю - хотя это я придумал название), и реализация всего довольно симпатичная (см картинку ниже). Но блин, фейл, и если бы подготовились - у нас бы было еще где-то часа 3-4 у каждого.

Logo

Очень зол на сериализацию. Очень. Надо переходить на нормальную, которая не теряет типы данных.

С базой данных не пришлось возиться вообще! В основном ей был занят Сергей, но и PostgreSQL, и nomad работали молча и ни разу не попросили кушать - хотя дропали мы базу и создавали заново регулярно. К korma зато нарекания есть - она слишком простая (не simple, а simplistic), нужно будет попробовать ClojureQL или clojure-sql.

ClojureScript разрывает! Код очень плотный (поначалу меня это напрягало, кстати - раза в три плотнее жс/кофескрипта), вместе с paredit’ом редактировать его удобно (структурное редактирование ftw), и даже чуваки, которые поначалу не были особенно счастливы от синтаксиса, к концу, мне кажется, прониклись серьëзнее. Очень хороший стандартный набор функции, клëвые структуры данных и вообще всë супер. Нужно только серьëзно озаботиться вопросом прямой интеграции с платформой, особенно с джаваскриптом - он своими мудацкими свойствами иногда подкидывал серьëзные приколы.

React - очень, очень хорош. Он быстрый, поверх него нам удалось построить очень простой синтаксис и разработка происходила в ситуации, когда с фреймворком не нужно бороться вообще, просто кайф. Все эти угловые и тлеющие не идут ни в какое сравнение. Надо только связаться с разработчиками и найти возможность вхукнуться внутрь, чтоб плотнее интегрировать кложурскрипт с реактом.

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

Mozilla Persona рулит. Буду юзать только еë, когда возможно, значительно меньше гемора с логином.

Flowdock - очень удобная штука. Их разделение на два экрана, когда в одном чат, а в другом всякие нотификации - отлично работает. Чат можно редактировать, на важные сообщения можно вешать теги и потом по ним искать, можно отдельно посмотреть список присланных ссылок, загруженных файлов. И нотификации - отлично, ничего не пропускаешь, куча интеграций со всякими сервисами. Когда тебя упоминают, выскакивают десктопные нотификации, а если ты не онлайн - шлëтся всë на почту. Мне очень понравился.

Всë было очень прикольно, хотя часто я повторять такое не хочу, таки тяжеловато. Но количество эндорфинов безумное, конечно, мы написали не просто много кода (1454 строки в warmagnet и 187 в pump’e), а много толкового кода, который работает, а не просто существует.

Zee &

Мы планируем зафиксить остаток багов, привести логику игры в порядок и сделать полноценный Риск! Сейчас запрещена любая публикация приложения до пятницы, к сожалению - но мне будет очень приятно, если вы за нас проголосуете. Cheers! :-)

UPD: War Magnet теперь на гитхабе с открытым кодом.

If you like what you read — subscribe to my Twitter, I always post links to new posts there. Or, in case you're an old school person longing for an ancient technology, put a link to my RSS feed in your feed reader (it's actually Atom feed, but who cares).

Other recent posts

Server-Sent Events, but with POST
ngrok for the wicked, or expose your ports comfortably
PostgreSQL collation
History Snapshotting in TwinSpark
Code streaming: hundred ounces of nuances