Интеграция программного проекта означает: взять все созданные компоненты проекта, скомпилировать их совместно и выполнить тесты (регрессионный набор).
Исторически большие проекты имели долгий итерационный цикл – недели или месяцы. Недостаток заключался не только в длительности итерации, но и в ее природе, который мы можем называть подходом "большого взрыва". В отличие от физики, где "большой взрыв" происходит в начале, в программном проекте "взрыв" происходил в конце итерации. В традиционном процессе различные группы проекта, начиная со старта итерации, шли своими собственными путями. В конце итерации они собирали все вместе ("большой взрыв") или пытались это сделать. Предсказуемо (действительно предсказуемо, если у вас есть опыт подобной разработки) такие попытки заканчивались слезами и кровью. Удивительно, как быстро предположения групп расходятся и компоненты становятся несовместимыми.
Два вклада в практику программирования, соответственно в инструментарий и методы, существенно улучшили ситуацию:
Наиболее заметным продвижением в этом направлении стала "ежедневная сборка", введенная в Майкрософт в восьмидесятые годы. Идея была проста. В конце каждого дня собираются все изменения, "введенные" (официально подписанные) разработчиками; система компилируется, прогоняются все тесты. Как отмечает менеджер Майкрософт [Cusumano 1995]:
Создание ежедневных сборок – одно из самых болезненных дел в мире, но это и самая величайшая вещь в мире, по-скольку вы получаете мгновенную обратную связь.
Центральное правило "ежедневной сборки" иногда называют правилом "китайского магазина": в фарфоровых лавках Китая каждая сломанная или разбитая вещица считается купленной тем, кто это сделал. В программном магазине сломанную сборку чинит тот, по чьей вине произошел сбой. В традиционном для Майкрософт процессе виновнику вручается некоторый знак осуждения (платишь 5 долларов – получаешь козий рожок), помимо этого, приходится оставаться, пока проблема не будет устранена. Такие меры не очень сочетаются с agile принципами устойчивого темпа, но идея интеграции и непосредственной проверки остается.
В восьмидесятые годы изменение не принималось, если из-за него сборка падала при компиляции. В наши дни ожидания идут дальше, мы хотим, чтобы на скомпилированной системе успешно работал набор регрессионных тестов. Новые разработки инструментария поддерживают эту эволюцию. Сегодняшние инструменты включают автоматические сборщики, которые анализируют зависимости между модулями и собирают адекватную систему. Точно так же инструментарии регрессионного тестирования автоматически запускают набор тестов и выдают полный отчет по каждому падающему тесту.
Правила agile, в частности XP, идут дальше и вместо "ежедневной сборки" рекомендуют "непрерывную интеграцию". Правило Бека [Beck 2005]:
Интегрируйте и тестируйте изменения не реже, чем через несколько часов.
Заметьте, акцентирование на тестирование. Многие команды, принимающие agile, не следуют этой рекомендации, придерживаясь ежедневной сборки. Частая интеграция имеет свои минусы, прежде всего интеграция требует времени. Даже при наличии инструментария, когда сборка и тестирование выполняются автоматически, все же необходимо дожидаться окончания процесса. Для больших систем и даже для малых, чем больше тестов, тем больше времени требует интеграция. Бек говорит, что при парном программировании программисты могут обсуждать свои проблемы, ожидая завершения интеграции.
В рекомендациях Поппендиков относительно частоты интеграции больше нюансов. Они предлагают несколько стратегий: каждые несколько минут, каждый день, каждую итерацию. Вот их комментарий:
Не всегда практично интегрировать весь код каждый раз. Как часто вы интегрируете и тестируете, зависит от того, занимаетесь ли вы обнаружением ошибок. Доказательство того, что вы интегрируете с достаточной частотой, основано на способности быстрой интеграции в любое время без обнаружения дефектов.
Акценты расставлены правильно. Не так важно установить точную частоту интеграции, более важно, чтобы разработка шла устойчивым темпом, сохраняя нужный уровень качества, отражаемый числом багов, обнаруживаемых во время интеграции.
Наблюдение Поппендиков подтверждается и моим собственным опытом, который говорит, что при надлежащем процессе и с упором на качество интеграция не должна выполняться часто, еженедельный период, например, может хорошо работать. Дело в том, что команда хорошо подготовлена к совместной работе и при внесении изменений постоянно осознает, как это может отразиться на других частях системы. Разработчики также сами выполняют регрессионные тесты прежде чем вводят изменения, так что во время интеграции "падение" теста – редкое явление. С таким мышлением реальные конфликты редки.
Вне зависимости от частоты обновлений методология, применяемая компетентными командами, существенно изменилась со времен технологии "большого взрыва". Методы agile и их акцентирование на частой интеграции внесли свой вклад в эту плодотворную эволюцию.
Парное программирование – один из краеугольных камней экстремального программирования. В начальный период, когда agile был представлен главным образом XP, все дискуссии об agile неизбежно скатывались к обсуждению этой провокационной идеи. Проводились эмпирические исследования оценки эффективности этого подхода, его сравнения с традиционным подходом анализа кода. Сегодня парное программирование практикуется от случая к случаю (хотя энтузиасты все еще остаются и приходят с новыми вариантами, такими как мобпрограммирование), но оно перестало быть в круге света; другие практики agile считаются более важными.
Споры, главным образом, были следствием настаивания XP, что парное программирование является единственным и универсальным способом разработки программ. Бек писал:
Разрабатывайте все производственные программы двумя людьми, сидящими за одной машиной.
Как и в других случаях с agile рекомендациями, они казались индустрии экстремальными. Немногие организации достаточно долго применяли парное программирование для всех своих разработок. Но многие программисты считают, что некоторая доза парного программирования полезна и эта техника заслуживает того, чтобы быть известной.
Два партнера, сидя за одним компьютером, работают над одной задачей. Один стучит по клавишам, создавая программу, одновременно вслух объясняет ход своего мышления, другой комментирует и корректирует. Процесс равных партнеров, поскольку они периодически меняются местами.
Рекламные преимущества подхода: увеличивается сосредоточенность на задачу, происходит мозговой штурм, улучшающий и проясняющий идеи, позволяется перехватывание инициативы при усталости одного из партнеров.
Бек и другие авторы дают практические советы: "Установите компьютер так, чтобы партнеры могли сидеть удобно бок о бок", "прикрывайте рот при кашле", "избегайте сильных ароматических средств" и так далее.
Я знаю очень мало книг по инженерии программ, в которых обсуждаются проблемы личной гигиены. Еще одно извлечение из книги Бека, которая заслуживает некоторого рентгеновского просвечивания: "Когда программисты молоды и не могут отделять близость от возбуждения" (способствуют ли духи возбуждению?), "работа с персоной противоположного пола может привести к появлению сексуальных чувств", что "не в интересах команды" (к сожалению, в тексте ничего не говорится об интересах самих людей). Но ведь это же книга о проектах, так что учитываются интересы команды: "даже если чувства взаимны, эта увлеченность вредит команде". Чтобы убедиться, что читатели все понимают, в книге приводится фотография, где "мужчина слишком близок к женщине". Как положено, для сохранения интриги фото находится на следующей странице. Пожалуйста, не говорите моей жене, но я решился перевернуть страницу. Некоторые читатели вздохнут с облегчением, другие будут разочарованы; картинка напоминает семейную сцену. Участникам далеко за 18, полностью одеты, видны со спины, и их отделяют добрых два дюйма. Читайте вдохновляющую книгу Бека, но не для приятного возбуждения. Это страстный, захватывающий, пугающий роман, возможно, сценарий фильма (Голливуду стоит заинтересоваться). Я с нетерпением жду первого автора, кто, вдохновленный цитируемым текстом, напишет: "My Pair Lady", "The Pair Karamazov", "Fifty Shades of Pair".
Парафразы названий известных произведений: "Моя прекрасная леди", "Братья Карамазовы", "Пятьдесят оттенков серого" (Моя парная леди, Пара Карамазовых, Пятьдесят оттенков пары).
Вернемся к менее романтичным аспектам. Вот реакция многих людей, которые впервые услышали о парном программировании: два программиста делают работу одного, так что производительность снижается вдвое. На это сторонники XP дают ответ: если они производят вдвое лучший код, то производительность растет, а не снижается.
Ответ корректен. После всего производительность, фигурирующая в индустрии, измеряется в числе строк кода, создаваемого программистом (метрика, всеми критикуемая и всеми используемая). Считается, что средняя производительность равна 20 строкам кода в день. Так как написание 20 строчек занимает несколько минут, то понятно, что большую часть времени программисты занимаются другой работой, в частности, размышляют о коде, который они напишут, корректируют очередной написанный код, исправляя ошибки. Если парное программирование действительно выдающийся процесс, то два программиста, работая вместе, могут создать более 40 строк кода в день. Если эти строки высокого качества, то проект получит преимущества. Так что тривиальные аргументы о снижении производительности не могут быть приняты без рационального анализа стоимости и преимуществ.
Эмпирические исследования не дают однозначного ответа [Miller 2005], [Navrocki 2001] в поддержку парного программирования. Когда оценивается этот подход по отношению к традиционным приемам обзора кода (когда предмет работы программиста подвергается коллективной инспекции) и PSP (Персональный Программный Проект), описанный в третьей главе (3.6.2), то парное программирование показывает примерно те же результаты как по производительности, так и по качеству кода. Примерно, поскольку нет надежных результатов, но общий тренд ясен – прорыва нет.
Ошибка, которую часто делают в индустриальном применении парного программирования, состоит в том, что его используют как способ работы с ментором, объединяя в пару начинающего и опытного программистов. Использование менторов – хороший прием, но его цель – образование, а не создание программного продукта.
Наивный менеджер, который пытается убить двух зайцев, – получить обещанные преимущества парного программирования и натренировать по ходу процесса молодого программиста, – будет разочарован. Произойдет двойная потеря. Молодой программист будет замедлять работу. Ментор вместо того, чтобы сосредоточиться на сложной задаче, должен будет объяснять простейшие вещи. Обучение тоже не выиграет, поскольку главная цель ментора – разработать программу, получить нужный результат и выдержать контрольные сроки, так что обучение остается в стороне.
Если вы ищете способ привести в ярость ваших лучших разработчиков, возможно, чтобы они совсем прекратили работу, то дайте им в пару зеленого новичка.
Идея парного программирования – это идея пары равных личностей. В этом случае при взаимодействии возникает обратная связь, дающая эффект усиления. Работа ментора это нечто другое. И то и другое имеют ценность, но их смешение дает обратный эффект. Учеба страдает из-за необходимости создания серьезной программы, парное программирование страдает из-за необходимости обучения, что вредит производительности и качеству создаваемой программы.
Если больше означает лучше, то зачем останавливаться на паре? Зуилл и некоторые другие энтузиасты XP провозгласили необходимость мобпрограммирования, определяя его так:
Все блестящие люди работают в одно и то же время в одном и том же помещении на одном и том же компьютере над одной и той же задачей.
Нет больше разделения ролей, команда действительно выступает как единое целое подобно батальону в опере Доницетти "Дочь полка".
Такие предложения показывают, что аджилисты становятся одним из наиболее плодовитых сообществ программной инженерии, лабораторией, вырабатывающей новые идеи. (Другие примеры еще слишком свежи, чтобы их можно было оценить в этой книге. Приведу лишь названия: трешинг – trashing, никаких оценок – no estimates, программистская анархия – programmer anarchy). Некоторые из них выживут, другие – нет. Слишком рано предсказывать судьбу каждой из них. Оценка, которая последует, ограничивается только парным программированием.
Чтобы оценить парное программирование так же хорошо, как и другие agile приемы, полезно вспомнить бессмертные слова Бека, процитированные чуть выше: мы должны быть "достаточно взрослыми, чтобы отделить близость от возбуждения".
По здравому смыслу парное программирование полезно, вне всякого сомнения. Многие разработчики рады, когда появляется возможность работать в паре с равным по силе коллегой, особенно, когда имеешь дело с заковыристой частью задачи. Основные приемы, в частности, необходимость проговаривать вслух свои мысли для непосредственной обратной связи, хорошо понятны и широко применяются.
(Как менеджер я регулярно слышал от разработчиков: По этой проблеме мне хотелось бы поработать в паре с X, несомненно, появится хорошая идея).
Загадкой остается настаивание адвокатов XP на том, что это единственный путь разработки ПО, и парное программирование должно применяться всегда. Такие утверждения не имеют смысла по двум причинам.
Первая – это неопределенность эмпирических оценок, отмеченная выше. Следует учитывать, что отсутствие данных часто используется как предлог для блокировки новых приемов. Когда идея очевидно продуктивна, не следует ждать массовых неопровержимых доказательств. Но в данном случае существует фактически значительное количество эмпирических данных, свидетельствующих об отсутствии явных преимуществ парного программирования. Парное программирование может хорошо работать в некоторых ситуациях, но, если бы это происходило всегда, то исследования подтвердили бы этот факт. В отсутствие научных свидетельств применение квантора всеобщности основано не на разуме, а на идеологии.
Вторая причина, по которой результаты проводимых исследований разнятся, связана с тем, что люди разные. Многие выдающиеся программисты любят общаться с кем-либо, когда пишут программы, но многие этого не любят. Другие любят сосредоточенность, предпочитают, чтобы их не тревожили. С точки зрения agile, следует поощрять коммуникации, а время одиночек, молчаливых гениев прошло. Прекрасно, но если в вашей команде есть выдающийся программист, которому в критические моменты необходимы мир, спокойствие и одиночество, то вы выкинете его из команды или заставите работать в течение дня, что для него может быть настоящей мукой?
Одно дело – требовать, чтобы люди объясняли свою работу другим, другое, совершенно опасное – принуждать следовать единому стилю работы, особенно в случае креативной и сложной интеллектуальной деятельности. Когда Линус Торвальд создавал Linux, он работал в одиночку, но это не помешало ему показать свой код и позже привлечь тысячи людей к сотрудничеству. Много других примеров приходит на ум: Билл Джой и Berkeley Unix, Ричард Столлмен и Emaks, Дональд Кнут и TeX. (На секунду подумайте, блестящей ли является идея заставить Дона Кнута работать в паре. Пусть кто-нибудь попытается.)
Заметим, парное программирование, по мнению ряда людей, влечет "излишнюю близость". Кокбурн предлагает "программирование бок о бок", где два человека пишут программы раздельно, каждый на своем компьютере, но достаточно близко, чтобы видеть экран соседа. Это предложение кажется едва ли предпочтительнее классического стиля, при котором люди концентрируются, когда им необходимо, с минимальным вмешательством по возможности и начинают обсуждение по мере необходимости.
Настаивание на том, что парное программирование является единственно правильным способом, встречало возражения и со стороны некоторых защитников agile. Ларман [Larman 2010], например, указывает:
Парное программирование – это только практика XP, она не требуется в Scrum.
Хотя первая часть этого комментария является преувеличением, поскольку сторонников парного программирования достаточно много, помимо XP, отказ Scrum принять догматическое применение парного программирования вполне объясним.
Заслугой XP является введение парного программирования, объяснение его правил и добавление этой техники в качестве важного элемента в набор приемов современного программирования. Навязывание его в качестве единственного ответа неуместно и отвергается профессиональным сообществом.
Методы Agile предполагают, что команды должны следовать строгим стандартам кодирования, способствующим повышению качества кода. В оригинальном описании экстремального программирования Бек [Beck 2000] пишет:
Если вы допускаете все эти переходы программистов от одной части системы к другой, постоянный обмен партнерами, рефакторинг кода, написанного другими, то невозможно не иметь некоторого множества приемов кодирования. При небольшой практике невозможно сказать, кто из членов команды создавал этот код.
Стандарты кодирования трудно назвать новой идеей. Каждая уважающая себя организация устанавливает точные правила стиля программирования. Что следует особо отметить в приведенной выше цитате, помимо необходимости иметь стандарты кодирования, так это замечание, что никто не может определить авторство кода, предполагающее "безликое программирование". Это тоже старая идея, появившаяся в девяностые годы и критиковавшаяся как некоторый стиль программирования в духе Джилберт-босса, подавляющего креативность и индивидуальность программирования. Странно, что эта идея возрождается в рамках совсем другой идеологии. Этому нельзя не удивляться. Все эти акценты agile на коммуникации и сотрудничество великолепны, но, в конечном счете, великие программы написаны великими программистами (такими как Кент Бек). Linux несет марку Торвальда, Berkeley Unix – Джоя, TeX – Кнута, xUnit – Бека и Гаммы, никого из них представлять не требуется. Даже в проектах с участием простых смертных наиболее сложные части проекта выполняются лучшими программистами.
Вне зависимости от того, согласны ли вы с этими частными замечаниями, присоединяйтесь к хору, выступающему за стандарты кодирования.
Agile альтернатива "предваряющему анализу" состоит в адаптации постоянно сменяющихся последовательных версий программы, поиске в проекте и коде "плохо пахнущих" участков (неудовлетворительных элементов) и их коррекции. Этот процесс известен как рефакторинг.
Типичным примером "плохо пахнущего кода" является дублирование. Всегда плохо в разных частях программы иметь тот же самый код или почти тот же: две части необходимо отлаживать, две части необходимо корректировать, если возникает потребность, две части следует изменять при смене требований.
Типичный рефакторинг дублирования состоит в применении абстракции – выделение отдельного модуля: в объектно-ориентированном программировании дублируемый код помещается в новый создаваемый класс, представляющий общую абстракцию, существующие классы становятся наследниками абстрактного класса.
Описанный прием – это один из способов удаления дублирования и не всегда самый подходящий. Программисты выполняют рефакторинг, идентифицируя "запах кода", находя в каждом случае, будет ли известный образец (паттерн) рефакторинга применим и желателен.
Некоторые приемы рефакторинга менее монументальны, но все же полезны. Например, можно изменять имя метода или поля класса в целях внесения ясности или согласованности.
Современные среды разработки включают инструментарий, позволяющий некоторые виды рефакторинга выполнять автоматически.
Не каждому образцу программных изменений соответствует паттерн рефакторинга. Необходимо выполнение двух условий:
Первое условие означает, что программа после рефакторинга должна давать те же результаты, как и перед проведением рефакторинга. Рефакторинг не означает поиск и устранение ошибок, изменение функциональности, даже пользовательский интерфейс не меняется при рефакторинге. Эти виды изменений также необходимы, но рефакторинг направлен только на улучшение качества архитектуры.
Из требования сохранения функциональности вытекает необходимость автоматической поддержки рефакторинга. Даже такое концептуально простое изменение, как переименование, не только громоздко, но и чревато ошибками, когда выполняется вручную, так как имя должно измениться не только в определении, но и во всех точках, где происходит обращение к этому имени. Другими словами, преимущество инструментария рефакторинга в том, что изменения выполняются не только автоматически, но и безопасно.
В соответствии со вторым условием требуется определить само понятие качества кода и, что более важно, качества архитектуры. В то время как нет единого всеобъемлющего определения, современная литература по проектированию предлагает ряд критериев. Ясно, например, что плохо спроектирован класс, содержащий единственный метод, плоха архитектура, содержащая глубокую и единственную ветвь наследования (где каждый класс, не являющийся корнем или листом, содержит ровно одного родителя и одного потомка). Эти примеры содержат признаки плохого проектирования, указывая на потенциально "плохо пахнущий код". (К слову, легче указать примеры плохого проектирования – "антиобразцы", чем примеры проектирования архитектуры высокого качества).
У Бека дается более специфическое условие: рефакторинг должен "упростить проект". Его понятие простоты включает отсутствие дублирования, минимальное число классов, минимальное число методов.
Внимание к важности рефакторинга было одним из наиболее видимых эффектов agile методов, в особенности экстремального программирования. Рефакторинг стал одним из принципиальных инструментов современного программиста.
Как и в случае многих других идей, положительный вклад (используйте эту технику) более интересен, чем негативный (эти приемы являются заменой традиционных). Стремление всегда искать возможные улучшения архитектуры прекрасно. Но рефакторинг не дает права отрицать необходимость предваряющего анализа. Если вы не уделили должного внимания начальному этапу проектирования, а просто строите "простейшее решение, которое еще может работать", то можно снова и снова переделывать проект, поскольку начальное решение, хотя и работающее, адаптируется с большим трудом. В описании этого процесса Бек также указывает на его ограничения:
Не всякий рефакторинг может быть выполнен в течение нескольких минут. Если вы обнаружите, что построили запутанную иерархическую структуру, то на ее распутывание может потребоваться месяц упорной работы. Но у вас нет месяца на такую работу, поскольку нужно поставлять очередную историю до окончания итерации.
В этом случае большой рефакторинг нужно выполнять малыми шагами (возрастющие изменения). Находясь в центре тестового варианта, вы найдете шанс сделать один шаг на пути к большой цели. Сделайте этот шаг. Передвиньте метод сюда, а переменную туда. Постепенно от большого рефакторинга останется малая часть. Тогда вы сможете закончить ее в течение нескольких минут.
По-настоящему "большой рефакторинг" не является суммой малых рефакторингов. Я знаком с проектом компилятора, в котором команда в некоторый момент не справилась с потерей производительности. Причиной была неэффективная структура данных, хранящая трек элементов системы – классов и методов. При повторном проектировании удалось идентифицировать каждый элемент одним целым числом, а не сложным объектом, как ранее. Но это была большая система – с тысячами классов, более чем двумя миллионами строк кода, для которой такой хирургический рефакторинг – ненавистная операция. Изменения запутанны, болезненны, влияют почти на каждый модуль системы, не привнося никакой новой функциональности. Единственное преимущество – существенное улучшение скорости и прочный фундамент для дальнейших разработок. Если вы решитесь на такой рефакторинг, то нет способа двигаться "малыми шагами", невозможно в одной части системы использовать целые, а в другой объекты. Вы должны согласиться на "месяц упорной работы", а может, и на больший срок. Вы можете найти, что "овчинка выделки не стоит", но решение идти вперед – это выбор "все или ничего".
Совет Бека – еще один случай "неправомерного обобщения". Некоторые изменения могут носить "возрастающий характер": сделайте немножко здесь, немножко там, и в одно прекрасное утро вы, к своей радости, увидите, что осталось совсем чуть-чуть, что можно выполнить за несколько минут. Переименовать классы и методы для большей согласованности, локально изменить отношения наследования между малым числом классов, превратить атрибут (поле класса) в локальную переменную – типичные примеры простого рефакторинга. Но некоторые изменения не могут идти таким путем.
Было бы прекрасно поверить в истинность мантры – "начните с простейшей вещи, которая может работать, и путем возрастающих улучшений вы придете к архитектуре, образующей великолепный продукт". К несчастью, это не тот случай. Применим старый принцип GIGO – "мусор сюда, мусор туда" (Garbage In – Garbage Out)
В следующих двух разделах обсуждаются детали этих двух утверждений.
Начальное проектирование может привести к несовершенной архитектуре по двум причинам: что-то не учли или вследствие существенного недопонимания. Непредвиденные несовершенства можно скорректировать через рефакторинг, но существенные нет. Несогласованное именование – непредвиденный фактор, ошибочный выбор абстракции – существенный. Проблемы компилятора, обсуждаемые выше, были примером существенного несовершенства. Вот еще один.
Вы создали множество классов, описывающих тесно связанные концепции, скажем, работы в компании. У вас есть также список объектов этих типов. Через некоторое время вы осознаете, что вам необходима новая функциональность, применимая ко всем объектам из списка. Например, вы хотите распечатать содержимое списка, так что придется добавить в каждый класс метод "print". Затем вы добавляете в классы метод "encode", позволяющий компактное хранение объектов. В следующий раз необходим метод, создающий XML форму.
Это все функциональные изменения, не рефакторинг, и вы их уже выполнили. Но вы осознаете, что такие ситуации будут возникать и в будущем, так что приходите к решению остановить постоянную модификацию существующих классов. Возможно, это произойдет и помимо вашей воли, поскольку классы уже попали в повторно используемую библиотеку и вышли из-под вашего контроля.
Техническое решение хорошо известно: следует использовать паттерн "Посетитель" (Visitor), который позволяет выполнять произвольные операции над произвольными экземплярами класса, где сами операции определены где-нибудь, совсем необязательно в самом классе. Адаптация этого решения требует одного изменения, применимого ко всем классам, – нужно сделать классы "посещаемыми" путем наследования их от общего класса Visitor с подходящим методом "update". Вы должны также удалить лишний для классов код, который был добавлен как временное решение, и поместить его в нужное место. Вы решаете, что долговременные преимущества гибкости проекта стоят кратковременной боли.
Это важное изменение. Оно требует, может быть, не месяца работы, но, по меньшей мере, нескольких дней в зависимости от числа классов и влияния на другие части архитектуры. Для достижения лучших результатов нежелательно выполнять их небольшими шажками. Лучше выполнить эту операцию за один присест.
Во избежание попадания в такие ситуации нет другого выхода, кроме предваряющего анализа. Даже и в этом случае не всегда возможно совершенное предвидение. Когда такие ситуации возникают, это приводит к повторному проектированию в глубину, не покрываемому никаким видом возрастающего рефакторинга, предлагаемого XP и другими agile подходами.
Понимание разницы между непредвиденным и существенным – это ключ к проблеме расширяемости системы, определяющий границы применимости рефакторинга. Различие связано с тем, что мы уже изучали ранее, – различие между аддитивной и мультипликативной сложностью. В целом изменение является непредвиденным, когда оно воздействует на аддитивные элементы – функциональность с немногими зависимостями с другими частями системы. Если же зависимости сложны (имеют мультипликативную сложность), изменения носят существенный характер и не могут быть устранены простым рефакторингом.
Рефакторинг, рассматриваемый защитниками agile как техника "Или – Или", может быть лучше использован как "И" техника. Он работает наилучшим образом, когда комбинируется с идеями, отвергаемыми аджилистами, в данном случае с предваряющим анализом.
Никакое количество рефакторинга неспособно скорректировать дефектную архитектуру. Первичная обязанность любого проектировщика – идентифицировать фундаментальные абстракции, составляющие скелет архитектуры. Сделаете это нужным образом, и вам останется еще много работы, которую необходимо выполнить. Но если ошибиться на этом этапе, в конечном счете вам придется (метафору выберите сами) ставить латку на латку, тушить огонь керосином, лепить пластырь на пластырь.
Если архитектура не отвечает сути, то нет другого способа, кроме ее перестройки, каких бы усилий это ни стоило. Когда суть схвачена, то это еще не означает, что вы выбрались из леса, поскольку не все прекрасно и несовершенства будут подстерегать вас, но здесь помогает рефакторинг.
Agile методы учат нас, что никогда нельзя терять готовность критически рассматривать собственные проектные решения, мы должны быть способны чувствовать запахи "плохо пахнущего кода и архитектуры", идентифицировать их и исправлять на лету.
Заключительная техническая практика, рассматриваемая в этой главе, является некоторым экстремальным следствием той центральной роли, которую подходы agile, начиная с XP, отводят тестам. Идея, предварительно рассмотренная при обсуждении принципов, состоит в том, что разработка должна быть разработкой, управляемой тестами, для краткости TDD (Test Driven Development). Разработка TDD является следствием TFD (Test First Development) – "Вначале Тест" разработки.
TDD не является приемом тестирования – это полноценный метод разработки ПО. В начале книги, в которой раскрывается эта идея, Бек [Beck 2003] определяет TDD как повторение следующего основного цикла:
Так работаем с самого начала, когда тест добавляется в первоначально пустую базу проекта. Процесс, определенный таким образом, имеет четыре важных следствия.
Первое следствие: тест всегда пишется, прежде чем создается соответствующий программный элемент. Если здесь остановиться, то получим TFD (разработку "вначале тест"), которая является подмножеством TDD, но только подмножеством, поскольку опущены шаги 2, 4 и 5 основного цикла TDD.
Второе следствие: процесс непрерывно возрастает, каждый раз добавляется новый тест, позволяющий проверить новую добавляемую функциональность или ранее не обработанный вариант использования.
Без шага 5 мы пришли бы к чистому, дилетантскому стилю разработки: обработай одно входное значение, обнови соответственно код, добавь следующее и так далее. Мы могли бы, в конечном счете, иметь проект в виде одного огромного "if … then …elseif … else…" с одной ветвью для каждого значения, встречаемого в тестах. TDD умнее, конечно, и шаг 5 является ключом – после каждого добавления кода выполняется рефакторинг. Нельзя быть полностью счастливым, когда проходит тест, поскольку нужно еще позаботиться о качестве полученной архитектуры и, если она недостаточно хороша (недостаточно проста по Беку, например, обрабатывает один случай за другим вместо того, чтобы некоторым образом унифицировать их), следует исправить ее, прежде чем двигаться дальше.
Четвертым следствием является правило, которое мы уже обсуждали при рассмотрении принципов agile. Оно отражено в четвертом шаге основного цикла – не идти дальше, пока все тесты не завершатся успешно. И это второй секрет (наряду с рефакторингом), благодаря которому разработка не превращается в дилетантскую рутинную работу. Если бы требовалось при внесении изменения просто проверить, что проходит тест, подготовленный для этого изменения, то жизнь была бы проста. Но следует убедиться, что внесенные изменения не изменяют корректность работы всей остальной функциональности проекта, не становятся причиной нарушения регрессионных тестов. Весь набор регрессионных тестов должен успешно выполняться на каждом шаге разработки, и этот набор пополняется с каждым новым шагом, он растет вместе с проектом, обеспечивая больше гарантий качества в сравнении с правилом, что все тесты должны всегда проходить.
Формулировка шага 2, на первый взгляд, кажется удивительной: почему мы должны ожидать, что тест не пройдет? Однако это согласуется с TDD как с методом разработки, так как метод запрещает реализовать новую функциональность до того, как для ее тестирования не будет написан тест. Следовательно, новый тест проверяет нечто большее, чем уже реализованная функциональность, поэтому он не должен проходить, пока не будет успешно реализована новая функциональность, для проверки которой он и написан. Наиболее очевидным примером является начало разработки, когда никакой код не написан, мы имеем дело с пустой программой, которая должна падать на любом нетривиальном тесте. Позднее, в принципе, возможно, что вновь созданный тест будет успешно проходить на уже работающей программе, но с позиций TDD это неправильный, неинтересный тест, поскольку он не ломается на некорректно реализованной функциональности.
А кстати, что такое тест? TDD имеет смысл только в сочетании с современной технологией тестирования, обеспечивающей механизм для подготовки многочисленных тестов, каждый описываемый множеством входов и ожидаемых результатов, автоматического выполнения всех этих тестов (регрессионного набора). Инструментарий, получивший общее имя xUnit, разработан, что, впрочем, неудивительно, людьми, предложившими XP. Он будет обсуждаться в главе, посвященной артефактам agile. Там мы рассмотрим, как задавать входные данные для теста, как специфицировать ожидаемые свойства результатов (этот механизм называется оракулом), о форме условий, которые должны выполняться, известных как "утверждения". Инструментарий позволяет затем автоматически запускать сотни тысяч точно определенных тестов и оценивать полученные результаты.
TFD и TDD внесли важный вклад в современную программную инженерию. Оценку достоинств этого вклада оставим напоследок, а начнем с тех аспектов, которые заслуживают некоторой критики.
Наиболее спорная идея, неявно высказанная в TDD, но лежащая в основе всего подхода, состоит в предположении, что тесты – это все, что нужно для специфицирования программы. Это очень плохая идея. Многое из сказанного ранее по поводу того, что сценарии и пользовательские истории не обладают достаточной общностью спецификации, применимо и здесь. На самом деле даже в большей степени, поскольку тест более специфичен, чем пользовательская история. В ранее упомянутом примере тесты, которые для начального отрезка целых вырабатывают значения 0, 1, 4, 9, 25, не обладают той общностью абстракции, которую несет задание функции $$f(n) = n2$$.
Действительно, с ростом набора тестов все меньше вероятность того, что некоторый вариант данных будет вести себя непредсказуемо и не будет охвачен набором тестов. Но малая вероятность не означает невозможность возникновения варианта. Многие отказы в программных системах связаны именно со специальными случаями, не охваченными тестами. Написание спецификаций означает абстрагирование от частных случаев и поиск общих правил. Следует также отметить, что тесты можно генерировать по спецификациям (в этом направлении проводится масса исследований в программной инженерии), но нет способа генерировать спецификации по тестам.
Возникают вопросы, связанные с еще одним аспектом TDD, правда, по другим причинам: следует ли запрещать двигаться дальше, разрабатывая новую функциональность, пока не пройдут все тесты. Доводы "за" и "против" этого принципа обсуждались в разделе 4.5.3.
На практике немногие организации применяют строгий процесс TDD в форме повторяющейся последовательности шагов, описанной выше. Реальный вклад имеет идея "вначале создай тест", а фактически более общая идея – "каждый новый код должен сопровождаться новыми тестами". Не так уж критично, что раньше – код или тест, никогда не создавайте одно без другого.
Эта идея получила широкое распространение, она должна приниматься как универсальная идея. В этом один из главных вкладов agile методов.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.