Будь как Мунк, или пару слов о техническом долге

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

    image

    За 8 лет IT-команда Dodo Pizza выросла с 2 разработчиков, обслуживающих одну страну, до 80 человек, обслуживающих 12 стран. Три года назад я пришел в Dodo Pizza в качестве Chief Agile Officer и стал помогать командам создавать процессы и внедрять инженерные практики. Зачастую эти внедрения было слишком медленными. Кроме того, обнаружилось, что когда несколько команд работают над одним продуктом, сложно заставить их поддерживать высокое качество кода.

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

    Background


    Dodo Pizza – это компания-киборг, которая продает пиццу. Наш бизнес основан на платформе Dodo IS, которая управляет всеми бизнес-процессами: приём заказов, приготовление пиццы, управление запасами, управление людьми (менеджмент) и многое-многое другое. Всего за 8 лет мы выросли с 2 разработчиков, обслуживающих одну пиццерию, до 80+ разработчиков, обслуживающих 498 пиццерий в 12 странах мира.

    Три года назад Dodo IS была монолитом, содержащим 1 миллион строк кода. Было небольшое покрытие unit-тестами, API/UI-тестов не было вовсе. Само качество кода было разочаровывающим. Все об этом знали или хотя бы догадывались. В мечтах о светлом будущем мы раскалывали монолит на дюжину сервисов и переписывали самые отвратительные части системы. Мы даже нарисовали диаграмму «будущей» архитектуры, но, честно говоря, так ничего и не сделали, чтобы приблизиться к ней.

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

    Shit happens


    В 2018 году команда маркетинга запустила нашу первую федеральную рекламную кампанию на ТВ с бюджетом 100 млн. рублей. Это было грандиозное событие для Dodo Pizza. IТ-команда тоже хорошо подготовилась к кампании. Мы автоматизировали и упростили наш деплой – теперь с помощью одной кнопки в TeamCity мы могли развернуть монолит в 12 странах. С помощью тестов производительности мы провели анализ уязвимостей. Мы сделали все возможное, но всё равно облажались.

    Рекламная кампания была потрясающей. Мы получали от 100 до 300 заказов в минуту. Это была хорошая новость. Плохая новость: Dodo IS не выдержала такой нагрузки и умерла. Мы достигли предела вертикального масштабирования и больше не могли обрабатывать заказы. Система перезагружалась каждые 3 часа. Каждая минута простоя стоила нам десятки тысяч рублей, не считая потери уважения со стороны разъяренных клиентов.
    Когда я пришел в Dodo Pizza три года назад, я сразу же начал внедрять инженерные практики. Большинство команд приняли парное программирование, модульное тестирование и DDD довольно быстро. Но не все было так просто. Мне пришлось преодолевать сопротивление разработчиков, продактов и команды саппорта.

    В отличие от идей инженерных практик, поначалу не все поддерживали идею feature teams. Разработчики привыкли думать, что команда, сосредоточенная на одном компоненте, пишет лучший код. Было неясно, как совместить быстрое развитие бизнес-функций с давно назревшим массивным рефакторингом сложной системы. Еще этот бесконечный поток багов постоянно требовал внимания… Мы выпускали продукт не чаще одного раза в неделю, и каждый релиз занимал довольно много времени, требовал огромного количества ручного регресса и поддержки UI-тестов. Я пытался исправить это, но изменение процесса шло слишком медленно и фрагментарно.

    История падения и подъема


    Initial state: монолитная архитектура


    В погоне за скоростью развития бизнес-функций мы не всегда хорошо продумывали технические решения. Сказался недостаток опыта. У нас было монолитное приложение с единой базой данных, содержащей все данные из всех компонентов в одном месте. Трекер, учет, сайт, API для целевых страниц – все компоненты системы работали с одной базой данных, которая была узким местом.

    True story


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

    Наш генеральный директор — Федор опубликовал пост в своем блоге. Этот пост быстро завоевал популярность. На сайте блога Федора есть счетчик, показывающий количество пиццерий нашей сети и общий доход всех пиццерий. Каждый раз, когда кто-то читал блог Федора, веб-сервер отправлял запрос в master базу данных для расчета выручки. Эти запросы настолько перегрузили базу данных, что она перестала обслуживать запросы от кассы ресторана. Мы быстро исправили проблему, но это был один из многих признаков, что наша архитектура не в состоянии удовлетворить потребности бизнеса и должна быть переработана. Однако мы продолжали игнорировать эти знаки.

    Ранний сбой в 2017 году


    14 февраля. Для любителей поздравить друг друга 14 февраля мы делаем специальную пиццу – «Пепперони» в форме сердца. Я навсегда запомню 14 февраля 2017 года, потому что в этот день, когда все пиццерии работали на полную загрузку, Dodo IS начала падать. В каждой пиццерии стоит 4-5 планшетов для управления производством: для какого заказа пиццемейкер раскатывает тесто, кладет ингредиенты, выпекает или отправляет на доставку. На тот момент количество пиццерий достигло 150+, каждый планшет обновлялся несколько раз в минуту. Все эти запросы создали такую огромную нагрузку на базу данных, что она перестала выдерживать и начала отказывать. Dodo IS умерла во время пика продаж. А ведь впереди был напряженный праздничный сезон: 23 февраля, 8 Марта, 1 и 9 Мая. Во время этих праздников мы ожидали еще большего роста заказов.

    День, когда ты умрешь. Зная наши планы роста и предел нагрузки, которую мы можем выдержать, мы выяснили, как долго мы можем оставаться в живых. Предполагаемая дата Армагеддона ожидалась примерно через полгода: в августе–сентябре 2017. Каково это — жить, зная дату своей смерти?

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

    Некоторые улучшения после года


    За год мы заметно выросли:

    • Мы автоматизировали и ускорили процесс деплоя до 4-5 часов
    • Наконец-то мы начали распиливать монолит: трекер и TV-борды были вынесены на отдельный сервис со своей собственной базой данных
    • Мы начали отделять кассу доставки — второй компонент, создававший высокую нагрузку
    • Переписали систему аутентификации пользователя и устройства

    Казалось бы, мы могли гордиться собой. Но впереди нас ожидало огромное разочарование.

    Провал во время Федеральной рекламной кампании. Второй кризис доверия



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

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

    Под нагрузкой Федеральной маркетинговой кампании мы снова легли. Система снова падала и перезагружалась каждые 3 часа. Наш бизнес терял десятки миллионов рублей.



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

    Вынесенные уроки


    После ретроспективы мы полностью перестроили наши процессы. Мы взяли за основу LeSS и дополнили его инженерными практиками. В течение следующих нескольких месяцев мы сделали прорыв во внедрении инженерных практик. Основываясь на LeSS, мы внедрили и продолжаем использовать:

    • Единый бэклог продукта
    • Полностью кросс-функциональные и кросс-компонентные команды
    • Парное и моб программирование
    • Настоящая непрерывная интеграция (CI) – интеграция кода 12-ю командами в одну ветку
    • Упрощенная работа с ветками (trunk-based development)
    • Частые релизы: непрерывный деплой для микросервисов, релиз каждый день для монолита
    • Отказ от отдельной команды QA, эксперты QA являются частью команд разработки

    6 практик, которые мы выбрали после кризиса:


    1. Сила фокуса. До кризиса каждая команда работала над своим собственным долгом и специализировалась в своей области. Во время кризиса у команд не было конкретных задач, у них была одна большая сложная цель. Например, мобильное приложение и API должны обрабатывать 300 заказов в минуту, несмотря ни на что. Команда берет цель и самостоятельно придумывает, как ее добиться. Команда сама формулирует гипотезы и быстро проверяет их на проде. Команды не хотят быть простыми кодерами, они хотят решать проблемы.

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

    2. Внутренние хакатоны. Мы провели хакатон «500 ошибок». Все команды дружно очистили логи и удалили причины 500 ошибок на сайте и в API. Целью было сохранить логи в чистоте. Когда логи чисты, новые ошибки хорошо видны, вы можете легко настроить пороговые значения для оповещений.

    Еще один пример хакатона – баги. Раньше у нас был полный бэклог багов, некоторые из них болтались там в течение многих лет. Казалось, они никогда не закончатся. И каждый день появлялись новые. Мы объединили работу над багами и обычными элементами бэклога.

    Политика #zerobugspolicy.
    1. Если баг находится в бэклоге более 3 месяцев, просто удали его. Он провалялся там уже целую вечность, и никто не умер.
    2. Оцени боль, которую причиняют оставшиеся баги клиентам. Оставь только те баги, которые усложняют жизнь большой группе пользователей.
    3. Устрой внутренний хакатон по багам. Мы сделали это за несколько спринтов. Каждый спринт каждая команда брала несколько ошибок и исправляла их. После 2-3 спринтов у нас был чистый бэклог. Теперь можно ввести #zerobugspolicy.
    4. #zerobugspolicy. Если баг попадёт в бэклог, он обязательно будет исправлен. Любой баг в бэклоге имеет более высокий приоритет, чем любой другой элемент бэклога. Но для того, чтобы попасть в бэклог, баг должен быть серьезным. Либо он наносит непоправимый вред, либо затрагивает большое количество пользователей.


    3. От проектных групп до стабильной команды. Была забавная история с проектными командами. Во время кризиса мы сформировали экспертные команды людей, которые были наиболее квалифицированными для задачи. После того, как кризис закончился, команды решили продолжить эту практику. Несмотря на то, что эта идея мне совсем не понравилась, мы попробовали. Всего за 2 недели (один спринт), на следующей ретроспективе, команды отказались от этой практики, (это решение меня осчастливило). Если команде не хватает некоторых навыков, они могут постепенно учиться. Но командный дух, поддержка и взаимопомощь формируются очень долго, на это уходят месяцы. Краткосрочные проектные команды постоянно находятся в стадии форминга и шторминга. Вы можете потерпеть это в течение нескольких недель, но не сможете работать так все время.

    4. Нет ручному регрессу. Мы поставили перед собой цель избавиться от ручных регрессий. Нам потребовалось 1,5 года, чтобы достичь ее. Но наличие долгосрочной амбициозной цели заставляет думать о шагах, ведущих к цели.

    Мы сделали это в 3 шага.
    1. Автоматизация критического пути.
      В июне 2017 года, мы сформировали команду QA. Задачей команды было автоматизировать регресс наиболее критичного функционала Dodo IS – прием и производство заказов. В течение следующих 6 месяцев новая команда QA из 4 человек покрыла автоматическими тестами весь критический функционал системы. Разработчики фича-команд активно помогали команде QA. Вместе мы написали красивый и понятный доменный язык (DSL), который понимали даже клиенты. Параллельно со сквозными тестами разработчики обвешивали код модульными тестами. Некоторые новые компоненты были переработаны с помощью TDD. После этого мы расформировали команду QA. Бывшие члены команды QA присоединились к командам, работавшим над бизнес фичами, чтобы передать опыто разработки и поддержки автотестов в команды.
    2. Теневой режим.
      Имея автотесты, во время 5 релизов мы делали ручной регресс в теневом режиме. Команды полагались только на автоматическое тестирование, но когда команда решала, что она готова к релизу, мы запускали ручной регресс, чтобы проверить, не пропустили ли наши автотесты какие-либо ошибки. Мы отслеживали, сколько ошибок было поймано вручную и не поймано автотестами. После 5 релизов мы проанализировали данные и решили, что можем доверять нашим автотестам. Никаких серьезных ошибок не было пропущено.
    3. Отказ от ручного регресса.
      Когда у нас было достаточно тестов, чтобы начать им доверять, мы полностью отказались от ручного тестирования. Чем больше мы пишем тестов, тем больше им доверяем. Но это произошло только через 1,5 года после того, как мы начали автоматизировать регрессионное тестирование.


    5. Нагрузочные тесты — часть регресса. Во время кризиса мы написали нагрузочные тесты. Это было совершенно новым для нас опытом. Тем не менее, всего за 2 недели нам удалось создать кое-что с помощью инструментов Visual Studio. Мы использовали их в том числе для генерации искусственной нагрузки на сервер, чтобы найти пределы производительности. Например, если органическая нагрузка на проде составляет 100 заказов/мин, мы добавляли еще 50 заказов/мин с помощью наших тестов, чтобы посмотреть, способна ли система обрабатывать повышенную нагрузку.

    На следующий год мы переписали нагрузочные тесты с помощью опытной команды PerformanceLab. Сегодня эти тесты запускаются еженедельно и дают быструю обратную связь командам разработчиков.



    6. Инженерные практики. Все наши команды используют парное программирование. Я считаю парное программирование одной из самых простых, но мощных практик. Если вы не знаете, с какой инженерной практики начать, я рекомендую парное программирование.

    Результаты


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

    В результате этих экспериментов за последний год мы значительно улучшили качество и стабильность Dodo IS. Если во время весенних каникул 2018 года наши пиццерии не могли работать из-за Dodo IS, то в 2019 году, с ростом от 300 до 498 пиццерий, Dodo IS работает безупречно. Мы спокойно пережили пик продаж в новом году, во время Второй маркетинговой кампании и весенних праздников.

    Впервые за долгое время мы уверены в качестве системы и можем себе позволить крепко спать по ночам. Это результат постоянного использования инженерных методов и концентрации на техническом совершенстве.

    Результаты для бизнеса


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

    Результаты для команд


    Сегодня мы используем широкий спектр инженерных методов:

    1. Полностью кросс-функциональные и кросс-компонентные команды
    2. Парное/Моб программирование
    3. Continuous Integration – непрерывная интеграция кода 12 команд в одну ветку
    4. Subject Matter Expert в команде
    5. Нет отдельной команды QA, эксперты QA являются частью команд разработки
    6. Замена ручного регресса автотестами
    7. Политика отсутствия багов (#Zerobugspolicy)
    8. Stop the Line в качестве драйвера для ускорения деплоя

    Чему мы научились


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

    1. Инженерные практики защищают бизнес от кризиса
    2. Не накапливать технический долг. Может стать слишком поздно и стоить слишком дорого
    3. Эволюционные изменения занимают в несколько раз больше времени, чем революционные
    4. Кризис – это не всегда плохо. Используйте кризис для революционных изменений процессов
    5. Тем не менее, длительная эволюционная подготовка требуется заранее
    6. Не применяйте слепо все методы, которые вам нравятся. Некоторые методы ждут своего часа, и когда он придет, команды будут использовать их без сопротивления. Ждите подходящего момента
    7. Со временем команды сами начинают принимать важные решения и реализовывать их. Дайте им благоприятную среду, чтобы попытаться, позвольте потерпеть неудачу и учиться на ошибках

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


    Благодарности


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

    Большое спасибо нашему генеральному директору Федору Овчинникову за доверие. Ты настоящий лидер компании с подлинной гибкой культурой.

    Большое спасибо Дмитрию Павлову, нашему Product Owner'у, моему старому другу и со-тренеру.

    Спасибо Александру Андронову и Андрею Моревскому за поддержку.

    Большое спасибо Даше Баяновой, нашему первому штатному Scrum-мастеру, которая всегда помогает и поддерживает меня со всей нашей инициативой. Вашу помощь трудно переоценить.

    Отдельное огромное спасибо Джоанне Ротман, которая помогала мне писать этот отчет в любом состоянии: находясь в отпуске, выздоравливая после болезни. Джоанна, было очень приятно работать с тобой. Мне очень помогли твои советы, внимание к деталям и трудолюбие.
    Dodo Pizza Engineering
    352,45
    О том как IT доставляет пиццу
    Поделиться публикацией

    Комментарии 21

      +6
      "Три года назад я пришел в Dodo Pizza в качестве Chief Agile Officer"

      А какая квалификация у человека который претендует на роль chief agile officer? Т.е. получается кто-то пришел на громкую должность в компании потом начал учиться методом проб и ошибок и помолом только начал применять самые известные инженерные практики? Или я как-то не так понимаю статью?


      В моем представлении на роль chief нужен человек который уже знает как это работает или по крайней мере теоретически как должно, и взятие инженерного долга должно быть просчитанным решением (типа на данном этапе развития нам не надо быстродействия но зато потом мы это наверстаем) но тогда это не стыд и позор.

        0
        Придя в компанию, у меня было примерно 10 лет опыта работы в XP/Scrum команде + 3 года работы в SmartStepGroup — на тот момент единственной компанией в России, которая зарабатывала тренингами и коучингом инженерных практик XP. Собственно благодаря нашему опыту мы и стали частью Додо в начале 2017.

        К сожалению, к этому времени в Dodo IS уже был накоплен большой технический долг. Но это было не осознанное, не просчитанное решение. Мы жертвовали качеством ради скорости, но не осознавали цену, которую придется за это заплатить. А когда осознали — стало слишком поздно.
          +6
          13 лет опыта и для вас оказалось новостью что говнокод в большом количестве ведёт к проблемам? Серьезно?
            0
            Конечно нет, именно поэтому с самого начала и взялись за инженерные практики. Но к сожалению, количество накопленного долго уже тогда было слишком велико. И поначалу мы недооценивали его последствия, были недостаточно настойчивы в своем праве писать хороший код. Это была ошибка.
              +4
              Статья скорее о масштабе проблем и об усилиях, которые потребовалось вложить для приведения системы в порядок.
                +6

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


                Есть большая разница между "начали писать" и "трогаем работающий продакшен".

            +5

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


            Но опыт дороже золота и его много не бывает!

              0
              Спасибо за поддержку :)
              +3
              первую федеральную рекламную кампанию на ТВ с бюджетом 100 млн. рублей

              т.е. заказать одноразовый спам стоит для бизнеса столько же, сколько год работы всей команды? o_O

                0
                Ну как одноразовый…
                2 недели проката на 6 каналах на ТВ, до этого продакшн и съемка ролика, билборды.
                Роликов было несколько, каждый подбирали под контент и даже конкретную передачу.
                0

                Странно что Вы не понимали на начальном пути что нужна «пирамида тестов»:) хотя о чем я, протестили на проде. Спасибо за статью! Пойду закажу пиццу у вас:)

                  +1
                  Для любителей поздравить друг друга 14 февраля мы делаем специальную пиццу – «Пепперони» в форме сердца.
                  Совсем как Pizza Hut? :)
                    0
                    А еще у нас была пицца-пирог с брусникой в форме сердца :-P
                    Такого ни у кого нет :)
                    +1
                    Вы пишите «Continuous Integration», как насчет тестов?
                    Расскажите, пожалуйста, как вы их пишите и как они влияют на развертывание приложения?
                      +1
                      4000 юнит тестов, около 400 API тестов, около 100 UI тестов.
                      Пишем вместе с кодом, некоторые команды пишут код по TDD.
                      Влияют прямым образом. Перед выкладкой нужно, чтобы все тесты прошли. UI тесты раньше были не совсем качественные, хрупкие, ненадежные, мигающие. Приходилось их запускать несколько раз, это удлинняет выкладку. Во время Stop the Line мы сфокусированно улучшали билд, чинили и переписывали мигающие тесты. Вот тут я писал об этом. Теперь намного лучше, хотя еще можно кое-что поулучшать.
                      0
                      Мы релизили по 20 раз в день.

                      Бедные пользователи они ведь качали обновление по 20 раз в день.
                        +1
                        У нас веб приложение
                        0
                        Прошу прощения, я не совсем понял: проводились изменения в фоновом режиме с дописыванием новой функциональности в том же ритме что и раньше, или когда бизнес ощутил существенную потерю денег, внезапно нашлось время ($) на исправление накопленных ошибок и рефактринг системы в целом?
                        Другими словами, почему технический долг не исправлялся до фейла? Былы ли ресурсы для этого или ритм работы не позволял отвлечься от новых фич?
                          0
                          Фонового режима не было. Мы полностью остановили бизнес разработку и занимались только архитектурным рефакторингом системы.
                          Почему техдолг не исправлялся до фейла? — потому что мы недооценивали угрозу, потому что мы прогибались под желание бизнеса сделать больше фич, и не отстаивали свое право на чистый код.
                          +2

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

                            0
                            Все правильно, не было бы. Монолитное решение было правильным выбором на первом этапе. Его и разрабатывать, и выкатывать, и тестировать проще всего.

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое