[Перевод] Cargo Cult Driven Development: нам стоило понять это раньше

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

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

Agile, микросервисы, DevOps, блокчейн или искусственный интеллект — мы все время пытаемся изобрести философский камень, кнопку «Сделать все хорошо». Как будто мы собираем паззл, и в нем недостает одного кусочка. И стоит нам его найти, как разгадка жизни, смерти и всего вообще вдруг станет настолько простой и очевидной, что мы лишь оглянемся назад и посмеемся: «Ха-ха, невероятно, нам потребовалось столько времени, чтобы понять эту простую штуку! И все это время она была у нас перед носом!»

3261bc25e8816871bdb02691aed44564.png

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

В чем задача этой статьи

Я пишу эту статью, потому что устал от новостей из мира технологий, в которых некто вдруг «возвращается к старым-добрым методикам и инструментам, потому что новые не работают». Из недавних примеров — статья о том, как команда Prime Video благодаря переходу от микросервисов к монолиту сократила свои расходы на 90%. Сегодня я рассмотрю подобные тенденции в культуре TDD, а в конце поделюсь своими соображениями о том, как этого избежать.

Но сначала я поясню смысл подзаголовка.

Что такое карго-культ и в чем его связь с технологиями?

Для тех, кто не знаком с термином «карго-культ», поясню. Во время Второй мировой войны на военные базы США, расположенные на островах в Тихом океане, было сброшено огромное количество грузов с провизией для армии. Война закончилась, и грузы сбрасывать перестали. Однако местным жителям такой расклад не понравился. Ни с того, ни с сего они стали подражать солдатам, имитировать их стиль одежды и привычки, строить копии самолетов из соломы в натуральную величину. Так они пытались возобновить поставки чудесных даров с небес.

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

b7138f1ea022010d1e99f2f88191ca45.png

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

Увы, подобные тенденции я нередко наблюдаю и в технической среде. Люди узнают о какой-то новой технологии, поскольку о ней все трубят тут и там, и думают, что полученные кем-то результаты являются прямым следствием использования этой технологии. При этом они совершенно не обращают внимания на контекст и условия, в которых сформировался нужный результат. Из-за этого появляются дурные тезисы вроде «с Agile ваша команда будет работать быстрее», «облака дешевле, чем on-premise», «юнит-тесты повышают безопасность кода и упрощают его сопровождение» или даже «NeoVim лучше, чем VS Code». В них есть доля истины, бесспорно. Но зачастую без компромиссов дело не обходится.

Давай попробуем демистифицировать пару популярных убеждений. И начнем с примера Prime Video.

Удивительная история Prime Video и синдром распределительной лихорадки

ad323822462f3f71ba47deb074cc2a64.png

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

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

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

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

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

8c95a912adaa54e4d2b3fa4ecccc4b8e.png

Я ухмыляюсь каждый раз, когда вспоминаю эту историю. И прикол вовсе не в монорепозитории: смехотворна сама идея держать целую систему только ради того, чтобы сохранять данные другой системы. В смысле, можно ли вообще считать микросервис микросервисом, если в условиях изоляции он теряет всякий смысл?

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

bfa7aacf06482970ab082b5ec32d5e6c.png

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

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

Вредные практики

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

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

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

Иногда тестирование заходит слишком далеко. Взять, например, стремление к максимальному покрытию кода тестами или идею о превосходстве юнит-тестирования и убеждение в том, что в юнит-тестах нужно мокать абсолютно все, что не SUT. Ирония в том, что для многих TDD-разработчиков такой подход — обычное дело, в то время как сам Кент Бек (автор концепции Test-Driven Development, разработки через тестирование), похоже, придерживается гораздо более адекватной политики (исходя из его ответа на вопрос о глубине тестирования на Stack Overflow):

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

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

Как разорвать порочный круг

Раз вы дочитали до этих строк, то, вероятно, уже припомнили целую кучу похожих неурядиц, произошедших из-за Cargo Cult Driven Development. Черт возьми, да вы наверняка сами были виновниками некоторых из них. Но не переживайте. Давайте-ка обсудим, как разорвать этот порочный круг. Неужели достаточно просто не поддаваться искушению и хайпу вокруг очередной новомодной технологии?

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

Технологии — это всегда история про компромиссы.

248dd99ad1b855430c0d02db92876d8a.png

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

Гораздо проще снизить затраты на облако, если известно, какие ошибки грозят привести к огромным счетам за вычислительные мощности. Гораздо проще (и быстрее) разрабатывать на кодовой базе с продуманными и качественными тестами, а не тестировать все, что двигается. NeoVim позволяет работать быстрее и эффективнее, чем другие редакторы кода. И т.д., и т.п., и ч.т.д.

Ладно-ладно, последнее мое утверждение, возможно, слегка предвзято. Но суть, я думаю, ясна. Давайте воспринимать хайповые штуки как интересные новинки, которые стоит изучить — и, возможно применить по назначению. Но никогда — слышите, никогда не вырывайте технологии из контекста!

© Habrahabr.ru