Когда «agile» (не) к месту
Онтогенез повторяет филогенез
—биогенетический закон
Да, вы не ошиблись, это еще одна из огромного множества статей, посвященных соотношению плановости и гибкости при разработке программного обеспечения. Поэтому эта статья — не более чем авторский пересказ уже не раз высказанных идей. Но, как известно, украденное у одного — плагиат, украденное у троих (и более) — научный труд. Так что знайте: вы читаете научный труд.
Статья состоит из трех частей.
Первая ее часть посвящена классике. Я расскажу об истории развития методологии разработки ПО, какие возникали задачи, какими методами они решались. Вы увидите, что в классике можно решить гораздо больше проблем, чем это пытаются представить фанатики «гибких» разработок.
Вторая часть статьи посвящена «гибким» методикам. В ней я расскажу об историческом контексте возникновении этого течения. Я расскажу об основных его идеях и сравню этот подход с классическим.
Наконец, в ее третьей части я остановлюсь на проблемах «гибких» методик. Я покажу, как по мере взросления продукта в его разработку приходится вносить все больше элементов классики: планирования, документирования…
Особое внимание в этой статье я уделяю способам поддержания качества продукта, включая его безопасность. Поэтому в каждой из трех частей есть раздел, посвященный именно этому вопросу.
Итак, в этой статье:
Зачем безопаснику SDLC
Статья написана для безопасников (хотя, я надеюсь, она будет интересна и более широкому кругу читателей). Однако обсуждаются в ней вопросы организации процесса разработки программного обеспечения. Казалось бы, тема совсем не соответствует аудитории, ведь мы знаем, что подавляющее большинство безопасников разработкой ПО не занимается.
Зачем же им тогда читать эту статью?
Многие из нас (безопасников) так или иначе имеют опыт взаимодействия с разработчиками. Кому-то приходилось писать требования безопасности, кому-то — проверять их выполнение, делать сертификацию, кто-то еще столкнется с этими задачами. Иногда бывает полезно понимать возможности компаний, предлагающих нам свои продукты, просто для того, чтобы сделать наилучший выбор.
·
Если вы пришли сюда в поисках информации по разработке безопасных программ, вас, возможно, заинтересует другая статья на этом сайте.
Проектирование и/или выбор программы — задача не самая простая.
Любой программный продукт — результат целого ряда компромиссов. Как мы знаем, существует целый ряд свойств, характеристик программы; назову только некоторые из них: функциональность, быстродействие, удобство использования и, конечно, безопасность. Также мы знаем, что безопасность часто противоречит другим свойствам продукта, например, его функциональности, его быстродействию. Немаловажно и то, что обеспечение безопасности программы требует дополнительных проектных ресурсов: и времени, и денег.
Методы обеспечения безопасности объективно мешают разработчикам. И, естественно, они — со своей позиции — отстаивают интересы развития проекта. «Геннадий! Вы не понимаете!» — эти слова мне приходилось слышать не раз и не два за время, когда я консультировал разные группы.
Приходится находить с ними общий язык. Поэтому знание того, как разрабатывается ПО, какими методами достигаются разные свойства программы, является очень важным и для безопасника. О важности этого знания косвенно свидетельствует и то, что (ISC)2 включила «Software Development Security» в список 10 ключевых областей, в которых обязательно должен разбираться специалист для успешного прохождения сертификации CISSP.
Поэтому я надеюсь, что статья будет безопасникам и интересна, и полезна.
История, классика
Начнем с классики.
Давайте договоримся отнести сюда все методы, которые противопоставляются «гибким». Я буду называть их классикой, вы можете встретить и другие их названия: плановые методы, упорядоченные (disciplined) методы.
Начать с классики вполне логично. С одной стороны, именно история развития этих методов привела, в конце концов, к появлению «гибких» подходов. С другой стороны, на критике возможностей классики основано восхваление «гибкости».
Поэтому давайте разберемся, какие возможности были у разработчиков до появления теперешней моды.
Code-and-Fix
Исторически, первой моделью разработки программного обеспечения была Code-and-Fix (см., например, [Boehm1988] или [McConnell1996]).
Используя Code-and-Fix, мы только пишем код.
Описать правила этого метода просто:
- получаем начальное понимание потребностей заказчика;
- начинаем программировать;
- когда что-то будет готово, показываем «это» заказчику;
- получив отзывы, исправляем наш код;
- повторяем цикл до полного удовлетворения заказчика (или пока у него не кончатся деньги, терпение…)
Используя Code-and-Fix, мы только пишем код. Здесь все очень просто: нет необходимости что-либо планировать, нет необходимости что-либо документировать. Поэтому Code-and-Fix требует минимальной квалификации разработчиков, соответственно, им можно платить меньше денег.
Но, у всего есть своя цена. Как показал опыт, такой подход очень скоро приводит к коду, который невозможно поддерживать: исправление одной ошибки приводит к появлению нескольких новых; внесение минимальных изменений в одну из частей программы, приводит к разрушению функций, реализуемых другими частями.
Все последующее развитие методик разработки выросло из стремления решить эти проблемы.
Водопад
Водопад стал первым решением проблем code-and-fix.
Водопадная модель хорошо работает, когда есть изученный образец системы и известная технология для реализации новой программы.
Я думаю, все более или менее знакомы с этой моделью, поэтому обращу внимание только на некоторые особенности.
Отличительная черта модели — ступенчатость. В простой водопадной модели вся жизнь программы жестко разделена на этапы, они следуют один за другим в предопределенном порядке и никогда не перекрываются; возвратов на более ранние ступени разработки не предусмотрено. На всякий случай напомню шаги этой модели: выявление и анализ требований, высокоуровневое проектирование, модульное проектирование, реализация, тестирование, внедрение и эксплуатация.
Опыт показал, что в водопаде два первых этапа являются критически важными. Известно множество исследований, в которых показывается, что цена исправления ошибок, сделанных здесь, существенно (в десятки раз), выше, чем сделанных позднее. Поэтому от успешности или неуспешности этих этапов в очень большой степени зависят результаты всего проекта.
Поэтому давайте обсудим их чуть подробнее.
Первый этап — разработка требований. В ходе этого этапа программа представляется в виде абстрактного описания функциональности. Причем «требования» должны быть полностью независимыми от технологий, которые будут использованы в реализации. Необходимость абстрактности в этом случае понятна, ведь выбор технологий — это уже следующий этап.
Существует несколько ситуаций, в которых первый этап может быть успешен.
Одна из таких возможностей — наличие опыта эксплуатации информационной системы, очень похожей на разрабатываемую нами (см., например, [Pohl2010]). Интересно, что такой — образцовой — информационной системой не обязательно должна быть компьютерная система. Десяток бухгалтеров со счетами тоже могут представлять основу для разработки. Главным условием является наше понимание происходящих в системе процессов.
Второй этап — высокоуровневое проектирование. На этом этапе происходит выбор технологий для реализации нашей абстрактной модели. Конечно, мы должны предложить такие решения, которые будут лучше, чем образцовая система.
Ключ к успеху второго этапа — изученность технологии. Мы должны уже на этом шаге очень хорошо представлять себе, как будет работать проектируемая нами программа, следовательно, нам необходимо иметь опыт работы с используемыми технологиями, адекватный решаемым задачам.
Таким образом, водопадная модель хорошо работает, когда есть изученный образец системы и известная технология для реализации новой программы.
Достоинства и недостатки
Водопадная модель оказалась очень удачной. Она настолько хорошо решила проблемы Code-and-Fix, что Министерство Обороны США потребовало, чтобы все программные проекты, выполняемые в ее интересах, использовали только эту модель.
Не потеряла она свою актуальность и сейчас. В тех случаях, когда модель водопада применима, у нее есть целый ряд достоинств (см., например, [McConnell1996]).
Во-первых, проект, выполняемый по этой модели очень предсказуем. Вы уже на ранних стадиях можете достаточно точно прогнозировать и время и материальные затраты на его реализацию. По окончании проекта вы получаете именно тот продукт, который был изначально запланирован.
Во-вторых, требования к квалификации программистов снижены. Только этапы проектирования требуют наличия высококвалифицированных специалистов, реализация может осуществляться начинающими программистами. Поскольку реализация требует больших временны́х затрат, то это свойство позволяет существенно снизить потребность в высококвалифицированных специалистах.
В-третьих, проект хорошо масштабируется. После завершения высокоуровневого проектирования работа по созданию разных модулей может проходить независимо друг от друга. Их могут реализовывать разные группы программистов, в том числе существенно разделенные географически, и даже, из разных компаний-субподрядчиков.
В-четвертых, водопадная модель очень экономна. В ней отсутствует необходимость дважды делать одну и ту же работу, и это существенно сокращает и время, и материальные затраты. С другой стороны, экономичность означает, что при одинаковых материальных затратах мы можем больше времени уделить качеству продукта, в том числе и его безопасности.
Но, несмотря на огромный успех, водопадной модели не удалось решить все проблемы. Именно благодарю попыткам применить модель везде, стали понятны ее ограничения и недостатки.
Приведу основные из них.
Во-первых, заказчик получает готовый продукт только в самом конце разработки. Причем типичный проект может занимать от одного года до нескольких лет.
Во-вторых, в огромном количестве проектов возникают проблемы с формулированием требований на раннем этапе разработки. Причин для этого бывает множество:
- заранее сформулировать требования может далеко не каждый заказчик;
- даже если он сможет это сделать, он сделает это на своем языке, который отличается от языка разработчиков;
- при формулировании и переформулировании требований — например, с языка заказчика, на язык разработчика — всегда будут допускаться неточности, будут опущены некоторые, иногда очень важные, детали;
- предположение о том, что требования не зависят от архитектуры системы не всегда верно (см., например, [Ralph2013] и [Swartout1982]).
В-третьих, не всегда удается с первого раза сконструировать систему, которая будет удовлетворять всем требованиям. А узнаем мы об этом только, когда уже готовы все модули, составляющие систему, когда переделывать приходится очень многое, когда исправление ошибок стоит очень дорого.
Эти недостатки привели к провалу огромного количества проектов. Таким образом, популярность сыграла злую шутку с водопадной моделью: разочарование ею стало настолько серьезным, что ее стараются не использовать даже там, где она была бы очень полезна.
Водопад в современном мире
Несмотря на плохую славу, водопадная модель жива и сейчас. Практически только водопад применяют для создания программ, к качеству которых предъявляются высокие требования: авионика, системы управления критическими производствами, высококачественные системы информационной безопасности. Я даже думаю, что водопад мог бы применяться чаще, если бы не его плохая репутация.
Модель не только жива, модель развивается. Хотя, казалось бы, в нее уже нечего добавить, в начале 2000-х годов была создана V-model, дополнившая водопад процессами валидации и верификации. И именно этот вариант часто применяется при разработке высоконадежных программ.
Поэтому, если задача позволяет использовать водопадную модель разработки, используйте ее безо всяких сомнений.
Поэтапная поставка
Давайте теперь посмотрим, какие изменения мы можем внести в водопад, чтобы получить другие модели.
Одна из особенностей водопада — длительный процесс разработки. Во многих случаях это не проблема, подумайте, например, о создании программного обеспечения для космического корабля, который будет направлен к Марсу. Но для большинства современных применений задержка даже на несколько месяцев может сделать программу устаревшей еще до ее выпуска.
Обычно здесь начинают рассказывать про необходимость agile. Тем не менее, даже в водопадной модели есть возможность делать ранние поставки частично готового продукта.
Модель, в которой поставка программного продукта осуществляется по мере готовности его частей, и не переделываются уже готовые модули, называют инкрементальной.
Вспомним, после завершения высокоуровневого проектирования все модули могут разрабатываться и реализовываться независимо.
Воспользуемся этой независимостью. Давайте спроектируем нашу систему так, чтобы существовали отдельные модули, которые несут самую необходимую клиенту функциональность; при этом они должны быть относительно независимы от других частей программы. Давайте теперь реализуем эти модули в первую очередь, а вместо других поставим «заглушки». Очевидно, мы можем в этом случае начать поставки сразу по готовности «срочных» модулей.
Потом мы будем расширять нашу программу. По мере разработки остальных модулей, мы будем заменять ими поставленные ранее «заглушки». Заметим, что разработанный ранее код при этом никак не изменяется. Таким образом мы приходим к инкрементальной модели — модели, в которой поставка программного продукта осуществляется по мере готовности его частей, и не переделываются уже готовые модули.
Используя инкрементальную разработку, мы можем решить проблему долгого ожидания поставки. Но у нас еще осталась необходимость с самого начала точно понимать требования к системе, и очень хорошо знать свойства задействованных технологий. Эта проблема тоже хорошо решается в рамках классики: для ее решения используются прототипы.
Прототипы
Итак, для успешной разработки с использованием водопадной модели или по модели поэтапной поставки, мы должны иметь:
- опыт эксплуатации системы, подобной создаваемой нами;
- опыт применения технологий, которые будут использоваться при разработке.
Все это у нас есть — можем использовать водопадную модель. А что же делать, если у нас нет такого опыта?
Надо его приобрести!
И для этого мы используем прототипы — программы, которые позволяют изучить какое-либо свойство создаваемой программы. Заметим, что использование прототипов, моделей это совсем не изобретение программистов, моделирование применяется практически во всех инженерных областях. Программисты просто украли хорошую идею.
Идея использования прототипов даже в программировании далеко не нова. Еще в своей основополагающей статье ([Royce1970]), Ройс предлагал не ту водопадную модель, которая стала потом классикой. Он предлагал разрабатывать систему дважды! Первый раз — прототип, и только затем — готовую программу.
С тех пор прототипы получили огромное распространение. Было предложено множество способов их создавать, множество способов их использовать. И, конечно, понадобилась возможность во всем этом многообразии разобраться, классифицировать прототипы.
Как и самих прототипов, есть много способов их классификации (см., например, [Carr1997]). Прототипы рабочие или «на выброс», прототипы для показа клиенту или для внутреннего использования, прототипы горизонтальные или вертикальные. Я воспользуюсь только одной из возможных классификаций, предложенной Флойдом еще в 1984 году ([Floyd1984]). Согласно этой классификации, существует три типа прототипов, различающихся по назначению: исследовательский, экспериментальный и эволюционный.
Такая классификация весьма условна. Мы можем начать с исследовательского прототипа, а он плавно перерастет в эволюционирующую версию нашей программы. А может случиться наоборот, мы начнем делать эволюционный прототип, но будем вынуждены его выбросить и начать разработку с чистого листа.
Тем не менее, такая классификация полезна для понимания, зачем мы занимаемся прототипированием.
Исследовательский прототип
Назначение исследовательского (exploratory) прототипа — уточнение требований к разрабатываемому продукту. Он служит своеобразным языком общения разработчика и клиента. Для краткости, в этой части, говоря прототип, я буду иметь в виду именно исследовательский прототип.
Прототип может быть очень простым. Например, небольшая программка, рисующая на экране окно с меню и другими элементами пользовательского интерфейса, — это прототип. Можно даже сказать, что набросок интерфейса на листке бумаги тоже является прототипом разрабатываемой программы.
С другой стороны, прототип может быть относительно сложен. Например, он может демонстрировать один из сценариев использования конструируемой системы. При этом он все равно остается существенно проще конечной программы, это достигается за счет отсутствия всяческих побочных ветвей, обработчиков ошибок, кода для чтения и интерпретации конфигурационных файлов и многого-многого другого.
С точки зрения клиента, ценность прототипа в возможности «потрогать» готовую программу. Он видит, как она выглядит, иногда, как она себя ведет. Вместо обсуждения абстрактных моделей он может говорить о конкретной, почти материальной, вещи. Он может сравнить свои фантазии по поводу создаваемого продукта с возможностями разработчика.
С точки зрения разработчика, ценность прототипа в его простоте. Мы можем создавать множество таких программ, показывать их клиенту, получать от него обратную связь, делать новые прототипы. И так, пока клиент не выразит свое удовлетворение.
Конечно, мы хотим разработать прототип как можно дешевле. Поэтому, чаще всего исследовательский прототип — вещь одноразового использования (throwaway), он разрабатывается очень быстро, без сложных техник, обеспечивающих качество. Для разработки исследовательских прототипов даже созданы специальные языки программирования, позволяющие создавать код быстрее за счет некоторого сокращения проверок. Примером такого языка является модный сейчас Python.
Таким образом, исследовательский прототип является самым простым и дешевым из рассматриваемых здесь трех типов.
Экспериментальный прототип
Назначение экспериментального прототипа — проверка технологии. Мы создаем его, чтобы убедиться, что сможем достичь поставленных задач, используя имеющиеся у нас средства. Если исследовательский прототип можно сравнить, скорее, с портфолио художника, то экспериментальный прототип надо сравнивать с уменьшенной моделью самолета, который мы продуваем в аэродинамической трубе.
Проверять мы можем самые разные свойства программы. Мы можем тестировать пользовательский интерфейс, быстродействие нашей программы, возможности реализации выбранного алгоритма на нашем «железе» и так далее. Любые аспекты выбранных нами технологий, в пригодности которых у нас есть сомнения, мы можем проверить с помощью таких прототипов.
Создавая экспериментальный прототип, мы создаем часть нашей программы. В случае успеха, он очень часто становится отдельным модулем разрабатываемой системы. Поэтому к нему предъявляются все те же требования к качеству кода, что предъявляются к конечному продукту. Изготавливать его необходимо со всей тщательностью; следовательно, стоит ожидать, что создание экспериментального прототипа будет стоить дороже, чем исследовательского.
В случае неудачи, мы теряем вложенные средства. Тем не менее, моделируя отдельные части создаваемой системы, мы снижаем общий риск нашего проекта, поскольку экспериментальный прототип является относительно небольшой частью программы. И если наша попытка будет неудачной, мы будем вынуждены переделывать не так много, как если бы мы это обнаружили в самом конце разработки.
И, конечно, чем больше часть, с которой мы экспериментируем, тем больше наши риски.
Эволюционный прототип
Назвать эволюционный прототип прототипом, можно только с очень большой натяжкой. Строго говоря, здесь мы имеем не модель разрабатываемой программы, а уже саму программу. Мы начинаем с самой простой версии и затем, на основе обратной связи от заказчика, вносим необходимые изменения.
В отличие от инкрементального, итерационный процесс предполагает возвращение к уже сделанной работе и ее улучшение.
И все же, мы рассматриваем эволюционный прототип именно как прототип. Флойд отмечал ([Floyd1984]), не существует четкой границы между этим типом прототипа и предыдущими двумя. Действительно, представим себе, что мы экспериментируем с небольшой частью программы; это экспериментальный прототип. Мы экспериментируем с программой целиком — это эволюционный прототип. Какого размера должна быть часть, с которой мы экспериментируем, чтобы сказать, что это, скорее, уже программа целиком?
Методология эволюционного программирования развивается уже давно. Формально она была предложена в 1982 году ([McCracken1982]), а в 1988 году Том Гилб опубликовал книгу «Principles Of Software Engineering Management»([Gilb1988]), ставшую классикой и описывающую, кроме всего, Evo метод — метод эволюционного развития программы. В качестве отступления замечу, Том Гилб сделал очень многое в области развития методологии разработки ПО. Кроме всего прочего, он был еще и одним из ведущих разработчиков методологии инспекций — систематического анализа кода и документации с целью улучшения их качества. Поэтому его работа в направлении эволюционного программирования заслуживает самого пристального внимания.
Конечно, у эволюционного программирования есть свои недостатки. Как отмечает Боэм ([Boehm1988]), при его применении существуют две опасности:
- от разработки эволюционного прототипа очень легко — при отсутствии достаточного опыта — скатиться к катастрофически проблемному code-and-fix;
- клиент может оказаться не готов воспринять проект, реализуемый без плана.
Тем не менее, именно эволюционное прототипирование является фундаментом современных «гибких» методологий.
И в завершение этой части, стоит сказать, что метод эволюционного прототипирования является наиболее ярким представителем итерационной разработки. В отличие от инкрементального, итерационный процесс предполагает возвращение к уже сделанной работе и ее улучшение (читай — переделывание). Естественно, цена этому — повышенная стоимость разработки.
Спиральная модель
Мы видим, что многообразие задач привело к многообразию методов их решений. Уже в конце 70-х, начале 80-х годов было создано немало вариаций водопадной модели, предложено большое число способов создания прототипов, начинали развиваться формальные методы. Каждый из этих подходов решал одну или несколько проблем, возникающих при разработке ПО.
Разобраться во всем этом зоопарке было не самой простой задачей. Необходимо было найти какой-то подход, который бы объединил бы все эти методики, позволил бы выбирать следующие шаги разработки в зависимости от сложившейся ситуации.
Эту задачу и решила спиральная модель.
Модель была создана в середине 80-х годов прошлого столетия Барри Боэмом. В 1986 году он впервые опубликовал описывающую ее статью ([Boehm1986]); сейчас в интернете доступна более поздняя версия этой статьи ([Boehm1988]). Модель получилась, мягко говоря, не самая простая, многими она была не понята (как и изначальная водопадная!), поэтому более чем через десять лет понадобилась еще одна, разъясняющая публикация ([Boehm2000]).
Свое название модель приобрела из-за выбранного ее автором представления развития проекта. График затраты-прогресс чертится в полярной системе координат: по радиусу откладываются ресурсы, уже затраченные в проекте, угловые координаты отражают достигнутый прогресс. Таким образом, график выглядит, как разворачивающаяся спираль. Рисунок, приведенный автором в его статье, стал визитной карточкой этой модели.
Одна такая спираль — один проект. В каждом проекте тестируется гипотеза, что планируемый программный продукт может быть создан, или что в уже существующий продукт может быть внесено некоторое изменение ([Boehm1988]). Если гипотеза верна — мы получаем успешно завершенный проект, если мы ошиблись — спираль завершается без результата.
Главная идея модели — раннее снижение риска. Очевидно, если спираль должна завершиться без результата, мы хотим, чтобы это произошло как можно раньше, поскольку в этом случае мы затратим на тестирование наименьшее количество ресурсов. Поэтому на каждом шаге спирали мы решаем вопрос, создающий на данном этапе самые большие риски.
Каждому проекту будет соответствовать спираль уникальной формы. В зависимости от нашего уровня понимания требований, знания технологий, наличия квалифицированных программистов, состояния рынка и многих других факторов, мы должны будем решать разные задачи.
Не все ясно осознают эту идею.
Многие принимают рисунок из статьи Боэма в качестве образца единственно возможного развития проекта. Это проект, в котором сначала уточняются требования пользователя, затем проверяется возможность их реализации. В нем активно используются как исследовательские, так и экспериментальные прототипы. Завершается этот проект в соответствии с водопадной моделью.
Сам Боэм призывает не воспринимать этот пример слишком буквально. В другом проекте может оказаться, что таких рисков нет, и спираль полностью выродится в водопад. В еще одном проекте может оказаться, что главный риск — неспособность пользователя описать свои потребности; тогда спираль будет описывать развитие эволюционного прототипа.
Понимаемая таким образом, спиральная модель является метамоделью для всех современных процессов разработки программного обеспечения, включая «гибкие». И, пользуясь ею, можно эффективно управлять программными проектами.
Впрочем, соглашусь: спиральная модель не слишком проста для понимания.
Качество и архитектура программы
Планирование в программном проекте не заканчивается на последовательности действий. Гораздо бо́льшую роль играет планирование архитектуры, дизайна и реализация кода. Я бы сказал даже, что планирование последовательности действий направлено, в конечном счете, на получение качественной архитектуры. Поэтому об архитектуре системы, ее влиянии на качество программы стоит поговорить подробнее.
Основное свойство программы — ее функции. Именно функции программы, что она делает, позволяют нам достигать своих целей; именно ради функций мы покупаем программы; именно ради функций нам, разработчикам, программы заказывают.
Некоторые нефункциональные свойства программы могут быть оценены только после определенного времени эксплуатации, иногда довольно длительного.
Но у любой программы есть и множество других характеристик, качественных, нефункциональных. Они определяют, как программа реализует свои функции, какая она. Для этих свойств англоязычные специалисты даже придумали специальный термин “-ilities”. Перечислять все нефункциональные свойства можно долго, приведу для примера только некоторые: быстродействие, удобство использования, удобство модификации, тестируемость и, конечно, наша любимая безопасность.
Нефункциональные свойства проявляют себя по-разному. Некоторые из них очевидны сразу: если программа «тормозит», то мы не будем ее использовать, несмотря на богатую функциональность. Другие мы сможем оценить только через длительное время: это и модифицируемость, и безопасность, и надежность.
Давайте запомним: некоторые нефункциональные свойства программы могут быть оценены только после определенного времени эксплуатации, иногда довольно длительного.
Качество программы зависит от ее архитектуры (см., например, [Bass2012] и [Taylor2009]).
Чтобы это утверждение не звучало слишком абстрактно, приведу очень простой пример. Пусть у нас есть веб приложение, состоящее из серверной и клиентской части. Все пользователи до начала реальной работы должны пройти аутентификацию, введя свой идентификатор и пароль.
Программа запрашивает и проверяет имя и пароль пользователя — это ее функциональность. Соответствующий код мы можем разместить как на серверной, так и на клиентской части; функциональность от этого вообще ни как не изменится, функциональное тестирование вообще не «увидит» никакой разницы: программа будет и запрашивать, и проверять личность пользователя. Но — надеюсь, это всем очевидно — от этого выбора будут зависеть и быстродействие, и безопасность, и много еще чего.
Конечно, пример элементарный. Не думаю, что найдется много разработчиков, у которых анализ этой ситуации вызовет трудности, но это только один пример. При создании сложной программной системы приходится принимать множество архитектурных решений, и каждое из этих решений может либо сделать нашу систему успешной, либо привести проект к провалу.
Вообще, архитектура программы определяет:
- части, из которых программа состоит;
- свойства этих частей;
- как эти части между собой взаимодействуют.
Приведу примеры архитектурных решений:
- мы используем реляционную базу данных или храним информацию в файлах;
- мы используем двух- или трехзвенную архитектуру;
- мы пишем нашу программу на Java или C++;
- мы используем IPSec или SSL.
Иногда определяют еще один уровень абстракции — дизайн программы. Тогда под этим термином понимается локальная архитектура, архитектура отдельных частей системы (см., например, [Eden2003]). Понятно, что дизайн, определяющий свойства отдельных частей, влияет на архитектуру, а через нее — на качество программы.
Наука об архитектуре и дизайне программ имеет такую же длительную историю, как и вся технология их разработки. Желающих подробнее познакомится с этой историей, я отсылаю к введению в сборник «Software architectures - advances and applications» ([Barroca2000]).
Я же очень коротко упомяну важнейшие достижения в этой области. Конечно, здесь обязательно надо сказать об абстракциях, поддерживаемых компиляторами: подпрограммах, модулях, объектах. Здесь же надо вспомнить о лучших практиках построения программ — образцов дизайна; книга «банды четырех», посвященная этой теме ([Gamma1995]), стала классикой и почти обязательным чтением для всех уважающих себя программистов. И здесь же надо упомянуть лучшие практики построения архитектуры системы: тактики и образцы архитектуры (см. уже упоминавшиеся [Bass2012] и [Taylor2009]).
Сейчас мы понимаем, что не можем сделать идеальную программу (пока или вообще?). Функции программы, различные ее нефункциональные свойства противоречат друг другу. Функциональность программы часто мешает ее безопасности; модифицируемость — быстродействию; улучшение практически любого свойства увеличивает стоимость разработки.
Любой программный продукт — это компромиссное решение. В ходе разработки мы выбираем, какие из свойств программы нас интересуют в наибольшей степени, а чем мы можем пожертвовать.
Компромисс в свойствах — компромисс в архитектуре. Именно выбор приоритетов определяет наши решения в области архитектуры и дизайна создаваемой программы. Эти решения, в свою очередь, в определяющей степени влияют на весь наш продукт: на его коды, на пользовательскую документацию. Напомню при этом, существуют свойства, которые проявляются не сразу, а только в ходе эксплуатации (наша любимая безопасность, например), то есть, проблемы мы можем заметить очень-очень поздно.
Понимаете, почему ошибка в архитектуре стоит дорого?
Поэтому классическое проектирование начинается с планирования архитектуры. Даже работая с эволюционным прототипом, мы планируем его развитие. Мы стараемся предусмотреть несколько наиболее вероятных вариантов изменения нашей программы в будущем. И мы делаем архитектуру такой, чтобы в пределах этих вариантов мы могли бы развивать программу, ничего не меняя в уже готовых модулях. Важно также, что при этом планировании мы учитываем еще и требования быстродействия, безопасности и все прочие важные для нас и для наших клиентов требования.
Мы можем ошибиться. И тогда нам придется переписывать какие-то части нашей системы. Но классический подход такой: если нам пришлось переписывать что-либо, давайте подумаем, как избежать повторения этого в будущем.
Итак, классический подход к разработке архитектуры: подумай сейчас, чтобы меньше делать потом.
Классика классики: RUP
Пожалуй, самая известная методика 90-х, это Rational Unified Process. Ее авторы постарались объединить все лучшее, что было известно к этому моменту. Получилось несколько монструозно, но этот монстр сыграл важную роль, и, более того, он до сих пор не потерял своей актуальности.
RUP считается «тяжелым» процессом.
Поэтому, обсуждая классику, RUP я не мог не вспомнить.
RUP — шаблон (framework) для построения процессов разработки (см., например [Jacobson2002]). Это означает, что вы должны построить свой процесс, используя средства, предоставляемые этим шаблоном. Было показано, что RUP очень хорошо масштабируется и может быть использован как для больших, так и для совсем крошечных проектов.
RUP считается «тяжелым» процессом. Действительно, с его помощью можно создать программный продукт со всей документацией, которая была бы разработана и в водопадном процессе. Поэтому RUP можно смело рекомендовать для использования в самых больших проектах и для разработки программ, качество которых необходимо будет доказывать (например, ее надо будет сертифицировать).
С другой стороны, процесс может быть масштабирован и вниз. Так в 2002 году Филипп Крачтен, один из разработчиков RUP, описал ([Kruchten2002]) выполнение минимального проекта с использованием этой методологии. Проект выполняется одним человеком за одну неделю!
Но, надо признать, масштабировать RUP не очень просто. Для этого требуется очень хорошее понимание методики, надо обладать немалым опытом, чтобы грамотно создать легковесный процесс на его основе. Поэтому большинство групп начинают внедрять RUP сверху: сначала они делают все-все-все, и только потом выбрасывают ненужные им элементы.
Как я уже сказал, авторы методики постарались внести в нее все лучшее, что было известно к этому времени. Чтобы подробно рассказать об этом процессе понадобилась бы целая книга. Я отмечу только главные его отличительные черты.
Первое, RUP является итерационным и инкрементальным процессом. Разработка ведется «итерациями», каждая из которых может занимать от двух до шести недель. Итерации, конечно, направлены на снижение рисков — явный реверанс в сторону спиральной модели.
Каждая итерация относится к одной из четырех фаз: запуск, проектирование, реализация, внедрение и поддержка. Разделение на фазы нестрогое, оно предназначено, скорее, для бо́льшего понимания прогресса проекта. В этом можно убедиться, посмотрев на соотношение фаз и действий в типичном проекте, которое демонстрируется на рисунке-визитной карточке методики.
Второе, RUP ориентирован на решение проблем пользователя. Поэтому одним из важнейших элементов планирования разработки являются варианты использования. Каждый вариант использования — это описание того, как пользователь достигает одной из своих целей, задействуя наш программный продукт. Кстати, варианты использования в упрощенном виде приняты и во многих (во всех?) современных гибких методиках под названием «пользовательские истории».
Третье, проектирование продукта в RUP направлено на его архитектуру. Мы уже обсудили, что качество программы зависит от архитектуры, поэтому с использованием RUP мы можем создавать программное обеспечение, к которому предъявляются жесткие требования качества (напоминаю, включая надежность, безопасность…).
Четвертое, автоматизация разработки. RUP неразрывно связан с программным обеспечением, поддерживающим процесс проектирования. Думаю, именно с RUP надо связать развитие и популяризацию языка визуального проектирования UML, ставшего классикой. Важным шагом вперед стала и поддержка этими программными средствами модели многомерного описания архитектуры программных систем «4+1» ([Kruchten1995]), тоже ставшей классикой.
Думаю, что именно поддержка процесса средствами автоматизации сыграла главную роль в популярности RUP. С другой стороны, покупка дорогостоящего программного обеспечения под силу далеко не всем группам разработчиков.
Подведу итог этой части. Несмотря на многие проблемы (сложность, дороговизна программного обеспечения), RUP сыграл свою важную роль в истории разработки. Как вы заметили, одна из самых часто упоминающихся фраз при его описании «ставший классикой». И этот процесс остается важной методикой, которую можно рекомендовать для внедрения в очень многих ситуациях.
Рождение «agile»
Как мы видим, уже к 90-м годам прошлого столетия инженеры и руководители проектов располагали всем необходимым для быстрой и гибкой разработки.
Итерационная и инкрементальная разработка, различные виды прототипов, спиральная модель, объектное программирование, шаблоны проектирования, начальные знания по шаблонам архитектуры, идеи линейки продуктов — все это позволяло «поднимать» проекты очень разной сложности и продолжительности.
Тем не менее, мы знаем, что в конце 90-х и начале 0-х годов возникло совершенно новое течение, я бы даже сказал, религия, в методиках разработки — «agile».
Чтобы лучше понять, как возникло новое течение, давайте вспомним ситуацию этого времени в отрасли высоких технологий.
Пузырь доткомов
В конце прошлого века началась новая эпоха в программировании. Зародилась она в конце 80-х, начале 90-х годов, когда Тим Бернерс-Ли и Роберт Кайо сделали пионерскую разработку: формат файла html и протокол обмена http. Результаты их деятельности послужили основой для переворота в сети Интернет. Те, кто помнит более раннюю Сеть, понимают, что я имею в виду.
Time-to-market! Time-to-market! Time-to-market!
Как всякие новые технологии, веб проходил определенные этапы развития: зарождение, интерес энтузиастов, бум, охлаждение и выход на «плато».
Конец 90-х — это бум веб технологий. Процессы, происходившие в те времена, можно было бы сравнить с временами «золотой лихорадки», о которых писал Джек Лондон. В том числе, можно было бы найти и аналог гонок на собачьих упряжках за владение потенциально золотоносными участками. Есть ли там золото — сейчас не важно, сейчас главное — застолбить этот участок, разрабатывать его будем потом, или это будут делать другие.
Главный показатель успешности разработки в это время — «time-to-market». Программистские компании очень быстро создавались и так же быстро распадались. У них не было времени организовываться, решать, какие технологии они будут использовать, у них не было времени закупать средства разработки, у них не было времени тщательно продумывать технические решения. Time-to-market! Time-to-market! Time-to-market!
Именно в это время и возник «agile». Да, многие технологии, попадающие по зонтик «agile», зародились в больших компаниях. Это и Экстремальное Программирование — XP (см., например, [Beck1999]), и Scrum ([Schwaber2001]) и другие. Но эти семена попали на плодородную почву (да и кто сказал, что внутри больших компаний нет своих стартапов?).
Окончательно оформилась идеология «гибкости» уже в начале 2000-х годов, с выходом «Agile Manifesto».
Agile Manifesto
Agile Manifesto был опубликован в 2001 году группой уважаемых специалистов по разработке программного обеспечения. Этот манифест стоит того, чтобы быть здесь процитированным:
We are uncovering better ways of developing software by doing it and helping others do it. Through this work we have come to value:
- individuals and interactions over processes and tools
- working software over comprehensive documentation
- customer collaboration over contract negotiation
- responding to change over following a plan
Последний параграф я хочу специально выделить, поскольку он является основанием для многих споров:
That is, while there is value in the items on the right, we value the items on the left more.
Этот параграф служит извинением для почти всех проблем «гибкости». Пропоненты «гибких» методик, когда кто-нибудь начинает критиковать их (методик) недостатки, сразу ссылаются на этот абзац: «мы же не утверждаем, что полностью отказываемся от ценности планов, контрактов, документации; мы просто говорим, что они имеют существенно меньшую ценность, чем взаимодействие и рабочий код».
Правда, как при этом определить, где кончается гибкость и начинается плановость? Сколько орехов — куча?
Поэтому давайте не будем здесь спорить о терминах. Я далее буду говорить о «чистом» «гибком» подходе; возможно, я даже буду немного утрировать, чтобы яснее показать разницу между классикой и «гибкостью». Тем более что мне приходилось встречать фанатиков «гибких» методик, которые настаивали именно на таком, утрированном, их понимании.
Гибкость против плановости
Давайте пройдемся по всем четырем «мы больше ценим…» в Agile Manifesto и посмотрим, что каждое из них обозначает на практике.
Итак, процессы и инструменты. В классической модели разработки мы начинаем работу над проектом, когда у нас уже есть команда или хотя бы ее ядро. Скорее всего, эта команда уже выполняла какие-то работы, у нее есть определенные методики, формализованные и не очень; у этой команды уже есть инструменты, которые все знают и которыми все пользуются. Если такой команды нет, работа начинается с ее создания.
В гибкой модели подразумевается, что команда и методика разработки «образуются» уже в процессе выполнения работ. Времени договариваться, планировать, закупать инструменты нет. Отсюда требование: высококвалифицированные специалисты, высокая мотивация, много общения.
Вообще, слово «образуется» («emerge») — вероятно, самое часто используемое при описании гибких методик. «Образуется» команда, архитектура, продукт…
Из всего широкого спектра возможностей в «гибких» методиках активно используют только эволюционные прототипы.
Документация. Классические методики подразумевают документирование: документируются результаты общения с заказчиком, документируются шаги по выполнению проекта, документируются рассмотренные варианты технических решений, документируются возникшие проблемы и их решения. Документируется опыт группы.
В гибкой модели считается, что главное — поставка программы заказчику. Разработка документации рассматривается как ненужная работа, которая только отвлекает от выполнения основной задачи — написание работающего кода. Поскольку документация отсутствует, возникает потребность как-то организовать обмен знаниями между членами группы — отсюда необходимость работы в тесном контакте, в одном помещении, в частых, хотя и коротких совещаниях.
Контракты. Классические методики подразумевают формализованные договоренности с заказчиком. Эта формализация — логичное следствие документированности общения. Прошла встреча, поговорили, разошлись, мы присылаем заказчику резюме встречи. Он в ответ присылает нам подтверждение, возможно, с некоторыми корректировками. Это, кроме всего прочего, позволяет нам лучше понять потребности заказчика. Во многих проектах такая переписка — вполне серьезные документы, на основании которых нам платят (или не платят) деньги.
В «гибких» методиках подразумевается, что высоко мотивирован не только разработчик, но и заказчик. Тогда для него выполнение заказа, получение рабочего продукта становится важнее возможных трений. Представитель заказчика, с правом принимать решения, становится практически частью группы разработчиков. Это, безусловно, позволяет спрямлять многие углы.
И, наконец, планирование. Главная мантра классической разработки: «работает — не трогай». С самого начала мы продумываем архитектуру продукта, его дизайн, пишем код так, чтобы при внесении изменений не возникала необходимость трогать уже готовые части. Мы планируем.
Мантра гибкой методики: YAGNI — You Aint't Gonna Need It. У нас нет времени планировать; даже если бы оно было, мы не смогли бы предсказать, что может понадобится заказчику в будущем. Поэтому мы делаем сейчас только необходимый минимум, код, который будет работать сегодня. Если завтра понадобится внести изменения — мы их сделаем. Заказчик должен быть удовлетворен сегодня (вообще-то, еще вчера)!
И в заключение этой части, хочу подчеркнуть: абсолютной «гибкости» не бывает ни в одном проекте! Как только мы начинаем говорить об использовании любой методологии, будь то XP или, даже, Scrum, мы уже отходим от чистой идеи.
Поддержание качества кода
Давайте обратим внимание, из всего широкого спектра возможностей в «гибких» методиках активно используют только эволюционные прототипы, программа «образуется».
Поддержание качества кода постоянно эволюционирующей программы — одна из самых сложных задач. Не справимся с ней — получим все «прелести» Code-and-Fix. Но я уже отмечал в этой статье, что эволюционное программирование возникло уже давно. За время его существования были найдены техники, позволяющие приспосабливать уже готовые части программы к изменяющимся обстоятельствам. Таких техник существует множество, назову некоторые из них: управляемая тестами разработка (test driven development), непрерывная интеграция (continuous integration), рефакторинг.
В управляемой тестами разработке во главу угла ставится тест. Тест является программой, которая пишется до того, как программист приступит к написанию основного кода; он является, по сути, требованием: основная программа корректна, если все тесты исполняются успешно. Таким образом, обсуждая прогресс проекта, мы можем говорить о том, сколько и какие тесты наша программа уже проходит.
Написание тестов продолжается постоянно. Если в программе открывается ошибка, которая не обнаруживается существующими тестами, пишется еще один, направленный на обнаружение всех подобных ошибок.
Получается, что вся функциональность разрабатываемого продукта должна быть покрыта тестами. Если в дальнейшем мы что-то в программе изменим некорректно, это будет сразу обнаружено при следующем полном тестировании. На практике, я ни разу не видел полного покрытия, но бо́льшая часть, самая главная функциональность всегда покрывается.
Таким образом, управляемая тестами разработка позволяет изменять нашу программу. Используя ее, мы можем не бояться, что улучшая одно, мы сломаем что-то другое, и что эти некорректные изменения попадут в поставку клиенту. Все проблемы будет быстро обнаруживаться и, по возможности, исправляться.
Непрерывная интеграция позволяет нам обнаруживать некорректность изменений как можно быстрее.
При непрерывной интеграции проводится частая, обычно, ежедневная сборка программы. В эту ежедневную сборку попадают все более или менее готовые изменения; собранная программа полностью тестируется. Полное тестирование — процесс долгий, но автоматизированный, и поэтому проводится он чаще всего ночью, без участия людей. Таким образом, если кто-то, что-то испортил, это будет ясно уже на следующее утро.
Получается, используя непрерывную интеграцию, мы постоянно имеем изменяющуюся, но работоспособную версию нашей программы. И мы можем отслеживать ее прогресс, мы можем показать его нашему клиенту.
В ходе рефакторинга мы изменяем архитектуру и дизайн программы, не изменяя при этом ее функциональности. Проводить этот процесс бывает необходимо, чтобы приспособить разрабатываемую программу к изменившимся условиям: требованиям заказчика, нашего понимания путей ее развития, появившимся новым технологиям.
Меняя структуру программы, мы меняем и ее свойства. Как мы помним, архитектура и дизайн определяют нефункциональные свойства программы, в том числе, модифицируемость, надежность, безопасность и многие другие. Если нас не удовлетворяет любая из этих характеристик в текущей версии, мы можем провести ее рефакторинг; на практике, разработчики больше всего заботятся о модифицируемости.
Таким образом, рефакторинг позволяет нам избежать в «гибких» методологиях проблем Code-and-Fix. Поэтому он рассматривается, как одна из ключевых практик таких методик, и способы его проведения хорошо проработаны (см., например, [Fowler1999]).
В свою очередь, рефакторинг поддерживается автоматизированным тестированием и (часто) непрерывной интеграцией. Делая регулярное тестирование, мы в любой момент можем быть уверенными, что наши изменения архитектуры и дизайна не изменили функциональности программы.
Хочу обратить ваше внимание на интересное противоречие. С одной стороны, использую «гибкие» методы, мы декларируем возможность не следовать каким-либо процессам. С другой стороны, если мы хотим в результате получить качественный продукт, мы должны выполнять вполне определенные, очень важные действия. Поэтому, например, один из самых известных методов «гибкой» разработки, eXtreme Programming (XP), считается процессом, который достаточно сложен для исполнения в полном объеме (см., например, [McBreen2002]).
Разрешается это противоречие при одном, очень важном условии: «гибкая» методика используется очень квалифицированной группой, которая понимает, что она делает. Только в этом случае такая группа сможет произвести продукт с приемлемым качеством.
Проблемы роста
«Гибкие» методики завоевали огромную популярность. Программисты их очень любят и стараются решать именно с их помощью практически все задачи, которые возникают при разработке ПО.
Однако надо понимать, что «гибкость» хороша не всегда. Существует множество публикаций, в которых ограничения этих методологий обсуждаются очень подробно. Например, я могу порекомендовать статью «Assumptions Underlying Agile Software Development Processes» ([Turk2005]); это довольно объемная работа (38 страниц), и тема ограничений раскрыта достаточно полно.
Я же только приведу некоторые примеры.
Давайте представим себе достаточно типичную ситуацию. Мы в наше быстроменяющееся время начали создавать новый программный продукт; мы создавали команду; мы искали концепцию продукта; мы искали клиентов, готовых купить нашу программу. Вполне естественно, что мы начали с «гибкой» методики разработки.
Мы пришли к успеху. Гонки на собачьих упряжках выиграны, мы вышли на рынок, наш продукт пользуется спросом, наша компания развивается. И мы начинаем понимать, что наша «гибкая» методика ограничивает наш рост.
Квалификация разработчиков
Одно из ограничений, с которым мы сталкиваемся, это отсутствие необходимого количества квалифицированных специалистов.
Сначала давайте договоримся, как мы будем характеризовать квалификацию. Алистер Коберн — один из ведущих мировых специалистов по разработке ПО, один из создателей «agile manifesto», создатель «гибкого» метода Crystal — различает три уровня квалификации специалиста (по аналогии с восточной оценкой компетентности Сю Ха Ри).
Первый уровень — начальный. При этом уровне квалификации специалист понимает методику, он знает и соблюдает ее правила. Специалист с квалификацией первого уровня в точности следует рецептам, предписаниям, исполняет инструкции.
Второй уровень — продвинутый. Специалист с этим уровнем квалификации понимает ограничения методики, он знает, что иногда полезно правила нарушать.
И, наконец, третий уровень — эксперт. Специалист этого уровня квалификации вообще может не задумываться, какую методику он использует; в каждый момент времени он делает то, что наилучшим образом приводит к успеху.
Выше квалификация — ниже требуемый уровень регулирования. Очевидно, что группа специалистов с компетентностью третьего уровня выполнит работу лучше, если она не будет ограничена какими-либо «формальными процессами». Такая группа либо повторит формализованный процесс — если он является в данном случае оптимальным, — либо найдет свой, более другой способ. Формальности такой группе только мешают работать.
Вернемся к нашей воображаемой компании. Предположим, мы начали ее, как группа высококлассных специалистов; конечно, «гибкие» методики очень помогли в нашем становлении. Мы создали классный продукт и теперь его развиваем. Нам надо расширяться, нанимать новых программистов. Найти таких же классных специалистов, как мы сами (!) очень сложно. Но нужно ли?
Классному программисту интересна далеко не всякая работа. Есть множество вещей в программировании, которые являются рутиной, которые отвлекают от Творчества. Конечно, именно для выполнения таких задач мы в первую очередь и ищем новых сотрудников; и мы не хотим им платить слишком много (зачем?). Конечно, мы приглашаем специалиста попроще.
Возникает задача адаптации новых сотрудников. Адаптировать высококлассного специалиста было бы не слишком сложно, он и сам легко бы во всем разобрался. Более того, новый высококлассный специалист внес бы в нашу группу новые идеи. Но мы же нанимаем специалиста попроще — с ним приходится повозиться. Наняв одного, мы с легкостью объясняем ему, как у нас принято работать; наняв второго, мы тоже ему легко все объясняем, но это уже начинает быть скучным делом. Чтобы не повторять одно и то же по нескольку раз, мы пишем инструкцию (или, даже, инструкции).
Так мы начинаем формализовать и документировать наши процессы.
Время и память
Документирование проекта — вещь крайне скучная. Найти решение сложной проблемы, написать хорошую программу — это «fun», описать, как это все сделано — почти наказание. Кроме того, документирование проекта — дополнительная работа, которая не вносит ни капли в главное — в коды продукта. Отказываясь от создания проектной документации, мы экономим наши силы раза в два, как минимум. Мы сокращаем стоимость нашего продукта, и мы можем выпустить его быстрее!
Но у всего есть своя цена.
У программистов есть мудрость: «вы начинаете комментировать свои программы после того, как один раз попробовали разобраться в своей же работе год спустя».
Мы начинаем документировать наши проекты после того как:
- мы нашли классное решение проблемы;
- мы реализовали это решение;
- мы обнаружили, что «это» ломает нашу программу;
- мы вспомнили, что уже делали «это» год назад, с тем же результатом.
О необходимости проектной документации ведутся непрекращающиеся споры. Пропоненты «бездокументального» программирования утверждают, что современные программы должны быть написаны так, чтобы сам их код был документацией.
Я поддерживаю несколько другую точку зрения. Никто не спорит — код должен быть понятен, должно быть понятно, что он делает. Документация отвечает на вопросы: зачем он это делает, и почему именно так, а не по-другому. Проблема проектной документации не в том, что приходится ее писать, а в том, что ее пишут плохо.
И мы все равно, рано или поздно, приходим к документированию. Сначала мы делаем подробные комментарии в коде, комментарии в системе управления версиями; мы записываем задачи в систему управления ошибками (bug tracker). Потом и этого становится мало, и мы начинаем записывать требования клиента, описывать архитектурные решения, дизайн программы.
Немного перефразирую известное высказывание, есть два типа программистов: одни документируют свой опыт, другие делают каждый раз все заново.
Магические «7±2»
В «гибких» подходах приняты «бездокументальное» программирование и коллективное владение кодом. Про бездокументальное программирование мы уже поговорили — оно позволяет не делать «лишней» работы. Коллективное владение кодом означает, что каждый член группы имеет право (и должен) вносить изменения в любую часть программы.
Коллективное владение кодом — очень разумный подход. Действительно, если мне надо добавить функцию, исправить какую-либо ошибку, то я не обязан это ни с кем это согласовывать, мне не надо сообщать другому программисту (или, что еще хуже, его руководителю). Я не должен ждать, пока кто-то это сделает; я просто делаю, что мне (и всей группе в целом) нужно. Никакой бюрократии!
Но программа растет, а наш мозг ограничен. Наверно, все вы слышали о классических «7±2». Эти магические цифры означают количество единиц информации, с которыми может работать наш мозг одновременно.
«Единица» информации в данном случае — это вещь, определяемая весьма приблизительно. Сильно упрощая, для целей нашей статьи можно считать, что это одна из абстракций, которыми мы оперируем: элемент архитектуры, дизайна, подпрограмма, функция, переменная… Чем больше их становится, тем сложнее уместить их в нашем сознании.
Начиная с какого-то размера программы, программист уже не может думать о ней в целом. Конечно, переход от полного понимания к полному непониманию не происходит резко. Сначала становится сложнее искать ошибки, продумывать способы их исправления, становится сложнее развивать программу. Потом эти задачи вообще становятся неразрешимыми.
Решением проблемы является разделение продукта на зоны ответственности. Каждый программист, или небольшая группа программистов отвечает за свою часть программы, за свои наборы модулей. При этом чтобы изменения в одной зоне ответственности не ломали работу других программистов, необходимо договариваться о «пограничном» взаимодействии.
Сначала, мы держим эти договоренности в голове. Но с ростом размеров продукта, их становится все больше и больше — мы вынуждены начинать их записывать.
Таким образом, ограничения нашего мозга вынуждают нас вносить в разработку программного обеспечения элементы планирования и документирования архитектуры.
Территориальная разъединенность
Разработка программы в группе — это обмен информацией. Мы должны взаимодействовать с заказчиком, мы должны взаимодействовать между собой.
В «гибких» методиках для обмена информацией используется личное общение. Поэтому разработчики должны постоянно находиться в очень тесном контакте как между собой, так и с заказчиком. Наилучшее место для их работы — большая общая комната, в которой выделены места для общения, и где на видном месте висят различные визуальные материалы, касающиеся текущего проекта.
Но мы живем в большом мире. Часто производить программный продукт выгоднее не там, где находится заказчик (подумайте, пресловутый «индийский код» появился не просто так). Даже в России мы можем наблюдать это: большинство заказчиков программного обеспечения находятся в Москве, но цена программиста — причем не самого опытного — здесь очень высока.
Разделяя наших сотрудников географически, мы можем получить более эффективную бизнес модель. Действительно, мы можем оставить (например) в Москве только специалистов, работающих с заказчиком, выясняющих его требования, производящих высокоуровневое проектирование. Модульное проектирование, реализацию, тестирование можно осуществлять там, где это удобнее.
Конечно, время от времени, разработчикам надо будет встречаться. Но все же, самый большой объем обмена информацией в этом случае будет происходить в письменном виде.
Таким образом, географическое разделение труда является еще одной причиной делать проектную документацию.
Разрешение конфликтов
«Гибкая» разработка подразумевает наличие мотивированного, сотрудничающего заказчика. Причем представлен этот заказчик (как минимум, в идеале) одним человеком, который разбирается в проблеме и имеет право принимать решения по поводу разрабатываемого продукта.
«Wow!» — сказали бы американцы.
В реальности, наш заказчик далеко не один человек. Требования к создаваемой программе являются результатом компромиссов между очень различными группами людей внутри компании-заказчика: это и конечные пользователи, и технические специалисты, и финансисты, и безопасники… Если вы сдаете даже небольшую программу в такой компании, не удивляйтесь, когда на приемке присутствует десяток человек. Часто, даже то, что за разработкой обратились к нам, а не к нашему конкуренту — результат компромисса, сложных игр.
Разрешение конфликтов в технической области — задача не самая легкая. Методикам решения возникающих при этом проблем уделяется очень много внимания, им посвящены даже не статьи, а целые книги. Я приведу только один пример.
ATAM, Architecture Tradeoff Analysis Method ([Bass2012]) был создан для использования в больших проектах. Согласно этой методике все заинтересованные стороны, как со стороны заказчика, так и со стороны разработчика, собираются на конференцию (даже не на совещание). Они обсуждают возможные решения, их достоинства и недостатки; в ходе обсуждение дается возможность высказаться всем заинтересованным сторонам, и, возможно, консультантам. В конце конференции производится голосование, в ходе которого выбирается техническое решение, и именно оно становится базой для создания нового программного обеспечения.
Понятно, ATAM — методика с другой от «гибкости» стороны. Но даже в относительно небольших проектах мы можем столкнуться с проблемами, вызванными конфликтами внутри компании-заказчика. Поэтому четко прописанные технические решения и процедуры их пересмотра бывают крайне необходимы.
Требования к качеству
Вы помните, среди качеств программы есть такие, которые проявляют себя только во время эксплуатации? Напомню, среди этих, неочевидных качеств есть и очень интересные нам, как, например, надежность, безопасность.
С другой стороны, главная задача «гибкой» разработки — удовлетворить клиента сейчас. Причем, представить продукт надо быстро, очень быстро, совсем быстро, быстрее, чем это сделают другие (time-to-market). Тогда, если клиент не видит какое-то свойство сейчас, надо ли тратить время на его обеспечение?
Да, конечно, исправление ошибок потом — дело дорогое. Но тогда клиент уже купит нашу программу, мы «подсадим» его на наши разработки, он оплатит новые, улучшенные версии.
Это похоже на ипотеку. Мы можем долго копить на свой дом, а можем сейчас взять в долг и наслаждаться собственностью. За возможность получить что-то быстро сейчас, мы вынуждены переплачивать потом. Ситуация настолько похожа, что ее часто называют термином «технический долг».
Получается, «гибкая» разработка, во многом, полагается на создание большого технического долга. В этом нет ничего плохого, пока мы понимаем, что делаем, пока не набираем долгов больше, чем можем отдать.
Со временем требования к продукту меняются. Клиенты начинают анализировать его безопасность, обращают внимание на его «глюки». Некоторое время мы можем не очень беспокоиться: мы уже «подсадили» клиента на наш продукт, и он будет прощать нам маленькие неприятности.
Но мы не одни на рынке. Проблемы с нашим продуктом могут в конце концов перевесить все сложности по переходу на продукт конкурента. Поэтому может так случиться, что требования к качеству станут очень важными, и мы будем вынуждены подтянуть к ним нашу программу.
Оставаясь полностью в рамках гибкого подхода, это сделать трудно.
Действительно, в рамках «гибкого» подхода мы вынуждены часто делать рефакторинг. Напомню, что рефакторинг — это изменение архитектуры программы без изменения ее функциональности. Мы осуществляем его, чтобы сохранить «высокое качество» кода.
Но мы знаем, разные качества противоречат друг другу. Мы меняем архитектуру, чтобы улучшить одно — другое ухудшается. Пока программа небольшая, пока требований к ней предъявляется не так много, следить за балансом один человек может легко. Увеличивается программа, появляется больше требований — уследить за соответствием всем критериям становится невозможно.
Поэтому после рефакторинга мы должны проверять полученный результат. Проверить, что функциональность не изменилась, позволяют автоматизированные тесты; они проходят почти без нашего участия, поэтому их можно проводить каждую ночь (правда, для больших продуктов полное тестирование может занять несколько суток, но все равно, оно проходит почти без нашего участия). А вот чтобы проверить, например, безопасность, надо приложить немало усилий.
Есть, конечно, и возможность упростить нашу задачу. Нам не надо проводить проверку безопасности после каждого рефакторинга, мы должны это сделать до поставки программного обеспечения клиенту. Но при «гибкой» разработке мы делаем частые поставки!
Поэтому, когда мы используем чисто «гибкие» методики, у нас есть два варианта. Во-первых, мы можем таки делать анализ перед каждой поставкой; тогда стоимость разработки будет очень велика. Во-вторых, мы можем допустить некоторое снижение безопасности в промежуточных поставках; тогда мы можем делать анализ не так часто; мы понижаем цену разработки, но повышаем риски заказчика.
Более интересным является вариант, когда мы отходим от «гибкой» методологии. Действуя по этой схеме, мы планируем архитектуру системы таким образом, чтобы она была разделена на две части.
Первая часть — ядро системы. Мы тщательно его проектируем, анализируем, кодируем, тестируем и фиксируем. В дальнейшем, для внесения в ядро изменений требуются очень серьезные обоснования. Поскольку мы хотим получить безопасность (среди всех прочих качеств) как можно дешевле, мы делаем так, чтобы вся защита полагалась на это ядро. Тогда необходимость в дорогостоящем анализе возникает редко, и поэтому мы можем проводить его более тщательно.
Вторая часть нашей системы — гибкая. Мы проектируем систему так, чтобы ошибки в этой части не приводили к серьезным уязвимостям (замечу, это уже само по себе не самая простая задача). Поэтому гибкую часть можно менять очень быстро, приспосабливать к изменяющимся запросам клиентов, не боясь испортить качества системы в целом. Мы должны только предпринять какие-то меры, чтобы изменения в этой части не портили ядро (например, в операционных системах ядро работает в особом режиме процессора).
Таким образом, мы видим, что по мере усиления требований к качеству продукта, становится более экономически выгодным отход от чисто «гибких» методик и переход к планированию архитектуру, переход от развития одной программы к созданию линейки продуктов.
Заключение
Получается, «гибкие» методики очень хороши, когда нужно завоевать рынок. Мы создаем продукт, который быстро удовлетворяет нужды клиента, а качество создаваемой программы здесь не играет такой уж важной роли.
Сниженные требования к качеству можно встретить достаточно часто. Так бывает, например, если мы работаем в сегменте рынка, где качеству вообще не уделяют большого внимания (посмотрите, например, на современные социальные сети). Другим примером может быть ситуация, когда для клиента риски связанные с дефектным программным продуктом покрываются выгодами от раннего выхода на рынок.
По мере роста размера программы и/или ужесточения требований появляется необходимость отходить от чисто «гибкой» разработки. Мы начинаем с добавления небольших элементов плановости: немного формализуем процесс, немного документируем продукт, немного планируем архитектуру и замораживаем код.
Сначала мы остаемся в рамках «гибкой» методики, несмотря на все элементы плановости. Возможно даже, наш проект никогда и не вырастет настолько, чтобы для его осуществления понадобилось применять «тяжелые» методики. Но чем сложнее продукт, чем больше требований к его качеству будет предъявляться, тем больше элементов плановости нам необходимо будет вносить в его разработку.
Возможно, в какой-то момент мы должны будем перейти к классике. Причем, такой переход будет означать не просто сказанное «все, мы уже не используем agile», это будет означать новые методы разработки, новые правила, новые инструменты, возможно, новые сотрудники.
Все то же самое можно сказать и более формально. Действительно, давайте рассмотрим «гибкие» методики с точки зрения спиральной модели. Очевидно, «гибкость» нужна, если главный риск проекта — опоздание с выходом продукта на рынок. Как только этот риск становится меньше, или другие риски становятся более существенными (например, недовольство клиентов низкой надежностью), мы должны задействовать другие методы решения наших задач.
В завершение давайте еще раз вспомним о безопасности. Мы, безопасники, участвуя в покупке программного обеспечения, должны понимать, на какой рынок ориентируется разработчик, какие методики он использует, и, исходя из этой информации, прогнозировать качество предлагаемого им продукта, оценивать наши риски. Участвуя в заказе программного обеспечения, выбирая исполнителя, мы должны понимать, какую модель разработки используют разные группы программистов, и как это повлияет на их способность качественно исполнить наш заказ.
Постскриптум
Из всего написанного мною в этой статье может сложиться впечатление, что я отпетый противник гибкого подхода. Но на самом деле все скорее наоборот: очень большая часть моего опыта разработки связана с академической средой, где используются именно гибкие методы. Поэтому в любых проектах я стремлюсь внести долю гибкости — насколько это возможно.
Допуская гибкость, вы получаете целый ряд преимуществ:
- увеличение скорости и снижение стоимости работы за счет отсутствия необходимости планирования;
- получение более качественного результата за счет отсутствия искусственных ограничений, наложенных на высококвалифицированных специалистов.
Поэтому разумная гибкость — дополнительное конкурентное преимущество. Даже SEI, организация, проповедующая очень плановый подход (это такие «тяжелые» методики, как PSP, TSP и CMMI), заявляет, что не видит ничего плохого в гибкости ([Glazer2008]).
Гибкость хороша везде, где работают высококвалифицированные специалисты. Поэтому большу́ю степень гибкости можно, например, допускать на стадиях выяснения требований и высокоуровневого проектирования — стадиях, на которых производится очень активное прототипирование.
Большая степень гибкости может помочь и на этапах оценки безопасности ПО, тестирования безопасности. Эти процессы также требуют участия высококвалифицированных специалистов, поэтому гибкость здесь, как минимум, не помешает. Оформление результатов этих исследований, конечно, уже другая тема.
На своих курсах для безопасников я критикую именно отсутствие гибкости. И я мог бы написать еще одну такую статью с описанием, как вредит излишняя косность.
Здесь нет противоречия. Каким-то группам не хватает гибкости, каким-то — плановости. Все зависит от решаемых в данный момент задач.
И в заключение, я рекомендую почитать книгу Боэма «Balancing Agility and Discipline - A Guide for the Perplexed» ([Boehm2004]), (да-да, это тот самый Боэм, который предложил спиральную модель). В ней Боэм очень подробно обсуждает риски как плановых, так и гибких методик, а также предлагает подход по уменьшению этих рисков. Если вы дочитали до конца этой статьи, то, почти уверен, книга будет вам тоже интересна.
Спасибо за внимание, и удачи вам в ваших разработках!
Литература
[Barroca2000] | Leonor Barroca, Jon Hall, Patrick Hall «Software architectures - advances and applications», Springer, 2000 |
[Bass2012] | (1, 2, 3) Len Bass, Paul Clements, Rick Kazman «Software Architecture in Practice», Addison-Wesley Professional, SEI Series in Software Engineering, 3 edition, 2012 |
[Beck1999] | Kent Beck «Extreme programming explained: embrace change», Addison-Wesley Professional, 1999 |
[Boehm1986] | Barry W. Boehm, «A spiral model of software development and enhancement», ACM SIGSOFT Software Engineering Notes, Volume 11 Issue 4, 1986 |
[Boehm1988] | (1, 2, 3, 4) Barry W. Boehm, «A spiral model of software development and enhancement», Computer, IEEE Computer Society Press Los Alamitos, Vol. 21, Issue 5, 1988 |
[Boehm2000] | Barry W. Boehm, «A spiral model of software development: Experience, Principles, and Refinements», Special Report CMU/SEI-2000-SR-008, 2000 |
[Boehm2004] | Barry W. Boehm and Richard Turner «Balancing Agility and Discipline - A Guide for the Perplexed», Addison-Wesley, 2004 |
[Carr1997] | Mahil Carr, June Verner, «Prototyping and Software Development Approaches», Techreport, City University of Hong Kong, Department of Information Systems, 1997 |
[Cialdini] | Роберт Чалдини, «Психология влияния», Питер, Harper Collins College Publishers, 3-е издание, 2000 |
[Eden2003] | Amnon H. Eden, and Rick Kazman «Architecture, Design, Implementation.» ICSE, page 149-159. IEEE Computer Society, 2003 |
[Floyd1984] | (1, 2) Christiane Floyd, «A systematic look at prototyping», in book Approaches to prototyping, Proceedings of the Working Conference on Prototyping, Springer, 1984 |
[Fowler1999] | Martin Fowler, Kent Beck, John Brant, William Opdyke, Don Roberts «Refactoring: Improving the Design of Existing Code», Addison-Wesley Professional, 1999 |
[Gamma1995] | Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides «Design Patterns: Elements of Reusable Object-Oriented Software» Addison-Wesley Longman Publishing Co., Inc., Boston, MA, USA, 1995 |
[Gilb1988] | Tom Gilb «Principles Of Software Engineering Management», Addison-Wesley Professional, 1988 |
[Glazer2008] | Hillel Glazer, Jeff Dalton, David Anderson, Mike Konrad, Sandy Shrum «CMMI® or Agile: Why Not Embrace Both!» Software Engineering Institute, Carnegie Mellon University, 2008. |
[Jacobson2002] | Айвар Якобсон, Грэди Буч, Джеймс Рамбо «Унифицированный процесс разработки программного обеспечения», Питер, Addison-Wesley, 2002 |
[Kruchten1995] | Philippe Kruchten, «Architectural Blueprints — The "4+1" View Model of Software Architecture», IEEE Software, Volume 12, pages 42--50, 1995 |
[Kruchten2002] | Philippe Kruchten «A Software Development Process for a Team of One», The Rational Edge, 2002 |
[McBreen2002] | Pete McBreen, «Questioning Extreme Programming», Pearson Education; 1st edition, 2002 |
[McConnell1996] | (1, 2) Steve McConnell «Rapid Development: Taming Wild Software Schedules», Microsoft Press, 1996 |
[McCracken1982] | Daniel D. McCracken, Michael Jackson «Life cycle concept considered harmful», SIGSOFT Softw. Eng. Notes, Vol. 7, Issue 2, 1982 |
[Pohl2010] | Klaus Pohl «Requirements Engineering: Fundamentals, Principles, and Techniques», Springer; 2010 edition, 2010 |
[Ralph2013] | Paul Ralph, «The Illusion of Requirements in Software Development» Requirements Engineering, Volume 18, Issue 3, September 2013 |
[Royce1970] | Winston W. Royce, «Managing the development of large software systems: concepts and techniques», Proc. IEEE WESTCON, IEEE Press, 1970 |
[Taylor2009] | (1, 2) R. N. Taylor, N. Medvidovic, and E. M. Dashofy. «Software Architecture: Foundations, Theory, and Practice». Wiley Publishing, 2009. |
[Schwaber2001] | Ken Schwaber, Mike Beedle «Agile Software Development with Scrum» Prentice Hall, 2001 |
[Swartout1982] | William Swartout, Robert Balzer, «On the inevitable intertwining of specification and implementation», Commun. ACM, Volume 25, Issue 7, 1982 |
[Turk2005] | Daniel Turk, Robert France, Bernhard Rumpe «Assumptions Underlying Agile Software Development Processes» Journal of Database Management, v. 16, pp. 62-87, 2005 |
Модифицировано:
Вопросы, мнения?
— адрес электронной почты не проверяется и никому, кроме меня, не показывается
— для форматирования комментариев можно использовать markdown
— для отслеживания комментариев на этой странице можно подписаться на atom feed