Работа с крупными устоявшимися кодовыми базами — один из самых сложных навыков, осваиваемых разработчиком ПО. Его невозможно практиковать заранее (нет, опенсорс не даст вам этого опыта). Личные проекты не научат этому, потому что они по определению маленькие и реализуются с нуля. Нужно уточнить, что когда я говорю «крупные устоявшиеся кодовые базы», то имею в виду следующее:
От одного до десятка миллионов строк кода (допустим, примерно пять миллионов)
Примерно от 100 до 1000 разработчиков, работающих над одной кодовой базой
Первая работающая версия кодовой базы была выпущена как минимум десять лет назад
Я уже больше десятка лет работают с такими кодовыми базами. В статье я поделюсь теми знаниями, которые бы мне очень пригодились в начале.
Несогласованность — смертный грех
Чаще остальных я встречаю одну ошибку, которая оказывается смертельно опасной: игнорирование остальной части кодовой базы и реализация своей фичи наиболее логичным образом. Иными словами, разработчик ограничивает точки соприкосновения с имеющейся кодовой базой, чтобы его красивый чистый код не загрязнялся легаси-мусором. Инженерам, в основном работавшим с маленькими кодовыми базами, этого соблазна избежать трудно. Но ему нужно противиться! На самом деле, нужно максимально глубоко погрузиться в легаси, чтобы обеспечить согласованность.
Почему согласованность настолько важна в крупных кодовых базах? Потому что она защищает нас от неприятных сюрпризов, замедляющих развитие кодовой базы и превращающих его в хаос, а также позволяет нам создавать улучшения в будущем.
Допустим, вы создаёте конечную точку API для определённого типа пользователя. Вы можете добавить в свою конечную точку логику вида «возвращать 403, если текущий пользователь не относится к этому типу». Но сначала вам следует изучить, что делают для аутентификации другие конечные точки API в кодовой базе. Если они используют какой-то конкретный набор вспомогательных функций, то вам тоже следует их применять (даже если они неудобны, сложны в интеграции или кажутся в вашем сценарии использования лишней тратой ресурсов). Вы должны сопротивляться искушению сделать ваш маленький уголок кодовой базы красивее, чем остальная её часть.
Основная причина этого заключается в том, что в крупных кодовых базах есть множество закопанных мин. Например, вы можете и не знать, что в кодовой базе есть концепция «ботов», которые подобны пользователям, но не совсем совпадают с ними и требуют особой обработки при аутентификации. Вы можете не знать, что внутренний инструментарий поддержки кодовой базы позволяет разработчику иногда выполнять аутентификацию от лица пользователя, что требует при аутентификации особой обработки. И наверняка есть ещё сотни тонкостей, которые вы можете не знать. Существующая функциональность представляет собой безопасный маршрут через минное поле. Если вы будете выполнять свою аутентификацию так же, как это долгое время делали другие конечные точки API, то сможете двигаться по этому маршруту, не зная, какие сюрпризы могла бы вам подкинуть кодовая база.
Кроме того, нехватка согласованности — главный убийца кодовых баз в длительной перспективе, потому что из-за неё становится невозможно вносить любые общие улучшения. Вернёмся к примеру с аутентификацией: если вам когда-нибудь понадобится добавить новый тип пользователя, то согласованная кодовая база позволит обновить имеющийся набор вспомогательных функций аутентификации. В несогласованной кодовой базе, где некоторые конечные точки API ведут себя иначе, вам придётся обновлять и тестировать каждую из таких реализаций. На практике это означает, что крупномасштабные изменения просто не вносятся или не затрагивают 5% самых сложных конечных точек, что, в свою очередь, ещё больше снижает согласованность, потому что теперь у вас есть тип пользователя, работающий на большинстве, но не на всех конечных точках API.
Поэтому когда вы приступаете к какой-то реализации в крупной кодовой базе, то вам всегда следует сначала поискать уже имеющиеся решения и по возможности следовать им.
Есть ли её какие-то важные аспекты?
Согласованность — это самое важное, однако я вкратце перечислю и другие аспекты:
Вам нужно выработать качественное понимание того, как сервис применяется на практике (то есть пользователями). К каким конечным точкам обращаются чаще всего? Какие конечные точки критичнее всего (например, они используются платящими клиентами и их деградация недопустима)? Каких гарантий задержек должен придерживаться сервис и какой код выполняется на горячих путях выполнения? При разработке крупных кодовых баз часто встречается такая ошибка: кто-то вносит «небольшое изменение», неожиданно оказывающееся на горячем пути выполнения для критичного потока, и это вызывает серьёзные проблемы.
Вы не можете надеяться на возможность тестирования кода так, как это происходит в мелких проектах. Любой крупный проект со временем накапливает состояние (например, как вы думаете, сколько типов пользователей поддерживает GMail?) Однажды настаёт такой момент, когда вы уже не можете тестировать все возможные комбинации состояний даже с использованием автоматизации. Вместо этого приходится тестировать только критичные пути, применять безопасное программирование (defensive coding), а для выявления проблем пользоваться постепенным выкатыванием и мониторингом.
Добавлять новые зависимости следует только при крайней необходимости. В крупных базах данных код часто работает вечно. Зависимости добавляют текущие затраты в виде уязвимостей безопасности и обновлений пакетов, которые наверняка переживут срок вашей карьеры в компании. Если новая зависимость нужна, то выбирайте широко используемую и надёжную, или же такую, которую можно при необходимости форкнуть.
Примерно по тем же причинам, если у вас когда-то появится шанс удалить код, то хватайтесь за него обеими руками. Это одна из самых рискованных задач в кодовых базах, так что не решайте её спустя рукава: сначала оснастите код, чтобы выявить все точки вызова в продакшене, и снизьте их количество до нуля для абсолютной уверенности в том, что код можно безопасно удалить. Но это всё равно стоит того. Лишь немногое в крупной кодовой базе важнее, чем возможность безопасного удаления кода.
Работайте маленькими пул-реквестами и в первую очередь загружайте изменения, влияющие на код других команд. Это важно и в маленьких проектах, но в крупных это просто необходимо, потому что для выявления того, что вы упустили, вам часто потребуются знания специалистов в предметной области из других команд (потому что крупные проекты слишком сложны, чтобы кто-то мог разобраться в них полностью). Если ваши изменения в рискованных областях будут оставаться маленькими и удобочитаемыми, то эти специалисты с гораздо большей вероятностью смогут выявлять проблемы и спасать вас от инцидентов.
Зачем этим заморачиваться?
В конце я бы хотел сказать слово в защиту этих кодовых баз в целом. Часто я встречаю примерно такие рассуждения:
Зачем вообще браться за работу над этим легаси-бардаком? Барахтаться в спагетти-коде — сложная задача, но это плохой инжиниринг. Столкнувшись с крупной устоявшейся кодовой базой, вы должны заняться её уменьшением при помощи разбиения на маленькие изящные сервисы, а не ввязываться в увеличение этого хаоса.
Я считаю, что это абсолютно ошибочное мнение. Главная причина заключается в том, что чаще всего крупные устоявшиеся кодовые базы обеспечивают 90% пользы. В любой крупной технологической компании основная часть генерирующих прибыль действий (то есть работы, которая позволяет компании получать деньги и выплачивать зарплату разработчикам) поступает из крупной устоявшейся кодовой базы. Если вы работаете в крупной технологической компании и не считаете, что это так, то, возможно, вы правы, но я восприму это мнение серьёзно, только если вы глубоко освоились с крупной устоявшейся кодовой базой, которая, с вашей точки зрения, не приносит никакой пользы. Я много раз видел, как маленький изящный сервис лежит в основе базовой фичи высокоприбыльного продукта, но весь его производственный код (настройки, управление пользователями, биллинг, корпоративная отчётность и так далее) всё равно находится внутри крупной устоявшейся кодовой базы.
Поэтому вы обязаны знать, как работать с «легаси-бардаком», потому что именно этим и занимается компания. Даже если это не хороший инжиниринг, это ваша работа.
Вторая причина заключается в том, что невозможно разбить на части крупную устоявшуюся кодовую базу, сначала не разобравшись в ней. Я видел примеры успешного разбиения крупных кодовых баз, но никогда не было так, чтобы этим занималась команда, ещё не освоившая в совершенстве выпуск фич внутри этих кодовых баз. Вы просто не сможете перепроектировать любой проект сложнее тривиального (то есть проект, приносящий реальные деньги) на чисто теоретической основе. Существует слишком много мелких подробностей, от которых зависят десятки миллионов долларов прибыли.
Подведём итог
Над крупными кодовыми базами стоит работать, потому что именно это обычно приносит вам зарплату
Самый важный аспект — это их согласованность
Никогда не приступайте к созданию фичи без предварительного изучения предыдущих решений в кодовой базе
Для того, чтобы не следовать готовым паттернам, должны быть очень веские причины
Разберитесь в том, как кодовая база влияет на продакшен
Не надейтесь охватить тестами все случаи, вместо этого полагайтесь на мониторинг
Избавляйтесь от кода при любой возможности, но подходите к этому крайне аккуратно
Максимально упростите специалистам в предметной области выявление ваших ошибок
Комментарии (23)
Gapon65
10.01.2025 15:04В моей практике, модификация устоявшегося кода (написанного другими) обычно включала следующие этапы:
попытка понять насколько новый код легко интегрируется со старым (и частое разочарование ввиду невозможности это сделать легко и быстро)
неприятие существующего ("легаси") кода и желание его переписать (обычно это быстро проходит)
этап изучение существующего кода (как правило, до уровня на котором становится понятно какие есть варианты для внесения изменений)
оценка вариантов (трудоемкость, потенциальные риски)
рефакторинг старого кода ц целью уменьшения резистивности кода для внесения новой функциональности (требуется в большинстве случаев)
тестирование переработанного кода
внесение планируемых изменений
Tim7456
10.01.2025 15:04Полностью поддержу то что написано в статье.
И часто дела обстоят еще хуже. Ты непрерывно пилишь эту огромную кодовую базу на компоненты, удаляешь старый код, разбиваешь зависимости только для того чтобы стоять на месте. Это позволяет добиться только того, что сложность не сильно быстро растет. Потому что все новые фичи, исправления ошибок только наращивают и сложность, и кодовую базу.Здесь нужно все время бежать только для того чтобы остаться на месте. Чтобы двигаться вперед нужно бежать вдвое быстрее!
ImagineTables
10.01.2025 15:04А нельзя написать логику классификации юзеров один раз, чтобы «искушения» (сделать свою обработку типа пользователя) вообще не появлялось? Smells like a DRY violation. Вот что настоящий смертный грех, независимо от величины проекта.
MonkAlex
10.01.2025 15:04За годы у вас появится много сценариев, на которые заранее не заложиться.
Сначала у вас только менеджеры и клиенты. Потом интеграции (разные). Где-то там же может появится "системный" пользователь для автоматизированных обработок. Потом чат-бот появится. Завтра бизнес захочет ИИ с какими-то доступами иметь.
И одной только классификацией (к чему она тут?) не обойтись в любом случае. Где-то завязки будут на неявное поведение, где-то будет строгий контракт описанный в ТЗ, где-то будет версионирование под мобильные приложения и их версии, где-то будут фичи для бизнеса которые уродские с точки зрения кода, но нужны.
ImagineTables
10.01.2025 15:04Что значит «к чему она тут»? Это постановка задачи! Не возвращать сразу 403, а делать весь комплекс приседаний, ведь среди юзеров могут быть боты и кто-то там ещё. Вопрос, а почему не вынести это всё в одно место и не вызывать автоматически, чтобы при разработке нового модуля у автора просто не было возможности всё испортить? Вместо того, чтобы давать советы смотреть, как у других (по сути дела, копипастить).
А аргумент про «многие годы» и «тысячи программистов» меня не убеждает. Я видел, как это происходит. Пока проект маленький — никто не хочет закладывать правильную архитектуру, потому что и так сойдёт, для нашего маленького проекта сложная архитектура не нужна. Когда проект вырастает, никто не хочет закладывать правильную архитектуру, потому что проект уже слишком большой. Плохим танцорам всё время что-то мешает.
LoveMeOrHateMe
10.01.2025 15:04Плохим танцорам всё время что-то мешает.
Пока хорошие танцоры предусматривают все возможные варианты и случаи, плохие уже запилили десяток других кривых косых проектов, один из них выстрелил и принес деньги. А бизнесу глубоко фиолетово насколько в вашем коде все предусмотрено или не предусмотрено, ему нужны только деньги. В итоге плохой танцор молодец, а хороший так и сидит что-то пилит никому не нужное.
Это мы конечно не говорим про условный гугл, мы говорим про условного ИП Пупкина, у которого есть какой то бизнес, который благодаря плохим танцорам будет развиваться в какое нибудь ООО
ImagineTables
10.01.2025 15:04Какая глубокая мысль: за деньги делать говно. Никто бы не догадался. Только одна проблема — автор не учит нас моральной гибкости, он учит правильно писать код.
LoveMeOrHateMe
10.01.2025 15:04Если код выполняет свою задачу и приносит деньги это не говно, это рабочий инструмент. А автор учит разбирать авгиевы конюшни и какие шаги по возможности следует предпринимать. Ну и вообще, при чем тут автор? Я же отвечал на ваш комментарий о том, что сразу надо делать правильно. Может и надо в идеальном мире, было бы хорошо. Да вот я живу в мире дедлайнов и всем по барабану как я напишу, им надо чтоб работало, ещё вчера.
MonkAlex
10.01.2025 15:04Это хорошо, если постановку задачи кто-то готов изначально дать хорошую (или вы обладаете опытом её качественно выполнить). Но иногда таких людей на проекте нет, либо просто требования меняются по ходу жизни проекта.
Ваше утверждение звучит как "делай хорошо" и я с ним согласен, но реальность обычно чуть хуже выглядит и получается обычно "хорошо на момент реализации", а не "хорошо на всю жизнь проекта".
ImagineTables
10.01.2025 15:04Ваше утверждение звучит как "делай хорошо" и я с ним согласен
Понимаете, в чём дело. Это принципиальный вопрос, и компромисса тут быть не может. Если вы соглашаетесь с этим, то соглашаетесь и с тем, что автор (его контора) совершил нарушение DRY, но оправдываете его тем, что трудно было не совершить (опыта не было, не успели за изменениями требований, решили, что и так сойдёт и т.п.)
Никто не спорит: трудно. Многие не справляются. Но они сидят на попе ровно, а не пишут статьи: «Не возвращай 403 с ходу, а посмотри (==скопипасти) код у коллег». Я всё ждал (учитывая высокий рейтинг статьи), что мне сейчас объяснят, что я понял автора неправильно. Но пока что, судя по этим ответам, получается, что всё я понял правильно.
MonkAlex
10.01.2025 15:04Вы хотите хорошо и чтобы все делали хорошо.
Автор рассказывает, как будет на самом деле.
ImagineTables
10.01.2025 15:04Если вы думаете, что я не видел крупных проектов, где удавалось следить за гигиеной (в том числе, не нарушать DRY), то ошибаетесь. Видел. Такое ведение дел и позволяло подстраиваться под меняющиеся требования, ничего не сломав.
MonkAlex
10.01.2025 15:04Было ли этим проектам много лет?
Потому что я сталкивался пока только с 3 проектами в 10+ лет и на всех них эта проблема была в разной степени. Да, можно делать лучше, но это сложно и не всегда организационно возможно, что приводит к разным странным решениям, которые разгребают только при появлении проблем.
ImagineTables
10.01.2025 15:04Тогда у меня один вопрос: с каких это пор люди, страдающие организационной немочью, начали писать статьи об «ошибках инженеров», пропагандируя пользу самоповторов? (Хочу подчеркнуть, что к переводчику у меня претензий нет: он переводит интересные статьи, иногда — [с моей точки зрения] провокационные).
Было ли этим проектам много лет?
Всякие повидал. И многолетние тоже. Однажды работал в проекте, который изначально пилили очень толковые инженеры, а потом он попал в руки карьеристов, индусов и китайцев (бестолочи всякой). Я его читал (без версионного контроля — не было его на нужную глубину), и прям видел: вот тут проходит разделение геологических слоёв. Досюда писали крутые чуваки: заложили DSL, хорошую архитектуру, боролись с самоповторами и т.д. А вот тут — пришли, извиняюсь, обезьяны и продукт ума человеческого начали заменять на хтонический ужас. А потом узнал, что у конторы в прошлом было слияние, и продукт делался изначально другой командой. Но это было видно из кода! Невооружённым взглядом!
Самое забавное, что новая команда очень гордилась своим вкладом. Особенно, почему-то, тем что объём исходников перевалил за гигабайт. Этого стыдиться надо (явно перераздутая кодебаза), а они гордились. Трудно не провести параллели с тем, как этот дядька гордо учит нарушать DRY.
MonkAlex
10.01.2025 15:04Я понимаю, о чём вы. Но к сожалению, работаем не в одиночку, работаем на деньги бизнеса и в условиях конкуренции. Не всегда выходит у всех делать хорошо. И работать чаще приходится с тем что уже написано. Я лично стараюсь делать код лучше, но на каждую фичу - не хватает ни сил ни желания. Поэтому я со статьей согласен - такое бывает и надо учитывать. Хотелось бы делать лучше, но это уже как получится.
LaRN
10.01.2025 15:04Мешает меркантильная математика. На рефакторинг большого нужно время и это время стоит денег. А кроме этого в точке есть куча задач от бизнеса, который не хочет ждать, и считает что новые ыичи принесут больше денег, т.е. это плюс, а рефакторинг только сожрет деньги - это минус. А зп у бизнесов обычно растет, если есть плюс.
ImagineTables
10.01.2025 15:04Ну так, тогда так и надо писать: вы оказались в крупной говноконторе. Рефакторить вам не дадут. Проект — мрачный ужас. Нормализовать кодебазу («любая задача решается ровно один раз») вам не дадут. Но хотя бы копипасьте код у коллег, чтобы не было конфликтов, а не сочиняйте своё заново.
С такими поправками вопросов не будет. Но и пафосно поучить про «ошибки инженеров» уже не получится.
Dhwtj
10.01.2025 15:04" в крупных кодовых базах есть множество закопанных мин. Например, вы можете и не знать, что в кодовой базе есть концепция "
Потому что эта концепция не описана. Не только нет документации и разработчика, который что-то помнит. Нет даже типов, нет спецификаций API, комментариев, ничего нет ибо "хорошему коду комментарии не нужны". И приходится чтобы понять лезть в код, в отладку, додумывать а что же тут должно быть.
А потом эти же люди мне говорят, что чистая архитектура и что статическая типизация слишком увеличивают трудоемкость написания.
MonkAlex
10.01.2025 15:04И за 10+ лет даже наличие документации, разработчика и комментов не поможет.
Потому что документация устарела, разработчик уже не помнит все детали, ещё куча других потенциальных "особенностей" появилась за это время.
Dhwtj
10.01.2025 15:04Комментарии и документацию нужно обновлять каждый раз при изменении поведения кода. И не забывать документировать требования.
Но да, часть всё равно будет только в голове программиста, а потом забудется или уйдёт
seregamorph
10.01.2025 15:04Полностью солидарен с автором. В любых священных войнах по поводу выбора технологии или фреймворка - я на стороне консистентности, даже если выбор не мой личный фаворит. В долгосроке разобравшись с тонкими нюансами существующего решения его можно переписать на новое или использовать классический популярный фреймворк (желательно одним махом или за короткий срок, чтобы не создавать риск параллельно существующих решений). Но этим не должны заниматься люди с порога, потому что в большинстве случаев это все уйдет не дальше прототипов, которые будут бельмом выбиваться из общей канвы.
Еще понравилось это предложение
Я видел примеры успешного разбиения крупных кодовых баз, но никогда не было так, чтобы этим занималась команда, ещё не освоившая в совершенстве выпуск фич внутри этих кодовых баз.
Действительно, в команды платформы, которые делают общий код и модуляризацию нужно либо переходить из фиче-команд, либо в случае внешнего найма работа в компании должна начинаться с онбординга в фиче-команде хотя бы месяц-два. Иначе, какой бы не был крутой опыт и бэкграунд, неверных доводов о текущей ситуации не избежать.
kualme
Не раз сталкивалась с ситуацией, когда инженеры (как опытные, так и нет), пытались переписать "легаси" код в проекте, а потом оказывалась, что, вообще говоря, все было написано по делу :)
mds-oof
Это уровни принятия ) вначале я ничего не понимаю, потом они ничего не понимают (перепишу ка код), а потом "да не, и я нормальный и они нормальные"