Книги

Постигая Agile

22
18
20
22
24
26
28
30

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

Разве не лучше предотвратить появление таких проблем за счет хорошего проектирования в начале проекта? может быть, безопаснее сразу создавать правильный код?

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

Этим также объясняется, почему XP-команды используют итеративную разработку и включают квартальные и недельные циклы в свои основные практики. Они выделяют достаточно времени в каждой недельной итерации для учета создания модульных тестов и рефакторинга. И каждая поставка работающего ПО помогает взаимодействовать с пользователями, чтобы улучшить понимание решаемой задачи и уточнить истории. Благодаря непрерывному поиску кода «с душком» и совершенствованию архитектуры они пишут и сохраняют исходный код, который легко поддается изменению.

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

Использование непрерывной интеграции для поиска проблем проектирования

Мы узнали о практике непрерывной интеграции в главе 6, и это один из тех мощных инструментов, которые современные команды имеют в своем распоряжении. Одна из причин, по которой непрерывная интеграция улучшает архитектуру и предупреждает проблемы с ней, заключается в том, что она позволяет команде обнаруживать их на ранних этапах (при возникновении проблем с интеграцией).

Рис. 7.8. Непрерывная интеграция выявляет проблемы на ранних стадиях

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

Неудача – это один из принципов XP, который мы обсуждали в главе 6. Непрерывная интеграция обеспечивает быструю неудачу в проекте в том случае, когда два члена команды одновременно добавляют несовместимый или конфликтующий код. Если члены команды имеют совместимое с ХР мировоззрение, то они будут положительно воспринимать интеграционные неудачи, потому что это помогает выявлять и устранять проблемы на ранних этапах, когда сделать это намного легче.

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

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

Избегайте монолитной архитектуры

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

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

Традиционные водопадные команды часто сталкиваются с проблемами, развивающимися по такому сценарию. Предварительное выяснение требований, их рассмотрение большой аудиторией, а затем создание всего кода сразу приводят к тому, что его трудно изменять. У команды нет особых стимулов менять архитектуру решения, потому что такие изменения находятся под жестким контролем. Разработчики никогда не выработают привычки (наподобие постоянного рефакторинга или отслеживания кода «с душком»), заставляющие их создавать легко изменяемый код.

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

В начале книги мы говорили о том, чем ПО отличается от строительных работ. Самое опасное, что вы можете натворить в строительстве, – это взять кувалду и разрушить стены. В программном обеспечении удаление кода не нанесет большого ущерба – его можно восстановить из хранилища контроля версий. Гораздо опаснее, если вы напишете плохой код, а потом создадите другой, который зависит от первого. Добавление зависимостей (иногда называемых «связками», потому что они соединяют вместе два куска кода) делает сложным изменение одной из частей без того, чтобы по крайней мере оценить влияние этого изменения на другую часть.

Монолитная архитектура часто характеризуется сильно связанным кодом, где типичный модуль имеет много связей с другими частями системы. Попытка изменить сильно связанный код становится затруднительной – часто эта связка дает эффект «стрельбы дробью» и другие антипаттерны. Именно такие (обычно недокументированные) связки приводят к ошибкам при переделке.

Связанный код можно сделать несвязанным, разорвав соединения между модулями (а лучше вовсе не добавлять эти связи в начале работы).

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

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

Инкрементальная архитектура и целостные XP-практики