Хабрахабр

Бытие современного фуллстек-разработчика

И на периферии в географическим смысле. Я живу на периферии технологической тусовки. А это значит, что:

  • Я никогда не был на профессиональных конференциях. Просто потому, что никогда не предоставлялось такой возможности.
  • Я никогда не покупал обучающие курсы: для меня странно платить за то, что можно изучить самому в этих ваших интернетах или по книгам. Заманчиво, конечно, получить концентрированные знания, подкрепленные выполнением практических заданий, заполучить сертификат. Но на это у меня никогда не было ни средств, ни времени.
  • Я адепт цифрового аскетизма: не по своей воле, но как порождение вечной перестройки в нашей стране. В короткие периоды финансовой стабильности я обновляю электронику по остаточному принципу. Вот и сейчас пишу этот текст на Core2Duo десятилетней давности. Все еще жду следующего стабильного плато на кривой моих доходов.
  • Раза три в своей жизни я покупал игры. В 90-е. Это были кассеты для отживающего своё Спектрума. На покупку ПО я смотрю с удивлением: когда все пользовались пиратским ПО, то и я пользовался, не понимая сути вопроса. А потом, в начале двухтысячных, полностью перешел на Linux и покупать стало нечего.
  • Удивительно, но я ни разу не терял мобильник, с самого начала появления GSM-связи. И они у меня никогда не ломались и не ремонтировались. Все три штуки стоят аккуратно на полочке, а четвертый, смартфон, лежит в кармане. Ему пять лет. Опыта покупки ПО в Google Play у меня нет, даже не представляю как это делать. Жена знает, а я — нет.
  • Я умоляю провайдеров внести меня в черный список и не звонить мне с предложениями «купить интернет» или пакет телевидения. Потому что у меня нет телевизора, а для интернета мне достаточно минимальной скорости. Я прошу не рассказывать мне про новые тарифы мобильной связи, потому что я выбираю самый дешевый из доступных.
  • Я никогда не видел в живую англоязычных иностранцев. И голосом никогда с иностранцами не говорил. Никогда не предоставлялось такой возможности в моем медвежьем углу, хотя было бы интересно. Я потихоньку прокачиваю свой английский, но он мне нужен только для чтения литературы.

Кто же я такой, и что я делаю на Хабрахабре? Наверно, достаточно. Другими словами, современный фуллстек-разработчик. Дело в том, что я… Я — перманентный выживальщик. Фуллстек… Что я им могу сказать? Ох, представляю, как поморщились сейчас профильные программисты! Рад бы был, как и вы, погрузиться в тему и становиться узкоспециализированным гуру. Ребята, я рад бы был прокачивать навыки в одном направлении. Но, к сожалению, реальность такова, что в регионах человеку на удаленке приходится хвататься за любую работу, лишь бы не идти аникеить или не становиться таксистом.

Если сделать выборку по шкале времени, то получится такой список: Какими языками мне приходилось заниматься, и выдавать законченные вещи?

Что за фееричные метания? Да, мне самому страшно от этой сборной солянки. Веб — прочностной анализ — складской учет — геймблинг… Атомные станции, картография, телефония. Вот прямо так? Абсолютли! Автор, ты серьезно? Жизнь прижмет — еще и не так раскорячишься.

Грань перехода

Для проекта из предпоследней строки "Картография и навигация, мобильная разработка под Android" обойдемся только ссылками. Чтобы прочувствовать грань перехода с проекта на проект, можно рассмотреть две самых нижних строки вышеприведенной таблицы.

И для полноты картины еще пара публикаций:

А про самую нижнюю строку таблицы "Веб-разработка и телефония" я расскажу далее в этом тексте.

Еще вчера разработчик писал бекэнд-код на C++ и фронт на QML, прикручивал нативный код к Java через JNI, а сегодня он судорожно должен искать вакансию C++ разработчика на удаленке. Итак, перспективный проект мобильного приложения под Android для кастомного навигационного оборудования резко закрывается. Все работодатели хотят видеть программиста C++ в офисе. Современный рынок C++ таков, что найти в России работодателя с C++ на удаленке — это большая удача. Пора переквалифицироваться, благо бэкграунд позволяет. Месяц в поиске — полный ноль.

Определение

Фуллстек-разработчик обладает магическими способностями: имеет бездонную память, ибо знает все современные языки и технологии; в мозг интегрирован глобальный понятийный аппарат, превосходящий по организации мыслительного процесса Владимира Ленина, Альберта Эйнштейна и Леонардо да Винчи; системное мышление такого специалиста способно производить дебаг чего угодно прямо в мозгу, без применения средств отладки. Фуллстек-разработчик (представитель семейства специалистов по всему) — мифический персонаж, предмет вожделения работодателя, мечтающего оптимизировать производство ПО до команды из одного человека. Неприхотлив, питается солнечным светом.

Переход на новую задачу

У него висит несколько задач, и на пробу я реализую прототип веб-сервиса. Неожиданно на меня выходит хабрапользователь itsar, и берет в свою команду. В результате появился сайт QrCall.org, про который уже писалось на хабаре: Поди туда — не знаю куда. По задумке, веб-сервис оповещает пользователей о различных событиях по различным каналам связи, включая телефонные звонки.

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

Чтобы было понимание, к сегодняшнему моменту этот веб-сервис выглядит так (девелопер-версия):

Времени на раскачку нет (С) Путин. Итак, нам нужно перепрыгнуть с C++ на PHP7 и соответствующий современным реалиям стек инструментов. Параллельно с написанием ТЗ я вспоминаю, что там понапридумывали в PHP7. Страуструп, Шилдт, Готтшлинг, Солтер с Клепером ставятся на дальнюю полку. Ага, пространства имен и импорт, новая разновидность тернарного оператора и всякий синтаксический сахар, скалярные типы и иже с ними, анонимные классы, доработка замыканий, генераторы… В большинстве своем все знакомо. Запрос "что нового в PHP7" дает несколько статей на Хабрахабре и в программистских блогах. В который раз отмечаю для себя, что PHP — это райское наслаждение по сравнению с суровыми плюсами.

Выбор инструментов

Телефония

Осилю ли я? Больше всего беспокоит будущая реализация телефонии. В мозгу болтается воспоминание, что когда-то игрался с каким-то консольным SIP-клиентом, и даже мог набрать телефонный номер и совершить звонок. Сразу понятно, что придется работать с SIP, но каким образом? В крайнем случае придется заморочиться с Asterisk. Для решения задачи этого будет достаточно. Вердикт однозначный — это Linphone и его консоль linphonec. Звоню знакомому связисту, описываю суть проблемы, прошу напомнить что за консольный клиент я мог щупать. Надо еще проиграть в виртуальную трубку звуковой файл. Но набрать номер в консоли — этого мало. Так, есть возможность переключиться со звукового дейвайса на файл. Устанавливаю Linphone, захожу в его консоль, смотрю возможности. И в консоли есть команда play, которая запускает звуковой файл на проигрывание. Это хорошо. В принципе, больше ничего и не нужно.

Обсуждаю её с itsar, он говорит что звонки-оповещения редкие, выстраивай их просто в очередь. Хотя нет, еще есть проблема параллельных звонков. Ну хорошо. Только длительность ограничь. Коли так, то по телефонии вопросов больше нет.

Фреймверк и пакетный менеджер PHP

Раньше я к Composer только присматривался и ставил через него Yii2 без компонентов, потому что в Yii2 все что нужно уже было включено. Далее нужно решить, на каком фреймверке делать проект, а заодно надо поразбираться с пакетным менеджером Composer. Читаю, как его устанавливать. Хорошо, какой бы фреймверк я не выбрал, Composer все равно понадобится. Ставлю, работает.

Выясняю, что в 2019 году Yii2 уже не актуален, а Yii3 застрял в каком-то промежуточном состоянии. Далее вопрос по фреймверку. Для Zend и Symphony я еще не созрел, поэтому вариантов практически нет — только Laravel. Что остается? После древнего Codeigniter и неактуального Yii фреймверк Laravel понимается легко, сразу видно как продвинулась программистская мысль в проектировании веб-приложений. Читаю документацию, смотрю обучалки, заказываю книжку русскоязычного автора (оказалась очень толковая, то что надо для старта). Да, проект планируется не нагруженным, так что могу себе позволить некоторую нубскую кривизну в реализации. Все, о чем мечталось уже реализовано, обкатано и обросло стандартными подходами.

Он ставит 5. Ставлю Laravel "по дефолту", предлагая Composer самому решить, какая версия Laravel в данный момент актуальна. Ну ладно, пусть будет эта версия, она более обкатана чем 5. 5. Мы не гонимся за нововведениями. 8, значит будет проще решать проблемы.

NPM

Js и написанный на JavaScript пакетный менеджер npm. Для работы некоторых компонентов Laravel, например для системы сборки и минификации CSS-файлов Mix (надстройка над Webpack), требуется серверная среда выполнения JavaScript-кода Node. Однако он достаточно древней версии, и не подходит для инфраструктуры Laravel 5. В Debian Linux Stable, который я использую, уже есть пакет npm. Разбираюсь, как ставить из сторонних источников, нахожу deb.nodesource.com, ставлю с него. 5. Js ставится и npm. Хм, странно, в том же пакете, вместе с Node. Главное, что работает. Это совсем не Unix-way, ну да ладно.

