Постигая Agile — страница 59 из 89

Как вы делаете договор простым? Так же, как и модули: принимаете решения о том, как они взаимодействуют, в последний ответственный момент. И у нас уже есть инструмент, чтобы помочь в этом: разработка через тестирование. Когда программист создает модульные тесты, это помогает убрать сложность и заставляет его использовать этот модуль, прежде чем он написан. Можно применять один и тот же модуль тестирования утилит или платформ, чтобы написать простые интеграционные тесты, проверяющие, как различные модули взаимодействуют друг с другом. И разработчик напишет эти тесты, прежде чем добавит код для взаимодействия. Например, если есть взаимодействие между модулями, где выход одного из них передается в качестве входных данных в другой, выход которого поступает в третий, то разработчик напишет тест, который имитирует, как они «общаются» – то есть интегрируются.

Причина, по которой это помогает сохранить контракт между модулями простым, заключается в том, что легко понять, когда он начинает усложняться. При простом контракте этот вид интеграционного теста очень легко написать. Но если контракт сложный, то интеграционный тест становится для разработчика кошмаром. Сначала ему надо инициализировать множество разных, казалось бы, несвязанных объектов, затем кое-как преобразовать данные из одного формата в другой и вертеться, как уж на сковородке, чтобы обеспечить взаимодействие модулей. И так же, как создание модульных тестов, написание в первую очередь интеграционных тестов поможет избежать этих проблем, прежде чем они будут встроены в программное обеспечение.

Отличная архитектура возникает из простых взаимодействий

Мы используем термин «возникает», когда сложное поведение появляется из простых систем. Возникновение – это распространенное явление: отдельные муравьи ведут себя примитивно, а весь муравейник демонстрирует гораздо более сложное поведение, возникающее в результате взаимодействия между муравьями. Таким образом, муравейник – это больше, чем сумма всех его обитателей.

Когда система разработана так, что ее поведение формируется из взаимодействия отдельных модулей, работающих вместе, и кажется, что они «вылупились» из одного модуля, это называется возникающей архитектурой. Системы, созданные с использованием возникающей архитектуры, практически всегда состоят из маленьких, независимых, несвязанных модулей (подобных утилитам Unix или муравьям). Эти части объединяются для выполнения сложных задач, и поведение системы зависит как от взаимодействия между этими модулями, так и от них самих.

Глубокая иерархия модулей, вызывающих один другой, применяется редко. Вместо этого система будет использовать сообщения, очереди или другие способы коммуникации модулей, не требующие централизованного управления. Утилиты Unix – хороший пример такой организации работы[71]. Входной сигнал передается от одной утилиты к другой, что облегчает их объединение в цепочку. Это приводит к очень простому, последовательному использованию: вызывается первая утилита, потом вторая, затем третья. Можно создать и более сложное взаимодействие между инструментами, но это приводит к простой, неглубокой структуре вызовов (в отличие от глубокой, вложенной иерархии вызовов со слоями модулей).

Если система спроектирована подобным образом (простые модули, взаимодействующие простым способом), то можно получить интересный эффект. Команде начинает казаться, что поведение возникает из системы в целом, а не из отдельных модулей. И возникает ощущение, что сложное поведение не имеет единственного источника. Это очень похоже на поведение муравейника, которое тоже не определяется отдельными особями. Команда обретает уверенность, что каждая часть системы проста и поведение ее как целого возникает из взаимодействия между этими модулями.

Людям, потратившим годы на создание набора утилит Unix, знакомо это чувство возникающего поведения. Можно получить первое поверхностное представление о возникающем поведении, наблюдая за тем, как сочетаются утилиты в приведенном примере с адресной книгой: cut умеет только извлекать символы из строки, а grep – искать соответствие шаблонам, но их сочетание обеспечивает обработку адресов без специальной команды, которая пояснит системе, что такое адрес и как его обработать.

ХР-команды, работающие с полной отдачей, считают естественным применение возникающей архитектуры. Все начинается с простоты: каждый модуль предназначен для конкретного использования. Разработка через тестирование гарантирует, что он остается простым и имеет только одно предназначение. Программист пишет тесты, чтобы убедиться: модуль выполняет одну функцию, – а потом создает код. И если он успешно проходит тесты, то его разработка прекращается. Поэтому никакого дополнительного поведения у модуля не появляется, и в нем нет кода, написанного без основания. Весь код в модуле необходим.

Команда избегает глубоких вложений вызовов, когда один модуль вызывает другой, тот, в свою очередь, третий, и т. д. Вместо этого структура вызова программы выглядит плоской и широкой, что снижает зависимость между модулями. Такие взаимодействия просты настолько, насколько это возможно: когда один блок нуждается в данных, он получает их из другого блока или из очереди сообщений, поэтому не нужно знать, откуда они взялись. Чтобы сохранить простоту системы, команда избегает многоэтапных, сложных взаимодействий между большим количеством модулей (подобно тому, как Unix-утилиты передают данные от одной утилиты другой по конвейеру).

Новая архитектура возникает, когда команда обнаруживает изменения, которые необходимо сделать. Чтобы добавить новое поведение в систему или переменить способ ее работы, команда должна изменить отдельные модули или способ их взаимодействия. Но поскольку каждый модуль несвязан и взаимодействия между ними просты, эти изменения, как правило, не носят каскадного характера по всей системе. Если встречается код «с душком» (скажем, программист обнаруживает, что занимается «стрельбой дробью», продирается через спагетти-код или сырые объекты), то это серьезное предупреждение, что во время работы были допущены ненужные усложнения. И разработчик знает (а что еще важнее – чувствует): стоит потратить время на рефакторинг, чтобы разделить код на более простые модули.

Система, построенная на возникающей архитектуре, может развиваться годами, оставаясь простой для поддержки и изменений. Успешные ХР-команды имеют инструменты для поддержания простоты исходного кода. Стиль их работы поощряет постоянно отслеживать появление сложного кода и выполнять его рефакторинг. Это, в свою очередь, позволяет им без напряжения вносить изменения, ведущие к возникновению архитектуры. Такой эффективный цикл приводит к простому, стабильному и очень качественному коду – и в результате команды обнаруживают, что таким образом можно создавать и поддерживать программное обеспечение намного быстрее. Когда система нуждается в поддержке (исправить ошибку или внести изменения), редко требуется серьезная модификация, затрагивающая много различных частей кода.

Поскольку члены команды знают, что система гибкая, и им известно, что можно расширять, а что – нет, они успешнее принимают решения в последний ответственный момент. Как у единой команды у них появляется понимание того, когда и какие архитектурные решения можно отложить на завтра. А это, в свою очередь, помогает сегодня сохранять простоту кода. Команда менее напряжена, а ее члены лучше работают вместе. Они производят качественный код очень быстро и, когда его требуется изменить, делают это легко и без ошибок.

Другими словами, команды сохраняют код простым, что позволяет им принимать изменения.

В этом суть ХР.


Описание: команда занимается разработкой фантазийного баскетбольного сайта

Джастин – первый разработчик

Даниэль – второй разработчик

Бриджит – менеджер проекта

Акт V. Окончательный результат

Даниэль и Джастин были заняты парным программированием, когда Даниэль вдруг воскликнула:

– Ты понимаешь, что мы работаем в паре уже почти три месяца?

Прошло около года с тех пор, как Даниэль рассказала команде об XP. Они делали выпуски каждую неделю, но последний недельный цикл был особенным. Компания объявила о рекламной акции в одной из крупнейших телевизионных сетей, и команда просто вытолкнула код в производство. Пару минут Даниэль и Джастин вспоминали, как шла работа на протяжении нескольких последних циклов.

Джастин сказал:

– Ты права. Все это время мы работали парами, но меняли партнеров так часто, что, по-моему, я успел поработать с каждым членом команды.

Даниэль спросила:

– Когда ты в последний раз засиживался допоздна?

Джастин задумался.

– Знаешь, мне придется поискать в телефоне последнее сообщение с извинениями. Давненько это было.

– Думаю, это потому, что мы стали лучше, – сказала Даниэль. – Я знаю, что теперь пишу более простой код, чем шесть месяцев назад.

– Ты права. Когда я впервые прочитал о разработке через тестирование, такая идея показалась мне чересчур абстрактной. Теперь же я вижу в ней смысл, – ответил Джастин. – Раньше мы рассуждали об этом всей командой, пытаясь убедить друг друга, что это пустая трата времени. Я не припомню, когда в последний раз мне нужно было спорить на эту тему.

Даниэль добавила:

– Самое забавное – это отсутствие ощущения, что я стала работать совсем по-другому.

Проходившая мимо Бриджит остановились и прислушалась.

– А я эту разницу чувствую, – сказала она. Джастин и Даниэль удивленно уставились на нее. Они не заметили, как она подошла. – Раньше моя работа в основном заключалась в том, чтобы убедить менеджеров продуктов дать нам больше времени. Мы всегда отставали от графика, и казалось, что любая мелочь требует нескольких месяцев работы.