Перфекционизм и технический долг: мнение компаний и отдельных программистов

О техническом долге
Иллюстрация DEV.BY

Чистота кода против сроков релиза и продуктовых характеристик, техническое совершенство против сиюминутных требований рынка — диалектика качества и скорости является одним из предметов спора в ИТ-сфере, — пишет DEV.BY.  

Правда ли, что для нашего инженера технологии важнее бизнеса, как часто приходится идти на компромисс и  что такое допустимое снижение качества кода ради быстрого выхода на рынок? 

Если он не сделает всё идеально с первого раза, то шанса исправить не будет»

Дорофей Пролесковский, разработчик-картограф, продакт-менеджер Kontur.io:

— У меня есть любимый вопрос, на который я пока не нашёл ответа. Компания/команда может поддерживать в нормальном состоянии в адекватной скорости разработки проект определённой сложности. То есть с какого-то момента фичи надо не добавлять, а убирать. Итак, вопрос: как понять, что уже пора, как выделить на это время и понять, что именно выпилить?

Технический долг — не про низкое качество. Он про то, что у тебя уже есть какая-никакая архитектура, и ты, разработчик, посреди неё вбиваешь костыль, заметный только для тебя, и сообщаешь о нём в будущее. Пишешь тикет на менеджера или тимлида в команде, ставишь TODO и FIXME в коде, если пилите стартап вдвоём, или записываешь в тетрадочку, если делаешь пет-проект для себя. При этом костыль может быть очень высокого качества, ведь его цель — сделать так, чтобы всё, что нужно прямо сейчас, заработало.

Код низкого качества сам по себе, без метода его учёта и планирования его «отдачи», когда тикеты будут закрываться, а костыли вытаскиваться — не долг. Случается, что разработчик погрязает в рефакторинге своего же кода, и никто его не может оттуда вытащить, потому что уже через неделю никто не помнит, что же рефакторилось и зачем, и чинятся просто все вещи, которые не нравятся — хотя они в принципе и не были поломаны. Может быть, задача была плохо сформулирована, может быть, это способ медитировать над кодом — регулярно переписывать его, чтобы удержать в голове, но это уже не про технический долг.

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

«Мы не всегда знаем, что пишется на уровне „сделал и забил“, а что будем долго и упорно тянуть»

Арсений Кравченко, ML инженер, ods.ai:

— Если проект маленький и простой, его вряд ли нужно будет серьёзно развивать, то кажется, что можно писать код, катаясь рожей по клавиатуре, кое-как работает — и ладно. Выплаты по техническому долгу несущественны. Так и пишутся простыни говнокода, прикрытые оправданиями «ну, вот если бы у меня был сложный проект…». 

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

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

В задачах машинного обучения есть ещё одна похожая дихотомия: сколько сил инвестировать в сами модели и алгоритмы, а сколько — в инфраструктуру вокруг данных. Иногда эти инвестиции оказываются очень удачными: например, в Wanna Kicks однажды инвесторы похвалили последний релиз, в котором был исправлен ряд заметных пользователю проблем. Ирония была в том, что с точки зрения ML не было добавлено вообще ничего, кроме новых обучающих данных, что не потребовало никаких усилий от R&D команды благодаря вовремя сделанной инфраструктуре.

«Баги допустимы, если время на их исправление минимально»

Николай Курдесов, руководитель R&D-подразделения Gurtam:

— Буду говорить субъективно о нашей компании, но, кажется, это происходит повсеместно в Беларуси. Если не брать во внимание случайных людей в разработке, средний уровень специалиста всё же растёт. Но в этом и заключается одна из проблем: сколько времени ни выделяй хорошему специалисту для решения задачи, он потратит всё. Просто, чем выше его уровень, тем лучше он заложит архитектуру, больше нюансов учтёт, использует лучшие алгоритмы с точки зрения оптимизации и т. п.

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

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

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

Что такое допустимое снижение качества кода? Давайте проведём аналогию с автомобилями. С одной стороны, производители хотят выпустить красивое, функциональное, с минимальной себестоимостью авто. С другой стороны, закладывать огромный ресурс экономически невыгодно. Да и давайте быть честными: кто захочет сесть за руль надёжного, но морально и технологически устаревшего авто? Новые технологии с каждым годом становятся всё доступнее, и каждый год есть возможность сделать продукт более совершенным «за ту же цену».

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

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

