Привет, меня зовут Паша! Я fullstack developer в Mad Devs.
Мы в компании создаём современный мир информационных технологий, тот самый, который ты хорошо знаешь и о котором читаешь тонну статей и материалов.
Чуть больше месяца назад я рассказал, почему недооценивал Go после 6 лет работы на Ruby. Теперь хочу поделиться мыслями о том, как программировать одновременно на трёх языках и не сойти с ума.
Почему 3 языка?
Тут всё просто. Последние полгода работаю над проектом Peklo Tool. Мы делаем ultimate инструмент для специалистов в контекстной рекламе, который призван сжимать человеко-недели, а то и человеко-месяцы, специалистов до человеко-секунд. А если быть точным: до машино-секунд.
Это профессиональное приложение. В таком форм-факторе эту задачу ещё никто никогда не решал. Peklo Tool — это стартап, а любой стартап должен иметь надёжного друга в виде продакт-оунера, который горит проектом. У нас такой есть! И настоящего фулстека, который будет так же гореть проектом и давать возможность проекту расти быстрее и быстрее. Чем настоящий фулстек отличается от ненастоящего по моему мнению, я написал в моей статье на Хабре. Открой эту статью в отдельной вкладке и почитай позже. Если тебя начнёт бомбить с того, что я написал в этом материале, иди на Хабр и прочитай материал про фулстеков, а потом вернись сюда.
Каждый язык программирования в проекте решает свою задачу:
- JS (Vue), очевидно, делает фронтенд;
- Ruby (Rails), реализует REST нашего проекта;
- Go реализует сложный алгоритм генерации текстов.
Мы не будем вдаваться в подробности, почему были взяты именно эти языки и фреймворки. Поговорим о том, как работать с тремя такими языками программирования одному специалисту. И первое, о чём сразу хочется поговорить это…
IDE
Да, выбор IDE для разработки на таком стеке языков — это сложная история. Я вообще не люблю людей, которые завязываются на конкретной IDE. Часто такое встречаю у джавистов. Знаю джавистов, которые потеряли свою душу в IDEA. Мало того, что переход на любую другую IDE — это для них стресс и шок (даже на такие для них, казалось бы, дружелюбные Eclipse и NetBeans), так IDEA настолько ограничила их, что некоторые даже не представляют, как работает Java. Пару лет назад объяснял джависту с 10-летним опытом, что такое JVM… Не все джависты, работающие в IDEA такие, но вот встречаются персонажи иногда.
Выбор IDE проблема, но только не для Vimеров :) Дальше будет позиционирование Vim, как хорошего инструмента. В принципе, вы можете заменить слово Vim на emacs или любой другой популярный текстовый редактор. Смысл не потеряется.
Начиная работать с новым языком программирования или фреймворком, первое, что я делаю — это ставлю нужные плагины на Vim, ограничиваю в ~/.vimrc область применения этих плагинов, чтобы они включались только в случае открытия файлов с нужным расширением, например. И радуюсь жизни. Если честно, то не всегда мне удаётся сделать идеальную среду разработки для себя. Порой даже получается, что и Vim начинает тормозить или как-то вести себя неадекватно. Но со временем текстовый редактор становится беспрецедентно удобным для разработки на том или ином языке программирования. Учитывая, что у меня есть доступ фактически в любое место моей IDE (все плагины, которые я использую опенсорсные), для удобной работы моей нужно только время, когда я сам пойму, как именно хочу работать с этим языком программирования. И реализую это.
Преимущество Vim в данном случае ещё то, что процесс компилирования и сборки не спрятан от тебя в недрах IDE. Это открытый процесс, которым ты опять же сам управляешь.
Пойнт 1, чтобы не сойти с ума: используй открытый текстовый редактор с открытыми плагинами. Имея возможность контролировать IDE, ты будешь лучше себя чувствовать при работе с несколькими языками.
Сборка
Естественно, у каждого из перечисленных языков программирования свой процесс сборки.
- для Go — это компиляция и запуск веб-сервера;
- для Ruby — это просто запуск веб-сервера;
- для JS — это транспилирование кода проекта. Не знаю, насколько термин транспилирование применим сейчас, учитывая, что большинство браузеров уже понимают ES6. Но пусть так останется.
Проект поставляется директориями, в каждой из которых описан свой Dockerfile для сборки сервиса. Безусловно, это лучший способ поставлять в команде проекты сегодня. Docker и docker-compose гарантируют однообразность окружения для запуска в development, staging, production, test и других окружениях, если они есть в вашем проекте.
При работе над проектами, я всегда поддерживаю, когда команда использует Docker в разработке.
Но все люди разные, и я тоже разный :) Мне не нравится использовать какие-то лишние ресурсы на запуск проекта в контейнере на моей рабочей тачке. В мире, где, чтобы держать открытыми Telegram, Slack, браузер и проект нужно минимум 16 гб, мне хочется максимально облегчить жизнь своему лэптопу и не слушать каждый раз, как он взлетает при сборке контейнеров.
Поэтому я всегда отдельно для себя держу Makefile для сборки каждого сервиса на своей тачке. В каждом Makefile безусловно есть инструкция install, которая устанавливает на машину все нужное для запуска, инструкция start, которая запускает сервис, ну и другие инструкции по надобности.
Поддержка актуальности Makefile вместе с Dockerfile — это моя личная ответственность.
Что мне это даёт?
- экономию ресурсов. Сейчас поклонники Docker скажут: “там ресурсы не тратятся”, “это же не в VM”, “ты просто не умеешь готовить”. Отвечаю им всем: “идите лесом”. Это моё окружение, мне удобно работать так. И да, ресурсы тратятся. Когда запускаю на своём линукс лэптопе контейнеры можно либо вместе с ним взлететь к Марсу, и гордо крикнуть: “Как тебе такое, Рич Хикки?!” (просто неймдроппинг, не последний раз), либо пожарить на нём яичницу, чем с удовольствием бы занимался, если бы процессор моего лэптопа не находился бы в районе клавиатуры;
- простоту отладки. Контейнеры — это дополнительная абстракция. Не всегда нужные мне для отладки тулы (а порой это бывают очень экзотичные вещи) могут чётко работать с докером. Настройка этих тулов очень часто отнимает очень много времени. Я это время лучше на фичи потрачу;
- ускорение разработки. Даже с SSD контейнеры стартуют на 5–10 секунд дольше, чем те же приложения на чистой тачке, стопятся на 10–20 секунд подольше. Стартую и останавливаю сервер я 40–50 раз за рабочий день, считайте.
Да, я лишён радости чистого окружения на рабочей машине и отсутствия мусора. Но я же не на маке работаю, где у большинства 256 гб SSD на всё. У меня SSD на 500 ГБ и HDD на 1 ТБ. Ни разу не заполнялось полностью место. Так что, до лампочки.
Повторюсь, я обожаю Docker в тестовом, стейджинг и продакшн окружении. И поддерживаю коллег, которые используют его в development. Это лично мой загон, так что нет смысла его комментировать.
Пойнт 2, чтобы не сойти с ума: твоё окружение — твои правила. Нравится работать в докере, работай в докере. Нравится работать в винде, работай в винде. Не ведись на то, что в твиттерах пишут высоколобые разработчики, делай, как нравится. НО! Своё окружение поддерживай сам. Твоя команда не должна страдать из-за того, что ты мудак.
Линтеры
Нужны, как никогда. Когда ты пишешь код на разных языках, будучи фулстеком, часто случается так, что большие фичи требуют изменений сразу в нескольких сервисах. В итоге может получится так, что ты с разницей в 3 минуты написал код на трёх разных языках программирования. И тут может начаться путанница: начиная от использования разного стиля форматирования, заканчивая применением синтаксиса одного языка в коде другого.
Знаете, сколько раз я написал в Ruby строки типа вот этой:
let myVariable := some_function argument
и задавался вопросами в течение пары секунд: где тут ошибка? И почему так косо всё выглядит?
Для тех, кто ещё не улыбнулся, опишу, почему всё так.
Это потрясающий пример применение смежного синтаксиса при работе над двумя и более языками программирования. Смежный синтаксис — очень популярный термин, придуманный мной специально для этой статьи. Нигде его больше не было.
- ключевое слово let это у нас из Javascript;
- myVariable название явно написано в CamelCase стиле, который не подходит для Ruby, а больше подходит для Go и JS;
- := это присвоение в Go, но не в Ruby;
- some_function argument но, слава Столлману, здесь хотя бы похоже на рубишный код: snake_case стиль, да и скобочки опущены.
В каждом типе файлов Vim использует линтер для этого языка программирования со сложно описанными правилами. Эти линтеры обращают внимание на форматирование, использование или неиспользование переменных (в Go это вообще компилятор делает, Андрей Минкин мне подсказал, что чаще в проекты перетаскиваетя gofmt и goimports, и они за это отвечают), именование переменных, немного прекомпиляции. В итоге большинство ошибок фиксируется в IDE сразу после того, как я написал. Экономит много времени. Хотя даже при таком раскладе, иногда линтеры пропускают какую-нибудь дичь.
Пойнт 3, чтобы не сойти с ума: позволь окружению помогать себе в работе. Чем больше оно помогает, тем больше нервов у тебя в порядке остаётся.
Тесты
И тут встаёт вопрос: на каком языке писать тесты?
Тесты в подобных ситуациях должны быть главными помощниками в работе функциональности. Все тесты у меня лежат в отдельной директории. Тестов, тестирующих отдельно сервисы, просто нет (почти нет).
Почему именно так? Почему не написать тесты для каждого из сервисов и отдельно тестировать его?
Отвечаю. Наша задача делать функциональность, а не код писать. Поэтому тесты должны работать именно с функциональностью. В этой связи пишу тесты только двух видов: end-to-end и интеграционные. Нагрузочное тестирование для проекта пока не нужно. С этой проблемой мы столкнёмся позже, и будем решать её позже.
End-to-end тесты всегда проверяют работу всех сервисов одновременно, интеграционные тесты проверяют работу отдельно бекенда. На фронт отдельно тестов не писал. У нас в проекте в данный момент нет компонентов, которые прямо отдельно стоит тестировать. А вся остальная функциональность на фронте проверяется end-to-end тестами.
Тесты я пишу на самом подходящем языке из трёх перечисленных языков для тестирования. На Ruby. У него есть интеграции со всеми нужными мне инструментами, а так же DSL, который позволяет писать тесты быстро и качественно.
Пойнт 4, чтобы не сойти с ума: пиши тесты!!! Старайся большую часть времени уделять end-to-end и интеграционным тестам.
Пойнт 5, чтобы не сойти с ума: пиши тесты на одном языке. Это позволит тебе относится к тестам, как к отдельному сервису, который надо поддерживать, как отдельный сервис. Поддерживать именно с точки зрения распределения твоего времени на сервисы.
Нейминг
Твои сервисы общаются между собой. Всегда сохраняй нейминг одних и тех же сущностей между сервисами. Главная проблема того, когда фронтендер делает фронт, а бекендер делает бек — это разница нейминга по сути одинаковых сущностей. Любой специалист, который работал в проекте в качестве бека или фронта, понимает, о чём я говорю.
Часто происходит, когда бекендер предоставляет тебе API, а там названия полей не такие, как у тебя на фронте в моделях (или что ты там используешь). И это нормально, ты делаешь функциональность на своём уровне абстракции, бекендер на своём. Правда, это приводит к тому, что в проекте появляются волшебные функции вида: convertResponseToModel, в которых просто названия полей в ответе меняются на названия полей в модели, которую использует фронтенд.
Чтобы этого избежать, когда ты на проекте пишешь все сервисы, используй одинаковые названия во всех местах. Не только в тех, где твои сервисы общаются. Когда создаёшь новую функцию, задумайся на секунду, а не является ли эта сущность по сути повторением какой-либо сущности в другом сервисе, и наоборот. Если да, назови её так же.
Правда тут на пути возникает другая проблема. JS и Go любят нейминг в CamelCase, Ruby — в snake_case. Я вышел из этой ситуации. Использую спецификацию JSON API. У каждого взрослого фреймворка в вебе, будь то бекенд или фронтенд есть либа для поддержания работы с этой спецификации и конвертирования именования в нативный для языка программирования формат. Если такой либы нет, она пишется очень быстро на динамических языках, чуть-чуть подольше на статических. В итоге ты как программист можешь использовать в разных сервисах нейминг, родной для того или иного языка программирования.
Пойнт 6, чтобы не сойти с ума: думай, как следует над именованием сущностей. Скорее всего в разных сервисах ты работаешь с одними и теми же сущностями, так и позволь им называться одинаково.
Используй простые и проверенные решения
Тут как-то возник вопрос, как организовать общение между Rails и Go серверами. И тут я вдохновлённый статьёй Андрея Минкина Go. REST или gRPC? пошёл искать имплементации gRPC на Ruby для Rails. Найдя пару неработающих из коробки вариантов, осознал, что потратил больше половины дня на то, чтобы просто найти работающий инструмент. Дальше пошла пляска с grpc-gateway — ещё полдня на поиск работоспособного инструмента.
Я никогда не беру инструменты, которые не заводятся из коробки. Это связано с тем, что если этот инструмент нужно поддерживать при запуске, непонятно, сколько же времени уйдёт на его поддержание в момент разработки функциональности.
Потратив день на изучение супермодного и крутого протокола передачи данных, я-таки вернулся к REST и за оставшийся день реализовал всю нужную мне функциональность. Вот вообще без проблем. Это связано с тем, что инструменты, которые поддерживают REST стабильные и старые, поэтому с ними проблем быть не может. А кейсы у меня не особенные.
Считаю, что когда работаешь с одним языком программирования или фреймворком, поковыряться в новой прикольной тулзе или протоколе — это в радость, только. Если даже она будет хреновая и плохо работать в дальнейшем, твоя задача будет поддерживать только её, а это возможность пушить в OpenSource, создавать issues и делать другие прелести жизни. Но, когда ты работаешь на нескольких языками программирования одновременно, поддерживать плохо работающие тулзы в разных местах проекта, тем более со сменой контекста — это путь в никуда. Если ты работаешь одновременно на двух или более языках, значит, ты фулстек. Будь добр, делай продукт, а не в игрушки играй.
Пойнт 7, чтобы не сойти с ума: используй проверенные инструменты. Времени играть в игрушки у тебя, к сожалению, нет.
Вывод
Вот, наверное, 7 пунктов, которых нужно придерживаться, чтобы успешно работать на двух и более языках программирования одновременно. Этот материал не претендует на истину в последней инстанции. Это лишь описание рабочих моментов, которые лично мне помогают выполнять свою работу.
Почему стоит идти работать в Mad Devs я сам лично писал в этой статье. В этой статье НЕТ МАРКЕТИНГОВОГО БУЛЩИТА! У нас вообще его нет :)
Ранее статья была опубликована тут.