Проектирования больше нет?
Мартин Фаулер
Тем, кто успел кратко познакомиться с принципами Extreme Programming (ХР), порой кажется, что в этой методологии нет места процессу проектирования программных продуктов. При этом высмеиваются не только "Большое и Подробное Предварительное Проектирование", но и такие техники как UML и гибкие каркасы приложений. Даже значение паттернов либо принижается, либо напрочь отрицается. На самом же деле, в ХР много проектирования, но подается оно по-другому, нежели в обычных устоявшихся процессах разработки ПО. Методология XP оживила эволюционное проектирование новыми техниками, благодаря которым его теперь можно считать вполне жизнеспособной стратегией. Кроме того, в ХР перед проектировщиком ставятся новые трудные задачи, требующие немалого мастерства. Во-первых, это необходимость проектировать максимально простым образом, во-вторых, рефакторинг, и наконец, использование паттернов в эволюционном стиле. Методология Extreme Programming (XP) бросила вызов многим устоявшимся представлениям о разработке программного обеспечения. Пожалуй, наиболее противоречивой идеей является отказ от предварительного проектирования в пользу более эволюционного подхода. Для тех, кто всячески чернит ХР, это возврат к разработкам типа "code and fix" ("пишем и правим"). Для приверженцев новой методологии, это отказ от техник проектирования (например, UML), их принципов и паттернов. Незачем беспокоиться о проектировании, считают они. Достаточно внимательно "вслушиваться" в свой код, и проектирование образуется само собой. Что касается меня, то я нахожусь непосредственно в эпицентре этих споров. Большая часть моей карьеры была посвящена графическим языкам моделирования - UML (Унифицированный язык моделирования) и его предшественникам, а также паттернам. Более того, я писал книги про UML и про паттерны. Раз я теперь принимаю ХР, не значит ли это, что я отрекаюсь от всего, что писал до сих пор? Не буду испытывать ваше терпение, и заставлять вас ждать, затаив дыхание, ответа на этот драматический вопрос. Краткий ответ - нет. Подробный ответ содержится в оставшейся части этой статьи. Проектирование предварительное и проектирование эволюционное В этой статье я опишу два стиля проектирования, принятых в разработке программного обеспечения. Наверное, наиболее привычным является эволюционное проектирование. Определение "эволюционный" указывает на то, что во время реализации системы ее дизайн растет вместе с ней. Проектирование, в этом случае, является частью процесса программирования, поэтому в ходе развития системы дизайн меняется. В большинстве случаев, эволюционное проектирование - это нечто ужасное. В конце концов, все равно вместо дизайна системы вы получаете просто набор из специфических решений, каждое из которых затрудняет дальнейшие изменения в программном коде. Часто это вообще нельзя считать дизайном (и, уж конечно, такой дизайн никак нельзя назвать хорошим). Как говорит Кент, дизайн существует для того, чтобы дать возможность оперативно вносить в систему любые изменения. Если дизайн плох, то такая возможность исчезает. В результате вы будете иметь дело с энтропией программного продукта, и со временем и без того плохой дизайн системы станет еще хуже. Теперь вам будет не только сложнее вносить в систему изменения, но и отыскивать и исправлять ошибки, которые начинают множиться с катастрофической быстротой. Все это - кошмар разработок в стиле "code and fix", когда с течением времени исправление ошибок обходится все дороже и дороже. Предварительное проектирование - полная противоположность эволюционному. Оно построено на идеях, заимствованных из другой области инженерной деятельности. Если вам надо построить собачью будку, то вы сами в состоянии сколотить несколько досок, чтобы получить удовлетворительное подобие желаемого. Если же вы решите построить небоскреб, то прежний способ не подойдет - небоскреб рухнет, прежде чем вы соорудите его хотя бы наполовину. Чтобы этого не случилось, вам нужно начинать с чертежей, которые разрабатывают в инжиниринговых компаниях (вроде той, в которой работает моя жена). Проектируя, она вычисляет все необходимые данные, иногда путем математического анализа, но чаще всего - с помощью "Строительных норм и правил". Эти "Нормы" представляют собой правила, по которым и создаются проектные конструкции. Все они основаны на опыте реальных работающих решений (ну, и небольшом количестве математики). После того, как работы по проектированию закончены, инжиниринговая компания передает проектные чертежи другой компании, которая занимается строительством. Приблизительно так же обстоит дело и с предварительным проектированием при разработке ПО. Проектировщики заранее продумывают все основные вопросы. При этом они не пишут программный код, поскольку не создают программный продукт, а только разрабатывают его дизайн. В своей работе они могут использовать такие техники, как UML, что позволяет им абстрагироваться от некоторых подробностей разработок, относящихся непосредственно к программированию. Как только проектный план готов, его можно передавать в другой отдел (или даже в другую компанию), где будут вестись работы по непосредственному созданию системы. Поскольку проектировщики работают на некотором уровне абстракции, им удается избежать принятия ряда тактических решений, ведущих к энтропии программного продукта. Программисты же могут руководствоваться проектным планом и (если они ему следуют) создавать качественно выстроенную систему. Такой подход к разработке ПО не нов - им активно пользуется множество людей, начиная с 70 годов. По многим показателям он гораздо лучше, чем эволюционное проектирование в стиле "code and fix", однако и у него есть существенные недостатки. Один из главных недостатков заключается в том, что невозможно заранее продумать все вопросы, с которыми придется столкнуться во время кодирования системы. Таким образом, в ходе работ непременно возникнет ситуация, когда у программистов появятся вопросы относительно спроектированного дизайна. А что, если проектировщики, закончив свою часть работы, уже переключились на другой проект? Тогда программисты начинают самостоятельно решать сложившуюся проблему, отступая от уже принятых проектных решений, и внося при этом в программный продукт долю энтропии. И даже если проектировщик еще работает над проектом и может помочь, все равно ему потребуется довольно много времени, чтобы выяснить ситуацию, внести изменения в диаграммы и уже затем менять код. А при разработке, как правило, вопрос времени всегда стоит остро. Отсюда энтропия (опять-таки). Кроме того, существует еще и проблема культур. Проектировщиками становятся благодаря высокому мастерству и большому опыту в программировании. Однако, став проектировщиком, программист настолько поглощается новой работой, что просто не имеет физической возможности заниматься написанием программного кода. При этом инструментарий и материалы программных разработок постоянно меняются. А когда вы перестаете сами писать код, вы не только теряете возможность отслеживать новшества в этой области. Вы теряете уважение тех, кто продолжает заниматься написанием программного кода. Противостояние между разработчиками и проектировщиками мы находим и в области строительства, однако там оно выражено не так ярко, как в сфере программирования. И это не случайно. В области строительства разница в навыках тех, кто проектирует и тех, кто строит, достаточно очевидна. В программировании это не так. Любой программист, работающий с дизайном высокого уровня, должен быть очень хорошим специалистом (достаточно хорошим для того, чтобы задать проектировщику нужные вопросы относительно проектных решений, которые тот предлагает, особенно в тех случаях, когда проектировщик не так хорошо осведомлен об особенностях платформы, для которой ведется разработка системы). Однако такие проблемы все же можно как-то урегулировать. Может быть, можно что-то сделать с напряженностью в отношениях между людьми. Может быть, можно найти таких проектировщиков, которые могли бы разбираться в большинстве вопросов, и такой дисциплинированный процесс разработки, который позволял бы вносить изменения в диаграммы. Однако остается еще одна проблема: изменяющиеся требования. Именно изменяющиеся требования являются проблемой номер один и главной головной болью во всех проектах, над которыми я работал. Бороться с изменяющимися требованиями можно по-разному. Один из возможных путей - делать дизайн достаточно гибким, чтобы при изменениях в требованиях его можно было легко менять. Однако для этого требуется заранее знать, какого типа изменения следует ожидать. Да, при проектировании системы можно попытаться угадать те области, в которых наиболее вероятны изменения, и учесть их в дизайне. В этом случае вы, действительно, облегчите себе работу с ожидаемыми изменениями в требованиях, но ничуть не облегчите (а возможно, только ухудшите) ситуацию с изменениями неожиданными. Кроме того, чтобы заранее определить те области, в которых наиболее вероятны изменения, вы должны прекрасно понимать требования, что по моим наблюдениям, очень непросто. Впрочем, не все проблемы с изменениями в требованиях возникают из-за их непонимания. Множество людей напряженно работает над разработкой технических требований к системе в надежде, что это убережет их от дальнейших поправок при проектировании. Но и так вы далеко не всегда сможете решить проблему. Многие изменения в требованиях диктуются изменениями в экономике и том виде бизнеса, для которого предназначается система. Такие изменения предугадать невозможно, сколько бы вы не сидели над разработкой требований. После всего этого можно подумать, что предварительное проектирование вообще невозможно. Но это не так. Да, есть серьезные трудности. Однако я вовсе не склонен полагать, что предварительное проектирование хуже эволюционного в его обычной манере "code and fix". Сам я предпочитаю предварительное проектирование, но тем не менее, прекрасно представляю себе все сложности, которые с ним связаны и пытаюсь найти новые пути их преодоления. Основополагающие практики ХР В методологии XP имеется много спорных моментов. Одним из ключевых таких моментов является то, что она базируется на эволюционном, а не предварительном проектировании. А как мы уже выяснили, использование эволюционного проектирования не может привести ни к чему хорошему из-за обилия сиюминутных проектировочных решений и энтропии программного продукта. В основе этого утверждения лежит кривая стоимости изменений в программном продукте. Согласно этой кривой, по мере развития проекта стоимость внесения изменений экспоненциально возрастает. Как правило, в ней используются понятия "фазы развития проекта" (как говорится, "изменение, которое на стадии анализа стоит 1 доллар, после поставки системы будет стоить многие тысячи"). В этом есть некая доля иронии, поскольку в большинстве проектов процесс разработки вообще не определен, и там просто нет стадии анализа, а экспоненциальная зависимость остается в силе. Получается, что если экспоненциальная кривая верна, то эволюционное проектирование вообще нельзя использовать в работе. Отсюда же следует, что нельзя делать ошибки в предварительном проектировании - затраты на их исправление будут определяться все той же зависимостью. В основе XP лежит предположение, что эту кривую можно сгладить до такой степени, чтобы можно было применять эволюционное проектирование. Такое сглаживание, с одной стороны, возникает при использовании методологии XP, а с другой, оно же в ней и используется. Это еще раз подчеркивает тесную взаимосвязь между практиками ХР: нельзя использовать те части методологии, которые предполагают существование сглаживания, не используя те практики, которые это сглаживание осуществляют. Именно это является основным предметом споров вокруг XP. Многие начинают критиковать использование, не разобравшись в том, как его нужно достигать с помощью осуществления. Зачастую тому виной собственный опыт критикующего, который упустил в разработках те практики, которые позволяют осуществлять другие. В результате, раз обжегшись, такие люди при упоминании об ХР и на воду дуют. У практик, с помощью которых осуществляется сглаживание, есть множество составляющих. В основе всех их лежит Тестирование и Непрерывная интеграция. Именно надежность кода, которую обеспечивает тестирование, делает возможным все остальное в этой методологии. Непрерывная интеграция необходима для синхронной работы всех разработчиков, так чтобы любой человек мог вносить в систему свои изменения и не беспокоиться об интеграции с остальными членами команды. Взятые вместе, эти две практики могут оказывать существенное влияние на кривую стоимости изменений в программном продукте. Я получил еще одно подтверждение этому в компании "ThoughtWorks". Даже применение всего двух практик, тестирования и непрерывной интеграции, существенно сказалось на результатах. Можно даже усомниться, действительно ли нужно (как это принято считать в ХР) следовать всем практикам, чтобы получить заметный результат. Подобный эффект имеет и рефакторинг. Те, кто делают рефакторинг в той строгой манере, что принята в ХР, отмечают значительное повышение его эффективности по сравнению с более бессистемной реструктуризацией. Именно это я и ощутил, когда Кент наконец-то научил меня, как правильно делать рефакторинг. В конце концов, это так меня впечатлило, что я даже написал об этом целую книгу. Джим Хайсмит (Jim Highsmith) в своем великолепном изложении методологии XP использует аналогию с обычными весами. На одной чаше лежит предварительное проектирование, на другой - рефакторинг. В более традиционных подходах к разработке ПО перевешивает предварительное проектирование, так как предполагается, что передумать вы не можете. Если же стоимость изменений снизится, то часть проектирования можно делать и на более поздних стадиях работы, в виде рефакторинга. Это вовсе не означает отказа от предварительного проектирования. Однако теперь можно говорить о существовании баланса между двумя подходами к проектированию, из которых можно выбрать наиболее подходящий. Что касается меня, то иногда мне кажется, что до знакомства с рефакторингом я работал, как однорукий калека. Все эти основополагающие практики (непрерывная интеграция, тестирование и рефакторинг) создают новую среду, в которой эволюционное проектирование выглядит вполне убедительно. Впрочем, мы еще не выяснили, где же у этих весов точка равновесия. Лично я уверен, что не смотря на поверхностное впечатление, методология ХР - это не просто тестирование, кодирование и рефакторинг. В ней найдется место и для предварительного проектирования. Частично оно производится еще до написания первой строчки кода, но большая его часть приходится на то время, которое предшествует реализации конкретной задачи в течение итерации. Впрочем, такая ситуация представляет собой новое соотношение сил между предварительным проектированием и рефакторингом. Преимущества простого дизайна В ХР очень популярны два лозунга: "Do the Simplest Thing that Could Possibly Work" ("Ищите самое простое решение, которое может сработать") и YAGNI ("You Aren't Going to Need It" - "Это вам не понадобится"). Оба они олицетворяют собой одну из практик ХР под названием Простой дизайн. По принципу YAGNI, вы не должны заниматься написанием кода сегодня, если он понадобится для того свойства программы, которое вы будете реализовывать только завтра. На первый взгляд в этом нет ничего сложного. Сложности начинаются, когда речь заходит о таких вещах, как программные каркасы для создания приложений, компоненты для повторного использования и гибкий дизайн. Надо сказать, что спроектировать их довольно сложно. Вы заранее добавляете к общей стоимости работ стоимость и такого проектирования и рассчитываете впоследствии вернуть эти деньги. При этом наличие заблаговременно встроенной в систему гибкости считается признаком хорошего проектирования. Тем не менее, ХР не советует заниматься созданием гибких компонентов и каркасов до того, как понадобится именно эта функциональность. Лучше, если эти структуры будут наращиваться по мере необходимости. Если сегодня мне нужен класс Money, который обрабатывает сложение, а не умножение, то сегодня я буду встраивать в этот класс только сложение. Даже если я абсолютно уверен, что умножение понадобится мне уже в следующей итерации, и я знаю как очень просто и быстро это сделать сейчас, все равно я должен оставить это на следующую итерацию - когда в нем появится реальная необходимость. Такое поведение оправдано с экономической точки зрения. Занимаясь работой, которая понадобится только завтра, я тем самым расходую силы и время, предназначенные для задач, которые должны были быть сделаны сегодня. План выпуска программы четко указывает, над чем мне нужно работать в настоящий момент. Если я отклоняюсь от него, чтобы поработать над тем, что понадобится в будущем, я нарушаю свое соглашение с заказчиком. Кроме того, появляется риск не успеть сделать все, записанное в требованиях для текущей итерации. И даже в том случае, если такой опасности нет, и у вас появилось свободное время, то решать чем вам заняться - прерогатива заказчика, который может попросить заняться вовсе не умножением. Таким образом, возможные препятствия экономического характера осложняются еще и тем, что мы можем ошибаться. Даже если мы абсолютно уверены в том, как работает эта функция, мы все равно можем ошибиться, особенно если у нас еще нет подробных требований заказчика. А чем раньше мы используем в работе над проектом ошибочные решения, тем хуже. Приверженцы методологии ХР считают, что в такой ситуации гораздо легче принять неправильное решение (и я полностью с ними согласен). Другая причина, по которой простой дизайн лучше сложного, это отказ от принципа "блуждающего огонька". Сложную конструкцию гораздо труднее понять, чем простую. Именно поэтому любая модификация системы делает ее все более сложной. Это, опять-таки, ведет к увеличению стоимость работ в период между тем временем, когда дизайн системы стал более сложным, и временем, когда это действительно стало необходимо. Такой стиль работы многим кажется абсурдным, и надо сказать, что они правы. Правы при одном условии - абсурд получится, если эту практику начать применять в обычном процессе разработки, а все остальные практики ХР игнорировать. Если же изменить существующий баланс между эволюционным и предварительным проектированием, то YAGNI становится очень полезным принципом (тогда и только тогда). Подведем итог. Вам не нужно расходовать силы на то, чтобы внести в систему новую функциональность, если она не понадобится до следующей итерации. Даже если это практически ничего не стоит, вам не нужно это делать, так как это увеличит общую стоимость модификации. Однако для того, чтобы осознанно применять такой принцип на деле, вам нужно использовать ХР или другую подобную методологию, которая снижает стоимость изменений. "Простой дизайн" - что же это за зверь такой? Итак, мы хотим, чтобы наш программный код был максимально прост. С этим никто не спорит. В конце концов, кому нужно, чтобы код был сложный и запутанный? Осталось только понять, что мы разумеем под словом "простой". В книге Extreme Programming Explained Кент приводит четыре критерия простой системы. Вот они в порядке убывания важности: Система успешно проходит все тесты Код системы ясно раскрывает все изначальные замыслы В ней отсутствует дублирование кода Используется минимально возможное количество классов и методов Успешное тестирование системы - довольно простой критерий. Отсутствие дублирования кода тоже вполне четкое требование, хотя большинство разработчиков нужно учить, как этого достичь. Самое сложное скрывается в словах "раскрывает изначальные замыслы". Действительно, что же это значит? Основное достоинство программного кода, в данном случае - его ясность. ХР всячески подчеркивает, что хороший код - это код, который можно легко прочесть. Скажите ХР-шнику, что он пишет "заумный код", и будьте уверены, что обругали этого человека. Но понимание замыслов программиста, написавшего код, зависит также и от опыта и ума того, кто этот код пытается прочесть. В своем докладе на конференции XP 2000, Джош Кериевски (Josh Kerievsky) приводит хороший пример на данную тему. Он подвергает анализу, возможно, самый открытый из всех кодов ХР - JUnit. JUnit использует декораторы (паттерн Decorator), для того, чтобы дополнить тестовые сценарии дополнительным поведением, таким как синхронизация доступа и установка начальных предусловий для групп тестов. Так как подобное поведение реализуется в отдельных классах-декораторах, код тестов становится проще, чем если бы эта функциональность присутствовала в них самих. Однако в данном случае нужно задать себе вопрос: а понятнее ли будет код, полученный в результате этих операций? С моей точки зрения да, но я-то знаком с паттерном Decorator. Для тех, кто не имеет о нем ясного представления, этот код может показаться довольно сложным. Аналогично этому, в JUnit используются методы-вставки (pluggable methods), которые, по моим наблюдениям, большинство программистов расценивают как что угодно, но только не как простое и понятное решение. Получается, что структура JUnit является простой для опытных проектировщиков, но сложной для менее опытных программистов? Я думаю, что одним из самых очевидных и полезных советов, которые только можно дать, это избегать повторов в коде, как провозглашается в ХР ("Once and Only Once") и в книге Pragmatic Programmer's (принцип DRY - Don't Repeat Yourself). Следуйте этому принципу, вы уйдете далеко вперед. Но это далеко не все, что необходимо для простого дизайна. А создать простой дизайн - это весьма сложная задача. Недавно мне пришлось работать над системой с весьма заумным дизайном. После проведенного мной рефакторинга дизайн лишился некоторой гибкости (за ненадобностью). Однако, как заметил один из разработчиков, "гораздо проще делать рефакторинг системы со сложным дизайном, чем рефакторинг системы, у которой дизайна вообще нет". Лучше всего быть немного проще, чем требуется, но нет ничего ужасного в том, чтобы быть немного сложнее. А самый лучший совет, который я слышал по этому поводу, исходил из уст "Дядюшки Боба" (Роберта Мартина). Заключается он в следующем: не стоит сушить голову над вопросом, как сделать дизайн максимально простым. В конце концов, позже вы сможете (и должны, и будете) заняться рефакторингом. В конце работы над проектом желание делать рефакторинг гораздо важнее, чем точное понимание того, какое решение является самым простым. Нарушает ли рефакторинг принцип YAGNI? Эта тема сравнительно недавно всплыла в списке рассылки, посвященном XP, и коль скоро мы заговорили о роли проектирования, нам стоит ее обсудить. Дело в том, что процесс рефакторинга требует времени, но не добавляет новой функциональности. С другой стороны, принцип YAGNI гласит, что надо проектировать только для текущей функциональности, а не для того, что понадобится в будущем. Не сталкиваемся ли мы здесь с противоречием? Принцип YAGNI состоит в том, чтобы не делать систему более сложной, чем того требует реализация текущих задач. Это является частью практики "Простой дизайн". Рефакторинг же необходим для поддержания системы в максимально простом состоянии. Его нужно проводить сразу же, как только вы обнаружите, что можете что-либо упростить. Простой дизайн одновременно задействует практики ХР и сам по себе является основополагающей практикой. Только при условии тестирования, непрерывной интеграции и рефакторинга, можно говорить об эффективном использовании простого дизайна. Но в то же время, простой дизайн абсолютно необходим для сглаживания кривой стоимости изменений. Любая излишне сложная конструкция затруднит внесение изменений в систему по всем направлениям, за исключением того из них, ради которого эта сложность в нее вносилась. Однако редко удается предсказать такое направление, поэтому лучше будет стремиться к простым решениям. И в тоже время, мало кому удается сделать все максимально просто с первого раза, так что вам придется заниматься рефакторингом, чтобы приблизиться к цели. Паттерны и ХР Пример JUnit постоянно наводит меня на размышление о паттернах. Вообще, отношение, существующее между ХР и паттернами, довольно интересно и часто обсуждается. Так, Джошуа Кериевски считает, что ХР отводит паттернам недопустимо маленькую роль. Его аргументация настолько красноречива, что я воздержусь от пересказа. Однако хочу заметить: многим кажется, что использование паттернов противоречит принципам ХР. Суть в том, что часто паттерны используются чересчур активно. Известна история о программисте, который, прочитав в первый раз книгу Банды Четырех(издана на русском языке в издательстве "Питер" под названием "Паттерны проектирования" - прим. переводчиков), ухитрился использовать 16 паттернов в 32 строчках кода. Помню замечательный вечер, подогретый всего-навсего одним стаканчиком солода, когда мы с Кентом набрасывали статью под названием "Не паттерны проектирования: 23 дешевых трюка", где рассказали о таких вещах, как использование оператора "if" вместо паттерна "стратегия". В каждой шутке есть доля правды. Паттерны нередко используются там, где без них вполне можно было бы обойтись, однако это не делает хуже саму идею. Весь вопрос в том, как вы их используете. Согласно одной из существующих теорий, стремясь к простому дизайну, вы придете именно к паттернам. Для некоторых видов рефакторинга это происходит совершенно явно, однако и без рефакторинга, принимая простые проектные решения, вы начинаете использовать паттерны, даже если до этого вы ничего о них не знали. Может быть, это и так, но так уж ли хорош этот путь? Конечно же, лучше заранее представлять себе, с чем вы столкнетесь, и иметь при себе книгу, чтобы не изобретать все самому. Каждый раз, когда я чувствую, что на подходе ситуация, когда можно использовать паттерн, я достаю с полки книгу Банды Четырех. Для меня само словосочетание "эффективный дизайн" свидетельствует о том, что использование паттерна оправдано. В конце концов, назначение паттернов состоит как раз в облегчении создания простого дизайна системы. Точно так же и Джошуа предлагает уделять больше внимания вопросу, как можно упростить постепенный переход к использованию паттернов. С этой точки зрения, паттерны в ХР используются несколько непривычным образом, однако это совершенно не означает, что при этом их значение как-то принижается. Читая некоторые списки рассылки, я прихожу к выводу, что многие вообще видят в ХР некое отрицание паттернов. И это притом, что большинство создателей этой методологии были, в свое время, в числе лидеров движения за использование паттернов! Не знаю, как для всех остальных, но для меня паттерны до сих пор совершенно необходимы. Методология ХР может служить процессом разработки, но паттерны - это основа искусства проектирования, искусства, без которого не обойтись, каким бы процессом вы не пользовались. Опять-таки, различные процессы могут использовать паттерны по-разному. Так, в ХР считается, что не нужно использовать паттерн до тех пор, пока в нем действительно не окажется необходимости, а также что нужно приходить к использованию паттерна постепенно, путем упрощения реализации. Тем не менее, знание паттернов было и остается совершенно необходимым. Мои советы тем, кто работает по методологии ХР и использует паттерны: Не бойтесь потратить время на изучение паттернов Хорошо подумайте, когда лучше всего применить паттерн (не слишком рано) Хорошо подумайте, как лучше всего реализовать паттерн в его наипростейшей форме, а уже потом вносите дополнения Если вы применили паттерн, а потом поняли, что без него было бы лучше - убирайте, не сомневайтесь. Мне кажется, что в методологию ХР стоило бы включить отдельным пунктом изучение паттернов. Боюсь, что мне не под силу придумать, как это можно внести в практики ХР, но у Кента это наверняка получится. Наращивание архитектуры Что мы называем архитектурой программного продукта? С моей точки зрения, термин "архитектура" передает идею основных элементов системы, тех ее частей, которые трудно изменить. Они являются фундаментом, на котором можно построить все остальное. Какую роль играет архитектура в эволюционном проектировании? Критики ХР считают, что эта методология вообще не признает работы над архитектурой, что вся суть ХР - сразу садиться за написание кода, и уповать на то, что рефакторинг решит все проблемы с проектированием. Смешно сказать, но они правы, и, может быть, в этом заключается некоторая слабость ХР. Всем известно, что самые агрессивные приверженцы ХР - Кент Бек (Kent Beck), Рон Джеффриз (Ron Jeffries) и Боб Мартин (Bob Martin) - прикладывают очень много сил, чтобы вообще избежать любого предварительного проектирования архитектуры. Не добавляйте в систему базу данных, пока она вам действительно не понадобилась. Работайте поначалу с файлами, а база данных появится в следующей итерации, в результате рефакторинга. Я заслужил репутацию трусливого ХР-шника, и в этом качестве вынужден не согласиться с подходом моих более смелых коллег. Мне кажется, что лучше начать с общего наброска архитектуры системы - подумать о том, как разделить приложение на уровни, как построить взаимодействие с базой данных (если она вам понадобится), какой подход применить к управлению веб-сервером. По сути своей, многое из таких ранних архитектурных построений - просто паттерны, которые приходят к нам с опытом. С течением времени количество освоенных паттернов растет, и у вас должно появиться свое разумно обоснованное мнение на то, как их использовать. Однако ключевое отличие здесь будет в том, что ранние архитектурные решения вовсе не высечены в камне, и что в случае ошибки, команде не нужно будет собирать всю свою смелость, чтобы ее исправить. Кто-то рассказал мне, как в одном проекте, уже перед самой поставкой, решили, что им больше не нужен EJB. Ну, и убрали его из системы. Конечно, это потребовало порядочного рефакторинга, но использование практик ХР позволило не только сделать такую операцию осуществимой, это сделало ее целесообразной. Интересно, а если перевернуть все наоборот? Если бы вы вначале решили не использовать EJB, не было бы сложнее в последний момент внести его в систему? Действительно ли не стоит начинать делать систему с EJB, пока вы окончательно не убедитесь, что без этого не обойтись? Этот вопрос затрагивает сразу несколько факторов. Разумеется, работать без такого сложного компонента легче и быстрее. Однако иногда легче что-то выбросить из системы, чем вставить. Итак, я бы все же посоветовал начать работу с приблизительной оценки архитектуры системы. Если вы видите большое количество данных и множество различных пользователей, смело включайте в архитектуру базу данных. Если вы должны работать со сложной бизнес-логикой, используйте модель предметной области. Однако не забывайте об уважении к богам YAGNI, и в сомнительных случаях отдавайте предпочтение более простым решениям. Кроме того, всегда будьте готовы выбросить кусок архитектуры, если видите, что он не приносит ничего полезного. UML и XP Мне задают довольно много вопросов относительно моей приверженности методологии ХР, причем чаще всего люди интересуются - как я могу сочетать ее с верностью UML. Разве они не исключают друг друга? Да, в ХР и UML есть несколько взаимоисключающих аспектов. Так, в ХР существенно снижается значение диаграмм. Не смотря на то, что официальная позиция ХР по этому поводу гласит: "используйте их, если это помогает вам в работе", но существует и неофициальный подтекст: "настоящий ХР-шник не рисует диаграмм". Это подчеркивается еще и тем фактом, что таким людям, как Кент, неудобно использовать диаграммы. Я, например, никогда не видел, чтобы Кент по своей воле рисовал диаграмму программного продукта, неважно даже на языке UML, или каком другом. Я думаю, что такая ситуация возникает по другим причинам. Во-первых, одни люди считают, что диаграммы полезны, а другие придерживаются противоположного мнения. Фокус в том, что те, которые так считают, полагают, что те, которые так не считают, должны изменить свое мнение, и наоборот. Вместо этого, мы должны просто принять тот факт, что одни люди будут использовать в работе диаграммы, а другие не будут. Во-вторых, диаграммы программных продуктов обычно ассоциируются с тяжелыми процессами разработки. В таких процессах большая часть времени уходит на построение диаграмм, что не очень помогает в работе, а иногда даже вредит. Именно поэтому я считаю, что людей нужно учить, как использовать диаграммы правильно и избегать различных ловушек, а не просто говорить "рисуй диаграмму, если тебе без нее совсем не обойтись, бедняга", как это обычно делают ярые ХР-шники. Вот мои советы тем, кто хочет правильно использовать диаграммы: Во-первых, пока рисуете диаграмму, не забывайте, для чего вы это делаете. Основное ее достоинство - коммуникация с людьми. Чтобы коммуникация была эффективной, нужно отображать на диаграмме только важные аспекты, не обращая внимания на все второстепенные. Такая избирательность - основа правильной работы с UML. Не надо отображать на диаграмме каждый класс - только самые важные. У классов не нужно задавать каждый атрибут или операцию - только самые важные. Не надо рисовать диаграммы последовательности для всех вариантов использования и сценариев - ну, и так далее. Самая распространенная проблема с использованием диаграмм это то, что их пытаются сделать максимально всеобъемлющими. Однако самый лучший источник всеобъемлющей информации - это программный код, так как именно его легче всего синхронизировать с кодом. Для диаграммы же всеобъемлимость - враг удобопонятности. Чаще всего диаграммы используются для того, чтобы проанализировать проектные решения еще до написания кода. Нередко при этом возникает чувство, что в ХР этого делать нельзя. Это совсем не так. Многие полагают, что перед разработкой сложной задачи стоит ненадолго собраться всей командой для ее предварительного проектирования. Тем не менее, когда проводите такие собрания, не забывайте, что: они должны быть действительно недолгими не нужно обсуждать все подробности (только самое важное) относитесь к полученному в результате проектному решению как к наброску, а не как к конечной версии, неподверженной изменениям Последний пункт стоит раскрыть подробнее. Когда вы занимаетесь предварительным проектированием, вы неизбежно обнаруживаете, что некоторые ваши решения неправильны. Причем обнаруживается это уже при кодировании. Разумеется, это не проблема, если вы после этого вносите соответствующие изменения. Проблемы начинаются тогда, когда вы полагаете, что с проектированием покончено, и не учитываете полученные сведения, сохраняя неверный дизайн. Изменения в дизайне вовсе необязательно подразумевает изменения в диаграммах. Абсолютно разумным будет просто-напросто выбросить диаграмму, после того, как она помогла вам найти нужное решение. Нарисовав диаграмму, вы решили стоявшую перед вами проблему, и этого совершенно достаточно. Диаграмма и не должна существовать как некий постоянный артефакт. Надо сказать, что лучшие UML-диаграммы такими артефактами как раз не являются. Многие ХР-шники используют CRC-карточки. Это не противоречит UML. Лично я все время задействую некую смесь из CRC и UML, и вообще, пользуюсь любыми техниками, которые облегчают мне выполнение текущей задачи. Кроме того, UML-диаграммы используются в качестве документации по проекту. Как правило, в своей обычной форме это модель, редактируемая при помощи некоторого CASE-инструмента. Идея здесь состоит в том, что ведение такой документации облегчает работу. На самом деле, чаще всего она вообще не нужна, поскольку: нужно постоянно тратить массу времени, чтобы не дать диаграммам устареть, в противном случае, они не будут соответствовать программному коду диаграммы находятся внутри сложного CASE-средства либо в толстенной папке, и никто туда не заглядывает Итак, если вы хотите иметь текущую документацию по проекту, учитывайте все вышеперечисленные проблемы: Используйте только те диаграммы, которые вы можете поддерживать без особых усилий Помещайте диаграммы туда, где их все видят. Я предпочитаю пришпиливать их на стену. Пусть остальные рисуют на ней ручкой все простые изменения, которые были внесены в изначальный вариант. Посмотрите, обращают ли ваши разработчики на диаграммы хоть какое-то внимание, и если нет, выбросите их. И, наконец, последний аспект использования UML для документации - передача проекта в другие руки (например, от одной группы разработчиков другой). Согласно методологии ХР, создание документации - такая же задача, как и все остальные, а значит, ее приоритет должен быть определен заказчиком. В этой ситуации может пригодиться UML, разумеется, при условии избирательности диаграмм, которые создавались с целью облегчения коммуникации. Помните, что программный код - это основной репозиторий подробной информации, а диаграммы служат для обобщенного представления основных аспектов системы. О метафоре Ну вот, теперь я могу признаться публично - я до сих пор не могу понять, что же это за штука такая, эта метафора. Я видел, как она работает (в проекте С3 она сработала просто великолепно), однако это вовсе не означает, что я понимаю, как это сделать, не говоря уже о том, чтобы объяснять это другим. Метафора - одна из практик ХР. Она строится на системе имен, подходе, разработанном Уордом Каннингэмом (Ward Cunningham). Суть этого подхода в том, что вы определяете некий набор имен, которые служат словарем для описания предметной области проекта. В дальнейшем система имен из этого набора служит для именования классов и методов системы. Что касается меня, то я строю систему имен с помощью концептуальной модели предметной области. Я делаю это вместе со специалистами в этой области, и использую UML (а раньше - его предшественников). Весь мой опыт показывает, что такая работа должна вестись очень осторожно. Вы должны использовать минимальный набор понятий и не допускать, чтобы в их числе в модель прокрались сложные технические термины. Если вам удалось это сделать, то у вас в руках находится основа для построения словаря предметной области, который будет понятен как специалистам, так и разработчикам системы, и с помощью которого те и другие будут общаться друг с другом. Конечно, эта модель не совсем совпадает с классами, которые появятся при проектировании, однако ее вполне достаточно для создания терминологической базы. По правде говоря, я не вижу причин, по которым такой словарь нельзя было бы сделать метафорическим, как в проекте C3, где построение платежной ведомости представили в виде фабричной линии сборки. С другой стороны, я не понимаю, почему нельзя определять систему имен, исходя из словаря предметной области, и уж тем более я не собираюсь отказываться от своего способа определять систему имен, который меня вполне устраивает. Часто критики ХР указывают на то, что для работы над проектом необходима хотя бы приблизительная архитектура системы. На что приверженцы этой методологии отвечают: "Так это и есть метафора". На самом деле, я до сих пор не слышал, чтобы кто-нибудь внятно объяснил, что же это такое, метафора. Мне кажется, что такой пробел все же нужно как-то ликвидировать. Малыш, хочешь быть архитектором, когда вырастешь? За последнее десять лет стало очень популярно словосочетание "архитектор программного обеспечения". Лично мне такой термин использовать очень трудно. Дело в том, что моя жена - инженер-строитель. А отношения между архитекторами и инженерами-строителями несколько.... натянуты. Инженеры полагают, что архитекторам ничего не стоит нарисовать целый ворох симпатичных рисуночков, а потом инженеры должны в поте лица вычислять, сможет такая симпатичная конструкция держаться вертикально, или сразу развалится. Именно поэтому я всегда старался избегать термина "архитектор". Сами судите, если моя собственная жена не станет относиться ко мне с профессиональным уважением, то чего мне ждать от остальных? В области программирования термин "архитектор" имеет множество значений. (Впрочем, в области программирования любой термин имеет множество значений.) Однако, как правило, здесь присутствует некий подтекст: "Я уже не просто какой-то там программист, я - архитектор", что можно понять как "Теперь я архитектор, и мне не пристало заниматься программированием". Вопрос только в том, действительно ли нужно отказываться от тривиального программирования, если собираешься стать техническим лидером. Такой вопрос обычно вызывает бурю эмоций. Сколько раз я видел, как люди не на шутку сердились при мысли, что как архитекторы они уже не играют столь важной роли. "В ХР нет места опытным архитекторам!" - такой вопль я слышу довольно часто. Все это напоминает положение дел с проектированием в целом. Я не думаю, что при использовании методологии ХР исчезает необходимость в умении хорошо проектировать. Более того, именно благодаря основоположникам ХР - Кенту Беку, Бобу Мартину, и конечно же, Уорду Каннингэму - я, в свое время, узнал, что такое настоящее проектирование. Однако сейчас многим может показаться, что эти люди перестали играть роль технических лидеров (в общепринятом понимании). Для примера можно взять одного из технических лидеров компании "ThoughtWorks", по имени Дэйв Райс (Dave Rice). Дэйв уже давно работает в компании и заслужил неофициальную мантию технического лидера в проекте, над которым работает около пятидесяти человек. Что же означает его лидерство? В первую очередь, то, что он проводит большую часть времени с программистами. Он помогает тем, кому это необходимо, и старается быть рядом на случай, если его помощь понадобится. Интересно отметить, где располагается его рабочее место. Как заслуженный сотрудник компании, он мог бы выбрать любое помещение по своему желанию. Какое-то время назад он сидел в офисе с Кара (Cara), который отвечал за выпуск системы. Тем не менее, вот уже несколько месяцев, как Дэйв перебрался в комнату к программистам (в том самом стиле "war room", который превозносит ХР). Для него очень важно находиться вместе с разработчиками, потому что в этом случае он знает, что происходит, и всегда может протянуть руку помощи тем, кто находится в затруднении. Те, кто уже знаком с методологией ХР, поймут, что я описал имеющееся там понятие "тренера" ("coach"). Это еще один пример игры словами, которую так любят в ХР. Технический лидер команды называется "тренером". Подтекст понятен каждому: в ХР лидер учит программистов и помогает им принимать решения. Чтобы быть тренером, нужны не только отличные технические знания, но и хорошие качества человеческой натуры. Как заметил Джек Боллс (Jack Bolles) на конференции ХР 2000, для знатоков-одиночек скоро вообще не останется места. Ключ к успеху - обучение и взаимопомощь. Позже, на обеде, который состоялся после конференции, Дэйв и я разговаривали с человеком, который критиковал ХР. Мы начали обсуждать все более подробно, и к удивлению, выяснили, насколько похожи наши подходы. Все мы предпочитали адаптивные, итеративные разработки. Все соглашались с важностью тестирования. Мы с Дэйвом никак не могли понять, откуда же тогда такое неприятие ХР? И тут наш оппонент сказал замечательную фразу: "последнее, что я допущу у себя на работе, это чтобы мои программисты делали рефакторинг и совали нос в проектирование". После этого все стало ясно. Итог нашим концептуальным разногласиям подвел Дэйв, когда сказал мне напоследок: "Интересно, зачем брать на работу программистов, если ты им не доверяешь?" В ХР самое ценное, что может сделать опытный разработчик - это передать свои знания младшим коллегам. Вместо архитектора, который один уполномочен принимать все важные решения, у вас есть тренер, который учит, как нужно принимать такие решения. Как говорит Уорд Каннингэм, таким образом опытный разработчик распространяет свои знания, и приносит проекту гораздо больше пользы, чем любой герой-одиночка. Задачи, плохо поддающиеся рефакторингу Можно ли вносить в систему все проектные решения путем рефакторинга, или же все-таки существуют некоторые вещи, которые настолько всепроникающи, что добавить их впоследствии будет очень и очень сложно? В настоящий момент ортодоксы ХР утверждают, что все можно добавить позже, а именно тогда, когда оно понадобится. Поэтому принцип YAGNI может быть применен в любой ситуации. Я все же, сомневаюсь, нет ли здесь исключений из правила. Возьмем, к примеру, поддержку нескольких языков (локализацию). Может быть, если работать над ней с самого начала, можно избежать мучений по поводу того, как ее внести в систему на более поздних стадиях разработки? Я могу легко назвать еще несколько вещей, которые попадут в ту же категорию. Однако в действительности у нас еще очень мало информации. Если вам нужно внести в систему что-то новое (например, локализацию), то непосредственно перед добавлением вы сможете адекватно оценить, каких усилий это потребует. Вы не сможете оценить это, если будете вкладывать усилия в локализацию на итерациях, предшествующих той, в которой она понадобится. Вы также не сможете адекватно оценить, какие усилия вам понадобятся, чтобы исправить ошибку, которую вы вполне могли допустить в начале работы (в таком случае, вам опять-таки понадобится рефакторинг). Принцип YAGNI частично оправдывается тем, что очень многие из таких потенциально необходимых задач, в конце концов, оказываются вовсе не так уж необходимы. По крайней мере, не в том виде, в котором вы ожидали. Вам потребуется гораздо меньше усилий на то, чтобы не выполнять пока такие задачи, чем на исправление ошибочных решений и рефакторинг. Кроме того, всегда стоит учитывать знание проблемы. Если вам уже приходилось несколько раз заниматься локализацией, то вы наверняка будете применять в работе некие паттерны, а значит, у вас больше шансов сделать все правильно с самого начала. Если вы опытный специалист, то, наверное, будет лучше разработать некие предварительные структуры (чего никак нельзя порекомендовать новичкам). Я бы сказал, что те, кто знает, как это делается, могут сами судить о затратах, которые нужны для выполнения такой задачи. Однако, если вы никогда раньше не занимались такими проблемами, вы не только не можете верно оценить задачу, но и скорее всего, будете допускать ошибки в ее решении. В таком случае, лучше будет внести нужные дополнения в систему позже. Если же вы так и поступили, а теперь испытываете массу трудностей, то поверьте, вам было бы куда тяжелее, начни вы работать над этой задачей с самого начала. Теперь ваша команда уже более опытна, вы лучше понимаете предметную область и требования к системе. Часто, оглядываясь назад, вам будет казаться, что раньше это было бы сделать намного проще. Смею вас уверить, все могло быть гораздо сложнее, если бы начали работать над этим в начале проекта. Эта проблема связана с вопросом порядка выполнения требований пользователя (user stories). В книге Planning XP мы с Кентом ясно обозначили наши разногласия. Кент считает, что единственным фактором, определяющим порядок работ над задачами, должна быть их важность для заказчика. Теперь на такую же точке зрения встал и Рон Джеффриз, который раньше придерживался другого мнения. Я, все же, не могу с ними согласиться. Мне кажется, что должен существовать некий баланс между важностью задачи и техническим риском. Так, если использовать наш пример, то я стал бы заниматься локализацией раньше, чем это потребовалось бы, именно чтобы снизить риск. Впрочем, это было бы оправдано, только если локализация должна была бы присутствовать уже в первом выпуске системы. Выпустить программу как можно раньше - одна из жизненно важных задач. Все, что не нужно в первом выпуске, нужно вносить в систему после него. Впечатление, которое производит на заказчика работающий программный код, просто неописуемо. Первый выпуск программы заставляет его сосредоточиться на проекте, повышает уровень доверия к разработчикам, и кроме того, является мощным источником новых сведений. Делайте все от вас зависящее, чтобы этот день наступил как можно быстрее. Даже если вы знаете, что затратите больше усилий, если внесете новую функциональность позже, все равно лучше будет сделать это после первого выпуска системы. Так что же, проектирования больше нет? Ни в коем случае. Однако изменилась сама суть проектирования. Проектирование в ХР требует от человека следующих качеств: Постоянное желание сохранять программный код простым и понятным насколько это только возможно Навыки рефакторинга, так чтобы вы могли уверенно вносить в систему изменения, как только почувствуете в этом необходимость Хорошее знание паттернов: рассматривать их не просто как готовые решения, а уметь оценивать их своевременность и использовать постепенно, от простого к сложному. Знать, как донести до тех, кому это нужно, решения по конструированию системы (используя для этого программный код, диаграммы и, самое главное, личное общение). Такой вот впечатляющий список требований. Впрочем, стать хорошим проектировщиком всегда было непросто. В данном случае, ХР не облегчает жизнь, по крайней мере, не мне. Однако я полагаю, что методология ХР позволяет нам по-новому взглянуть на проблему эффективности проектирования, потому что именно она снова сделала эволюционное проектирование разумной стратегией программных разработок. А я большой фанат эволюции. Если бы не она, на каком дереве я бы сейчас сидел? Благодарности За последние несколько лет я заимствовал и крал идеи у огромного количества замечательных людей. По большей части их имена исчезли в дебрях моей памяти. Впрочем, я еще помню, что перехватил несколько хороших идей у Джошуа Кериевски. Кроме того, я помню, сколько замечательных замечаний получил от Фреда Джорджа (Fred George) и Рона Джеффриза. Не могу не сказать о том потоке идей, которые беспрестанно исходят из Кента и Уорда. Я всегда испытываю благодарность к людям, которые задают вопросы и находят опечатки. Я поленился заводить длинный список всех тех, кто мне в этом помог, но в этом списке обязательно были бы имена Крэйга Джоунса (Craig Jones), Нигела Торна (Nigel Thorne) и Свена Гортса (Sven Gorts).
Страница сайта http://silicontaiga.ru
Оригинал находится по адресу http://silicontaiga.ru/home.asp?artId=508 |