Кроме того, если у вас код весь на «копипастах», то о быстром внесении изменений, например схемы БД, API, не может идти и речи. А значит, архитектура и сам код должны быть такими, чтобы максимально быстро можно было разобраться и внести правки. 

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

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

Перфекционизм
Image by 41330 from Pixabay

«Мне хорошо — жертвовать архитектурой не приходится вовсе»

Иван Подобед, Technical Fellow в Awem:

Нормального белорусского инженера я вижу не перфекционистом, а, скорее, здравомыслящим человеком, работающим на свою карьеру. В большинстве известных мне случаев  «перфекционизм» объяснялся желанием инвестировать в технологическую экспертизу, иногда в ущерб балансу качество/сроки/стоимость: экспертизу потом можно и перепродать на рынке труда, а полумифическое «понимание бизнес-необходимости выпуска продукта с техническим долгом» продаётся не так бодро. У нас с командой есть хорошее взаимопонимание, и, вроде бы, найдено равновесие между инвестированием в экспертизу и поддержанием баланса качество/сроки/стоимость. Хотя многие решения со стороны всё ещё выглядят как over-engineering, за каждым из них стоит оценка рисков.

Иногда стоит допустить небрежность, потому что клиент ждёт? В целом — согласен, хотя над формулировкой стоило бы поработать.

Более того, иногда сработать надёжно, надолго, в соответствии с архитектурой значит совершить преступление перед работодателем.

Зачем полировать прототип, который создан для проверки продуктовой гипотезы? Зачем строить масштабируемую систему доставки, потолок расчётной производительности которой измеряется десятком событий в секунду? Очень вредно проектировать пользовательский интерфейс внутренней системы, когда её можно запустить на стандартной админке, а сэкономленное время направить на исследование опыта использования первой версии для получения более релевантных требований. В целом возможно много сценариев, когда работать надо именно на скорую руку, но при этом важно сохранять осознанность: фиксировать trade-offs, риски, потенциальные улучшения для следующей версии и явные причины жертвовать чем-то ради скорости.

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

Каждое решение, ведущее к увеличению технического долга, имеет весьма определённую цену — стоимость исправления этого самого долга. И так как эта цена имеет свойство расти со временем, «грань допустимого снижения качества ради скорости» вычисляется как точка сходимости — в какой момент времени вы сможете инвестировать достаточно времени и ресурсов на исправление техдолга. Бывают случаи, когда ожидаемое время жизни системы меньше, чем время окупаемости возвращения технического долга — в этом случае борьба с долгом так себе затея. Однако важно понимать, что все прогнозы — срок жизни системы, стоимость возвращения долга, риски от создания техдолга — величины вероятностные. 

Риски имеют неприятное свойство выстреливать. Недавно на проекте одно из решений на нас отыгралось — пришлось возвращать долг быстро и вне очереди. А продукт показал на рынке такой выдающийся результат, что его время жизни серьёзно выросло, и многие архитектурные решения тут же превратились из «очень хороших» в «весьма посредственные». Но ничего страшного — все решения можно пересмотреть за определённую цену, и если прибыль превысит стоимость, можно хоть весь продукт переписать. Главное — принимать решение осознанно, тогда, даже если оно окажется неправильным, это будет лишь повод извлечь урок.

Скорость в нашем домене всегда в приоритете, она идёт сразу за качеством. Именно поэтому в наших архитектурных процессах заложен принцип разблокирования — достаточно четкие градации допустимых инвестиций в инжиниринг в зависимости от потребности продукта. Если мы блокируем доставку фичи, смело влезаем в технический долг, но с оглядкой: интерфейсы фиксируем, пользовательский опыт бдительно сохраняем. Как разблокируем, тут же возвращаемся в бэклог на предмет оплаты техдолга, а в нем уже аккуратно сложены задачи по управлению конфигурацией, оптимизации, масштабированию, унификация и т. д.

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

Технический долг и перфекционизм
Photo by Annie Spratt on Unsplash

«Компромиссные решения часто приводят к негативным результатам»

Сергей Щегрикович, Director of Engineering в Gismart:

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

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

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

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

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