Верстка

А это значит, что посетители будут заходить на сайт с мобильных устройств, с помощью камер которых этот QR-код будет сканироваться. Идея проекта QrCall.org — вызов пользователя через QR-код. Значит, без адаптивной верстки не обойтись. В то же самое время, регистрация пользователя, настройка оповещений и печать QR-кодов, скорее всего, будет производиться с десктопных компьютеров.

Это не наш подход для 2019 года. Сразу отметаю генерацию мобильного/десктопного контента на сервере путем анализа UserAgent. Вообще, верстка веб-приложений — это отдельная, гигантская, большая тема, которой должен заниматься отдельный специалист. Тут однозначно нам поможет CSS-фреймверк Bootstrap. Я давно понял, что у меня версточный кретинизм. Для меня в веб-разработке нет ничего более сложного, чем ковыряния с версткой. Но ресурсов на верстальщика у нас нет, поэтому приходится делать как можешь, желательно чтоб результат был ровным и красивым. Я трачу колоссальное количество времени, чтобы сделать очередной правильный отступ, или выровнять несколько элементов.

3 или 4? Встает вопрос: какую версию Bootstrap использовать? 5, и это версия 3.x. Выясняется, что Bootstrap сразу идет в комплекте с Laravel 5. В конце концов, в интернете сотни тысяч сайтов используют Bootstrap 3, а значит это достойная для использования технология. Разбираться с тем, как переделывать окружение на Bootstrap 4 времени нет, поэтому оставляю версию 3.

Самое интересное, что в результате получилось сделать адаптивную верстку не только для "открытой" части сайта, но и для личного кабинета.

Вот как выглядит страница в десктоп-версии:

А вот она же в мобильном представлении:

Разработка

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

Фреймверк Laravel — это большой фреймверк с множеством реализованных абстракций и со своим подходом к структуре кода. О чем я говорю? Есть сложные, но понятные вещи, например, реализация очередей (которые придется использовать для телефонии). В нем есть простые вещи, давно и успешно применяемые как в Laravel, так и в других фреймверках. Например, это связка Сервис-контейнер + Сервис-провайдер + Фасад. А есть действительно сложные фундаментальные вещи, вникнуть в которые с наскоку не получится. Но для чего это нужно делать — я пока не осознал. К настоящему моменту я пока понял как чисто механически сделать Сервис-провайдер, разместить его в Сервис-контейнере и прикрутить ко всему этому фасад. И еще использование фасадов позволяет легко организовывать автоматизированное тестирование веб-приложения, а как побочный эффект от всего этого удобства, при использовании сервис-провайдера автоматизируется внедрение зависимостей. Вроде как этот подход сокращает код, можно обращаться к абстракции и ее методам в статическом стиле, не используя ключевое слово new (сомнительное достоинство). В общем, пока понимания нет, мне проще всего обходиться обычными классами-хелперами, что я и делаю.

Технологии

Итак, подытоживая вышесказанное, у меня получился следующий, вполне традиционный стек технологий:

  • Операционка: Linux (я выбираю Debian Stable)
  • Язык: PHP7
  • Фреймверк: Laravel
  • Фронтэнд-свистелки: Bootstrap 3 + Vue.js
  • База данных: MariaDB (она же MySQL)
  • Веб-сервер: NGinx
  • Телефония: SIP+linphonec/linphonecsh
  • VCS: Git

Но нам нужно делать дело, а не упражняться в оборачивании окружения в Docker-контейнеры и не продвигать идею JS-only разработки. Да, не самый модный и продвинутый стек.

Очередь

В нашем случае сотен серверов не наблюдается, поэтому необходимо использовать очередь максимально просто и по делу. Очереди — это весьма специфическая предметная область, и в крупных командах за очереди обычно отвечает специально обученный специалист, обеспечивающий работу очередей на сотнях серверов. Был некоторый соблазн сделать по уму, например на базе Redis, но разбираться с этой NoSQL БД времени просто не было. Поэтому в качестве хранилища заданий я решил использовать уже используемый в проекте движок реляционной базы MySQL.

