Каждый проект требует жертвы. Главное, чтобы не слишком большой. Команда Mail.Ru Cloud Solutions перевела статью Алекса Стейвли про минусы технического долга и его способность уничтожить даже самый успешный проект. Предупреждение автора: в этой статье не так много практики, как хотелось бы, но она может натолкнуть на размышления.
Не путайте технический долг с плохими инженерными решениями
Понятие технического долга используют в разработке программного обеспечения для выражения дополнительной сложности, которая добавляется к проекту из-за выбора худших, но более быстрых решений. Он аналогичен финансовому долгу. Берете кредит для быстрого достижения какой-то цели, но потом обязательно его гасите. Иначе кредит с процентами будет расти — и в итоге вы рискуете обанкротиться.
Как и с финансовыми кредитами, некий технический долг вполне допустим. В конце концов, если слишком долго искать идеальное решение, ваши клиенты уйдут к кому-то другому.
В то же время, если вы берете слишком много технических долгов и не выплачиваете их, то софтверная энтропия растет со временем — также происходит и с процентами по кредиту. В конечном счете ваш продукт начнет стагнировать. Какая-то новая задача, которая должна решаться всего за месяц, вдруг занимает три месяца, затем шесть месяцев, затем лишь несколько человек во всей компании могут сделать ее за шесть месяцев, затем некоторые из них уходят, затем…
Аналогия не срабатывает лишь в том, что технический долг слишком часто приравнивают к плохой инженерии. Но плохие инженерные решения относятся к другой категории.
В случае технического долга приняты хорошие тактические решения с полным пониманием того, что краткосрочные приоритеты стоят такой жертвы.
Когда становится ясно, что такое решение в свое время было хорошей тактикой, гораздо легче понять, что нужно провести рефакторинг и погасить технический долг.
Когда этот термин используют в качестве вежливого синонима плохой инженерии, то стратегия возврата долга становится затруднительной. И ее еще труднее создать, потому что сначала нужно убедить людей, что есть какая-то «плохая инженерия». Затем нужно убедить людей, что это вызывает проблемы. Затем вы должны продумать лучший подход к рефакторингу и затратам на его проведение.
Наконец, вы должны убедить все заинтересованные стороны, что инвестиции стоят того, и держать пальцы скрещенными, что на этом пути вы не оскорбили ни одного эго. Это как пытаться выиграть пять матчей в гостях, когда в каждой игре шансы против вас.
Как понять, что ваш технический долг уже слишком велик
Итак, каковы признаки того, что ваш технический долг (преднамеренный или случайный) слишком высок — и не за горами заявление о банкротстве?
Давайте порассуждаем. В мире Agile мы хотим разрабатывать и выпускать обновления продукта короткими циклами, получая от клиентов быструю обратную связь, и повторяя циклы снова. Это возможно только при наличии большого количества высококачественных и хорошо спроектированных автоматизированных тестов, которые работают быстро и дают уверенность, что никакое изменение ничего не поломало. Не имеет значения, какова разбивка тестов по категориям: 68% модульных и 32% интеграционных или 91% модульных и 9% интеграционных, тесты должны выполняться быстро и надежно. В противном случае релизы станут проблематичными и не получится делать их регулярно.
Это означает, что работать по методологии Agile будет очень трудно, даже с самыми качественными сессиями backlog grooming, на которых анализируется набор требований, полученных от бизнеса, и формулируются задачи для разработки.
Именно технический долг, неважно, намеренный или случайный, обычно мешает разработчикам писать хорошие тесты.
Сейчас есть много инструментов для измерения технического долга и его интеграции на красивую панель SonarQube, но обычно они учитывают только тривиальные вещи — неиспользуемый импортированный код и тому подобное. Кому до этого дело? Такие тривиальные вещи никому не помешают.
Технический долг в информационной панели SonarQube
Настоящие технические проблемы с долгом — это когда замедляется разработка, затрудняется внесение изменений, исправление ошибок, добавление функциональности, потому что значительно усложняется тестирование. К сожалению, эти технические проблемы вряд ли вам автоматически покажут такие инструменты, как IDE, PMD или Checkstyle. Как правило, они гораздо глубже по своей природе — ближе к архитектурным проблемам.
Несколько примеров:
- Отсутствие инкапсуляции и неизменяемости данных, приводящее к огромной цикломатической сложности, непредсказуемым путям выполнения кода и трудностям в прогнозировании воздействия.
- Несоответствие импеданса, то есть несоответствие типов данных, которые предусмотрены в языке прикладного программирования и в поддерживаемой модели данных.
- Отсутствие модульности и слишком сильное сцепление компонентов программы.
- Отсутствие или плохое применение шаблонов.
- Проприетарный язык, введенный без поддержки IDE, без механизма простого модульного тестирования или отладки кода. На Stack Overflow о нем ничего не знают.
- Порождение потоков и асинхронных путей вызова, когда есть лучшие подходы, которые намного легче тестировать
Именно здесь нарушается аналогия технического и финансового долга. Если у вас архитектурный долг, у вас большие проблемы. Если он более поверхностный, то проблема не так велика.
В финансовых кредитах нет такого фундаментального различия в качестве долга. Если не сравнивать долг перед дружественным и симпатичным банком, с которым у вас хорошие отношения, и долг перед сумасшедшим вышибалой, который заявится к вам домой с бейсбольной битой.
Технический долг есть. Что же делать?
Итак, вы приближаетесь к кредитному лимиту, что делать? Сначала следует обеспечить надежность тестов. Они должны тестировать ключевые функциональные возможности и быть ремонтопригодными. Это важнее скорости. Если нет надежности, вы не можете выпускать обновления продукта. Какой толк от пятиминутных тестов, если никто не уверен, что протестированные функции действительно будут работать.
Затем, когда вы уверены в тестах (даже если это уродливые сквозные тесты), нужно заставить тесты работать быстро. Потом начинайте рефакторинг. Сквозные тесты должны облегчить изменение путей вызова; например, для разбиения монолитной структуры можно применить Sprout method\class. Это поможет перейти к классической пирамиде тестов.
Теперь нужно понять, почему так сложно сделать качественные тесты. Слишком сильное сцепление компонентов программы, плохая обработка исключений, плохое разделение программы на части (слабая декомпозиция) — вероятно, список можно продолжать и продолжать.
Понимание причин, почему ваш код трудно покрыть тестами — ключевой архитектурный и инженерный навык, поскольку он требует не просто понимания сложности программы, но и знания, как уменьшить эту сложность. Тогда уменьшение технического долга поможет упростить код для тестирования и так сделать хорошие и быстрые тесты. В итоге мы начинаем побеждать в битве с возможной стагнацией проекта.
Напоследок: как Agile рождает технический долг
Методология разработки Agile стала настолько общим стандартом, что возникает проблема «фальшивого» Agile. Мы видим книги, курсы, блоги с подробным описанием новых понятий, таких как стори поинты (сюжеты), диаграммы сгорания задач, стендапы.
Всё это хорошо, но мало внимания уделяется качеству разработки. Без него неизбежно возникнут архитектурные проблемы — и появится код, который плохо тестируется, хотя вы составляете идеальные стори поинты, точно формулируете определения, крайне эффективно переводите бизнес-кейсы в программистские задачи и наклеиваете желтые листочки в нужные места на доске.
Это хорошо и полезно, но по сравнению с качеством разработки почти мелочь. Такая деятельность легко видна со стороны, но сама по себе никогда не отражает сложности технического долга. Именно отношение технического долга к качеству разработки определяет, насколько легко писать тестируемый код и обеспечить регулярные короткие итерации, что является целью Agile. Разве не так?
Когда ребенок пробует силы в каком-то виде спорта, мы всегда ему говорим: «Если начинаешь проигрывать, никогда не сдавайся, сражайся до конца!». Технический долг — естественное явление, он есть у каждого проекта в этом мире. Ваша задача держать его под контролем, а когда он слишком возрастает, придумать способ обойти его и в итоге извлечь из этого урок.
Что еще почитать: