Работа с крупными устоявшимися кодовыми базами — один из самых сложных навыков, осваиваемых разработчиком ПО. Его невозможно практиковать заранее (нет, опенсорс не даст вам этого опыта). Личные проекты не научат этому, потому что они по определению маленькие и реализуются с нуля. Нужно уточнить, что когда я говорю «крупные устоявшиеся кодовые базы», то имею в виду следующее:
От одного до десятка миллионов строк кода (допустим, примерно пять миллионов)
Примерно от 100 до 1000 разработчиков, работающих над одной кодовой базой
Первая работающая версия кодовой базы была выпущена как минимум десять лет назад
Я уже больше десятка лет работают с такими кодовыми базами. В статье я поделюсь теми знаниями, которые бы мне очень пригодилось в начале.
Несогласованность — смертный грех
Чаще остальных я встречаю одну ошибку, которая оказывается смертельно опасной: игнорирование остальной части кодовой базы и реализация своей фичи наиболее логичным образом. Иными словами, разработчик ограничивает точки соприкосновения с имеющейся кодовой базой, чтобы его красивый чистый код не загрязнялся легаси-мусором. Инженерам, в основном работавшим с маленькими кодовыми базами, этого соблазна избежать трудно. Но ему нужно противиться! На самом деле, нужно максимально глубоко погрузиться в легаси, чтобы обеспечить согласованность.
Почему согласованность настолько важна в крупных кодовых базах? Потому что она защищает нас от неприятных сюрпризов, замедляющих развитие кодовой базы и превращающих его в хаос, а также позволяет нам создавать улучшения в будущем.
Допустим, вы создаёте конечную точку API для определённого типа пользователя. Вы можете добавить в свою конечную точку логику вида «возвращать 403, если текущий пользователь не относится к этому типу». Но сначала вам следует изучить, что делают для аутентификации другие конечные точки API в кодовой базе. Если они используют какой-то конкретный набор вспомогательных функций, то вам тоже следует их применять (даже если они неудобны, сложны в интеграции или кажутся в вашем сценарии использования лишней тратой ресурсов). Вы должны сопротивляться искушению сделать ваш маленький уголок кодовой базы красивее, чем остальная её часть.
Основная причина этого заключается в том, что в крупных кодовых базах есть множество закопанных мин. Например, вы можете и не знать, что в кодовой базе есть концепция «ботов», которые подобны пользователям, но не совсем совпадают с ними и требуют особой обработки при аутентификации. Вы можете не знать, что внутренний инструментарий поддержки кодовой базы позволяет разработчику иногда выполнять аутентификацию от лица пользователя, что требует при аутентификации особой обработки. И наверняка есть ещё сотни тонкостей, которые вы можете не знать. Существующая функциональность представляет собой безопасный маршрут через минное поле. Если вы будете выполнять свою аутентификацию так же, как это долгое время делали другие конечные точки API, то сможете двигаться по этому маршруту, не зная, какие сюрпризы могла бы вам подкинуть кодовая база.
Кроме того, нехватка согласованности — главный убийца кодовых баз в длительной перспективе, потому что из-за неё становится невозможно вносить любые общие улучшения. Вернёмся к примеру с аутентификацией: если вам когда-нибудь понадобится добавить новый тип пользователя, то согласованная кодовая база позволит обновить имеющийся набор вспомогательных функций аутентификации. В несогласованной кодовой базе, где некоторые конечные точки API ведут себя иначе, вам придётся обновлять и тестировать каждую из таких реализаций. На практике это означает, что крупномасштабные изменения просто не вносятся или не затрагивают 5% самых сложных конечных точек, что, в свою очередь, ещё больше снижает согласованность, потому что теперь у вас есть тип пользователя, работающий на большинстве, но не на всех конечных точках API.
Поэтому когда вы приступаете к какой-то реализации в крупной кодовой базе, то вам всегда следует сначала поискать уже имеющиеся решения и по возможности следовать им.
Есть ли её какие-то важные аспекты?
Согласованность — это самое важное, однако я вкратце перечислю и другие аспекты:
Вам нужно выработать качественное понимание того, как сервис применяется на практике (то есть пользователями). К каким конечным точкам обращаются чаще всего? Какие конечные точки критичнее всего (например, они используются платящими клиентами и их деградация недопустима)? Каких гарантий задержек должен придерживаться сервис и какой код выполняется на горячих путях выполнения? При разработке крупных кодовых баз часто встречается такая ошибка: кто-то вносит «небольшое изменение», неожиданно оказывающееся на горячем пути выполнения для критичного потока, и это вызывает серьёзные проблемы.
Вы не можете надеяться на возможность тестирования кода так, как это происходит в мелких проектах. Любой крупный проект со временем накапливает состояние (например, как вы думаете, сколько типов пользователей поддерживает GMail?) Однажды настаёт такой момент, когда вы уже не можете тестировать все возможные комбинации состояний даже с использованием автоматизации. Вместо этого приходится тестировать только критичные пути, применять безопасное программирование (defensive coding), а для выявления проблем пользоваться постепенным выкатыванием и мониторингом.
Добавлять новые зависимости следует только при крайней необходимости. В крупных базах данных код часто работает вечно. Зависимости добавляют текущие затраты в виде уязвимостей безопасности и обновлений пакетов, которые наверняка переживут срок вашей карьеры в компании. Если новая зависимость нужна, то выбирайте широко используемую и надёжную, или же такую, которую можно при необходимости форкнуть.
Примерно по тем же причинам, если у вас когда-то появится шанс удалить код, то хватайтесь за него обеими руками. Это одна из самых рискованных задач в кодовых базах, так что не решайте её спустя рукава: сначала оснастите код, чтобы выявить все точки вызова в продакшене, и снизьте их количество до нуля для абсолютной уверенности в том, что код можно безопасно удалить. Но это всё равно стоит того. Лишь немногое в крупной кодовой базе важнее, чем возможность безопасного удаления кода.
Работайте маленькими пул-реквестами и в первую очередь загружайте изменения, влияющие на код других команд. Это важно и в маленьких проектах, но в крупных это просто необходимо, потому что для выявления того, что вы упустили, вам часто потребуются знания специалистов в предметной области из других команд (потому что крупные проекты слишком сложны, чтобы кто-то мог разобраться в них полностью). Если ваши изменения в рискованных областях будут оставаться маленькими и удобочитаемыми, то эти специалисты с гораздо большей вероятностью смогут выявлять проблемы и спасать вас от инцидентов.
Зачем этим заморачиваться?
В конце я бы хотел сказать слово в защиту этих кодовых баз в целом. Часто я встречаю примерно такие рассуждения:
Зачем вообще браться за работу над этим легаси-бардаком? Барахтаться в спагетти-коде — сложная задача, но это плохой инжиниринг. Столкнувшись с крупной устоявшейся кодовой базой, вы должны заняться её уменьшением при помощи разбиения на маленькие изящные сервисы, а не ввязываться в увеличение этого хаоса.
Я считаю, что это абсолютно ошибочное мнение. Главная причина заключается в том, что чаще всего крупные устоявшиеся кодовые базы обеспечивают 90% пользы. В любой крупной технологической компании основная часть генерирующих прибыль действий (то есть работы, которая позволяет компании получать деньги и выплачивать зарплату разработчикам) поступает из крупной устоявшейся кодовой базы. Если вы работаете в крупной технологической компании и не считаете, что это так, то, возможно, вы правы, но я восприму это мнение серьёзно, только если вы глубоко освоились с крупной устоявшейся кодовой базой, которая, с вашей точки зрения, не приносит никакой пользы. Я много раз видел, как маленький изящный сервис лежит в основе базовой фичи высокоприбыльного продукта, но весь его производственный код (настройки, управление пользователями, биллинг, корпоративная отчётность и так далее) всё равно находится внутри крупной устоявшейся кодовой базы.
Поэтому вы обязаны знать, как работать с «легаси-бардаком», потому что именно этим и занимается компания. Даже если это не хороший инжиниринг, это ваша работа.
Вторая причина заключается в том, что невозможно разбить на части крупную устоявшуюся кодовую базу, сначала не разобравшись в ней. Я видел примеры успешного разбиения крупных кодовых баз, но никогда не было так, чтобы этим занималась команда, ещё не освоившая в совершенстве выпуск фич внутри этих кодовых баз. Вы просто не сможете перепроектировать любой проект сложнее тривиального (то есть проект, приносящий реальные деньги) на чисто теоретической основе. Существует слишком много мелких подробностей, от которых зависят десятки миллионов долларов прибыли.
Подведём итог
Над крупными кодовыми базами стоит работать, потому что именно это обычно приносит вам зарплату
Самый важный аспект — это их согласованность
Никогда не приступайте к созданию фичи без предварительного изучения предыдущих решений в кодовой базе
Для того, чтобы не следовать готовым паттернам, должны быть очень веские причины
Разберитесь в том, как кодовая база влияет на продакшен
Не надейтесь охватить тестами все случаи, вместо этого полагайтесь на мониторинг
Избавляйтесь от кода при любой возможности, но подходите к этому крайне аккуратно
Максимально упростите специалистам в предметной области выявление ваших ошибок
kualme
Не раз сталкивалась с ситуацией, когда инженеры (как опытные, так и нет), пытались переписать "легаси" код в проекте, а потом оказывалась, что, вообще говоря, все было написано по делу :)
mds-oof
Это уровни принятия ) вначале я ничего не понимаю, потом они ничего не понимают (перепишу ка код), а потом "да не, и я нормальный и они нормальные"