Проблема была в том, что после успешного выполнения задания, задание из очереди удаляется, и нет никакой возможности проверить, выполнялось ли, например, определенное задание в течении последних 10 минут? В ходе разработки появился запрос на задачу, которую невозможно было напрямую решить средствами очереди на MySQL, но которая решалась бы при использовании Redis. Поэтому пришлось реализовывать подобный функционал просто на основе аналитики лога действий. При использовании хранилища Redis это можно было бы реализовать через Rate Limitting, а вот при использовании MySQL такой возможности нет. Благо что лог действий — это непременная часть нашей небольшой информационной системы.

Платные сервисы

Хостинг и доменное имя — это всегда практически обязательные платежи. При размещении сайта в сети Интернет, необходимо закладываться на оплату различных услуг. В тарифе шло 2 ядра микропроцессора, хотя с нашими нагрузками, думаю, отлично справилось бы и одно. Хостинг взяли недорогой, оплатили 2Gb оперативки, из расчета 1Gb на базу данных, остальное — на операционку и выполнение скриптов. В процессе развертывания потребовалась компиляция linphonec, потому что на моем десктопе разработчика стоял более древний Debian Linux, чем в готовом образе виртуалки, предоставляемой хостером, а стандартный пакет из репозитария содержал древнюю версию этой программы с несколькими неприятными "особенностями". Дисковое пространство в 20Gb более чем достаточно для нашего проекта. Магия шаблонов в C++ выжирает память как не в себя, поэтому пришлось настроить своп, после чего сборка успешно завершилась. И вот, на компиляцию linphonec, мне 2Gb и не хватило.

Единственным непонятным моментом стало то, что при заказе тарифа оператор убеждал, что тарификация будет посекундная, а на деле оказалась поминутная. SIP-телефония тоже никогда не была бесплатной, но благо в России есть несколько крупных операторов IP-телефонии, которые конкурируют друг с другом и предлагают весьма малозатратные тарифы. Но с этим надо разбираться отдельно.

А Email-рассылка в нашем случае нужна для отправки различных оповещений. В наших современных реалиях нельзя расчитывать на то, что найдутся бесплатные сервисы Email-рассылки. Mail приводят только к тому, что почтовые серверы принимающей стороны, после трех-четырех писем, помечают сообщения как спам. По моему опыту, все попытки организовать отправку множества писем через Яндекс.Почту или через Google. Поэтому пришлось заморочиться с сервисом Mailgun, через который письма доставляются быстро и без проблем. То есть проблемы возникают уже на этапе отладки, не говоря про продакшен. А на сайте самого Mailgun как-то скользко написано, что я понимаю как 10000 писем с момента регистрации. С Mailgun только одна непонятка: в некоторых статьях пишут, что они дают бесплатно отправить 10000 писем ежемесячно. В любом случае, этот предел сервис пока не преодолел, так что надо просто понаблюдать.

Вначале надо заморочиться, и сделать правильные настройки в аккаунте, привязав его к своему доменному имени. Трудностей с настройкой Mailgun немного. А в Laravel есть даже готовый драйвер соединения с Mailgun, благодаря которому даже не нужно настраивать почтовую подсистему для отправки писем на уровне операционной системы. После чего, можно сделать тестовую отправку письма с сайта с помощью любой подходящей команды или скрипта, например через SMTP.

Фаирвол

Первое правило для INPUT разрешает прохождение пакетов для установленных соединений. Фаирвол я всегда настраиваю просто и примитивно, всего тремя правилами. Главное не ошибиться, и не забыть про порт для SSH, а то будет ой. Второе правило разрешает установку соединений на определенные порты. Конечно, в этой схеме возможны варианты, но базис именно такой. Третье правило — DROP всех остальных пакетов цепочки INPUT.

Еще, если хост спрятан за NAT, рекомендуют включать модуль nf_nat_sip, но в моем случае это не потребовалось, работает и так. Так как в системе используется SIP-телефония, то чтобы ядро правильно видело пакты SIP-протокола для установленных соединений, и не обрубало их, нужно обязательно иметь подгруженным модуль ядра nf_conntrack_sip.

Бэкап

Он получает на вход перечень команд и перечень каталогов. Сейчас на сервере крутится самописный скрипт, который обкатан на других проектах. А перечень каталогов — это каталоги, содержимое которых надо забекапить. Перечень команд — это команды, которые нужно выполнить перед бекапом, например команды снятия дампа MySQL. Если больше — удаляются более старые файлы архивов. В результате получается zip-архив, который складывается в определенный каталог, и скрипт следит, чтобы таких архивов не складывалось больше определенного количества. Каталог с архивами синхронизируется с другим хостом через rsync.

Но пока настроено таким образом, как мне проще и удобней. Я понимаю, что такой подход — это сплошные костыли, поэтому планирую переехать на бэкапы через borg.

Деплой

