Когда дело доходит до планирования технической стороны проекта, XP-команды используют похожий прием – применяют две основные ценности простоты к архитектуре, дизайну и коду. Так же как scrum-команды, они принимают решения в последний ответственный момент. В большинстве случаев этот момент наступает после того, как код написан.
Вам не кажется странным этот подход? ХР-разработчики так не считают, потому что постоянно занимаются рефакторингом своего кода: изменением структуры кода без изменения его поведения. Рефакторинг используется не только в XP, это распространенная и очень эффективная практика в программировании. Действительно, большинство IDE (интегрированная среда разработки – программы, которые применяют разработчики для редактирования, запуска и отладки кода) имеют встроенные инструменты для рефакторинга.
Приведем пример рефакторинга – даже если вы не разработчик, то наверняка заметите, как он делает код чище и проще для понимания. Когда мы писали книгу «Изучаем C#»[63], мы включили приведенный ниже блок кода в качестве решения для одного из проектов (симулятор улья, поэтому все названия переменных связаны с пчелами), чтобы наши читатели увидели, как он построен.
Рис. 7.6. Это был оригинальный фрагмент кода из одного проекта, включенного в нашу книгу «Изучаем C#»
В ходе технического обзора один из наших рецензентов отметил, что этот код слишком сложен – в нем используется очень большой метод. Поэтому мы сделали то, что должны предпринять большинство XP-команд: переработали этот код, чтобы он стал простым и легким для понимания. В данном случае мы взяли блок из четырех строк кода и перенесли их в метод, который назвали MoveBeeFromFieldToHiveQ («перенеси пчелу с поля в улей»). Затем сделали то же самое с другим блоком из четырех строк, извлекли их в метод, который назвали MoveBeeFromHiveToFieldQ («перенеси пчелу из улья в поле»). Вот как выглядел код, когда он наконец отправился в печать (два новых метода появились в коде позже).
Рис. 7.7. Мы провели рефакторинг кода путем извлечения двух методов. Новый код стал проще, и было легче понять, что он делает
Так гораздо понятнее. До рефакторинга для понимания того, что делает этот код, требовалось больше знаний о структуре программы, поэтому нам пришлось добавить примечания, помогающие читателям понять два блока кода. Их перемещение в именованные методы сделало работу кода понятнее. В переработанной версии видно, что эти блоки делают: в одном пчела перемещается с поля в улей, а в другом – из улья обратно в поле.
Этот рефакторинг не только сделал блок кода более понятным, но и снизил сложность всего проекта. Разумно предположить, что где-то в другом месте проекта программисту может понадобиться переместить пчелу между полем и ульем. Если эти методы уже существуют, то он, скорее всего, будет использовать именно их – это путь наименьшего сопротивления. Но даже если поначалу он поступит иначе и заметит дублирующийся код позже, то наверняка выполнит быстрый рефакторинг, чтобы удалить повторяющийся код и заменить его вызовом упомянутого метода.
Поставка кода в первый раз подобна влезанию в долги. Небольшой долг ускоряет разработку до тех пор, пока он погашается быстрой доработкой… Опасность возникает, если задолженность не оплачивается.
Плохое проектирование и плохое кодирование вызывают дополнительные затраты времени. Даже наиболее профессиональные разработчики пишут код, который может быть улучшен. Чем дольше проблемы с архитектурой и кодом остаются нерешенными, тем сильнее они сцепляются друг с другом, что в итоге приводит к эффекту «стрельбы дробью». Эти застаревшие проблемы в архитектуре и коде называются техническим долгом. Успешная ХР-команда в каждой итерации оставляет время для «погашения долга». Это правильное использование временного запаса – одной из основных практик, рассмотренных в главе 6. При этом команда добавляет истории и задачи в каждый недельный цикл в качестве буфера для поглощения непредвиденных работ.
Любой хороший финансовый консультант скажет вам, что лучший способ избежать проблем с деньгами – не делать долгов. То же самое касается технического долга. Именно поэтому XP-команды занимаются беспощадным рефакторингом, постоянно выискивая код «с душком» и способы его упрощения. Чем чаще программисты употребляют рефакторинг, тем больше узнают, как фактически применяется их код и чем это отличается от ожиданий. Благодаря постоянному пересмотру каждый модуль исходного кода все лучше соответствует тому, как он фактически используется. Этот итеративный характер постоянного кодирования и пересмотра заменяет многим командам необходимость планировать в начале проекта. Но хотя беспощадный рефакторинг требует дополнительного времени, на самом деле он его экономит, поскольку с простым исходным кодом работать проще, чем со сложным.
Когда все члены команды постоянно занимаются рефакторингом, они создают легко изменяемый исходный код. И если они находят, что необходимо реализовать новую историю, или оказывается, что они поняли одну историю неверно и работу программы надо изменить, то переделать простой код гораздо легче. Они готовы принять изменения (это основная цель каждой ХР-команды), потому что не борются против этого.
Да, это переделка, и она способствует появлению дополнительных ошибок. Если пользователям нужно что-то изменить в конце проекта, то в ходе доработки команда может случайно их допустить. Но рефакторинг – это переделка особого рода, которая предотвращает ошибки. Рефакторинг на протяжении всего проекта дает вам исходный код, состоящий из более мелких естественных многоразовых модулей. Альтернатива хорошо знакома большинству опытных программистов – это трудно изменяемый исходный код.
Переделка – это традиционный источник ошибок в водопадных проектах. Одна из причин в том, что архитектура становится более сложной, хрупкой, код приобретает «душок», что затрудняет внесение изменений. Но что если ошибки добавляются в процессе рефакторинга? Предположим, блок кода выполняет две разные функции, а разработчик извлекает фрагмент кода в процедуру, обеспечивающую только одну из них. Ответный ход XP-команды – разработка через тестирование, одна из основных XP-практик. Когда у программиста есть набор тестов для модуля, который подвергается рефакторингу, то эта процедура оказывается безопаснее. И действительно, гораздо спокойнее заниматься серьезным рефакторингом, имея модульные тесты. Поскольку рефакторинг – это, по определению, изменение структуры кода, не влияющее на его поведение, модульные тесты должны успешно завершаться и до рефакторинга, и после него. И на практике тесты в самом деле отлавливают почти все ошибки, которые могут быть внесены даже при обширном рефакторинге.
Да, безопаснее. И это было одной из основных задач разработчиков ПО, системных аналитиков и менеджеров проектов в течение многих лет. Но очень маловероятно, что код сразу получится правильным, потому что понимание командой проблем эволюционирует по мере развития проекта и написания модулей. Обычно команда со временем меняет свое понимание проекта. Это естественный результат постоянной поставки пользователям работающего ПО (а оно более подходящий инструмент для оценки возникающих проблем, чем исчерпывающая документация).
Этим также объясняется, почему XP-команды используют итеративную разработку и включают квартальные и недельные циклы в свои основные практики. Они выделяют достаточно времени в каждой недельной итерации для учета создания модульных тестов и рефакторинга. И каждая поставка работающего ПО помогает взаимодействовать с пользователями, чтобы улучшить понимание решаемой задачи и уточнить истории. Благодаря непрерывному поиску кода «с душком» и совершенствованию архитектуры они пишут и сохраняют исходный код, который легко поддается изменению.
Но не будем слишком строги к командам, которые в прошлом разрабатывали программное обеспечение при помощи обширных требований и предварительного проектирования. У них не было необходимых инструментов. Под этим словом мы понимаем не только программные утилиты, но и командные практики, которые разрабатывались на протяжении многих лет. И эти инструменты облегчили рефакторинг и модульное тестирование. Даже развертывание и выпуск были весьма сложными. Простая компиляция кода занимала дни, а то и недели, компьютеры не были объединены в сеть, поэтому приходилось копировать программы на компакт-диски, дискеты и даже ленты. Переделка требовала больших затрат, поэтому приходилось сначала разрабатывать документацию и тщательно ее оценивать, прежде чем приступать к созданию кода.
Мы узнали о практике непрерывной интеграции в главе 6, и это один из тех мощных инструментов, которые современные команды имеют в своем распоряжении. Одна из причин, по которой непрерывная интеграция улучшает архитектуру и предупреждает проблемы с ней, заключается в том, что она позволяет команде обнаруживать их на ранних этапах (при возникновении проблем с интеграцией).
Рис. 7.8. Непрерывная интеграция выявляет проблемы на ранних стадиях
Способ, разработанный для немедленного информирования о сбоях, называется