«Опенсорс-проект — это маленький инженерный храм». Как Software Engineer разработал и развивает свой Python-фреймворк на 500 тыс. скачиваний

Technical Leader в SoftServe Роман Могилатов в IT уже 14 лет. Он начинал свой карьерный путь в Украине, последние три года живет и работает в США. Роман был техлидом в продуктовых и аутсорсинговых компаниях, имел опыт в стартапе. Параллельно с основной работой он развивает фреймворк Dependency Injector. Занимается этим проектом уже 6 лет. Сейчас у фреймворка 550 000 скачиваний в месяц, документация и статьи о проекте находятся на первых позициях в поиске по запросу «Python dependency injection».

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

— Как возникла идея создать фреймворк Dependency Injector?

Мне хотелось сделать вклад в переиспользование кода.

За время своей работы в ІТ я часто сталкивался с необходимостью внесения изменений в уже написанные решения. Софтвер, который мы используем, изменяется каждый день. Насколько легко вносить эти изменения? Делая это, можно ли переиспользовать те части системы, которые не изменились, или нужно переписывать все заново? Мне приходилось много заниматься системами с высокой степенью связности (high coupling) и наблюдать, как небольшие функциональные изменения приводят к переписыванию большей части кода. Высокая связность мешает переиспользовать код, и это проблема.

Часто большие проекты, на разработку которых потрачено много «человеко-лет», невозможно модифицировать, их приходится переписывать с нуля. Мне хотелось найти решение, которое позволило бы экономить «человеко-годы», которые мы, вся наша индустрия, тратим на переписывание уже существующих вещей просто потому, что какие-то 5–10% системы невозможно изменить изолированно.

В этой борьбе с высоким связыванием я и познакомился с принципом Dependency Injection. Он позволяет разрабатывать системы с низкой степенью связности (low coupling) и высоким сцеплением (high cohesion). Это увеличивает гибкость и помогает переиспользовать код.

Концепция Dependency Injection придумана не мной, но я начал создавать библиотеку, инструмент, который помогает реализовывать этот подход в Python. Так Dependency Injector и появился.

Dependency Injector — Dependency Injection фреймворк для Python. Он помогает разрабатывать системы с низким связыванием и высоким сцеплением. А в конечном итоге это проект, который дает возможность переиспользовать код.

— Почему для решения этой проблемы нужен отдельный фреймворк?

Вопрос «зачем делать фреймворк, это же так просто» звучит довольно часто. Но, на самом деле, когда начинается разработка, оказывается, что все-таки нужно написать код, какой-то «микрофреймворк» для Dependency Injection. Он появляется внутри каждого проекта. Этих «микрофреймворков» может быть сто, тысяча... И каждый раз, когда разработчик будет переходить на новый проект, с которым уже кто-то работал, то будет разбираться с новым кастомным решением, написанным под него, либо добавлять свой код (с прошлого проекта). Это все нужно тестировать, документировать и так далее. То есть мы опять возвращаемся к теме переиспользования кода.

Dependency Injector — это инструмент, который решает достаточно простую задачу, но решает ее хорошо. Разработчики могут использовать готовый модуль, он уже протестирован (в Dependency Injector сейчас больше тысячи юнит-тестов, которые запускаются при каждом релизе), и по нему есть много примеров. С одной стороны — разработка микрофреймворка для Dependency Injection кажется простой задачей, таковой и является на первых порах, но с другой — со временем придется строить более сложные графы объектов, поддерживать многопоточность и асинхронность. В Dependency Injector эти проблемы уже решены. Проекту 6 лет, и он поддерживает много use case’ов. Провайдеры, контейнеры, метаклассы — там тоже есть вещи, которые можно переиспользовать. Dependency Injector — это универсальное решение, а не кастомное.

— Нужен ли такой фреймворк для других языков программирования?

Dependency Injection — это подход, который не зависит от языка программирования и даже не зависит от направления инженерной деятельности. Это касается инжиниринга в любом его проявлении: можно делать системы, которые разбираются легко, и системы, которые разобрать сложно.

Сам подход DI получил широкую известность после статьи Мартина Фаулера. Инженер описал этот принцип и показал его применение в Java, где тот до сих пор очень популярен.

Мне кажется, что популярность Dependency Injection в каком-то языке программирования связана и с популярностью самого языка. В то время, когда Java была абсолютным мейнстримом, мы понимали Dependency Injection как ее неотъемлемую часть. Сейчас JavaScript вырвался вперед и составил сильную конкуренцию Java. И теперь мы говорим об этом подходе в мире JavaScript: там тоже появляются соответствующие фреймворки и решения. Dependency Injection — популярная штука в AngularJS, React.js и так далее.

В мире Python сейчас тоже растет интерес к этой теме. Я сделал документацию для своего Dependency Injector, выложил ее на сайт Read the Docs. Там есть возможность подключать Google-аналитику. Соответственно, у меня есть данные о том, откуда люди приходили в документацию, какие запросы они использовали в search-консоли за последние 6 лет.

Немного цифр. За последний год количество пользователей, пришедших в документацию Dependency Injector, выросло на 65%, а количество просмотров страниц — на 145% и составило 184 000. Такая тенденция. Что-то изменилось в течение года: возможно, это связано с тем, что Python 2 уже считается неподдерживаемым языком, в 2020 и 2021 годах все мигрируют на Python 3. В последнем есть вещь, связанная с типизацией, то есть типизацию можно использовать опционально. И мне кажется, что рост интереса к теме Dependency Injection в Python может быть связан с этим.

— Бывают случаи, когда подход Dependency Injection не стоит использовать?

Не всегда системы, которые сложно разбирать, плохо сделаны. Есть системы, которые лучше ни при каких обстоятельствах не разбирать, у них есть свои положительные характеристики, они обладают большим перформансом и так далее.

Все мои статьи о Dependency Injector заканчиваются тем, что в первую очередь нужно использовать здравый смысл. Представьте себе такую историю: вы покупаете в магазине телефон, приносите его домой, распаковываете коробку, а там лежит 250 элементов: камера, кнопка левая, кнопка правая, дисплей, динамик... Все в виде отдельных деталей. Вы берете инструкцию и начинаете собирать телефон. Этот процесс занимает какое-то время. Кто-то скажет: «Класс!», но, скорее всего, большинству пользователей это не очень понравится.

Такая же история с Dependency Injection: если разобрать систему на очень много отдельных компонентов и построить ее полностью по принципу внедрения зависимостей, с ней будет неудобно работать. При слишком высокой гранулярности вы утонете в деталях. Можно разбить систему на 2 компонента, можно на 4, можно на 224. Если вы пишете простую программу и ее разработка начинается с того, что нужно выделить 224 компонента, то у вас должны быть веские обоснования для такого решения.

— В статьях о Dependency Injector вы пишете о том, что ваш фреймворк построен по принципу «Explicit is better than implicit» (явное лучше неявного). Что это значит?

«Явное лучше неявного» — один из принципов Zen of Python. Zen of Python — это набор правил для Python, которые, наверное, больше стоит воспринимать с философской точки зрения. Но часто они помогают решить конструкторские противоречия или по крайней мере продвинуться в их решении.

Если говорить более детально, то Dependency Injector — не единственный DI-фреймворк или библиотека для Python. Большая часть фреймворков построены по такому принципу: есть софтвер, который вы разрабатываете, вы применяете DI-библиотеку, и она встраивается в каждый компонент системы. Потом ваша система без этого фреймворка уже не работает.

Идея Dependency Injector и то, чем он существенно отличается от других библиотек, — не внедряться внутрь вашего кода, не «загрязнять» код приложения фреймворком. Dependency Injector используется как бы поверх приложения, он не встраивается в него, не делает себя зависимостью, без которой программа не сможет больше работать.

Если библиотека для Dependency Injection не встраивается внутрь приложения, а накладывается поверх, то все связи между компонентами видно явно. Основа Dependency Injector — это контейнер зависимостей. В нем указаны все компоненты, их классы, зависимости и время жизни. Контейнер зависимостей — это своего рода карта компонентов приложения. Посмотрев на него, можно сформировать впечатление о том, как работает система, как выглядит ее архитектура.

Но у всех вкусы разные: для некоторых то, что связи явные, — это хорошо (как, например, для меня), а для кого-то — это слишком много деталей. Если вернуться к примеру с телефоном, то, как правило, пользователь не хотел бы собирать его из 250 компонентов, а предпочел бы просто вставить сим-карту и начать использовать. Здесь нет правильного ответа, все зависит от предпочтений разработчика.

— У вас есть статистика, с каких стран люди чаще всего используют ваш фреймворк?

Есть несколько метрик, по которым можно оценивать популярность опенсорс-проекта. Первое (и, наверное, это самое важное) — количество скачиваний. Общее количество скачиваний Dependency Injector — 550 000 в месяц. Но с этой метрикой есть некая сложность, потому что Python Package Index, его официальный веб-сайт, не дает встроенной аналитики, которую можно посмотреть. Они выгружают эту информацию в third party базу данных. Я использую third party-проект PePy, в котором можно посмотреть скачивания по версиям библиотеки, по версиям Python. Но я не видел там аналитики, которая бы делала это по странам.

Вторая метрика — это документация. Фактически эти метрики коррелируют: количество скачиваний и количество пользователей, которые приходят в документацию. Обычно, когда люди начинают использовать фреймворк, у них возникают какие-то вопросы и они читают инструкцию. Google-аналитика прекрасно показывает, откуда приходят пользователи: США — 20%, Германия — 7%, Россия — 6%, Индия — 5%... Если посмотреть на карту, то везде, где живут и работают софтвер-инженеры, она покрыта. Всего стран, откуда приходили пользователи, 160, если исключить те, из которых было меньше 100 пользователей — 46.

Как еще узнать, откуда приходят люди и как применяют Dependency Injector? Частота использования библиотеки в опенсорсе точно коррелирует с тем, как часто вам приходят баг-репорты на GitHub. Если вашей библиотекой кто-то пользуется, она точно будет ломаться.

Dependency Injector — это своего рода конструктор. Его невозможно протестировать на 100%. В какой-то момент мне начали приходить неожиданные баг-репорты. По мере того как ссылка на Dependency Injector поднималась в поисковике Google на первое место, их становилось все больше и больше. Я отмечаю все баги на GitHub специальным тегом, статистику по ним еще не делал, но сейчас, если придет одна ошибка в неделю, — это хорошо. Меньше одного бага или вопроса в неделю бывает редко. Когда начинаешь общаться с людьми, которые задают вопросы, оказывается, что они из Amazon, Google, Facebook, Raiffeisen Bank, Visa, Mastercard... Такая корпоративная география.

— Dependency Injector скачивают 550 000 раз в месяц. Это количество новых пользователей или, возможно, кто-то скачивает обновления несколько раз в месяц?

550 000 — это общая сумма всех скачиваний. Сейчас в большей части систем сборка автоматизирована. Проект может собираться десятки раз в день, и каждая из этих сборок требует скачивания зависимых библиотек, в том числе Dependency Injector. То есть эти цифры нельзя относить к тому, что где-то сидит 550 000 человек и раз в месяц скачивает фреймворк.

Я не могу точно сказать, что есть пользователь Роман, Николай и другие, потому что в этой системе нет какой-то регистрации, аккаунтов. Это открытый исходный код. Количество тех, кто пользуется документацией, можно сопоставить з количеством реальных юзеров. Если посмотреть за последние 365 дней, то было 34 000 уникальных пользователей. Это привело к 184 000 просмотров страниц. Примерно такой порядок цифр.

Стоит сказать, что у аналогов Dependency Injector скачиваний меньше. Например, у Pinject (аналога от Google) 80 000 скачиваний в месяц. В GitHub есть своя метрика — это звездочки (что-то среднее между лайками и закладками). И там среди других соревновались это два фреймворка. Какое-то время гугловский аналог был популярнее, чем Dependency Injector, но потом тренд поменялся, и в этом году Dependency Injector его уверенно обогнал.

График роста звездочек на GitHub у Dependency Injection фреймворков для Python

— Какие трудности были в работе над Dependency Injector?