«Граница недопустимого там, где проценты по долгу выходят на траекторию банкротства»

Сергей Мушинский,  Computer Vision Engineer, HD maps team, Mapbox:

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

На начальном этапе, когда еще непонятен product-market fit, конечно же, допустимо снижение качества кода ради выхода на рынок. Более того, оно может быть неизбежным, первая версия должна быть прототипом «на выброс», потому что всегда обнаруживается, что клиентам нужно что-то другое, и чем меньше было вложено в начальный вариант, тем проще выкинуть и полностью переделать. 

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

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

В прошлом году несколько раз нам надо было продемонстрировать готовые решения на международных выставках, то есть дедлайны были строгие. Такие моменты чётко показывают, с одной стороны, что в продукте действительно важно, а что просто рюшечки для «инженерной красоты», с другой — все слабые стороны продукта, чего не хватает и где ошибки в архитектуре. Важно после таких авралов провести ретроспективу, выделить время на то, чтобы оценить свою работу, и подумать, как сделать лучше.

Технический долг, как и любой другой, нужно выплачивать. Занимая у «себя будущего» для «релиза сейчас», надо понимать, что завтра придётся платить, значит, надо выделить время на прополку сорняков в коде. Граница недопустимого проходит там, где проценты по долгу выходят на траекторию, которая ведет к банкротству.

Перфекционизм и компромиссы
Image by Nandhu Kumar from Pixabay

«Вся работа над продуктом состоит из компромиссов» 

Левон Авакян, руководитель департамента разработки World of Tanks Game Logic:

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

Про жертвование архитектурой в угоду клиенту: архитектура — вещь фундаментальная, в ней не может быть небрежности. Тут, кстати, возникает вопрос: почему было выбрано архитектурное решение, которое не удовлетворяет такому требованию, как скорость разработки? Может, проблема не в том, что требует клиент, а в том, что изначально не были учтены все требования, включая нефункциональные? Вообще, во многих компаниях и командах любят рассуждать об архитектуре, совсем не понимая, что это такое и зачем она нужна. Да, иногда бизнесу нужно просто быстро что-то сделать, чтобы проверить какую-то гипотезу, и закапывать туда человеко-месяцы работы никто не хочет, это невыгодно, но на любом этапе бизнес-процесса нужно просто понимать, зачем мы делаем те или иные вещи, и слушать, а также слышать друг друга. В процессе разработки есть огромное количество вещей помимо архитектуры, которыми можно пожертвовать и занести в «технический долг», например, тесты, инструменты оперирования, мониторинг, документацию и так далее. И вот у каждой такой жертвы есть своя цена, которую нужно согласовать и с теми, кто отвечает за качество, и с теми, кто отвечает за надёжность, — в общем, со всеми. Вся работа над продуктом состоит из компромиссов, потому что идей и задумок много, а часов в сутках мало. 

Когда мы говорим про качество, нужно понимать, как мы это качество измеряем. Чтобы что-то начать оценивать, нужно определиться, что именно конкретная компания или команда вкладывает в понятие качества. Во многих ли командах, компаниях занимаются измерением качества кода? Обычно, если инженер говорит: «Качество кода упало», — никому не известно, что он имеет в виду, это вкусовщина в чистом виде. С другой стороны, вы можете измерять 100 500 параметров, и по приборам всё будет хорошо, а по факту — не очень. Так что сначала нужно определиться, что вы вкладываете в понятие качества, потом начать его измерять, а потом уже выбирать грань между допустимым и недопустимым. 

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

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

Технический долг
Photo by Joshua Aragon on Unsplash

«В угоду клиенту мы жертвуем не только архитектурой, но и пожеланиями нашей бизнес-команды»

Андрей Шелег, заместитель директора ООО «Октонион технолоджи»:

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

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

В случае, когда требования к прототипу решения противоречат фундаментальным принципам нашей платформы, то, как правило, мы отказываемся от разработки. Типичным примером является пожелание отключить безопасность в платформе, на что мы говорим категоричное «нет». Опыт показывает, что такое сотрудничество не является продуктивным ни для заказчика, ни для команды, ни для платформы.

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

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

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

[customscript]techrocks_custom_after_post_html[/customscript]

[customscript]techrocks_custom_script[/customscript]

Оставьте комментарий

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

Прокрутить вверх