Изменения в сложных программных системах, кажется, занимают вечность, не так ли? Даже инженерам часто кажется, что изменения идут больше положенного, хотя мы сознаём всю сложность системы!
Для заказчиков ситуация ещё более непонятна. Проблема усугубляется случайной сложностью, которая добавляется с течением времени из-за плохой поддержки систем. Возникает чувство, будто мы пытаемся вычерпать воду из корабля с тысячей пробоин.
Поэтому рано или поздно заказчик пришлёт письмо: «Почему, чёрт возьми, это занимает так много времени?» Не будем забывать, что у нас как инженеров-программистов есть окно в мир, которого они зачастую лишены. Они очень нам доверяют, но иногда кажущееся незначительным изменение отнимает действительно много времени. Из-за этого и возникают вопросы.
Не обижайтесь на этот вопрос; воспринимайте его как возможность проявить сочувствие и дать человеку более чёткое представление о сложности системы. В то же время, вы можете предложить пути улучшения ситуации. Когда кто-то расстроен, это наилучший момент, чтобы предложить решение!
Ниже опубликовано письмо, которое в том или ином варианте мы неоднократно отправляли на протяжении многих лет. Надеемся, оно поможет вам отвечать на подобные вопросы.
Письмо
Дорогой Клиент,
Я видел ваш комментарий на карточке «Уведомить перед истечением срока выполнения задания» и буду рад обсудить его на нашей следующей встрече. Здесь для справки я обобщу свои мысли, необязательно отвечать.
Перефразируя вашу заметку:
Изменение срока выполнения задач на один день для уведомления по почте должно занимать одну строчку. Как оно может занять 4−8 часов? Что я упускаю?
С одной стороны, я с вами согласен. Достаточно просто изменить часть запроса с tasks due <= today
на tasks due <= tomorrow
.
С другой стороны, сводя его к такой упрощённой идее, мы непреднамеренно игнорируем присущую сложность и принимаем ряд инженерных решений. Некоторые из них мы должны обсудить.
Часть 1. Почему это небольшое изменение больше, чем кажется?
Это простое, небольшое изменение, одна строка кода. Тратить на него целый день, даже полдня, кажется чрезмерным.
Конечно, нельзя просто выкатить изменение в продакшн, не запустив хотя бы локально или на тестовом сервере. Следует убедиться, что код выполняется правильно, а в случае изменения запроса нужно сравнить выдачу и убедиться, что она выглядит более-менее правильно.
Здесь сравнение выходных данных может быть минимальным, лишь небольшая выборочная проверка: убедиться, что результаты имеют смысл и т. д. Это уведомление для внутренних сотрудников. Если математика по дате неверна (лёгкая ошибка), мы быстро услышим об этом от команд. Если бы это было, скажем, электронное письмо для ваших клиентов, потребовалось бы более глубокое изучение. Но для этого лёгкого тестирования и ревью достаточно 20−40 минут, в зависимости от того, появляется ли что-то странное или неожиданное. Копание в данных может съесть время. Выпуск изменений без проведения ревью — просто непрофессиональная небрежность.
Таким образом, добавим время для нормальной логистики, такой как коммит кода, слияние изменений, развёртывание и т. д.: от начала работы до выпуска в продакшн проходит минимум час у компетентного, профессионального инженера.
Конечно, это предполагает, что вы точно знаете, какую строку кода изменить. Рабочий процесс задачи в основном живёт в старой системе, но некоторые части логики живут в новой системе. Перемещение логики из старой системы — это хорошо, но это означает, что функциональность задачи в настоящее время разделена на две системы.
Поскольку мы так долго работали вместе, наша команда знает, какой процесс отправляет электронное письмо с просроченной задачей и может указать на строку кода в новой системе, которая инициирует процесс. Так что нам не нужно тратить время на выяснение этого.
Но если мы посмотрим на код задачи в старой системе, есть по крайней мере четыре разных способа определить, что наступил срок выполнения задачи. Кроме того, глядя на шаблоны и поведение электронной почты, есть по крайней мере ещё два места, где, похоже, реализована нестандартная логика для этой задачи.
И тогда логика уведомлений сложнее, чем вы думали. Она различает общие и индивидуальные задачи, открытые и приватные, повторяющиеся, функцию дополнительного уведомления менеджера в случае просроченной задачи и т. д. Но мы можем довольно быстро выяснить, что фактически для уведомлений используются только 2 из 6+ определений просроченной задачи. И только одно нужно изменить, чтобы достичь цели.
Такое ревью может легко занять ещё полчаса или около того, может быть, меньше, если вы недавно были в этой части кодовой базы. Кроме того, скрытая сложность означает, что мы можем превысить нашу оценку для ручного тестирования. Но давайте просто добавим 30 минут для дополнительных усилий.
Таким образом, мы достигли 1,5 часов, чтобы чувствовать себя уверенно, что изменение будет выполнено как положено.
Конечно, мы ещё не проверили, используют ли какие-либо другие процессы изменяемый запрос. Мы не хотим случайно нарушить другие функции, изменив концепцию «срок выполнения» на день, который предшествует последнему дню для выполнения задачи. Мы должны рассмотреть кодовую базу с этой точки зрения. В этом случае, похоже, нет никаких основных зависимостей — вероятно, потому что основная часть пользовательского интерфейса всё ещё в старой системе. Поэтому не нужно беспокоиться об изменении или тестировании других процессов. В лучшем случае, это ещё 15−30 минут.
О, и поскольку основная часть пользовательского интерфейса задачи всё ещё в старой системе, мы действительно должны сделать быстрый обзор функциональности задачи в этой системе и убедиться в корректной обратной связи. Например, если пользовательский интерфейс выделяет задачи, чей срок наступил, мы можем изменить эту логику, чтобы она соответствовала уведомлению. Или, по крайней мере, вернуться и спросить клиента, как он хочет сделать. Я в последнее время не смотрел на функциональность задачи в старой системе и не помню, есть ли у нее какое-либо представление о сроке/просрочке. Такое ревью добавляет ещё 15-30 минут. Возможно, больше, если в старой системе также есть несколько определений «задачи» и т. д.
Таким образом, мы вышли в диапазон 2–2,5 часа для выполнения задачи с уверенностью, что всё пройдёт нормально, без непреднамеренных побочных эффектов или путаницы в работе пользователя.
Часть 2. Как можно сократить это время?
К сожалению, единственным результатом этих усилий является только выполнение поставленной задачи. Это неоптимально, что весьма разочаровывает. Знания, полученные разработчиком в ходе работы, являются личными и эфемерными. Если другому разработчику (или нам самим через 6 месяцев) снова потребуется внести изменения в эту часть кода, процесс придётся повторить.
Есть две основные тактики для исправления ситуации:
- Активно чистить кодовую базу для уменьшения дублирования и сложности.
- Писать автоматизированные тесты.
Примечание: мы уже обсуждали документацию, но в данном случае это не лучшее решение. Документация полезна для высокоуровневых идей, например, для объяснения бизнес-логики или часто повторяемых процессов, таких как список новых партнёров. Но когда дело доходит до кода, документация быстро становится слишком объёмной и устаревает по мере изменения кода.
Вы заметили, что ни одна из этих тактик не включена в наши 2–2,5 часа.
Например, поддержание чистой кодовой базы означает, что вместо простого выполнения поставленной задачи мы задаём вопросы:
- Почему существует так много разных способов определить задачи, чей срок подошёл/истёк?
- Все ли они нужны и над ними работают?
- Можно ли свести эти способы к одной или двум концепциям/методам?
- Если концепция разделена между старой и новой системами, можно ли её консолидировать?
И так далее.
Ответы на эти вопросы могут быть довольно быстрыми: например, если мы встречаем явно мёртвый код. Или могут занять несколько часов: например, если задачи используются во многих сложных процессах. Как только у нас будут эти ответы, потребуется ещё больше времени для рефакторинга, чтобы уменьшить дублирование/путаницу и получить единственное описание концепции «наступившего срока» — или переименовать понятия в коде, чтобы ясно понять, как они отличаются и почему.
Но в конце концов, эта часть кодовой базы станет намного проще, её будет легче читать и изменять.
Другая тактика, которую мы обычно используем, — автоматизированное тестирование. В некотором смысле автоматизированные тесты подобны документации, которая не может устареть и которую легче обнаружить. Вместо того, чтобы вручную запускать код и просматривать выдачу, мы пишем тестовый код, который запускает запрос и программно проверяет вывод. Любой разработчик может запустить этот тестовый код, чтобы понять, как система должна работать, и убедиться, что она всё ещё работает таким образом.
Если у вас система с приличным тестовым покрытием, эти изменения займут значительно меньше времени. Вы можете изменить логику, а затем запустить полный набор тестов и убедиться, что
- изменение работает правильно;
- изменение ничего не сломало (это ещё более ценная информация, чем в первом пункте).
Когда мы в компании Simple Thread строим системы с нуля, то всегда включаем в оценку сроков время для написания автоматических тестов. Это может замедлить начальную разработку, но значительно повышает эффективность работы и обслуживания. Только когда система растёт, вы действительно понимаете важность тестов, но к этому моменту вернуть тесты в систему может оказаться очень сложно. Наличие тестов также намного упрощает работу новых сотрудников, а изменение поведения системы происходит гораздо быстрее и безопаснее.
Часть 3. Откуда мы пришли? Куда мы идём?
На сегодняшний день мы редко обозначаем в оценке для вас время очистки кода или написания тестов. Это отчасти потому, что написание тестов с нуля представляет собой незначительные накладные расходы, а добавление тестов в кодовую базу задним числом — большая работа, вроде восстановления фундамента под домом, в котором живут люди.
Это также частично объясняется тем, что начиная работать с вами, мы сразу переходим в режим реанимации. У нас почти ежедневные проблемы с синхронизацией сторонних данных, еженедельные проблемы с генерацией отчётов, постоянные запросы на поддержку небольших изменений данных, неадекватный мониторинг и ведение журнала системы и т. д. Кодовая база тонет под тяжестью технических долгов, и мы лихорадочно пытаемся удержать системы на плаву, одновременно заклеивая дыры изолентой.
Со временем системы становятся более стабильными и надёжными, мы автоматизируем/предоставляем UI для самообслуживания частых запросов на поддержку. У нас всё ещё много технических долгов, но мы вышли из аварийного режима. Но я не думаю, что мы когда-нибудь полностью уйдём от этого менталитета реанимации к более проактивному, зрелому менталитету «планировать и выполнять».
Мы пытаемся очищать код на ходу, и мы всегда тщательно тестируем. Но быть осторожным и прилежным — это не проактивный рефакторинг и не создание инфраструктуры, необходимой для хороших автоматизированных тестов.
Если мы не начнём выплачивать некоторые технические долги, мы никогда не сможем значительно улучшить ситуацию. Высококвалифицированным, компетентным разработчикам потребуются месяцы, чтобы сориентироваться и внести нетривиальные изменения.
Другими словами, 4−8 часов на данную задачу — это запас примерно в 2−4 раза, но он значительно уменьшит усилия для подобных изменений в будущем. Если бы эта часть кодовой базы была чище и имела хорошее покрытие автоматическими тестами, то компетентный опытный разработчик выполнил бы её за час или меньше. И ключевой момент в том, что у нового разработчика работа займёт ненамного больше времени.
Для такого изменения сроков нам нужно ваше согласие. Это сознательная попытка на фундаментальном уровне улучшить работу вашей системы, а не только то, как пользователи её воспринимают. Я понимаю, что на такие вложения трудно согласиться именно потому, что нет никакой видимой пользы, но мы рады сесть с вами и подготовить некоторые чёткие цифры, которые покажут, как эти инвестиции окупятся в долгосрочной перспективе с инженерной точки зрения.
Спасибо,
Эл
Автор: m1rko