next up previous

Глава 6

Процесс

Программисты-любители все время ищут какой-то волшебный инструмент, который мог бы сделать процесс разработки программ тривиальным. Признак профессионализма - понимание того, что такой панацеи не существует. Любители стремятся действовать по "поваренной книге"; профессионалы же знают, что безупречно грамотный подход ведет к нелепым проектным решениям. За словом "система проектирования" разработчики пытаются спрятаться от ответственности за ошибки в проектных решениях. Любители либо игнорируют документацию вообще, либо выстраивают весь проект вокруг нее, заботясь больше о том, как продукт выглядит на бумаге, чем о его сути. Профессионал признает, что без документации не обойтись, но никогда не поступится ради нее полезными архитектурными новациями.

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

6.1. Основные принципы

Характерные черты удачных проектов

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

Архитектура. Признак добротности архитектуры - ее концептуальное единство и целостность. По утверждению Брукса, "концептуальная целостность в проектировании важнее всего" [1]. Как показано в главах 1 и 5, архитектура объектно-ориентированной программной системы содержит структуры классов и объектов, имеющие горизонтальное и вертикальное слоение. Обычно конечному пользователю нет дела до архитектуры системы. Однако, как указывает Страуструп, "ясная внутренняя структура" играет важную роль в построении системы, которая будет понятна, тестируема, устойчива и сможет развиваться и перестраиваться [2]. Более того, именно ясность архитектуры дает возможность выявить общие абстракции и механизмы, которые можно свести воедино, тем самым делая систему проще, меньше и надежнее.

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

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

Хорошей архитектуре присущи следующие свойства:

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

Хорошая архитектура всегда демонстрирует баланс между стратегическими и тактическими решениями. При слабой стратегии даже очень изящно задуманный класс не сможет вполне соответствовать своей роли. Самые прозорливые стратегические решения будут разрушены, если не уделить должного внимания разработке отдельных классов. В обоих случаях пренебрежение архитектурой рождает программные эквиваленты анархии и неразберихи.

Цикл итеративного развития. Рассмотрим две крайности - полное отсутствие формализованного жизненного цикла разработки и очень жесткие, строго соблюдаемые правила разработки. В первом случае мы имеем анархию; тяжким трудом (преимущественно нескольких своих членов) команда разработчиков в конце концов может родить что-то стоящее, но состояние проекта всегда будет неизмеримо и непредсказуемо. Следует ожидать, что команда отработает весьма неэффективно, а, может быть, и вообще не создаст ничего пригодного для передачи заказчику. Это - пример проекта в свободном падении [Есть шанс, что проект в свободном падении приземлится благополучно, но вам не нужно ставить в связи с этим на кон будущее своей компании]. Во втором случае мы имеем диктатуру, в которой инициативы наказуемы, экспериментирование, которое могло бы привнести больше элегантности в архитектуру, не поощряется, и действительные требования заказчика никогда корректно не доходят до разработчиков нижнего уровня, скрытых за настоящей бумажной стеной, воздвигнутой бюрократией.

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

Итеративно развивающийся процесс является антитезой традиционного "водопада" и не сводится к одностороннему движению сверху-вниз или снизу-вверх. Обнадеживающие прецеденты этого стиля есть в опыте создания как аппаратуры, так и программ [3, 4]. Например, пусть надо сформировать штат фирмы, занимающейся проектированием и изготовлением сложной уникальной аппаратуры. Можно использовать горизонтальный подход, когда проект катится водопадом, так, что архитекторы передают его конструкторам, а те электронщикам. Это - пример проектирования сверху-вниз, когда мы приглашаем узких (хотя и глубоких) специалистов в своей области [5]. Можно пойти по другому пути, наняв мастеров на все руки, каждому из которых можно поручить вертикальный сегмент проекта от начала до конца. Это уже гораздо больше похоже на итеративно развивающийся процесс.

По нашему мнению, процесс объектно-ориентированного проектирования не сводится к одностороннему движению сверху-вниз или снизу-вверх. Друк считает, что хорошо структурированные сложные системы можно создать методом "возвратного проектирования" (round-trip gestalt design). В этом методе основное внимание уделяется процессу поступательного итеративного развития путем совершенствования различных, но, тем не менее, совместимых между собой логических и физических моделей системы. Мы считаем, что возвратное проектирование составляет необходимую основу процесса объектно-ориентированного проектирования.

В отдельных случаях решаемая задача может быть уже хорошо изучена и много раз запрограммирована. Процесс разработки можно привести в идеальный порядок: проектировщики новой системы уже понимают, какие абстракции являются главными; они уже знают, какие механизмы нужно использовать и каким, в общих чертах, будет поведение системы. Творчество все еще важно в таком процессе, но здесь проблема достаточно сужена и большинство стратегических решений предопределены. Тогда, поскольку риск исключен, можно достичь очень высоких показателей производительности [6]. Чем больше мы знаем о задаче, тем легче ее решить.

Большинство промышленных задач не таковы: они связаны с балансированием уникальных требований к функциональности и эффективности и требуют полной творческой отдачи всего коллектива разработчиков. Более того, любая человеческая деятельность, которая требует творчества и инноваций, идет путем проб и ошибок, итеративно развивающегося процесса, который опирается на опыт, компетентность и талант каждого члена коллектива [Эксперименты Кертиса и его коллег подкрепляют эти наблюдения. Они изучали работу профессиональных разработчиков программного обеспечения, записывая видеокамерой их действия и затем анализируя их содержание (анализ, проектирование, реализация и т.п.) и время на выполнение. В результате исследований был сделан вывод, что "создание программ представляется набором итеративных, плохо упорядоченных и взаимно перекрывающихся процессов под приспосабливающимся управлением... Развитие по сбалансированной схеме сверху-вниз проявляется как особый случай, когда схема проектирования оказалась вполне подходящей или задача мала по размеру... Хорошие проектировщики работают одновременно на нескольких уровнях абстракции и детализации" [8]]. Так что нет и не будет стандартных рецептов для проектирования программных систем.

Рациональный процесс проектирования

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

Упорядоченный процесс проектирования чрезвычайно важен для организаций, разрабатывающих программное обеспечение. Хэмфри перечисляет следующие пять уровней зрелости таких процессов [7]:
 
• Начальный Процесс разработки организован как придется и нередко хаотичен. На этой стадии налаживание элементарного управления проектом - уже прогресс.
• Воспроизводимый Организация в разумной степени управляет своими планами и обязательствами.
• Определенный Процесс разработки в разумной степени определен, понятен и применяется на практике; он позволяет выбирать команду и предсказывать ход разработки. Следующая цель - оформить выработанную практику разработки как инструментальную среду.
• Управляемый Организация выработала количественные показатели процесса. Цель состоит в снижении затрат на сбор данных и налаживание механизмов обратной связи, позволяющих данным влиять на процесс.
• Оптимальный Организация имеет отлаженный процесс, устойчиво выдающий результаты высокого качества, своевременно, предсказуемо и эффективно.
 
К сожалению, как отмечают Парнас и Клеменс: "Мы никогда не отыщем процесс, который дал бы нам возможность проектировать программы строго рациональным образом", поскольку дело это творческое и новаторское по определению. Однако, продолжают они, "хорошей новостью является, то, что мы можем его имитировать... (Поскольку) разработчики нуждаются в руководстве, мы приблизимся к рациональной разработке, если будем следовать процессу, а не действовать, как попало. Когда организация занята многими программными продуктами, есть смысл иметь стандартную процедуру... Если мы держим в голове идеальный процесс, становится легче измерять успехи проекта" [9].

С приобретением опыта у организации встает вопрос: "Как примирить творчество и новации с возрастающей управляемостью?". Ответ состоит в разграничении макро- и микроэлементов процесса проектирования. Микропроцесс родственен спиральной модели развития, предложенной Боемом, и служит каркасом для итеративного подхода к развитию [10]. Макропроцесс близок к традиционному "водопаду" и задает направляющие рамки для микропроцесса. Примиряя эти два в корне различных процесса, мы имитируем полностью рациональный процесс разработки и обретаем основу для определенного уровня зрелости в деле создания программного обеспечения.

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

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

6.2. Микропроцесс проектирования

Обзор

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

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

В микропроцессе традиционные фазы анализа и проектирования умышленно перемешаны, а управление осуществляется "по возможности". Как отмечает Страуструп, "не существует рецептов, которые могли бы заменить ум, опыт и хороший вкус в проектировании и программировании... Различные фазы программного проекта, такие, как проектирование, программирование и тестирование, неотделимы друг от друга" [11].

Как показано на рис. 6-1, микропроцесс обычно состоит из следующих видов деятельности:

Теперь рассмотрим каждый из этих видов деятельности подробно.

Выявление классов и объектов

Цель. Цель выявления классов и объектов состоит в том, чтобы найти границы предметной области. Кроме того, эта деятельность является первым шагом в продумывании объектно-ориентированной декомпозиции разрабатываемой системы.

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

Результаты. Главным результатом этого шага является обновляющийся по мере развития проекта словарь данных. Вначале достаточно составить список действующих лиц, состоящий из всех заметных классов и объектов, названых именами, отражающими их смысл [12]. Когда словарь разрастется, можно сделать простейшую базу данных, или более специальный инструмент проектирования, непосредственно поддерживающий выбранный метод разработки [Формально, словарь данных объектно-ориентированной разработки должен содержать спецификации каждого элемента архитектуры]. В своих более формальных разновидностях словарь данных служит предметным указателем для всех остальных компонентов проекта, включая диаграммы и спецификации обозначений объектно-ориентированного проектирования.
 

Рис. 6-1. Микропроцесс.

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

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

Виды деятельности. Как мы описывали в главе 4, выявление классов и объектов связано с двумя видами творческих актов: открытием и изобретением.

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

В любом случае основой для выявления классов и объектов служат методы классификации, описанные в главе 4. Обычный порядок действий таков:

В каждом из этих подходов CRC-карточки являются эффективным катализатором "мозгового штурма" и помогают теснее сплотить коллектив, подталкивая его членов к общению [Это ужасно банально, но некоторые проектировщики программ и в самом деле не очень общительны].

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

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

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

Выяснение семантики классов и объектов

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

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

Результаты. На этом шаге получаются несколько результатов. Первым является уточнение словаря данных, с помощью которого мы изначально присвоили обязанности абстракциям. В ходе проектирования мы можем выработать спецификации к каждой абстракции (как описано в главе 5), перечисляя имена операций в протоколе каждого класса. Затем, как можно скорее, мы выразим интерфейсы этих классов на языке реализации. Для C++ это означает создание .h-файлов, в Ada - спецификаций пакетов, в CLOS - обобщенных функций для каждого класса, в Smalltalk - это объявление, но не реализация методов каждого класса. Если проект связан с базой данных, особенно с объектно-ориентированной, на этом шаге мы получаем общий каркас нашей схемы данных.

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

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

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

Виды деятельности. С этим шагом связано три вида деятельности: раскадровка, проектирование изолированных классов и поиск шаблонов.

Главными объектами раскадровки являются основные и второстепенные сценарии, полученные в макропроцессе. В ходе этой деятельности происходит нисходящее выяснение семантики. Там, где это касается функциональных точек системы, принимаются стратегические решения. Типичный ход выполнения действий может быть таким:

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

В начале разработки проекта мы можем задавать семантику классов и объектов в свободной форме, просто описывая обязанности каждой абстракции. Обычно достаточно фразы или предложения; если этого мало - мы встречаем верный признак того, что данная обязанность является чрезмерно сложной и должна разделиться на меньшие. На более поздних стадиях разработки, когда мы будем заниматься доводкой протоколов отдельных абстракций, можно указать имена специфических операций, не определяя их полные сигнатуры, которые мы выясним потом. Таким образом, мы получим соответствие: каждая обязанность выполняется набором операций, а каждая операция как-либо участвует в выполнении обязанностей соответствующей абстракции. После этого, чтобы отразить динамическую семантику протоколов классов [Как мы описывали в главе 3, протокол определяет, что некоторые операции должны вызываться в определенном порядке. Для всех случаев кроме самых тривиальных операции редко встречаются в одиночестве; выполнение каждой из них имеет свои предусловия, проверка которых часто требует вызова других операции], имеющих управляемое событиями или зависящее от состояния поведение, мы можем построить конечные автоматы для них.

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

Проектирование изолированных классов - это восходящее выяснение семантики. Здесь мы концентрируем наше внимание на отдельных абстракциях и, применяя описанные в главе 3 эвристики для проектирования классов, рассматриваем их операции. Это действие по своей природе более тактическое, потому что здесь мы затрагиваем проектирование классов, а не архитектуры. Порядок его выполнения может быть следующим:

Важно избегать преждевременного определения отношения наследования - это часто ведет к потере целостности типа.

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

Третий вид деятельности - поиск шаблонов - связан с обобществлением абстракций. Выявляя семантику классов и объектов, мы должны отмечать шаблоны поведения, которые могут пригодиться где-нибудь еще. Этот процесс может проистекать в следующем порядке:

Выяснение и описание семантики применяется к категориям классов так же, как к отдельным классам. Семантика классов и их категорий определяет роли, обязанности и операции. Для отдельного класса операции могут быть со временем выражены как его функции-члены; в случае категории классов эти операции представляют экспортируемые из категории услуги, и в конечном счете реализуются набором сотрудничающих классов или отдельным классом. Таким образом, действия, описанные выше, применимы и к проектированию классов, и к проектированию архитектуры.

Путевые вехи и характеристики. Мы благополучно завершим этот шаг, когда будем иметь более или менее достаточный, примитивный и полный набор обязанностей и/или операций для каждой абстракции. В начале разработки достаточно иметь неформальный список обязанностей, а в дальнейшем мы постепенно уточняем семантику.

Качественные показатели включают все эвристики классов, описанные в главе 3. Сложные и туманные обязанности и операции говорят о том, что абстракции еще недостаточно определены. Невозможность написать конкретный файл заголовков или как-либо по другому формализовать интерфейс классов также говорит о том, что абстракции плохо сформулированы, или что основные понятия определяли не те люди [Остерегайтесь аналитиков и архитекторов, если они не хотят или не могут выразить конкретно семантику своих абстракции; это признак надменности или беспомощности].

При просмотре сценариев ожидайте бурных дебатов. Это помогает разработчикам делиться архитектурными представлениями и развивать искусство определения абстракций. Не проверенные абстракции не стоит пытаться кодировать.

Выявление связей между классами и объектами

Цель. Цель выявления связей между классами и объектами - уточнить границы каждой обнаруженной ранее в микропроцессе абстракции и опознать все сущности, с которыми она взаимодействует. Это действие формализует концептуальное и физическое размежевание между абстракциями, начатое на предыдущем шаге.

Мы применяем этот шаг в анализе для спецификации связей между классами и объектами (включая некоторые важные отношения наследования и агрегации).

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

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

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

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

Результатом анализа на данном этапе являются диаграммы классов, которые содержат категории классов, идентифицирующие кластеры абстракций, сгруппированные по слоям и разделам. Эти результаты пригодятся и для документирования.

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

При реализации мы должны принять решения о физическом разбиении нашей системы на модули и о распределении процессов по процессорам. Эти решения мы можем выразить на диаграммах модулей и процессов.

На этом же шаге также обновляется словарь данных. В нем отражаются распределения классов и объектов по категориям и модулей по подсистемам.

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

Виды деятельности. С этим шагом связано три вида деятельности: спецификация ассоциаций, идентификация различных взаимодействий и уточнение ассоциаций. Спецификация ассоциаций является одним из основных действий в анализе и на ранней стадии проектирования. Как объяснялось в главе 3, ассоциации семантически слабы: они обозначают только некоторую семантическую зависимость, роль каждого участника связи и кардинальность связи и, возможно, направление допустимого перехода. Однако для анализа и ранней стадии проектирования этого часто достаточно, ибо передаются все важные детали связей между двумя абстракциями, при этом предохраняя нас от поспешных решений о реализации. Типичный порядок выполнения данного этапа таков:

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

Отношения наследования, агрегации, инстанцирования и использования - важнейшие типы ассоциаций, представляющие для нас интерес вместе с такими свойствами, как метки, роли, кардинальность и т.д. Типичный порядок уточнения ассоциаций таков:

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

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

Реализация классов и объектов

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

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

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

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

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

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

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

Типичный порядок действий таков:

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

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

6.3. Макропроцесс проектирования

Обзор

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

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

Макропроцесс заботит в первую очередь технического руководителя команды разработчиков, цели которого несколько отличаются от задач отдельного разработчика. Они оба заинтересованы в качестве конечного программного продукта, удовлетворяющем требованиям заказчика [Ну, конечно, не все, а большинство. К сожалению, некоторые менеджеры больше заинтересованы в развитии своей империи, чем в развитии программного продукта. Прибавьте к этому предыдущее примечание относительно аналитиков и проектировщиков. Я думаю, Данте мог бы найти для них подходящее место]. Однако, конечного пользователя мало волнует, правильно ли использованы в проекте параметризованные классы или полиморфизм; заказчик гораздо более обеспокоен сроками, качеством, полнотой и правильностью работы программы. Поэтому макропроцесс сконцентрирован на управлении риском и выявлении общей архитектуры - двух управляемых компонентах, имеющих решающее значение для сроков, полноты и качества проекта.

В макропроцессе в большой степени сохранены традиционные фазы анализа и проектирования и процесс в меру упорядочен. Как показано на рис. 6-2, макропроцесс обычно включает следующие действия:

 
Рис. 6-2. Макропроцесс.

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

Основная философия макропроцесса состоит в постепенном развитии. Как его определяет Вонк, "при разработке методом последовательного развития, система выстраивается шаг за шагом, причем каждая новая версия содержит функциональность предыдущей, плюс новые функции" [14]. Этот подход чрезвычайно хорошо сочетается с объектно-ориентированной парадигмой и дает много возможностей для управления риском. Как утверждает Гилб: "Постепенная передача программ заказчику изобретена для того, чтобы заранее предупредить нас о надвигающихся неприятностях" [15].

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

Концептуализация

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

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

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

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

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

Виды деятельности. Концептуализация по самой своей природе - творческая деятельность, и, следовательно, она не должна быть скована жесткими правилами разработки. Возможно, самое важнее для организации - создать структуру, которая обеспечивала бы достаточные ресурсы для возникновения и исследования новых идей [Если организация не сделает этого сама, то отдельные разработчики все равно сделают это, не спрашиваясь у компании, в которой они работают. Так и возникают новые программистские фирмы. Их появление хорошо для индустрии в целом, но не для самой осиротевшей компании]. Новые идеи могут исходить из самых различных источников: конечных пользователей, групп пользователей, разработчиков, аналитиков, проектировщиков, распространителей и т.д. Для руководства важно вести регистрацию этих идей, располагая их по приоритетам и распределяя ограниченные ресурсы так, чтобы исследовать самые многообещающие из них. Когда для исследования выбрано конкретное направление, типичен следующий порядок дальнейших действий:

Концептуализация не содержит ничего специфически объектно-ориентированного. Каждая программная парадигма должна предусматривать опробование концепций. Однако, как часто бывает, разработка прототипов обычно происходит быстрее в тех случаях, когда на лицо зрелая объектно-ориентированная среда.

Довольно часто концепции опробуются на одном языке (например, на Smalltalk), а разработка конечного продукта ведется на другом (скажем, C++).

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

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

Анализ

Цель. Как утверждает Меллор, "цель анализа - дать описание задачи. Описание должно быть полным, непротиворечивым, пригодным для чтения и обозрения всеми заинтересованными сторонами, реально проверяемым" [16]. Говоря нашим языком, цель анализа - представить модель поведения системы.

Надо подчеркнуть, что анализ сосредоточен не на форме, а на поведении. На этой фазе неуместно заниматься проектированием классов, представлением или другими тактическими решениями. Анализ должен объяснить, что делает система, а не то, как она это делает. Любое, сделанное на стадии анализа (вопреки этому правилу) утверждение о том "как", может считаться полезным только для демонстрации поведения системы, а не как проверяемое требование к ее проектированию.

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

Сосредоточившись на поведении, мы приступаем к выяснению функциональных точек системы. Функциональные точки, впервые описанные Аланом Альбрехтом, обозначают видимые извне и поддающиеся проверке элементы поведения системы [17]. С точки зрения конечного пользователя, функциональная точка представляет некоторое простейшее действие системы в ответ на некоторое событие [Как отмечает Дрегер, в теории управления информационными системами функциональная точка представляет отдельную бизнес-функцию конечного пользователя [18]]. Функциональные точки часто (но не всегда) обозначают отображение входов на выходы и таким образом представляют преобразования, совершаемые системой. С точки зрения аналитика, функциональные точки представляют кванты поведения. Действительно, функциональные точки - мера сложности системы: чем их больше, тем она сложнее. На стадии анализа мы передаем семантику функциональных точек сценариями.

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

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

Результаты. ДеШампо считает, что результатом анализа должно быть описание назначения системы, сопровождаемое характеристиками производительности и перечислением требуемых ресурсов [19]. В объектно-ориентированном проектировании мы получаем такие описания с помощью сценариев. Каждый сценарий представляет одну функциональную точку. Мы используем первичные сценарии для иллюстрации ключевого поведения и вторичные для описания поведения в исключительных ситуациях.

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

Часто эти результаты анализа объединяют в один формальный документ, который формулирует требования анализа к поведению системы, иллюстрируя их диаграммами, и показывает такие неповеденческие аспекты системы, как эффективность, надежность, защищенность и переносимость [20].

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

Виды деятельности. С анализом связаны два основных вида деятельности: анализ предметной области и планирование сценариев.

Как мы описали в главе 4, анализ области должен идентифицировать обитающие в данной проблемной области классы и объекты. Прежде, чем взяться за разработку новой системы, обычно изучают уже существующие. В этом случае мы можем извлечь выгоду из опыта других проектов, в которых принимались сходные решения. Лучшим результатом анализа предметной области может явиться вывод, что нам не надо проектировать новый продукт, а следует повторно использовать или адаптировать существующую программу.

Планирование сценариев является центральным действием анализа. Интересно, что по этому вопросу, кажется, имеется совпадение мнений среди других методологов, особенно у Рубина и Голдберга (Rubin adn Goldberg), Адамса (Adams), Вирфс-Брока (Wirfs-Brock), Коада (Coad) и Джекобсона (Jacobson). Типичный порядок его выполнения следующий:

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

Путевые вехи и характеристики. Мы благополучно завершим эту фазу, когда мы будем иметь уточненные и подписанные сценарии для всех фундаментальных типов поведения системы. Говоря подписанные, мы предполагаем, что конечные результаты анализа проверялись экспертами, конечными пользователями, аналитиками и архитекторами; говоря фундаментальные, мы имеем в виду типы поведения, основные для данного приложения. Повторим, мы не ожидаем полного анализа, - достаточно рассмотреть только основные и несколько второстепенных видов поведения.

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

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

Проектирование

Цель. Цель проектирования - создать архитектуру развивающейся реализации и выработать единые тактические приемы, которыми должны пользоваться различные элементы системы. Мы начинаем процесс проектирования сразу после появления некоторой приемлемой модели поведения системы. Важно не начинать проектирование до завершения анализа. Равным образом важно избегать затягивания проектирования, пытаясь получить идеальную, а следовательно, недостижимую аналитическую модель [Такая ситуация обычно классифицируется как паралич анализа].

Результаты. Имеется два основных результата проектирования: описание архитектуры и выработка общих тактических приемов.

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

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

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

Мы описываем единые приемы в сценариях и действующих релизах каждого механизма.

Виды деятельности. С проектированием связано три действия: архитектурное планирование, тактическое проектирование и планирование релизов.

При архитектурном планировании мы занимаемся вертикальным и горизонтальным расчленением системы. Оно охватывает логическую декомпозицию, состоящую в группировании классов, и физическую декомпозицию, состоящую в разбиении на модули и назначении заданий процессорам. Типичный порядок действий таков:

Архитектурное планирование сконцентрировано на том, чтобы создать в самом начале жизненного цикла каркас системы, а потом постепенно развивать его.

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

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

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

Основным признаком совершенства является простота. Хорошая архитектура имеет характеристики организованной сложной системы (см. главу 1).

Главные выгоды от этой деятельности - раннее выявление архитектурных просчетов и утверждение единых приемов, которые позволяют получить более простую архитектуру.

Эволюция

Цель. Цель эволюции - наращивать и изменять реализацию, последовательно совершенствуя ее, чтобы в конечном счете создать готовую систему.

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

Таким образом, эволюция - это и есть процесс разработки программы. Как пишет Андерт, проектирование "есть время новшеств, усовершенствований, и неограниченной свободы изменять программный код, чтобы достигнуть целей. Производство - управляемый методичный процесс подъема качества изделия к надлежащему уровню" [24].

Пейдж-Джонс называет ряд преимуществ такой поступательной разработки:

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

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

Для кого делается действующий релиз программы? В начале процесса разработки основные действующие релизы передаются разработчиками контролерам качества, которые тестируют их по сценариям, составленным при анализе, и накапливают информацию о полноте, корректности и устойчивости работы релиза. Это раннее накопление данных помогает при выявлении проблем качества, которые будут учтены в следующих релизах. Позднее действующие релизы передаются конечным (альфа и бета) пользователям некоторым управляемым способом. "Управляемым" означает, что разработчики тщательно выверяют требования к каждому релизу и определяют аспекты, которые желательно проверить и оценить.

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

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

Виды деятельности. Эволюция связана с двумя видами деятельности: микропроцесс и управление изменениями.

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

После каждого релиза следует перепроверить сроки и требования в основном плане релизов. Как правило, это незначительные корректировки дат или перенос функциональности из одного релиза в другой.

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

При эволюции системы на практике ожидаются следующие типы изменений:

Каждый тип изменений имеет свою причину и стоимость.

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

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

Подобным образом можно было бы изменить представление класса (в C++ - защищенные и закрытые члены класса). Обычно это делается, чтобы получить более эффективные (с точки зрения памяти или скорости) экземпляры класса. Если представление класса инкапсулировано, что возможно в Smalltalk, C++, CLOS и Ada, то изменение в представлении не будет разрушать логику взаимодействия объектов-пользователей с экземплярами класса (если, конечно, новое представление обеспечивает ожидаемое поведение класса). С другой стороны, если представление класса не инкапсулировано, что также возможно в любом языке, то изменение в представлении класса чрезвычайно опасно, так как клиенты могут от него зависеть. Это особенно верно в случае подклассов: изменение представления суперкласса вызовет изменения представления всех его подклассов. Во всяком случае, изменение представления класса имеет цену: нужно произвести перекомпиляцию интерфейса и реализации класса, сделать то же для всех его клиентов, для клиентов тех клиентов и т.д.

Реорганизация структуры классов системы встречается довольно часто, хотя и реже, чем другие упомянутые виды изменений. Как отмечают Стефик и Бобров, "Программисты часто создают новые классы и реорганизуют имеющиеся, когда они видят удобную возможность разбить свои программы на части" [26]. Изменение структуры классов обычно происходит в форме изменения наследственных связей, добавления новых абстрактных классов и перемещения обязанностей и реализации общих методов в классы более верхнего уровня в иерархии классов. На практике структура классов системы особенно часто реорганизуется вначале, а потом, когда разработчики лучше поймут взаимодействие ключевых абстракций, стабилизируется. Реорганизация структуры классов поощряется на ранних стадиях проектирования, потому что в результате может получиться более лаконичная программа. Однако реорганизация структуры классов не обходится даром. Обычно изменение положения верхнего класса в иерархии делает устаревшими определения всех классов под ним и требует их перекомпиляции (а, значит, и перекомпиляции всех зависимых от них классов и т.д.).

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

Редко, но встречается удаление существующего метода; это обычно делается только для того, чтобы улучшить инкапсуляцию абстракции. Чаще мы добавляем новый метод или переопределяем метод, уже объявленный в некотором суперклассе. Во всех трех случаях это изменение дорого стоит, потому что оно логически затрагивает всех клиентов, требуя как минимум их перекомпиляции. К счастью, эти последние виды изменений, добавление и переопределение методов, совместимы снизу вверх. На самом деле вы обнаружите, что большинство изменений интерфейса, произведенного над определенными классами при эволюции системы, совместимы снизу вверх. Это позволяет для уменьшения воздействия этих изменений применить такие изощренные технологии, как инкрементная компиляция. Инкрементная компиляция позволяет нам вместо целых модулей перекомпилировать только отдельные описания и операторы, то есть перекомпиляции большинства клиентов можно избежать.

Почему перекомпиляция так неприятна? Для маленьких систем здесь нет проблем: перекомпиляция всей системы занимает несколько минут. Однако для больших систем это совсем другое дело. Перекомпиляция программы в сотни тысяч строк может занимать до половины суток машинного времени. Представьте себе, что вам понадобилось внести изменение в программное обеспечение компьютерной системы корабля. Как вы сообщите капитану, что он не может выйти в море, потому что вы все еще компилируете? В некоторых случаях цена перекомпиляции бывает так высока, что разработчикам приходится отказаться от внесения некоторых, представляющих разумные усовершенствования, изменений. Перекомпиляция представляет особую проблему для объектно-ориентированных языков, так как наследование вводит дополнительные компиляционные зависимости [27]. Для строго типизированных объектно-ориентированных языков программирования цена перекомпиляции может быть даже выше; в этих языках время компиляции принесено в жертву безопасности.

Все изменения, обсуждавшиеся до настоящего времени, сравнительно легкие: самый большой риск несут существенные изменения в архитектуре, которые могут погубить весь проект. Часто такие изменения производят чересчур блестящие инженеры, у которых слишком много хороших идей [28].

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

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

Грубо говоря, скорость обнаружения ошибок - это мера того, как быстро отыскиваются новые ошибки [29]. Вкладывая средства в контроль качества в начале разработки, мы можем получить количественные оценки качества для каждого релиза, которые менеджеры команды смогут использовать для определения областей риска и обновления команды разработчиков. После каждого релиза должен наблюдаться всплеск обнаружения ошибок. Стабильность этого показателя обычно свидетельствует о том, что ошибки не обнаруживаются, а его чрезмерная величина говорит о том, что архитектура еще не стабилизировалась или что новые элементы неверно спроектированы или реализованы. Эти характеристики используются при уточнении цели очередного релиза.

Показатель изменчивости архитектурного интерфейса или тактических принципов является основной характеристикой стабильности архитектуры [30]. Локальные изменения вероятны в течение всего процесса эволюции, но если структуры наследования или границы между категориями классов или подсистем постоянно перестраиваются, то это признак нерешенных проблем в архитектуре, что должно быть учтено как область риска при планировании следующего релиза.

Сопровождение

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

Леман и Белади сделали несколько неоспоримых наблюдений, рассматривая процесс "созревания" уже внедренной программной системы:

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

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

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

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

Путевые вехи и характеристики. Путевыми вехами сопровождения являются продолжающееся производство эволюционирующих релизов и устранение ошибок.

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

Выводы

Дополнительная литература

Ранняя форма процесса, описанного в этой главе, была впервые опубликована Бучем (Booch) [F 1982]. Берард (Berard) позднее развил эту работу в статье [F 1986]. Среди родственных подходов можно назвать GOOD (General Object-Oriented Design) Сейдвица и Старка (Seidewitz and Stark) [F 1985,1986,1987], SOOD (Structured Object-oriented Design) корпорация Локхид (Lockheed) [С 1988], MOOD (Multipleview Object-oriented Design) Керта (Kerth) [F 1988], и HOOD (Hierarchical Object-oriented Design), предложенный CISI Ingenierie и Matra для европейской космической станции [F 1987]. Более свежие ссылки: Страуструп (Stroustrup) [G 1991] и Microsoft [G 1992], где предложены сходные процессы.

В дополнение к работам, упомянутым в дополнительной литературе к главе 2, ряд других методологов предложил специфические процессы объектно-ориентированного развития. На эти работы есть много библиографических ссылок. Вот наиболее интересные из них: Алабио (Alabios) [F 1988], Бойд (Boyd) [F 1987], Бур (Buhr) [F 1984], Черри (Cherry) [F 1987,1990], деШампо (deChampeaux) [F 1992], Фелсингер (Felsinger) [F 1987], Файерсмит (Firesmith) [F 1986,1993], Хайнс и Юнгер (Hines and Unger) [G 1986], Дже-кобсон (Jacobson) [F 1985], Джамса (Jamsa) [F 1984], Кади (Kadie) [F 1986], Мазиеро и Германо (Masiero and Germano) [F 1988], Ниелсен (Nielsen) [F 1988], Ниес (Nies) [F 1986], Рэйлич и Сильва (Railich and Silva) [F 1987], Грэхем (Graham) [F 1987].

Сравнение различных процессов объектно-ориентированного развития можно найти в работах Арнольда (Arnold) [F 1991], Боем-Дэвиса и Росса (Boehm-Davis and Ross) [H 1984], деШампо (deChampeaux) [В 1991], Криббса, Муна и Ро (Cribbs, Moon, and Roe) [F 1992], Фоулер (Fowler) [F 1992], Келли (Kelly) [F 1986], Манино (Mannino) [F 1987], Сонга (Song) [F 1992], Вебстера (Webster) [F 1988]. Брукман (Brookman) [F 1991] и Фичмэн (Fichman) [F 1992] сравнили структурные и объектно-ориентированные методы.

Эмпирические исследования процессов создания программного обеспечения можно найти в работе Кертис (Curtis) [H 1992], а также в трудах Software Process Workshop [H 1988]. Еще одно интересное исследование принадлежит Гвиндону (Guindon) [H 1987], изучавшему процессы, которые разработчики использовали раньше. Речтин (Rechtin) [H 1992] предложил прагматическое руководство для системного архитектора, который должен управлять процессом развития.

Интересная ссылка по вопросу о "созревании" программного продукта - это работа Хэмфри (Hamphrey) [H 1989]. Классическая ссылка на то, как симитировать этот процесс, - статья Парнаса (Parnas) [H 1986].


next up previous
Используются технологии uCoz