Постигая Agile Грин Дженнифер
• сохранять темпы разработки на первоначальном уровне, несмотря на изменения.
Продолжать инвестировать в проектирование на протяжении всего проекта и вносить изменения маленькими шагами, чтобы сохранить поток новых ценных функциональных возможностей, – цена этой стратегии заключается в том, что она требует дисциплины.
Кент Бек. Extreme Programming Explained: Embrace ChangeМы уже знаем, что scrum-команды принимают решения по планированию проекта в последний ответственный момент. Это позволяет им иметь гораздо более простой план – зачастую обычный набор историй и карточки задач в сочетании с бэклогом – и дает возможность проводить ограниченные по времени совещания, потому что обсуждается только актуальная информация. Иначе говоря, scrum-команды используют идею последнего ответственного момента, чтобы привнести простоту в планирование проекта. Это помогает повысить производительность и принимать более взвешенные решения, избегая распространенных недостатков управления проектами.
Когда дело доходит до планирования технической стороны проекта, XP-команды используют похожий прием – применяют две основные ценности простоты к архитектуре, дизайну и коду. Так же как scrum-команды, они принимают решения в последний ответственный момент. В большинстве случаев этот момент наступает после того, как код написан.
Вам не кажется странным этот подход? ХР-разработчики так не считают, потому что постоянно занимаются рефакторингом своего кода: изменением структуры кода без изменения его поведения. Рефакторинг используется не только в XP, это распространенная и очень эффективная практика в программировании. Действительно, большинство IDE (интегрированная среда разработки – программы, которые применяют разработчики для редактирования, запуска и отладки кода) имеют встроенные инструменты для рефакторинга.
Приведем пример рефакторинга – даже если вы не разработчик, то наверняка заметите, как он делает код чище и проще для понимания. Когда мы писали книгу «Изучаем C#»[63], мы включили приведенный ниже блок кода в качестве решения для одного из проектов (симулятор улья, поэтому все названия переменных связаны с пчелами), чтобы наши читатели увидели, как он построен.
Рис. 7.6. Это был оригинальный фрагмент кода из одного проекта, включенного в нашу книгу «Изучаем C#»
В ходе технического обзора один из наших рецензентов отметил, что этот код слишком сложен – в нем используется очень большой метод. Поэтому мы сделали то, что должны предпринять большинство XP-команд: переработали этот код, чтобы он стал простым и легким для понимания. В данном случае мы взяли блок из четырех строк кода и перенесли их в метод, который назвали MoveBeeFromFieldToHiveQ («перенеси пчелу с поля в улей»). Затем сделали то же самое с другим блоком из четырех строк, извлекли их в метод, который назвали MoveBeeFromHiveToFieldQ («перенеси пчелу из улья в поле»). Вот как выглядел код, когда он наконец отправился в печать (два новых метода появились в коде позже).
Рис. 7.7. Мы провели рефакторинг кода путем извлечения двух методов. Новый код стал проще, и было легче понять, что он делает
Так гораздо понятнее. До рефакторинга для понимания того, что делает этот код, требовалось больше знаний о структуре программы, поэтому нам пришлось добавить примечания, помогающие читателям понять два блока кода. Их перемещение в именованные методы сделало работу кода понятнее. В переработанной версии видно, что эти блоки делают: в одном пчела перемещается с поля в улей, а в другом – из улья обратно в поле.
Этот рефакторинг не только сделал блок кода более понятным, но и снизил сложность всего проекта. Разумно предположить, что где-то в другом месте проекта программисту может понадобиться переместить пчелу между полем и ульем. Если эти методы уже существуют, то он, скорее всего, будет использовать именно их – это путь наименьшего сопротивления. Но даже если поначалу он поступит иначе и заметит дублирующийся код позже, то наверняка выполнит быстрый рефакторинг, чтобы удалить повторяющийся код и заменить его вызовом упомянутого метода.
Поставка кода в первый раз подобна влезанию в долги. Небольшой долг ускоряет разработку до тех пор, пока он погашается быстрой доработкой… Опасность возникает, если задолженность не оплачивается.
Уорд Каннингем, автор Аgile-манифеста
Плохое проектирование и плохое кодирование вызывают дополнительные затраты времени. Даже наиболее профессиональные разработчики пишут код, который может быть улучшен. Чем дольше проблемы с архитектурой и кодом остаются нерешенными, тем сильнее они сцепляются друг с другом, что в итоге приводит к эффекту «стрельбы дробью». Эти застаревшие проблемы в архитектуре и коде называются техническим долгом. Успешная ХР-команда в каждой итерации оставляет время для «погашения долга». Это правильное использование временного запаса – одной из основных практик, рассмотренных в главе 6. При этом команда добавляет истории и задачи в каждый недельный цикл в качестве буфера для поглощения непредвиденных работ.
Любой хороший финансовый консультант скажет вам, что лучший способ избежать проблем с деньгами – не делать долгов. То же самое касается технического долга. Именно поэтому XP-команды занимаются беспощадным рефакторингом, постоянно выискивая код «с душком» и способы его упрощения. Чем чаще программисты употребляют рефакторинг, тем больше узнают, как фактически применяется их код и чем это отличается от ожиданий. Благодаря постоянному пересмотру каждый модуль исходного кода все лучше соответствует тому, как он фактически используется. Этот итеративный характер постоянного кодирования и пересмотра заменяет многим командам необходимость планировать в начале проекта. Но хотя беспощадный рефакторинг требует дополнительного времени, на самом деле он его экономит, поскольку с простым исходным кодом работать проще, чем со сложным.
Когда все члены команды постоянно занимаются рефакторингом, они создают легко изменяемый исходный код. И если они находят, что необходимо реализовать новую историю, или оказывается, что они поняли одну историю неверно и работу программы надо изменить, то переделать простой код гораздо легче. Они готовы принять изменения (это основная цель каждой ХР-команды), потому что не борются против этого.
Да, это переделка, и она способствует появлению дополнительных ошибок. Если пользователям нужно что-то изменить в конце проекта, то в ходе доработки команда может случайно их допустить. Но рефакторинг – это переделка особого рода, которая предотвращает ошибки. Рефакторинг на протяжении всего проекта дает вам исходный код, состоящий из более мелких естественных многоразовых модулей. Альтернатива хорошо знакома большинству опытных программистов – это трудно изменяемый исходный код.
Переделка – это традиционный источник ошибок в водопадных проектах. Одна из причин в том, что архитектура становится более сложной, хрупкой, код приобретает «душок», что затрудняет внесение изменений. Но что если ошибки добавляются в процессе рефакторинга? Предположим, блок кода выполняет две разные функции, а разработчик извлекает фрагмент кода в процедуру, обеспечивающую только одну из них. Ответный ход XP-команды – разработка через тестирование, одна из основных XP-практик. Когда у программиста есть набор тестов для модуля, который подвергается рефакторингу, то эта процедура оказывается безопаснее. И действительно, гораздо спокойнее заниматься серьезным рефакторингом, имея модульные тесты. Поскольку рефакторинг – это, по определению, изменение структуры кода, не влияющее на его поведение, модульные тесты должны успешно завершаться и до рефакторинга, и после него. И на практике тесты в самом деле отлавливают почти все ошибки, которые могут быть внесены даже при обширном рефакторинге.
Да, безопаснее. И это было одной из основных задач разработчиков ПО, системных аналитиков и менеджеров проектов в течение многих лет. Но очень маловероятно, что код сразу получится правильным, потому что понимание командой проблем эволюционирует по мере развития проекта и написания модулей. Обычно команда со временем меняет свое понимание проекта. Это естественный результат постоянной поставки пользователям работающего ПО (а оно более подходящий инструмент для оценки возникающих проблем, чем исчерпывающая документация).
Этим также объясняется, почему XP-команды используют итеративную разработку и включают квартальные и недельные циклы в свои основные практики. Они выделяют достаточно времени в каждой недельной итерации для учета создания модульных тестов и рефакторинга. И каждая поставка работающего ПО помогает взаимодействовать с пользователями, чтобы улучшить понимание решаемой задачи и уточнить истории. Благодаря непрерывному поиску кода «с душком» и совершенствованию архитектуры они пишут и сохраняют исходный код, который легко поддается изменению.
Но не будем слишком строги к командам, которые в прошлом разрабатывали программное обеспечение при помощи обширных требований и предварительного проектирования. У них не было необходимых инструментов. Под этим словом мы понимаем не только программные утилиты, но и командные практики, которые разрабатывались на протяжении многих лет. И эти инструменты облегчили рефакторинг и модульное тестирование. Даже развертывание и выпуск были весьма сложными. Простая компиляция кода занимала дни, а то и недели, компьютеры не были объединены в сеть, поэтому приходилось копировать программы на компакт-диски, дискеты и даже ленты. Переделка требовала больших затрат, поэтому приходилось сначала разрабатывать документацию и тщательно ее оценивать, прежде чем приступать к созданию кода.
Мы узнали о практике непрерывной интеграции в главе 6, и это один из тех мощных инструментов, которые современные команды имеют в своем распоряжении. Одна из причин, по которой непрерывная интеграция улучшает архитектуру и предупреждает проблемы с ней, заключается в том, что она позволяет команде обнаруживать их на ранних этапах (при возникновении проблем с интеграцией).
Рис. 7.8. Непрерывная интеграция выявляет проблемы на ранних стадиях
Способ, разработанный для немедленного информирования о сбоях, называется системой быстрых неудач. Если вы хотите получить отказоустойчивую систему и вам важно обнаружить первопричину возможных проблем, то нужно, чтобы вероятный сбой случился как можно раньше (поэтому метод и назвали «быстрые неудачи»). Быстрые неудачи обеспечивают петлю обратной связи, которая позволяет использовать полученные знания в проекте. Эту идею можно применить не только к способу разработки, но и к создаваемому программному продукту.
Неудача – это один из принципов XP, который мы обсуждали в главе 6. Непрерывная интеграция обеспечивает быструю неудачу в проекте в том случае, когда два члена команды одновременно добавляют несовместимый или конфликтующий код. Если члены команды имеют совместимое с ХР мировоззрение, то они будут положительно воспринимать интеграционные неудачи, потому что это помогает выявлять и устранять проблемы на ранних этапах, когда сделать это намного легче.
Рис. 7.9. Когда проблемы проектирования выявлены в самом начале проекта, их легче исправить, и это предотвращает появление запутанных клуджей в дальнейшем. Таков один из способов, которым непрерывная интеграция улучшает общую архитектуру
Разработка через тестирование помогает команде создавать небольшие независимые части, которые легче интегрировать, поэтому непрерывная интеграция – это меньшее бремя для XP-команды. Она постоянно тестирует каждый модуль, чтобы убедиться, что части работают. Когда все члены команды непрерывно интегрируют свой код в общую базу кода, это не дает им углубляться в свою задачу и создавать модули, которые не вполне подходят друг другу.
Допустим, вы трудитесь над проектом программного обеспечения и в процессе работы многое узнаете о проблеме, которую это ПО должно решить. Большинству команд такой сценарий хорошо знаком, и ваша команда должна принимать решения в последний ответственный момент, чтобы обойти эту проблему.
Рис. 7.10. Когда разработчики не привыкли создавать простой несвязанный код, в итоге они получают монолитную архитектуру
Традиционные водопадные команды часто сталкиваются с проблемами, развивающимися по такому сценарию. Предварительное выяснение требований, их рассмотрение большой аудиторией, а затем создание всего кода сразу приводят к тому, что его трудно изменять. У команды нет особых стимулов менять архитектуру решения, потому что такие изменения находятся под жестким контролем. Разработчики никогда не выработают привычки (наподобие постоянного рефакторинга или отслеживания кода «с душком»), заставляющие их создавать легко изменяемый код.
Это почти автоматически ведет к монолитной архитектуре, то есть дизайну, при котором программное обеспечение состоит из больших взаимосвязанных модулей, имеющих много взаимозависимостей и трудноотделимых друг от друга.
В начале книги мы говорили о том, чем ПО отличается от строительных работ. Самое опасное, что вы можете натворить в строительстве, – это взять кувалду и разрушить стены. В программном обеспечении удаление кода не нанесет большого ущерба – его можно восстановить из хранилища контроля версий. Гораздо опаснее, если вы напишете плохой код, а потом создадите другой, который зависит от первого. Добавление зависимостей (иногда называемых «связками», потому что они соединяют вместе два куска кода) делает сложным изменение одной из частей без того, чтобы по крайней мере оценить влияние этого изменения на другую часть.
Монолитная архитектура часто характеризуется сильно связанным кодом, где типичный модуль имеет много связей с другими частями системы. Попытка изменить сильно связанный код становится затруднительной – часто эта связка дает эффект «стрельбы дробью» и другие антипаттерны. Именно такие (обычно недокументированные) связки приводят к ошибкам при переделке.
Связанный код можно сделать несвязанным, разорвав соединения между модулями (а лучше вовсе не добавлять эти связи в начале работы).
Код «с душком» и рефакторинг помогут создать несвязанный код. Вернемся к примеру рефакторинга с кодом улья, где мы извлекли два метода из блока кода C# для перемещения пчел между полем и ульем. Мы увидели, что эти два метода могут повторно использоваться в другой части программы, которая должна перемещать пчел таким же образом. Но что если эти методы – часть более крупного модуля, который нуждается в большой инициализации? Программист с хорошими навыками, попытавшись повторно использовать эти простые методы, почувствует непродуманный код и проведет рефакторинг, чтобы удалить дополнительную инициализацию. Теперь этот код можно применить в двух различных частях программы, и для этого не нужно «знать», как его вызывать, потому что он отделен от тех частей кода, которые к нему обращаются. И если необходимо, чтобы он был вызван из третьей части кода, то он будет отделен и от этой части.
Систему легче поддерживать, если она собрана из небольших независимых модулей, то есть каждая часть кода отделена от любой другой, насколько это возможно (степень этой зависимости может быть удивительной), так что между ними существует очень мало зависимостей. Это главный принцип создания программы, которая может быть изменена без серьезной доработки. Если ваша архитектура не отягощена лишними связями, то вам редко придется прибегать к «стрельбе дробью» или производить целую череду взаимосвязанных изменений. И когда вы начнете так поступать и выработаете в себе привычку не откладывать рефакторинг на завтра, чтобы удалить эти сцепления, код будет становиться все лучше и лучше: каждый модуль станет выполнять только одну задачу и окажется отделен от посторонних модулей.
Инкрементальная архитектура и целостные XP-практики
В главе 6 мы говорили о 10 из 13 основных XP-практик, разделив их на категории: программирование, интеграция, планирование и командные практики. Существуют еще три основные XP-практики, и мы придумали для них отдельную категорию: целостные практики. Мы намеренно отложили их рассмотрение, поскольку о них надо говорить как о комплексе мер и в контексте всех прочих практик. Название «целостные» было выбрано, потому что они тесно взаимосвязаны и не работают одна без другой (в отличие, скажем, от парного программирования или недельных циклов, где команды могут продвигаться маленькими шажками).
Первая целостная ХР-практика, о которой мы будем говорить, – это инкрементальная архитектура. Она самая трудная для понимания. В этой главе мы постараемся помочь вам понять основы этой практики и то, как она влияет на проект и команду в целом.
Вы можете найти замечательные примеры инкрементальной архитектуры во многих зрелых, качественных проектах с открытым исходным кодом, таких как Linux, Apache HTTP Server и Firefox. Эти проекты создаются вокруг стабильного ядра. Разработчики используют архитектуру, основанную на плагинах (или других способах отделения кода от ядра), чтобы создавать дополнительные функции, и только самые повторяющиеся, наиболее стабильные из них встраиваются в ядро. Такая архитектура приводит к весьма слабо связанному коду, и программисты, работающие над этими проектами, взяли за правило часто делать рефакторинг, писать много тестов и выполнять непрерывную интеграцию. (Следует отметить, что многие практики, включенные в XP, возникли или были отточены разработчиками, участвующими в проектах с открытым исходным кодом.)
Каждый из таких проектов создан программистами, добавлявшими отдельные модули, работающие независимо друг от друга и созданные специально для взаимодействия с ядром проекта. Создается впечатление, что исходный код проекта постепенно вырастает из этого ядра. Любой разработчик может добавить свои собственные модули независимо от остальной команды и рассчитывать на то, что все пишут почти полностью разъединенный код, поэтому риск столкновения новых модулей со старыми невелик. Однако работа ведется не в вакууме, и члены команды хорошо осведомлены об этом. Все дополнения происходят в том же исходном коде, где эти команды проводят автоматизированную или ручную непрерывную интеграцию. (Некоторые непрерывные интеграции и серверы сборки, доступные сегодня, возникли в ходе этих проектов.) Кроме того, каждый разработчик старается избежать кода «с душком» и антипаттернов и чувствует большую ответственность за их исправление, если они будут выявлены.
В результате применения этих отличных практик наряду с правильным поведением разработчиков исходный код растет инкрементально. Это пример, как очень большая команда с участниками, находящимися в разных странах, может создать качественное программное обеспечение[64]. Фактически инкрементальная архитектура – это создание проектного решения в последний ответственный момент, что дает возможность избежать одной из самых распространенных ловушек, в которую попадают даже опытные, высококвалифицированные программисты, – попытки создать все и сразу.
Команды, попадающие в такую ловушку, выработали дурные привычки, приводящие к монолитной или замысловатой архитектуре программного обеспечения. Например, они могут сосредоточиться на решении проблемы, которая крупнее текущей задачи или вовсе с ней не связана (фокусируя внимание на крайних случаях, а не на работе каждого конкретного модуля). Или посвящать массу времени обсуждению будущих дополнений и добавлять слишком много хуков. Они могут также создавать большие абстрактные платформы для решения мелких конкретных задач. Что объединяет все эти ситуации? Речь идет о командах, которые принимают чересчур много архитектурных решений и делают это слишком рано.
Однако когда команда выработала привычки, которые ведут к созданию несвязанного кода, состоящего из небольших, надежных, независимых модулей, ей не нужно продумывать архитектуру заранее. Они могут создавать архитектуру высокого уровня, не думая обо всех возможных сценариях, которые могут в нее не вписаться. И уверены, что если обнаружится непредвиденный пограничный случай, то найдутся инструменты, при помощи которых можно будет решить проблему позднее. Это раскрепощает людей и позволяет им свободно обдумывать проблемы, а также дает возможность создавать код по частям, по мере того как накапливается понимание задачи. Такая идея лежит в основе инкрементальной архитектуры, что приводит к очень гибкому, поддающемуся изменениям коду. Но такой подход эффективен, когда каждый разработчик по-настоящему верит в проектирование кода и принятие решений в последний ответственный момент. Любой член команды должен быть убежден, что быстрее создать небольшую несвязанную часть системы сегодня и доверить команде внесение в будущем исправлений, если они понадобятся.
Рис. 7.11. Инкрементальная архитектура приводит к более надежной и пригодной к обслуживанию системе
Речь идет не только о том, как программисты проектируют и собирают код. Имеет значение и атмосфера внутри команды: как люди взаимодействуют, в какой обстановке работают и, главное, как относятся друг к другу.
Многие команды испытывают дефицит времени. Еще хуже, когда они понимают, что он искусственно создан руководством, которое думает, что невыполнимые сроки – этот лучший мотиватор. Команда с таким мировоззрением воспринимает любую работу, не добавляющую строки в код, как избыточную.
Когда вы чувствуете, что вам не хватает времени выполнять работу как следует, почти не имеет значения, насколько хороши ваши навыки. Чем сильнее сроки и план давят на вас, тем вероятнее, что вы откажетесь от таких полезных практик, как рефакторинг, разработка через тестирование и непрерывная интеграция. Например, вы не будете заниматься рефакторингом, потому что код и так работает, а у вас нет времени улучшать его. Ваш код будет выполняться и с модульными тестами, и без них, поэтому каждая минута, потраченная на их написание, расценивается как недобавленная новая функция. Умом мы понимаем, что эти практики помогают писать код быстрее, но, находясь под сильным давлением, мы подсознательно начинаем считать, что на них не хватает времени.
Рассмотрим случай, когда команда находится под прессингом руководителя. Возможно, он пообещал пользователям законченный продукт, но согласился на нереальные сроки и хочет уложиться в них. Поэтому он требует от разработчиков отказаться от модульного тестирования, рефакторинга и всего того, что напрямую не связано с добавлением новых функций. В такой команде обычное явление, когда программисты, сталкиваясь с кодом «с душком», думают: «У меня нет времени разбираться! Мне нужно как можно скорее закончить эту новую функцию и перейти к следующей». Конечно, они будут создавать сильно связанный код (потому что затрата нескольких минут, чтобы развязать его, расценивается как избыточная работа, ведь у них просто нет времени на обдумывание!). Руководитель такой команды фактически мешает ей эффективно внедрять ХР, и в результате появляется код «с душком», который трудно изменять и поддерживать. Их проекты практически всегда выполняются с опозданием, а некоторые полностью проваливаются[65].
В ХР есть действенное средство против этого, поэтому поговорим о двух оставшихся целостных практиках: энергичной работе и единой команде.
Разработка программного обеспечения – это игра на понимание, и прозрение приходит к подготовленному, отдохнувшему, расслабленному разуму.
Кент Бек. Extreme Programming Explained: Embrace Change
Энергичная работа означает создание среды, в которой каждый член команды имеет достаточно времени и свободы делать свою работу. Это поддерживает в них ментальное состояние, в котором они способны вырабатывать и использовать хорошие привычки, ведущие к лучшему, естественно изменяемому коду. В таком состоянии они могут производить гораздо больше кода и поставлять более ценные функции потребителям за меньшие отрезки времени.
Разработка программного обеспечения – это преимущественно умственная деятельность[66]. Любой хороший разработчик тратит часы, размышляя над проблемой. Озарение может прийти внезапно – во время обеда, принятия душа, катания на велосипеде и т. д. Каждый программный проект опирается на ряд небольших инноваций, следующих одна за другой.
Чтобы разработчик вошел в состояние «потока», включающее в себя высокую концентрацию и максимальную продуктивность[67], требуется время (обычно от 15 до 45 минут). Перерывы и отвлекающие факторы могут вывести его из этого состояния. Но когда он находится под давлением необходимости поставить код в немыслимые сроки, войти в «поток» невозможно. Он так стремится поскорее разделаться с кодом, что ему некогда думать.
Неуважение, установка нереальных сроков и другое негативное поведение руководителя может вызвать состояние пассивности, при котором люди не имеют возможности принимать решения или внедрять инновации.
Когда менеджеры создают атмосферу постоянного отставания от сроков, назначаемых произвольно, они неизбежно будут халтурить, создавая код. Обычно это сопровождается отказом от многих полезных ХР-практик. Кроме того, в такой ситуации разработчики с трудом входят в состояние «потока», необходимое для использования в проекте более простых решений.
XP-команды стремятся создать совсем иную рабочую среду, наполненную энергией, означающую, что каждый член команды ощущает свою автономность.
Каждый сам решает, как выполнять свою работу, и понимает, что имеет право вносить изменения не только в код, но и в план проекта. ХР-команды делают это путем применения практик, ориентированных на планирование: используя недельные циклы. Поэтому они не принимают заранее конкретных решений, если есть возможность сделать это позже. Кроме того, они используют временной резерв, добавляя работы с низким приоритетом, которые легко перенести в следующий цикл. Эти практики повышают чувство командной автономии, предоставляя больше гибкости при планировании. Автономия может исходить от самого кода: избегая антипаттернов и собирая легко изменяемый код, команда открывает для себя возможность выбора в будущем.
В главе 3 мы узнали об agile-принципе устойчивого темпа: гибкие процессы способствуют такой же разработке. Спонсоры, разработчики и пользователи должны иметь возможность поддерживать постоянный темп в течение неопределенного времени. Считаете ли вы, что мужество как ХР-принцип также требуется для того, чтобы поддерживать устойчивый темп?
Многие ХР-практики используют термины «энергичная работа», «40-часовая рабочая неделя» и «устойчивый темп» как синонимы, поэтому XP-команды создают среду, где они могут энергично работать путем выделения разумного количества рабочих часов. Идее 40-часовой рабочей недели много лет – в свое время профсоюзы требовали «восемь часов на работу, восемь часов на отдых и восемь часов на сон»[68]. К 1950-м годам после бесчисленных исследований производительности и изучения диаграмм, показывающих ее снижение и увеличение проблем качества при рабочей неделе, превышающей 40 часов, многие отрасли приняли этот принцип. ХР-команды знают: при устойчивом темпе работы и возможности жить полноценной жизнью вне офиса люди трудятся качественнее и испытывают чувство удовлетворенности.
Людям нужно чувство команды, мы принадлежим к ней. Мы вместе делаем одно дело. Мы поддерживаем друг друга в работе, развитии и обучении.
Кент Бек. Extreme Programming Explained: Embrace Change
Хорошие команды вместе могут сделать гораздо больше, чем те же самые люди, работая по отдельности. Почему? Приглашая специалистов, обладающих необходимыми навыками и взглядами, и создавая для них среду, поощряющую открытое общение и взаимное уважение, вы помогаете рождению инноваций. Члены команды делятся своими идеями, а это создает еще больше новых идей.
Практика единой команды помогает отдельным ее членам объединяться. Когда они сталкиваются с препятствиями, то преодолевают их совместно. Важные решения, влияющие на направление развития проекта, также принимаются совместно. Все члены команды учатся доверять друг другу и определять, какие решения можно принимать самостоятельно, а какие – командой в целом.
В единой команде каждый принимает участие в обсуждении того, какие функции ценны для пользователей, какую работу команда возьмет на себя и как будет создано программное обеспечение. Если команда доверяет любому своему участнику принимать решения о кодировании, которое будет поставлять наибольшую ценность, то риск, что некоторые программисты начнут тратить время на дополнительный код, невелик.
Обратная сторона доверия – это понимание того, что каждый может ошибиться. Когда «единая команда» работает динамично, ее члены не боятся совершить оплошность, потому что знают: коллеги их поймут. Ведь единственный способ продвинуться вперед – вместе учиться на неизбежных ошибках.
Единая команда, имеющая энергичную рабочую среду, проектирует лучше, чем разобщенная, в которой царит атмосфера подавленности.
Инновации могут казаться высокими ценностями, но для эффективной ХР-команды это ежедневная реальность. Разработчики, регулярно достигающие состояния «потока» и работающие в информационном пространстве, которое стимулирует осмотическую коммуникацию, часто подпитывают друг друга идеями. А инкрементальное проектирование ПО дает каждому программисту свободу писать новый код с меньшим числом ограничений. Хорошие привычки, выработанные командой, помогают ей избегать антипаттернов, поэтому, когда она наращивает исходный код постепенно, она не накладывает на себя ограничений, который могут сказаться в будущем.
Если менеджер такой команды доверяет ей, то работа идет лучше. Он помогает команде понять ценность, которую она поставляет, не устанавливает нереальных сроков и создает атмосферу, в которой можно сосредоточиться на создании лучшего продукта. Команда с таким менеджером работает быстрее и имеет больше шансов создать продукт, соответствующий требованиям пользователей, в который легко вносить изменения.
Рис. 7.12. Все практики взаимосвязаны и усиливают друг друга
Мы отложили рассмотрение целостных практик до главы 7, потому что в сочетании с другими основными ХР-практиками они становятся понятнее, и команда не сможет освоить их по-настоящему, не имея образа мыслей, совместимого с ХР-ценностями и принципами. Если рассматривать практики по отдельности и реализовывать их одну за другой, то, скорее всего, получится результат «лучше-чем-ничего», который не окажет глубокого влияния на архитектуру продукта.
В хорошей XP-команде каждый усвоил принципы и ценности, поэтому практики формируют экосистему, в которой они усиливают друг друга. Создание тестов до написания кода помогает писать небольшие, независимые, несвязанные модули, благодаря которым проблемы в коде и антипаттерны проще исправлять.
Члены команды работают в паре, что помогает им стать единой командой, заставляет быть более собранными, создает энергичную рабочую среду и дает время обдумать архитектуру. Они постоянно занимаются интеграцией, поэтому, когда одна пара вносит в код дефект, влияющий на другую часть программы, модульное тестирование быстро выявляет ошибку. Это позволяет команде быстро устранить проблемы еще до того, как они окажутся погребены под дополнительными слоями кода, и сделать его чище. Непрерывная интеграция не представляет сложности, потому что существует 10-минутная сборка, которая, помимо прочего, облегчает команде возможность сосредоточиться и позволяет меньше отвлекаться.
Разработчику с правильным ХР-мышлением все это кажется очень простым. Он считает, что просто создает ПО именно так, как надо, а все эти принципы реализуются автоматически.
Команда, не обладающая таким мышлением, будет натыкаться на барьеры при внедрении ХР. Она застрянет в реализации отдельных практик и почувствует, что внедрила ХР, как только все начнут писать тесты, кодировать в парах, работать недельными циклами или использовать сервер непрерывной интеграции. Это простые, осязаемые вещи, выполнение которых можно отметить галочками в списке. Но существует разрыв между применением практик и получением эффекта «экосистемы», о котором XP-команда, возможно, читала, но так и не достигла. Так же как scrum-команды, реализовавшие все практики, но так и не добившиеся поразительных результатов или гиперпродуктивности.
Как команде сформировать правильное мышление? Как совершить переход от реализации практик к коренным образом измененным способам проектирования и создания программного обеспечения?
Эндрю. Итак, проектирование для повторного использования – это не всегда здорово?
Ауке. Это не очень удобный подход к разработке программного обеспечения. Обычно, работая с открытым кодом, ты сначала создаешь нечто для одного пользователя.
Затем кто-то другой берет это решение и начинает приспосабливать к своим нуждам. И он может использовать его повторно. И как только вы сделали это – применили, а затем использовали повторно, – вы знаете, что в этих решениях общего, и можете выполнить рефакторинг общей функциональности.
Вы видите довольно много таких случаев. Проектирование для повторного использования слишком статично. Оно не работает таким образом и очень плохо для этого подходит. Его можно пытаться к чему-то применить, но это не имеет особого смысла в обозримом будущем. Повторное использование подходит для разработки гораздо лучше.
Из интервью с Ауке Джилдерда, Beautiful Teams (глава 8)
Мы уже говорили о том, как команда может начать формировать правильное мышление для XP, внедряя его практики.
Во-первых, она могла бы получить результаты «лучше-чем-ничего». Но в ходе использования практик люди начинают понимать, как те взаимодействуют, и представление каждого члена команды о том, как создавать ПО, начинает меняться. Это справедливо не только для Scrum, но и для ХР. И ключ к обладанию ХР лежит в инкрементальной архитектуре.
Набор утилит Unix – классический пример инкрементальной архитектуры, и это то, что позволило тысячам разработчиков добавлять к нему маленькие и большие части на протяжении многих лет. Утилиты Unix состоят из множества маленьких кусочков[69], разработанных различными программистами, намеревавшимися решить свои конкретные проблемы (они не старались создать огромную, монолитную операционную систему). На примере набора утилит Unix мы рассмотрим, как инкрементальная архитектура может помочь множеству людей (большинство из которых никогда не встречались) внести свой вклад в создание большой, устойчивой, высококачественной системы, продолжающей развиваться на протяжении десятилетий.
Вот пример того, как работают утилиты Unix. Допустим, у вас есть масса больших текстовых файлов с множеством данных – может быть, это адреса для списка рассылки, конфигурационные файлы операционной системы или числа, которые должны быть обработаны. Нужно быстро придумать способ их преобразовать (например, вытащить только имена и номера телефонов, изменить определенные разделы конфигурации нужным образом или найти значения данных, соответствующих шаблону). Как бы вы это сделали?
Можно написать программу, которая считывает данные из файла, обрабатывает строки, разделы, последовательности и формирует выходные данные. Но если вы знакомы с Unix-утилитами (такими как cat, ls, cut, awk, sed и т. д.), то знаете, что они созданы для решения подобных проблем. Вы пишете скрипт (или даже просто команду, которую выполняете в командной строке), считывающий данные из файла, выполняющий преобразования и записывающий результаты в другой файл.
Например, у вас есть большая, разделенная запятыми адресная книга в файле addr.txt. В ней восемь столбцов: имя, должность, адрес электронной почты, номер телефона, почтовый адрес, город, штат и почтовый индекс. Вы хотите найти имя, должность и номер телефона адресатов, проживающих в Миннесоте. Если полное имя и должность записаны в первых двух столбцах, номер телефона – в четвертом, а штат и почтовый индекс в последних столбцах, то эта команда в Unix будет производить правильный вывод и записывать его в файл output.txt[70]:
EGREP “,MN, [0–9]{5}([-] [0–9]{4})?$” ADDR.TXT | CUT-D, – FL,2,4 > OUTPUT.TXT
Разве Unix-утилиты создавали люди, желавшие упростить процесс обработки адресных книг? Конечно, нет. Утилиты для Unix основаны на философии простоты: каждая выдает результат, который любая другая утилита может использовать в качестве входных данных, и каждая делает одну конкретную работу. Утилита cut последовательно читает строки ввода, вырезает определенные поля или столбцы и вставляет их в выходные данные. Утилита командной строки grep проверяет каждую строку на входе и передает на выход только те, которые соответствуют заданному шаблону. Но эти две утилиты, особенно в сочетании с другими средствами Unix, могут выполнять практически неограниченное количество задач. Например, системный администратор может комбинировать их с такими утилитами, как find (она ищет файлы в файловой системе) или last (называет пользователей, последними работавших на этом компьютере), чтобы выполнить множество сложных задач системного администрирования.
Эти утилиты – основа того, благодаря чему Unix стала наиболее популярной операционной системой для интернет-сервисов и бизнес-применений. И в течение многих лет общая архитектура системы утилит развивалась среди людей, использующих эти утилиты каждый день, вместе с культурой системного администрирования. Со временем появлялись новые утилиты и возможности их использования. Так, сжатие файлов получило широкое распространение в 1980–1990-х годах. Когда в 1992 году gzip добавилась в набор утилит для Unix, для этого уже существовал простой шаблон. Еще до написания первой строки было ясно, как она будет получать данные и отдавать их (конвейер), выполняться (из командной строки) и даже в каком виде появится документация (страницы man). Утилиты могут легко расширяться, а поскольку все инструменты не связаны друг с другом, возможность сжатия и распаковки файлов легко добавляется, не требуя каких-либо изменений в других частях системы.
Уже более сорока лет расширение набора утилит Unix происходит именно таким способом. Тысячи разработчиков внесли свой вклад в отдельные утилиты или улучшили существующие. Unix-система росла естественным образом. А вместе с ней развивались культура и база знаний. Это позволило людям использовать Unix для выполнения все более сложных задач, что, в свою очередь, помогло найти новые способы ее совершенствования.
Все утилиты Unix придерживаются очень строгого шаблона входных и выходных данных.
Символ «|» в командной строке обозначает конвейер, он передает выходные данные одной утилиты и вход другой. Символ «<» выполняет ввод из файла, а символ «>» – вывод в файл. Все стандартные утилиты Unix создают выходы, работающие с этими конвейерами, – такова часть договора, которого каждая из них придерживается. Договор между утилитами Unix очень простой, что позволяет легко расширить всю систему. Пока каждая дополнительная утилита придерживается того же договора, она будет соответствовать другим утилитам.
Важный инструмент для оказания помощи команде в создании системы, использующей поэтапный подход, – это очень простой договор между ее модулями. Как они взаимодействуют друг с другом, передают ли сообщения? Вызывают функции или методы? Предоставляют ли услуги доступа в сети? Чем проще и последовательней коммуникационный механизм между модулями, тем легче команде добавить новые.
Как вы делаете договор простым? Так же, как и модули: принимаете решения о том, как они взаимодействуют, в последний ответственный момент. И у нас уже есть инструмент, чтобы помочь в этом: разработка через тестирование. Когда программист создает модульные тесты, это помогает убрать сложность и заставляет его использовать этот модуль, прежде чем он написан. Можно применять один и тот же модуль тестирования утилит или платформ, чтобы написать простые интеграционные тесты, проверяющие, как различные модули взаимодействуют друг с другом. И разработчик напишет эти тесты, прежде чем добавит код для взаимодействия. Например, если есть взаимодействие между модулями, где выход одного из них передается в качестве входных данных в другой, выход которого поступает в третий, то разработчик напишет тест, который имитирует, как они «общаются» – то есть интегрируются.
Причина, по которой это помогает сохранить контракт между модулями простым, заключается в том, что легко понять, когда он начинает усложняться. При простом контракте этот вид интеграционного теста очень легко написать. Но если контракт сложный, то интеграционный тест становится для разработчика кошмаром. Сначала ему надо инициализировать множество разных, казалось бы, несвязанных объектов, затем кое-как преобразовать данные из одного формата в другой и вертеться, как уж на сковородке, чтобы обеспечить взаимодействие модулей. И так же, как создание модульных тестов, написание в первую очередь интеграционных тестов поможет избежать этих проблем, прежде чем они будут встроены в программное обеспечение.
Мы используем термин «возникает», когда сложное поведение появляется из простых систем. Возникновение – это распространенное явление: отдельные муравьи ведут себя примитивно, а весь муравейник демонстрирует гораздо более сложное поведение, возникающее в результате взаимодействия между муравьями. Таким образом, муравейник – это больше, чем сумма всех его обитателей.
Когда система разработана так, что ее поведение формируется из взаимодействия отдельных модулей, работающих вместе, и кажется, что они «вылупились» из одного модуля, это называется возникающей архитектурой. Системы, созданные с использованием возникающей архитектуры, практически всегда состоят из маленьких, независимых, несвязанных модулей (подобных утилитам Unix или муравьям). Эти части объединяются для выполнения сложных задач, и поведение системы зависит как от взаимодействия между этими модулями, так и от них самих.
Глубокая иерархия модулей, вызывающих один другой, применяется редко. Вместо этого система будет использовать сообщения, очереди или другие способы коммуникации модулей, не требующие централизованного управления. Утилиты Unix – хороший пример такой организации работы[71]. Входной сигнал передается от одной утилиты к другой, что облегчает их объединение в цепочку. Это приводит к очень простому, последовательному использованию: вызывается первая утилита, потом вторая, затем третья. Можно создать и более сложное взаимодействие между инструментами, но это приводит к простой, неглубокой структуре вызовов (в отличие от глубокой, вложенной иерархии вызовов со слоями модулей).
Если система спроектирована подобным образом (простые модули, взаимодействующие простым способом), то можно получить интересный эффект. Команде начинает казаться, что поведение возникает из системы в целом, а не из отдельных модулей. И возникает ощущение, что сложное поведение не имеет единственного источника. Это очень похоже на поведение муравейника, которое тоже не определяется отдельными особями. Команда обретает уверенность, что каждая часть системы проста и поведение ее как целого возникает из взаимодействия между этими модулями.
Людям, потратившим годы на создание набора утилит Unix, знакомо это чувство возникающего поведения. Можно получить первое поверхностное представление о возникающем поведении, наблюдая за тем, как сочетаются утилиты в приведенном примере с адресной книгой: cut умеет только извлекать символы из строки, а grep – искать соответствие шаблонам, но их сочетание обеспечивает обработку адресов без специальной команды, которая пояснит системе, что такое адрес и как его обработать.
ХР-команды, работающие с полной отдачей, считают естественным применение возникающей архитектуры. Все начинается с простоты: каждый модуль предназначен для конкретного использования. Разработка через тестирование гарантирует, что он остается простым и имеет только одно предназначение. Программист пишет тесты, чтобы убедиться: модуль выполняет одну функцию, – а потом создает код. И если он успешно проходит тесты, то его разработка прекращается. Поэтому никакого дополнительного поведения у модуля не появляется, и в нем нет кода, написанного без основания. Весь код в модуле необходим.
Команда избегает глубоких вложений вызовов, когда один модуль вызывает другой, тот, в свою очередь, третий, и т. д. Вместо этого структура вызова программы выглядит плоской и широкой, что снижает зависимость между модулями. Такие взаимодействия просты настолько, насколько это возможно: когда один блок нуждается в данных, он получает их из другого блока или из очереди сообщений, поэтому не нужно знать, откуда они взялись. Чтобы сохранить простоту системы, команда избегает многоэтапных, сложных взаимодействий между большим количеством модулей (подобно тому, как Unix-утилиты передают данные от одной утилиты другой по конвейеру).
Новая архитектура возникает, когда команда обнаруживает изменения, которые необходимо сделать. Чтобы добавить новое поведение в систему или переменить способ ее работы, команда должна изменить отдельные модули или способ их взаимодействия. Но поскольку каждый модуль несвязан и взаимодействия между ними просты, эти изменения, как правило, не носят каскадного характера по всей системе. Если встречается код «с душком» (скажем, программист обнаруживает, что занимается «стрельбой дробью», продирается через спагетти-код или сырые объекты), то это серьезное предупреждение, что во время работы были допущены ненужные усложнения. И разработчик знает (а что еще важнее – чувствует): стоит потратить время на рефакторинг, чтобы разделить код на более простые модули.
Система, построенная на возникающей архитектуре, может развиваться годами, оставаясь простой для поддержки и изменений. Успешные ХР-команды имеют инструменты для поддержания простоты исходного кода. Стиль их работы поощряет постоянно отслеживать появление сложного кода и выполнять его рефакторинг. Это, в свою очередь, позволяет им без напряжения вносить изменения, ведущие к возникновению архитектуры. Такой эффективный цикл приводит к простому, стабильному и очень качественному коду – и в результате команды обнаруживают, что таким образом можно создавать и поддерживать программное обеспечение намного быстрее. Когда система нуждается в поддержке (исправить ошибку или внести изменения), редко требуется серьезная модификация, затрагивающая много различных частей кода.
Поскольку члены команды знают, что система гибкая, и им известно, что можно расширять, а что – нет, они успешнее принимают решения в последний ответственный момент. Как у единой команды у них появляется понимание того, когда и какие архитектурные решения можно отложить на завтра. А это, в свою очередь, помогает сегодня сохранять простоту кода. Команда менее напряжена, а ее члены лучше работают вместе. Они производят качественный код очень быстро и, когда его требуется изменить, делают это легко и без ошибок.
Другими словами, команды сохраняют код простым, что позволяет им принимать изменения.
В этом суть ХР.
Описание: команда занимается разработкой фантазийного баскетбольного сайтаДжастин – первый разработчик
Даниэль – второй разработчик
Бриджит – менеджер проекта
Акт V. Окончательный результат
Даниэль и Джастин были заняты парным программированием, когда Даниэль вдруг воскликнула:
– Ты понимаешь, что мы работаем в паре уже почти три месяца?
Прошло около года с тех пор, как Даниэль рассказала команде об XP. Они делали выпуски каждую неделю, но последний недельный цикл был особенным. Компания объявила о рекламной акции в одной из крупнейших телевизионных сетей, и команда просто вытолкнула код в производство. Пару минут Даниэль и Джастин вспоминали, как шла работа на протяжении нескольких последних циклов.
Джастин сказал:
– Ты права. Все это время мы работали парами, но меняли партнеров так часто, что, по-моему, я успел поработать с каждым членом команды.
Даниэль спросила:
– Когда ты в последний раз засиживался допоздна?
Джастин задумался.
– Знаешь, мне придется поискать в телефоне последнее сообщение с извинениями. Давненько это было.
– Думаю, это потому, что мы стали лучше, – сказала Даниэль. – Я знаю, что теперь пишу более простой код, чем шесть месяцев назад.
– Ты права. Когда я впервые прочитал о разработке через тестирование, такая идея показалась мне чересчур абстрактной. Теперь же я вижу в ней смысл, – ответил Джастин. – Раньше мы рассуждали об этом всей командой, пытаясь убедить друг друга, что это пустая трата времени. Я не припомню, когда в последний раз мне нужно было спорить на эту тему.
Даниэль добавила:
– Самое забавное – это отсутствие ощущения, что я стала работать совсем по-другому.
Проходившая мимо Бриджит остановились и прислушалась.
– А я эту разницу чувствую, – сказала она. Джастин и Даниэль удивленно уставились на нее. Они не заметили, как она подошла. – Раньше моя работа в основном заключалась в том, чтобы убедить менеджеров продуктов дать нам больше времени. Мы всегда отставали от графика, и казалось, что любая мелочь требует нескольких месяцев работы.
Джастин спросил:
– А как теперь?
– Совершенно иначе! Раньше мне все время приходилось говорить «нет». А теперь я обычно соглашаюсь, даже если меня просят о действительно крупной задаче. Вы сделали мою работу намного проще.
– Я догадываюсь, почему вы больше не кричите на нас, – сказала Даниэль. – И думаю, именно поэтому мы не вкалываем в выходные, пытаясь наверстать упущенное время.
С минуту Бриджит смотрела на Джастина и Даниэль.
– Постойте, так, значит, вы могли бы работать больше?
Джастин решил, что она пошутила. Даниэль не была уверена в этом.
– Я думаю, нам еще надо подучиться, – сказала она.
Ключевые моментыРазработчики исправляют код «с душком» путем его постоянного рефакторинга или улучшения структуры кода без изменения его поведения.
За счет беспощадного рефакторинга XP-команды избегают технического долга или проблем в коде, которые известны, но еще не были исправлены.
Временной резерв помогает убедиться в том, что в каждом недельном цикле достаточно времени для оптимизации и погашения технической задолженности.
Непрерывная интеграция помогает командам найти проблемы интеграции на ранней стадии, когда их легче исправить.
Когда команда занимается рефакторингом, оплачивает технический долг и исправляет проблемы кода, в итоге она получает код, который легко изменить.
XP-команды используют инкрементальную архитектуру, создавая несвязанный код, который состоит из маленьких независимых модулей.
Команды инкрементально собирают только те модули, которые непосредственно необходимы для решения текущей проблемы, и доверяют друг другу принимать работающие решения в последний ответственный момент.
Энергичная работа позволяет XP-командам создать среду, где каждый сотрудник может программировать в стабильном темпе и располагает достаточным временем, чтобы сделать все правильно.
Члены ХР-команды доверяют друг другу и чувствуют, что они единое целое.
Инкрементальная архитектура – это звучит слишком абстрактно. Неужели кто-то действительно создает программные продукты таким образом?
Когда люди называют что-то «абстрактным», зачастую это означает: «я еще не пробовал так делать, но, по-моему, будет очень непросто».
В ХР нет ничего абстрактно-теоретического. Каждая практика здесь – это определенный навык, которому программисты должны учиться, практиковаться в нем и совершенствоваться. Чем лучше они усваивают такие практики, тем быстрее превращают их в полезные привычки при написании кода.
Ценности и принципы – это вполне практические идеи, которые помогут улучшить навыки работы каждого в отдельности и команды в целом.
Вот почему при работе с XP нужна практика. Невозможно, купив гитару, ноты и самоучитель, стать за ночь великим музыкантом. Точно так же нельзя, начав применять XP-практики, сразу их полностью освоить.
Когда бы я ни добавлял элементы технического долга в качестве временного резерва, они просто не выполняются. Как же мне погасить этот долг?
Если вы обнаруживаете, что ваша команда редко принимается за выполнение тех элементов в недельном цикле, которые были добавлены в качестве временного запаса, значит, вы закладываете в каждый цикл слишком много задач. Элементы временного резерва не относятся к числу необязательных. Они всего лишь не препятствуют команде выпустить готовое ПО в конце итерации. Это важные элементы, и большинство из них должно быть выполнено. Если их не удается сделать, то попробуйте уменьшить общее количество элементов при планировании недельного цикла.
К тому же, если эти элементы технического долга так и не выполняются, то, возможно, у вашей команды есть проблемы с мировоззрением. Действительно, существует ловушка, связанная с техническим долгом, в которую попадают многие команды. Они будут выявлять технический долг, но вместо его безотлагательного погашения добавят эти задачи в бэклог, используя карточки, электронные таблицы, тикеты и другие механизмы отслеживания. Как только техническая задолженность определена, она получает более низкий приоритет, чем развитие новых возможностей. Вряд ли команду, которая никак не соберется погасить технический долг, а просто признает его и помечает как задачу на будущее, можно назвать хорошей.
Команда, не устраняющая технический долг, воспринимает его как естественный побочный продукт разработки. Для нее важнее выпустить новую функцию – даже если это приводит к читерским манипуляциям или клуджам, – чтобы проект мог двигаться дальше.
И действительно, нет ничего предосудительного в том, чтобы считать поставку ценного программного обеспечения более важной, чем создание красивого технического решения. Причина, по которой успешные ХР-команды постоянно устраняют технический долг, заключается в том, что таким образом легче продолжить поставку ценного ПО в будущем.
Если вы считаете нормой устранение технического долга во время добавления дополнительного кода, а также проведение постоянного рефакторинга в ходе работы, то вы чувствуете, что делаете важное дело. Каждый в вашей команде знает, что нужно гасить технический долг, как только он обнаружен, потому что это дает возможность легко изменять исходный код. Вот почему члены XP-команды резервируют в недельном цикле достаточно времени (и действительно чувствуют, что его хватит), чтобы устранять проблемы.
Если вы обнаружите, что технический долг накапливается, то следует обсудить это с командой. Постарайтесь выяснить, не связано ли это с внешним давлением, цель которого – добавить как можно больше новых функций. Или дело в том, что вы не считаете погашение настолько важным, чтобы заняться им немедленно. А может, это происходит потому, что вашу команду поощряют за добавление новых «крутых» функций, а не за пломбирование дыр. Если погашение технического долга требует столько же усилий, сколько создание новых функций, но вы собираетесь продемонстрировать серьезные улучшения и руководство знает, что вы пишете новый «крутой» модуль, от которого пользователи будут в восторге, то есть смысл отодвинуть технический долг на нижние позиции в списке приоритетов. Независимо от причины обсуждайте это друг с другом, чтобы принять верное решение.
Некоторые XP-принципы звучат сомнительно. Не потому ли здесь говорится о самоподобии, что это звучит внушительно, или дело в том, что методология создавалась в 1990-е, когда все обсуждали самоподобие, фракталы и теорию хаоса?
Вовсе нет. Легко отказаться от принципа, если вы еще не видели, насколько успешно он улучшает команду и ее понимание ХР. Но каждый из этих принципов действительно помогает правильно понять ХР. Самоподобие не исключение.
Оно означает, что одни и те же паттерны проявляются в различных масштабах – от очень больших до совсем маленьких. В хорошей архитектуре программного решения существует большое количество самоподобия. Основательное, надежное и простое в использовании программное обеспечение часто состоит из слоев с точно такими же характеристиками, а те в свою очередь, – из таких же модулей. И когда вы открываете один из этих модулей и смотрите на код, то что вы там увидите? Правильно – основательный, надежный и легкий для понимания код.
Самоподобие присутствует и при энергичной работе: команды достигают состояния потока, когда мировоззрение отдельных разработчиков также соответствует этому состоянию. Самоподобие можно найти и в планировании. Разработчик планирует тесты, затем пишет код, а команда планирует функции и выполняет недельный цикл, она также определяет тематику работы и начинает квартальный цикл.
Так нужно ли искать самоподобие в каждом аспекте вашей работы? Конечно, нет, это просто один из принципов. Но это поможет выявить закономерности, которые дадут лучшее понимание того, как именно идет работа над вашим проектом. И любой другой принцип делает то же самое. Так что не отмахивайтесь от них – это ценные инструменты, которые помогут понять методологию. Если они кажутся лишними, то постарайтесь подумать о том, какое отношение они имеют к работе, которую вы выполняете. Это поможет глубже воспринять ХР.
Я читал в интернете, что разработка через тестирование умерла. Это правда?
Разработка через тестирование – это инструмент. Говорить, что он умер, так же нелепо, как сообщить о смерти отверток. Многие команды используют разработку через тестирование каждый день и находят ее успешной. До тех пор, пока команды используют эту методику, она остается живой и активной.
Однако можно обсуждать этот вопрос на более глубоком уровне. Одна из причин, по которой разработчики (особенно имеющие большой опыт использования Agile и ХР) поднимают этот вопрос, заключается в том, что существуют команды, воспринимающие TDD не только как инструмент, но и как разновидность верования. Они сосредоточиваются на тестах как на конечной цели, а не как на средстве для создания лучшего программного обеспечения. Такие команды попадают в платформенную ловушку, затрачивая много времени на создание обширных и сложных платформ для тестирования. Разработка через тестирование предполагает помощь команде в сохранении простого исходного кода. Но когда он становится сложным, это значит, что-то пошло не так[72].
Те, кто заявляет, что «TDD умерла», считают, будто ХР-практики (особенно TDD и парное программирование) создают значительное эмоциональное сопротивление у команд, которые недостаточно знакомы с ними.
Команда, использующая ХР, не считает своей целью написание модульных тестов, так же как водопадные команды не рассматривают в качестве цели создание спецификаций. Главное – собрать работающее программное обеспечение, а TDD и спецификации лишь средства для достижения этой цели.
Есть еще один аспект, который поможет вам лучше разобраться в этом споре.
В главах 6 и 7 мы использовали термины «разработка через тестирование» и «разработка с опережающим написанием тестов» как синонимы. В течение многих лет их именно так и рассматривали, и мы решили, что для понимания концепции это упрощение было допустимо. Но некоторые люди рассматривают разработку с опережающим написанием тестов лишь как создание сначала тестов, а потом кода, в то время как TDD подразумевает более широкий подход к проектированию.
Надо ли писать тесты первыми, чтобы получить хорошо спроектированный код? Конечно, нет – многие команды делают прекрасный код без использования TDD. Но является ли TDD ценным инструментом для сборки хорошо продуманного кода? Безусловно. Если вы еще не применяли TDD, то без труда придумаете гипотетические причины не делать этого («Мне нравится делать наброски того, что я создаю, и видеть, как оно скрепляется вместе. В противном случае мне незачем писать тесты, поэтому ясно, что при применении TDD мне придется идти на компромисс!»)
Но дело в том, что многие команды используют TDD в строгом соответствии с методикой[73], а многие из этих гипотетических причин исчезают, когда вы делаете серьезные попытки применить эту практику.
Полезно заглянуть в статью, с которой началась большая дискуссия на тему, мертва ли TDD, и не пропустить ответ Кента Бека[74], опубликованный в Facebook. В нем он подробно объясняет, как TDD помогает решать проблемы программирования и лучше проектировать архитектуру.
Покойся с миром, TDD29 апреля 2014 года, 11:10
DHH послал TDD на свалку истории http://david.heinemeierhansson.com/2014/tdd-is-dead-long-live-testing.html. Мне грустно прежде всего не потому, что я спас TDD от попадания на свалку истории, а потому, что теперь нужны новые методы, чтобы я мог решить многие проблемы текущего программирования.
• Чрезмерное проектирование. У меня есть склонность «забираться» в функциональность, о которой я «знаю», что она «понадобится». Превращения одного теста из красного в зеленый (параллельно со списком будущих тестов) мне достаточно, чтобы избавиться от этого «копания». Мне нужно найти новый способ, чтобы оставаться сосредоточенным.
• Обратная связь по API. Мне нужно найти новый способ, чтобы получать быструю обратную связь о моих решениях, принятых по API.
Логические ошибки. Мне нужно найти новый способ, чтобы ловить эти надоедливые ошибки, которые я так часто делаю.
• Документация. Мне нужно найти новый способ сообщать коллегам, каким я предполагаю использование API, и записывать мои мысли во время разработки.
• Ощущение подавленности. Я действительно буду скучать по тем временам, когда, применяя TDD, я, написав тест, мог представить реализацию, даже если поначалу у меня не было никаких мыслей по этому поводу. Мне нужно найти новый способ, чтобы сделать следующий шаг к вершине.
• Размышление о том, как отделить интерфейс от реализации. У меня есть склонность загрязнять решения по дизайну API его предположительными реализациями. Мне нужно найти новый способ разделения двух уровней мышления, обеспечивая оперативную обратную связь между ними.
• Соглашение. Мне нужно найти новый путь, чтобы точно передавать свои мысли партнерам по программированию о тех проблемах, которые я решаю.
• Тревога. Скорее всего, я буду больше всего скучать по возможности, которую дает мне мгновенная кнопка TDD «Все хорошо?».
Я уверен, что найду другие способы решить эти проблемы. Со временем. Боль исчезнет. Прощай, TDD, старый друг.
(Facebook, страничка Кента Бека)
Так каков же будет вердикт? Одни команды пробовали использовать разработку через тестирование и нашли ее очень полезной. Другие считают, что ее трудно внедрить. Самый лучший способ решить это для себя – попробовать. Если вы попробуете – а мы очень рекомендуем это сделать, – то для получения хороших результатов, прежде всего, нужно быть очень осторожными, чтобы не попасть в платформенную ловушку во время создания модульных тестов.
Кент Бек отвечал на эссе Давида Хейнемейера Ханссона (известного в сообществе Ruby как DHH), создателя Ruby on Rails и одного из идеологов agile-сообщества, озаглавленное так: «TDD умерла. Да здравствует тестирование!»[75]. DHH смог идентифицировать некоторые важные признаки модульных тестов, которые попали в платформенную ловушку.
Первоочередное тестирование модулей приводит к чрезмерно сложной сети промежуточных объектов и косвенным обращениям в запросе, чтобы избежать их медленной работы. Как попадание в базу данных. Или файловый ввод-вывод. Или тестирование в браузере всей системы. Это породило некоторых архитектурных монстров. Густые джунгли сервисных объектов, паттернов команд и много чего еще.
Я редко разрабатываю модульные тесты в традиционном смысле этого слова, где все зависимости являются смешными и тысячи тестов можно выполнить в считаные секунды.
Вам это описание знакомо? Должно быть, поскольку оно очень похоже на примеры кода «с душком», о котором вы узнали в этой главе. Модульные тесты подвержены точно таким же проблемам сложности, какие могут повлиять на любой код. Если вы поймаете себя на создании очень сложных модульных тестов, то вполне возможно, что вы попали в такую же ловушку, которую так хорошо сформулировал DHH.
DHH также отмечает важную особенность TDD.
Это было не так. Когда я впервые открыл для себя TDD, это было похоже на вежливое приглашение в лучший мир написания программного обеспечения.
Разум заставляет вас применять практику тестирования там, где тестирования никогда не было. TDD открыла для меня спокойствие, вызываемое хорошо протестированным кодом, и блаженство, с которым я мог вносить изменения в программное обеспечение.
Предваряющее тестирование было замечательным курсом тренировок, который научил меня думать о тестировании на более глубоком уровне, но который я быстро завершил.
Это отличный повод, чтобы попробовать TDD. Это поможет вам думать о тестировании на более глубоком уровне, как это делает Кент Бек в упомянутом сообщении на Facebook. Единственный ли это способ для реализации перечисленных в сообщении приемов? Нет. Но он очень эффективен и поэтому является очень важной частью XP.
Предлагаем несколько вариантов действий, которые вы можете предпринять уже сегодня (самостоятельно или вместе с командой).
• Если вы разработчик, то попробуйте сделать рефакторинг своего кода. Возможно, вы уже используете IDE, которая имеет встроенные инструменты рефакторинга. Сможете ли вы найти простой способ изменить структуру кода, с которым вы работаете сейчас, упростив его, но не меняя поведения?
• Посетите сайт WikiWikiWeb[76] и прочитайте страницу о коде «с душком». Можете ли вы найти подобный код у себя? Если нет, то всмотритесь внимательнее.
• Легко ли проверить ваш исходный код из системы управления версиями и собрать его? Если вам необходимо выполнить много различных действий, то попробуйте найти способ автоматизировать этот процесс при помощи скрипта сборки. Каждый шаг в сторону упрощения проверки и сборки делает вашу жизнь проще в долгосрочной перспективе.
• Вы уже пробовали разработку через тестирование? Нет? Попробуйте. Возьмите пользовательскую историю или функцию, которую вас просили создать. Перед тем как приступить к написанию кода, придумайте один-два теста для того модуля, который вы собираетесь написать. Вам не нужно увлекаться полным набором тестов. Достаточно одного или двух, а затем соберите код, который пройдет их.
Ниже перечислены ресурсы, которые помогут вам узнать больше об идеях, описанных в этой главе.
• Вы можете узнать больше о простоте, инкрементальной архитектуре и других целостных практиках в книге Кента Бека и Синтии Андрес Extreme Programming Explained: Embrace Change (Addison-Wesley, 2004).
• Можно узнать больше о рефакторинге в книге Мартина Фаулера, Кента Бека, Джона Бранта и Уильяма Апдайка «Рефакторинг. Улучшение существующего кода» (М.: Символ-Плюс, 2008).
• Узнайте больше о потоке и о том, как создавать более энергичную рабочую среду, в книге Тома Демарко и Тимоти Листера «Человеческий фактор. Успешные проекты и команды» (М.: Символ-Плюс, 2014).
Здесь мы предлагаем советы для agile-коучей, помогающих своей команде разрабатывать идеи этой главы.
• Попадает ли ваша команда в платформенную ловушку? Пробует ли она решать большие абстрактные проблемы вместо маленьких и конкретных? Помогите ей осознать, что это сложное мышление. Изменить его на простое – один из самых важных способов, ведущих команду к принятию ХР.