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

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

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

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

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

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

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

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

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

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

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


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

Определение

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

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

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

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

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

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

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

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

Телефония

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

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

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

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

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

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

NPM

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

Верстка

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

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

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

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

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

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

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

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

Разработка

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

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

Технологии

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

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

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

Очередь

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

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

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

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

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

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

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

Фаирвол

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

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

Бэкап

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

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

Деплой

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

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

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

Недоработки

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

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

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

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

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

Итог

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

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

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

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

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

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

Let’s block ads! (Why?)

Читайте также:

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

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