Самая большая трудность — это отсутствие времени, чтобы заниматься фреймворком. Проект с открытым исходным кодом несмотря на то, что приносит какую-то живую пользу, используется в компаниях с мировым именем, — полностью «нон-профит» и «нон-комершал», все держится на идее. То, что сейчас лежит на GitHub и в документации Dependency Injector — это сотни, может даже тысячи часов работы по вечерам и в выходные. Это непросто. Иногда приходится меньше спать, отдыхать, меньше времени проводить с семьей.

— Вы не думали о том, чтобы как-то монетизировать свой фреймворк?

Модель, которая, наверное, могла бы подойти Dependency Injector — это платный саппорт. Но, честно говоря, я пока далеко не копал в этом направлении.

Все опенсорсные лицензии говорят о том, что вы используете софтвер с открытым кодом на свой страх и риск, грубо говоря. Договоренности с комьюнити держатся на честном слове, на репутации. Когда вы начинаете оказывать платную поддержку, у вас появляются точные обязательства: например, ответить на проблему, пофиксить баг в течение такого-то времени, выпустить новую платную версию и так далее. Эта работа перестает быть тем самым опенсорс-проектом с духом свободы и открытости.

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

Мне писали с платформы Tidelift, предлагали оформить Dependency Injector как проект Tidelift и осуществлять на нем платную поддержку. Я особо не вникал в предлагаемые условия, отказался. Пока не готов делать из этого работающий бизнес. В течение всей своей карьеры я работаю как наемный сотрудник, софтвер-инженер. Думаю, что для перехода в статус человека, который создает бизнес, фреймворка Dependency Injector пока маловато. Возможно, в какой-то момент эта трансформация произойдет, но не завтра.

— Ваш проект опенсорный. Насколько активны были другие контрибьюторы в разработке системы?

В основном разработкой Dependency Injector занимаюсь я сам. Но GitHub трекает всех контрибьюторов, которые вносили какие-то изменения, даже однострочные. Таких у фреймворка сейчас 17. Кто-то пришел и поправил опечатку, сделал однострочный или однобуквенный фикс, и это очень приятно, человек потратил какое-то время, разобрался в чужом проекте, помог.

Я начинал разработку на СPython. Между 2 и 3 версиями Dependency Injector я перешел на Cython. Это позволяет писать похожий на Python код, но он компилируется в C код и позволяет потом скомпилировать это в машинный код. Это хорошо подняло производительность проекта, но увеличило порог входа для контрибьюторов. Cython — далеко не мейнстримовая технология, для того чтобы на нем писать, нужно обладать определенной квалификацией, понимать, как он работает. Чтобы найти сейчас полноценного контрибьютора, который мог бы поддерживать проект и дописывать в нем новые фичи, нужно постараться.

Но самые классные вещи начали происходить буквально несколько месяцев назад. Я общался с одним из своих бывших коллег, который разобрался в Dependency Injector, и задал мне очень точные вопросы с глубоким пониманием того, как фреймворк написан. Кроме того, из комьюнити пришел человек, тоже пользователь инструмента. Он предложил разработать новый компонент. Я согласился, и теперь один из компонентов Dependency Injector полностью создан сторонним контрибьютором на Cython. Для меня это большая поддержка: я почувствовал силу сообщества, когда приходят люди, которые хорошо разбираются с технологиями, и помогают проекту.

Был еще один интересный запрос на изменения, он звучал примерно так: «Мне нравится проект, но придирчивость к деталям заставила меня сделать этот pull request». Он был о главной странице Dependency Injector на GitHub, и там были исправления, связанные с моим неидеальным английским. Pull request, конечно, принял, поблагодарил контрибьютора и пошел учить артикли.

То есть контрибьюшен есть. Раньше он был «косметический», сейчас начал переходить в полномасштабную помощь.

— За последний год вы сделали много коммитов. Что именно улучшали в фреймворке?

Появилась новая фича — связывание (wiring). Она заключается в том, что Dependency Injector стало гораздо легче интегрировать с другими фреймворками. Если посмотреть на него в общем, то довольно сложно понять, что это такое, зачем он нужен, как это применять. Но если сделать пример приложения с каким-то фреймворком (Flask, FastAPI, AIOHTTP, Django, etc), то человек может увидеть, как это приложение работает, какая прикладная польза от библиотеки, легче делать свое приложение по готовому примеру. Все эти примеры доступны в документации и в репозитории.

Эти интеграции вызвали высокий интерес к Dependency Injector. Пользователи начали с ними работать, что-то где-то ломалось, они просили починить или улучшить. Все это входит в циклическую зависимость: более высокий интерес к проекту ведет к улучшениям интеграций и других фич, не связанных с интеграциями. Люди начинают более широко смотреть на проект, применять его где-то еще.

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

Есть случаи, когда сообщали о проблеме, что Dependency Injector не работает на AWS Lambda. Раньше я никогда не пробовал его там запускать. Оказалось, что есть определенные особенности, которые приводили к неожиданному поведению. Пришлось чинить. Оказывается, люди такое делают. Или Dependency Injector не работает в Anaconda, именно установка. Или обращался инженер с Apple: они собирали что-то под ARM-процессоры и просили сделать билды под ARM.

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

— Как быстро вам удается отвечать на заявки с проблемами?

Бывает по-разному. Иногда приходят простые вопросы. Возможно, ответ уже описан в документации, и я просто даю ссылку. На такие заявки я могу ответить в течение 15 минут.

Если это более сложный сценарий: например, человек приходит с проблемой и куском кода из 50–100 строк, где что-то не работает. Перед тем как отвечать, я воспроизвожу у себя на компьютере проблему или ситуацию, пытаюсь понять, как с этим справиться. Потом, если это баг, исправляю его. Если ошибку исправить просто — это может занять 15 минут, если сложно — несколько дней. Есть тикеты, которые висят месяцами, так как не имеют простого решения.

Раньше фичи в Dependency Injector появлялись исключительно из моей головы, потом — из идей узкого круга людей, от которых я получал фидбэк. Теперь (с приходом условной «популярности» к проекту) все фичи появляются от комьюнити. У кого-то что-то не работает, где-то находится баг... Люди показывают, как они используют фреймворк, я делаю из этого выводы, думаю, как сделать удобней, добавляю новую фичу и описываю это в документации.

— Кроме вопросов и сообщений о каких-то проблемах, вы получаете положительные отзывы от пользователей? Или обычно люди пишут только в тех случаях, когда что-то идет не так?

Как правило, своих счастливых пользователей вы не знаете. Знаете тех людей, у которых что-то не работает, это правда. Но разговор о вопросах или проблемах обычно начинается (или заканчивается) фразой «Спасибо за классно сделанный проект!» (или что-то другое в этом духе). И это мотивирует. Конечно, в какой-то степени это дань уважительному общению в мире опенсорса. Тем не менее, даже если что-то не работает — это не обязательно негативный отзыв. Более того, я стал любить баг-репорты. Во-первых, если они есть, значит проект живет, им пользуются. Во-вторых, за каждым баг-репортом следует фикс, и это поднимает качество продукта для всего комьюнити. Мне как человеку продукт-ориентированному это доставляет радость. Благодарен всем пользователям, которые нашли время написать об ошибках или задать вопрос. Пишите еще.

Есть смешная история про то, как познакомиться с теми самыми счастливыми пользователями. В какой-то момент в Dependency Injector пришел человек, у которого был сложный кейс и что-то не работало. Мы разобрались, в чем проблема, исправили и залили релиз на менеджер пакетов PyPI, чтобы он был доступен всем пользователям. Часто люди при использовании библиотеки не закрепляют жестко текущую версию, а оставляют версию с открытым номером. Обновление может произойти практически при каждой пересборке проекта.

Утром я проснулся и увидел, что у меня на GitHub появилось неожиданно много звездочек. Обычно приходит 1–3 звездочки в день, а тут 25 или 30. Как оказалось, «скачек» был связан с тем, что в процессе прошлого фикса появился другой баг, который влиял практически на всех пользователей. Пока я спал, у всех обновилась версия фреймворка и все сломалось, юзеры увидели этот баг и пришли попросить его починить. Заодно поставили какие-то дополнительные звездочки. Получилось, что случайно зарелизенный баг привел к скачку популярности проекта.

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

— Как вы продвигаете свой фреймворк?

Первый этап — это прикладное применение. В компаниях, в которых я работал, я использовал свой фреймворк, делился идеями с коллегами, показывал, как он функционирует. Если в команде 10 человек, то с большой вероятностью через несколько лет они разойдутся по 10 разным компаниям. Когда специалист переходит из одной компании в другую (или из одного проекта в другой), он всегда приносит с собой какие-то технические привычки, библиотеки, которые ему нравились и так далее. То есть это ведет к какой-то популяризации — маленькой, локальной, но ведет.

Достаточно много писал о фреймфорке: материал Dependency injection and inversion of control in Python, статьи на DOU, на «Хабре», Medium, Reddit. Кстати, через одну из публикаций на DOU я познакомился с ребятами из Scala-комьюнити. Они разрабатывают DI-фреймворк для Scala, и у нас была онлайн-встреча с техлидом сообщества. Мы довольно продуктивно побеседовали. Несмотря на то, что я не знаю Scala, несколько дельных советов и фич пришли в Dependency Injector именно после этого разговора. Получилась интересная коллаборация. Спасибо ДОУ и ребятам из Scala-комьюнити.

Еще один способ популяризации — публичные выступления. У меня их было немного: несколько раз готовил доклад внутри компании SoftServe, выступал на KharkivPy, рассказывал там о проекте. Хотелось бы в будущем еще где-то принять участие, в каких-то украинских или американских конференциях.

В целом опыт создания фреймворка очень ценен для меня. Кроме разработки, это опыт решения проблем, связанных с продакт-менеджментом и маркетингом: мне приходится решать, какие фичи добавлять, какие не добавлять, думать, какие действия к чему приведут, как заинтересовать пользователей. Мне нравится производить, нравится оптимизация, эффективность. Dependency Injector — не панацея, не решение, которое спасет мир от глобального потепления, но связность кода он снизит и код переиспользовать поможет. Возможно, это небольшой, но все же шажок в сторону экономии ресурсов и увеличения эффективности.


P. S. Если вы размышляйте над созданием опенсорс-проекта, лучшее, с чего можно начать, это небольшой прототип. Сформулируйте, какую проблему вы решаете, и сделайте что-то работающее. Когда прототип будет готов — покажите коллегам и получите фидбэк. Обратная связь — главное, что должно стимулировать развитие проекта. Еще, конечно, нужно найти время. Тут простого решения нет, проект сам себя не сделает. Хорошая новость в том, что есть комьюнити. Опенсорс — это коллективная работа. Найдите единомышленников и организуйте процесс. Главное, что я понял за 6 лет работы над Dependency Injector — это сила маленьких шагов. Небольшие ежедневные изменения имеют сильный кумулятивный эффект. Опенсорс-проект — это отличный способ прокачать технические скилы, продуктовое мышление и расширить круг знакомств. Пробуйте! Успехов!

👍НравитсяПонравилось25
В избранноеВ избранном3
Подписаться на автора
LinkedIn



Підписуйтесь: Soundcloud | Google Podcast | YouTube


4 комментария

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

Опенсорс це сила, особливо в глобальному масштабі

График роста звездочек на GitHub у Dependency Injection фреймворков для Python

видно как гугловые накрутили сразу себе где-то 300 звездочек на старте

Достаточно много писал о фреймфорке: материал Dependency injection and inversion of control in Python, статьи на DOU, на «Хабре»

Особисто я дуже рідко використовую хабр і тут ключове слово «використовую». Публікуватись на хабрі — це по суті вже його розвивати, займатись колаборацією з окупантом. Що ви відчуваєте, коли публікуєтесь на хабрі, де Крим вказано як російська територія? Чи вам просто «так удобно», і ви так само думаєте, як ани лорак, светлана лобода... макс ищенко що С РФ можно сотрудничать?

Крутая история, очень интересно. Коммьюнити это сила 👌

Подписаться на комментарии