Разрабатываемый код пушится на сервер центрального репозитария. Для деплоя я использую систему контроля версий GIT и специально написанные скрипты, разбитые на этапы, имеющие один центральный скрипт запуска. Для этого сделаны настройки в /etc/sudoers и заданы права доступа на файлы скриптов так, чтобы они могли выполняться определенным пользователем, но не могли быть изменены никаким другим сторонним пользователем. В момент, когда нужно обновить сайт, запускаются скрипты, которые выполняют часть команд от рута (остановка и старт всяких сервисов), а часть команд от веб-сервера (получение кода через git, запуск утилиты artisan).

По сути, при деплое происходит только перенос кода, и этого достаточно. Благодаря тому, что в Laravel есть система миграций, никаких сторонних утилит для обновления структуры БД и наполнения таблиц первичными данными не требуется. Система пережила уже несколько штатных обновлений, пока что полет нормальный.

— скажут мне. Эй, товарищ! А я отвечу: побойтесь Бога! А где же твоя непрерывная интеграция? Если проект станет приносить дивиденты, вот тогда мы наберем команду программистов, и торжественно водрузим поверх всего еще и CI-систему, и тогда все будет по фен-шую. Проект не настолько обширен, чтобы еще и с системами CI заморачиваться.

Недоработки

Пора подумать, были ли допущены стратегические просчеты? Все мы умны задним числом. Можно было бы что-то сделать лучше? Да! Ниже я перечислю просчеты и недоделки этого проекта. Конечно, да! Некоторые вещи исправить достаточно просто, некоторые требуют больших временных затрат.

Все-таки Bootstrap 4 шагнул далеко вперед по сравнению с Bootstrap 3, и помимо flex-верстки в нем появилось много тех вещей, которых в Bootstrap 3 просто не было, и которые приходилось делать руками. Крупным просчетом было то, что я понадеялся на дефолтную установку Laravel, и использовал в качестве CSS-фреймверка Bootstrap 3 вместо Bootstrap 4. Сейчас можно переползти на Bootstrap 4, но верстка обязательно поедет, а с моим версточным кретинизмом выправлять её придется очень долго.

К сожалению, у меня пока не дошли руки до этого этапа, а этап очень важный. Все современные сайты, работающие с аутентификацией пользователей, обязаны работать на протоколе HTTPS. У меня уже есть опыт перевода сайтов с HTTP на HTTPS, я даже написал себе памятку Настройка сертификатов Let's Encrypt HTTPS на веб-сервере NGinx, однако нужно выделить время чтобы этим делом заняться.

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

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

Итог

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

Вопрос здесь в слове "разбираться". Должен ли современный разработчик разбираться во всем этом феерическом калейдоскопе знаний? Разбираться можно по-всякому, поэтому нужно еще ввести такое понятие, как глубина погружения в тему. Что значит разбираться? Гораздо важнее иметь способность быстро разбираться в новых вопросах, если придется трудиться над задачами из новых предметных областей. Так вот, с этой точки зрения, я считаю, что человеку достаточно иметь представление о технологиях, и не загружать мозги подробностями.

Современные средства разработки колоссально сложны и обширны. Понятно, что разработчик, работающий в режиме "и швец, и жнец, и на дуде игрец" не может глубоко знать технологии, с которым он работает. Изучение должно сопровождаться полным набором сопутствующих вещей: профессиональный коллектив, общение с коллегами и возможность обсуждения вопросов "на коленке", конференции, обучение, литература, академическая среда, большие и интересные проекты. Тот же язык Си++ нужно учить и использовать пару десятилетий, пока не появится легкость в его применении. Фуллстек-разработчик на удаленке этого набора априори лишен, и может рассчитывать только на самого себя. Это касается любого языка и ИТ-технологии. Тематические форумы и телеграм-каналы тут мало помогают: куче народа давным давно плевать друг на друга, и если обсуждение какого-либо вопроса не выльется в флейм, оскорбления, лицемерие и оценку профессиональных и умственных способностей вопрошающего, то это будет большой удачей. К сожалению, я вижу, что освоение сложных вещей очень непроизводительно в одиночку.

Можно называть такие продукты прототипами, proof-of-concept, или еще как, но факт в том, что лучше иметь прототип и развить его в более крупное дело, чем не иметь вообще ничего. Более поверхностные знания — это та плата, которую приходится платить фуллстек-разработчику за то, что он может, с помощью своих обширных знаний и навыков, делать достаточно дёшево разнообразные информационные продукты. Или, другая крайность — сильно потратиться на разработку продукта, а потом выяснить что его невозможно никуда приткнуть.

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

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть