«ОСНОВАНИЯ ПРОГРАММИРОВАНИЯ УДК 519.682 Непейвода Н. Н., Скопин И. Н. Основания программирования Книга представляет собой первое издание в серии, предназначенной для студентов, готовящихся к работе по современным ...»
4.3.4. Моделирование итеративного наращивания возможностей системы В предыдущих моделях итеративного жизненного цикла программного обеспечения не был наглядно выделен важный аспект подхода: постепенное наращивание возможностей системы по мере развития проекта. Для его отражения можно предложить представление жизненного цикла в виде спирали
ГЛАВА 4. ЖИЗНЕННЫЙ ЦИКЛ
развития, которая показана на рис. 4.127.Предоставляемые Рис. 4.12. Спираль развития объектно-ориентированного проекта На рисунке горизонтальные отрезки с пометками, имеющими тот же смысл, что и в предыдущей модели, — это итерации. Они помещены в пространство предоставляемых в зависимости от времени возможностей системы. Линии, параллельные временной оси, отображают уровни пользовательских возможностей, реализуемых на итерациях (римскими цифрами справа указаны номера итераций). Стрелки-переходы между итерациями учитывают условия совмещения работ, о которых шла речь выше. Этой моделью подчеркивается тот факт объектно-ориентированного развития проектов, что возможности, предоставляемые очередной итерацией, никогда не отменяют уровня, достигнутого на предшествующих итерациях.
Постепенное наращивание возможностей системы по мере развития проекта часто изображают в виде спирали, раскручивающейся на плоскости от центра, как это показано на рис. 4.13. В соответствии с этой грубой модеВ несколько модернизированном виде здесь приводится ставшая классической модель Г.
Буча [16], ставшая основой ООП.
(программирование и тестирование) Рис. 4.13. Модель расширения охвата прикладной области объектно-ориентированной системой лью развитие проекта описывается как постепенный охват расширяющейся области плоскости по мере перехода проекта от этапа к этапу и от итерации к итерации. По существу, данная модель делает акцент на том, что объектно-ориентированное развитие (как и любое другое экстенсивное развитие, ориентированное на переиспользование) приводит к постепенному расширению прикладной области, для которой используются конструируемые рабочие продукты.
Про объектно-ориентированное развитие проектов часто говорят: «Оно предполагает, что традиционные этапы жизненного цикла разработки программной системы никогда не кончаются». Модель раскручивающейся спирали наглядно показывает смысл этого тезиса.
В данной модели можно усмотреть еще один аспект конструирования программных систем — типичную схему развития коллектива разработчиков, который, начиная от первого своего проекта, постепенно пополняет накапливаемый багаж переиспользуемых в разных системах компонентов.
В отличие от предыдущих моделей, обе спиралевидные модели никак не отражают тот факт, что у проекта есть фаза завершения. Как следствие, они предполагают, что все модификации какой-либо версии программной системы, которые требуются после ее выпуска, будут относиться к одной из слеГЛАВА 4. ЖИЗНЕННЫЙ ЦИКЛ дующих версий. На практике очень часто это положение нарушается: приходится поддерживать (и, в частности, модифицировать) сразу несколько версий системы.
ТРЕБОВАНИЯ К ПРОГРАММНОМУ ИЗДЕЛИЮ
И ЖИЗНЕННЫЙ ЦИКЛ
§ 4.4.Есть еще один важный мотив моделирования жизненного цикла программных изделий. Это изучение и систематизация требований при развитии программных проектов.
Традиционные технологии рассматривают определение и анализ требований в рамках предварительного этапа, предшествующего собственно разработке, за который выявляется вся информация для последующего конструирования. Утверждается, что успешность дальнейшей работы над проектом прямо зависит от того, насколько полно и тщательно выполнен аналитический этап, что внесение корректив в зафиксированные требования приводит к необходимости повторения проектирования и всех других последующих этапов. Иными словами, изменение требований в процессе разработки рассматривается как ошибка аналитического этапа.
Однако эта парадигма явно противоречит практике, что нашло отражение в известном афоризме:
любая полезная программа нуждается в модификациях, а бесполезная — в Поэтому сейчас получили распространение технологии программирования, основывающиеся на методе итеративного наращивания предоставляемых пользователям средств системы, в частности, на базе объектно-ориентированного проектирования. Как уже было сказано, в таких технологиях постулируется, что все этапы разработки системы рассредоточиваются по итерациям, каждая из которых завершается предъявлением продукции, реализующей не все, а только выделенные для нее требования. Соответственно, на следующих итерациях этапы анализа, конструирования и т. д. продолжаются, а не повторяют пройденное в стиле исправления ошибок.
Понятно, что проблемы, связанные с определением и анализом требований, не исчезают и в этом случае, но благодаря специальной организации труда преодолевать трудности можно с меньшими затратами. Именно это обстоятельство побуждает к исследованию моделей жизненного цикла, которые более точно отражают рациональные схемы анализа и оперирования с
4.4. ТРЕБОВАНИЯ К ПРОГРАММНОМУ ИЗДЕЛИЮ И ЖИЗНЕННЫЙ ЦИКЛ
требованиями.4.4.1. Проблемы определения и анализа требований В наиболее общем виде понятие требований сводится к следующим двум аспектам, фиксируемым для выполнения конструкторских работ:
• средства программного изделия, в которых нуждается пользователь для решения своих проблем или достижения определенных целей;
• характеристики программного изделия, которыми должна обладать система в целом или ее компонент, чтобы удовлетворять соглашениям, спецификациям, стандартам или другой формально установленной документации.
Даже это не очень точное определение понятия требования указывает, что в реальности очень трудно, исходя из аморфных и противоречивых пожеланий, выявить, что конкретно и в каком виде должно быть воплощено в программном изделии. Требования первичны по отношению к программной разработке, определяют все ее развитие, являются начальным звеном в слагаемых качества конструируемых программ. А потому задача управления требованиями должна рассматриваться в качестве одной из главных задач проекта, претендующего на реальную полезность для пользователя.
Основные проблемы управления требованиями, с которыми приходится сталкиваться при их анализе, сводятся к следующему:
1. Требования имеют много источников Даже если программная система разрабатывается по заказу, существует широкий круг людей, так или иначе заинтересованных в развитии проекта. Это, разумеется, и будущие пользователи, и заказчик, и другие лица, которые осознают как необходимость автоматизации деятельности с помощью данной системы, так и рамки, за которые выходить не стоит. Все прикосновенные (в частности, сами разработчики и их руководители) имеют свои, как правило, взаимно противоречивые представления о задачах проекта. Это тем более так, когда ее разработка претендует на удовлетворение рыночной потребности. Лица, от которых зависит, какие работы целесообразны для реализации в проекте, называются инициаторами работ;
2. Требования не всегда очевидны ГЛАВА 4. ЖИЗНЕННЫЙ ЦИКЛ
3. Требования не всегда легко выразить словами Интуитивное представление о том, какие средства должны предоставляться, чаще всего не формулируются явно. Вместо этого приводится множество противоречивых примеров, наводящих соображений. В этой связи одна из главных задач анализа — представить требования в виде согласованных между заказчиком и разработчиками (одинаково понимаемых) утверждений, схем, диаграмм, моделей и т. п.;
4. Существует множество различных типов требований и различных уровней их детализации Совокупность требований весьма многопланова и соотносится с различными аспектами проекта. Следовательно, одной из задач анализа является типизация имеющихся сведений о требованиях и распределение их по этапам и итерациям разработки;
5. Требования почти всегда взаимосвязаны и взаимозависимы, и часто противоречивы Связи между требованиями обусловлены, в первую очередь, тем, что пожелания к разработке даются в системе понятий, которая исходит из предметной области и поведения пользователя, решающего задачи из этой области. Не следует ожидать, что связи между требованиями будут хорошо отслежены, что заранее будет сформулирована система объектов, которые воплощаются в программном изделии. Все, на что можно рассчитывать, получая сведения о требованиях, — это неформальное представление о том, кто будет работать с системой и зачем ему это нужно. Как следствие, в задачу анализа входит выявление взаимосвязей и взаимозависимостей и устранение противоречий (в достижимом, но практически не встречающемся в современном индустриальном программировании идеале путем преобразования их в идеи решений);
6. Требования всегда уникальны 4.4. ТРЕБОВАНИЯ К ПРОГРАММНОМУ ИЗДЕЛИЮ И ЖИЗНЕННЫЙ ЦИКЛ
7. Набор требований чаще всего является компромиссом Поскольку в данный момент средства выявления требований и запросов во всех имеющихся технологиях и методиках первопорядковые (они даже не учитывают опыта перехода к надсистемам и принципиальных переформулировок, накопленного ТРИЗ, синектикой, школой де Боно и другими методиками творческого решения задач), они направлены на нахождение механического компромисса между пожеланиями инициаторов работ и других заинтересованных лиц8 и выявление “усредненных” требований;
Заметим, что в методиках творческого мышления компромисс рассматривается как худший возможный выход. Противоречивость требований в них является проблемным противоречием, которое преобразуется в новое системное решение. Но в программировании такому подходу к задачам мешают по крайней мере три фактора:
• отсутствие методик преобразования задач, аналогичных ТРИЗ или методике де Боно, и ориентированных на программирование как деятельность;
• ориентация на жесткие и точные решения задач, которые якобы удовлетворяют всех, а не на действительные решения проблем, как это делается по указанным методикам • преобладание среди сотрудников программистских фирм лиц с мышлением комбинационного уровня и практическое отстутствие тех, кто анализирует задачу на уровне Кроме того, есть и субъективный фактор: система оплаты труда в индустрии программирования, ориентированная на оплату человеко-часа: программистской фирме в определенных пределах выгодно повышать количество и долю неквалифицированных человеко-часов, поскольку разница в 80–100% в оплате часа эксперта и часа исполнителя не компенсирует финансовый проигрыш за счет сокращения в разы работы исполнителей в результате хорошего решения, предложенного экспертом.
ГЛАВА 4. ЖИЗНЕННЫЙ ЦИКЛ
8. Требования изменяются Фиксируемые в заказе на разработку требования к системе, претендующей на широкую сферу применения и долгую жизнь, не являются застывшими и незыблемыми. Они изменяются как из-за учета новых факторов и пожеланий, так и в связи с выявлением особенностей проекта в ходе его разработки. Следовательно, необходимо так строить аналитическую работу, чтобы иметь возможность оперативно изменять получаемые результаты, учитывать в них изменения и дополнения исходной информации;9. Требования зависят от времени Это положение указывает на то, что пробное и экспериментальное знакомство с первыми получаемыми результатами (программными и документными) вероятно повлечет за собой корректировку требований.
Как следствие, нужно иметь в виду, что при выпуске очередной версии промежуточных рабочих продуктов вполне реальна ситуация проведения анализа требований вновь, а потому анализ и следующие за ним этапы должны быть организованы так, чтобы минимизировались переделки программ и документов.
Список проблем, связанных с требованиями, легко продолжить, но уже и этого достаточно, чтобы понять, что необходимы специальные приемы и методы оперирования с потоками требований, сопровождающих развитие проекта. Применительно к настоящей работе следует выделить то, как эти обстоятельства отражаются на моделях жизненного цикла развивающихся проектов. Существенно, что учет появляющихся требований приводит к необходимости продолжения аналитических работ за пределами этапа анализа. Это можно делать по-разному, но всегда приходится выполнять так называемую трассировку требований, обсуждению которой посвящен следующий параграф.
4.4.2. Трассировка требований Независимо от уровня первоначальной проработки требований к проекту, не стоит рассчитывать, что требования всегда будут оставаться неизменными. Необходимо быть готовым к тому, что в любой момент развития появятся новые требования, некоторые старые требования изменятся, другие — отпадут. Но основная сложность управления процессом изменения требований не
4.4. ТРЕБОВАНИЯ К ПРОГРАММНОМУ ИЗДЕЛИЮ И ЖИЗНЕННЫЙ ЦИКЛ
в этом, а в том, что изменения одних требований влияет на другие и нужно отслеживать такие влияния. Влияние изменений требований естественным образом распространяется на все рабочие продукты проекта, в том числе на программные рабочие продукты.Любое предложение по развитию конструируемой системы может быть классифицировано как требование одного их трех видов:
1. дополнительное требование, которое отражает ранее не рассмотренный аспект системы;
2. модифицирующее требование, которое изменяет одно или несколько уже существующих требований;
3. отменяющее требование, принятие которого исключает одно или несколько уже существующих требований.
Разные виды требований анализируются по-разному. Целью анализа является прежде всего поддержка целостности системы требований: нахождение противоречий между требованиями и решение возникших проблемных противоречий. Следует отметить, что требования могут оказаться противоречащими не только друг другу, но и уже принятым проектным решениям.
Поэтому вопрос о том, принять или отклонить требование, является очень ответственным, зачастую влекущим за собой цепь связанных решений на всех уровнях проектирования. Чтобы ответ на него был обоснованным, необходимо выполнение как минимум двух условий:
1. требования должны быть заданы в виде, допускающем однозначное представление в моделях уровня анализа и конструирования, и способ такого представления должен быть унифицирован для всего проекта;
2. в проекте должны инструментально и организационно поддерживаться связи как между требованиями, так и между требованиями и другими компонентами рабочих продуктов.
Разберем оба этих условия.
Представление требований и пожеланий, исходящие от инициаторов работ, обычно ни в коей мере не способствует соблюдению первого условия.
Следовательно, они должны быть трансформированы, т. е. преобразованы к виду, приспособленному для анализа. Прохождение исходного требования
ГЛАВА 4. ЖИЗНЕННЫЙ ЦИКЛ
через последовательность трансформаций от одного представления к другому, сопровождающееся соответствующим анализом, называется трассировкой требования. Основное назначение трассировки в том, чтобы в любой момент развития проекта сохранялась целостность и непротиворечивость конструируемой системы, реализующей принятые требования9.Перейдем ко второму условию. В работах с меняющимися требованиями большое место занимает отслеживание связей проекта, благодаря которому планируется деятельность, необходимая как для непосредственной реализации требований, так и для распространения изменений, связанных с новыми требованиями, по проекту. Для такого отслеживания служат упоминавшиеся выше модели уровня проектирования, в которых выделяется подкласс моделей уровня анализа. Важнейшим технологическим инструментом согласования понятий, используемых в программной разработке, является глоссарий проекта. Глоссарий отражает текущее понимание проекта в целом и отдельных используемых в нем понятий. Глоссарий может пополняться на любой стадии трассировки требований, когда появляются новые понятия, смысловую трактовку которых нужно зафиксировать.Важно подчеркнуть, что когда разработчики игнорируют деятельность по ведению глоссария, система понятий проекта все равно складывается, но стихийность этого процесса приводит к дополнительным издержкам коммуникаций работников.
Трассировка — это основной инструмент анализа, проводимого в рамках управления изменениями требований. В первую очередь трассировке подвергаются требования, предъявленные первоначально, т. е. до того, как проект начал развиваться. Но было бы неправильно ограничиваться только ими, поскольку их связи с другими требованиями как явные, так и обнаруживаемые в ходе анализа, также требуют соответствующего анализа и других работ, связанных с реализацией требований.
В результате трансформаций строятся представления требований, вид которых приспособлен для выяснения целесообразности реализации требований. Если на некотором уровне трансформаций установлено, что данное треСледует обратить внимание на то, что целостность и непротиворечивость — не характеристика принимаемых требований, а качества, которыми должна обладать конструируемая система. При построении системы, предназначенной для практического применения, всегда решаются противоречия между требованиями. Противоречия предъявляемых требований есть следствие различий интересов инициаторов работ, именно они обычно становятся стимулом для поиска новых решений, для перехода от одной версии системы к другой, т. е. являются источником развития системы. Таким образом, общая картина в программировании точно такая же, как и в других областях реальной творческой деятельности человека.
4.4. ТРЕБОВАНИЯ К ПРОГРАММНОМУ ИЗДЕЛИЮ И ЖИЗНЕННЫЙ ЦИКЛ
бование отвергается, то дальнейшие преобразования его не производятся. В настоящий момент в методиках программирования выделяются следующие представления требований:1) Исходное представление — текстовое описание пожеланий к системе, заданное в свободной форме. Это описание, в частности, может фактически содержать одновременно несколько требований, отражающих разные аспекты проекта, — элементарные составляющие требования.
2) Унифицированные представления — исходное представление требования разбивается на элементарные составляющие, которые описываются в виде, приспособленном для дальнейшего использования на всех проектных уровнях. В частности, здесь могут применяться формализованные описания элементарных составляющих требований. Во всяком случае, на уровне унифицированного представления достигается однозначность понимания требований.
3) Типизированное представление — каждое из элементарных составляющих требования ассоциируется с некоторым типом данных. В результате формируется набор атрибутов элементарных требований и их значений. Эта информация допускает формальное сопоставление представлений данных, соответствующих новым элементарным требованиям, с данными, соответствующими требованиями, уже представленными в проекте. Сопоставление проводится на разных уровнях иерархии типов требований к системе. Более того, некоторые элементарнейшие аспекты смысла новых данных (обычно отражаемые мнемоническими идентификаторами) также могут проверяться почти формально.
4) Модельные представления уровня анализа — образы элементарных требований как элементы аналитических моделей системы: моделей ситуаций использования и динамики взаимодействий, которые используются для оценки требований.
Если требование принимается на уровне анализа, то трассировка продолжается на следующих уровнях, и можно говорить о продолжении последовательности трансформаций вплоть до реализации требования:
5) Модельные представления уровня конструирования — образы элементарных требований в диаграммах классов, состояний и других компонентах архитектуры системы. На этом уровне требования трансформируютГЛАВА 4. ЖИЗНЕННЫЙ ЦИКЛ ся или отклоняются в зависимости от их соответствия уже разработанной части проекта.
5) Программные представления — программные рабочие продукты и их фрагменты, которые рассматриваются в качестве образов требований, представленных очередной версией системы.
5) Документные представления — фрагменты документов, сопровождающих программный код и предназначенных для поддержки деятельности пользователей.
Схема на рис. 4.14 иллюстрирует приведенную последовательность трансформаций. Первые три представления требований изображены в виде совокупностей стрелок, которые при переходе от одного представления к другому становятся все более упорядоченными.
Иерархия типов требований представлена на рисунке следующим образом. Верхний уровень — это абстрактный тип, свойства которого присущи требованиям всех типов (они сводятся к стандартизованному набору операций объединения, пересечения атрибутов, сравнения значений атрибутов и др.). Можно сказать, что Табстр задает регламент, которого следует придерживаться при оперировании с требованиями. Следующий уровень содержит четыре обязательных типа: Тэкон, Тфунк, Тинт и Тэфф, которые объединяют требования экономического характера (пределы стоимости, рентабельность и пр.), функциональные требования, требования к интерфейсу и эффективности. Многоточием обозначены типы, которые добавляются из-за специфики проекта.
это конкретные типы, к которым приписываются элементарные составляющие требований (в скобках указаны их атрибуты).
Модельные представления уровней анализа и конструирования изображены в виде условных схем различных видов. Программные и документные представления — это текстовые файлы, пиктограммы которых показаны на рисунке.
Приведенная схема наглядно показывает то, что вынуждены делать разработчики для преодоления трудностей управления требованиями. Она может рассматриваться в качестве проекции жизненного цикла на задачи анализа
4.4. ТРЕБОВАНИЯ К ПРОГРАММНОМУ ИЗДЕЛИЮ И ЖИЗНЕННЫЙ ЦИКЛ
Рис. 4.14. Схема трансформации требованийГЛАВА 4. ЖИЗНЕННЫЙ ЦИКЛ
требований. Каждое требование, поступающее для анализа, проходит вполне традиционные этапы жизненного цикла, правда, в несколько специфичном виде: учитываются только те работы, которые имеют отношение к моделированию требований. Явное выделение задач управления требованиями способствует более успешному их решению.4.4.3. Учет трассировки требований в модели жизненного цикла При построении модели жизненного цикла следует указать этапы, когда производятся шаги, связанные с трассировкой. По этой причине следует различать два варианта работы с требованиями в объектно-ориентированном проекте:
1. Требование или группа требований обрабатываются до начала работ над итерацией;
2. Требование или группа требований поступают, когда работы итерации начались.
Первый вариант полностью укладывается в схему модифицированной модели фазы-функции (см. рис. 4.9, 4.10). Если требование (группа требований) принимается для данной итерации и используется при разработке сценария, который будет реализовываться (контрольные точки 2, 8), то указанные на схеме трассировки работы включаются в аналитическую и конструкторскую деятельность. В противном случае оно либо откладывается до последующих итераций, либо отклоняется.
Второй вариант прерывает последовательный процесс выполнения итерации — необходима немедленная реакция на поступающие требования, после которой (а во многих случаях и параллельно с которой) прерванный процесс выполнения итерации возобновляется. По существу, выполняется миницикл обработки требований, который нужно изобразить в качестве дополнительного элемента модели, описывающей итеративное развитие проекта с учетом трассировки. При этом в модели, как и в первом варианте, следует отразить возможные результаты анализа требования:
1. требование отклоняется — работа с требованием прекращается;
2. требование принимается к реализации на текущей итерации;
3. реализация требования откладывается до следующих итераций.
4.4. ТРЕБОВАНИЯ К ПРОГРАММНОМУ ИЗДЕЛИЮ И ЖИЗНЕННЫЙ ЦИКЛ
Анализ нового требования Требование реализуется на текущей итерации Рис. 4.15. Фазовое измерение модели жизненного цикла при объектно-ориентированном развитии проекта, дополненное обработкой требования в миницикле На рис. 4.15 показано фазовое измерение модифицированной матрицы Гантера (см. рис. 4.9, 4.10), дополненное мини-циклом обработки одного требования или группы требований, обрабатываемых совместно. Контрольные точки (события) в данной модели те же, что и в прежней матрице фазы — функции. При построении модели используется прием, который ранее (при учете итеративности в модели — см. § 4.3.2) был назван расщеплением линии жизненного цикла. Следует обратить внимание на прерывистую часть линии, ведущей от точки принятия решения к линии итеративного зацикливания. Она отражает, что для анализируемого требования, реализация которого отложена до одной из последующих итераций, работы этапа программирования не проводятся. Возобновление непрерывности линии указывает, что на этапе оценки для данного требования начинаются работы по обоснованию включения его в планы реализации одной из будущих итераций.Понятно, что в этой модели отобразить поток требований, поступающих при развитии проекта, невозможно (по этой причине на рисунке контрольные точки (a) и (b) выделены пунктиром). Постулируется, что все они обрабатываются в четыре этапа:
• поступление требования или группы требований (контрольная точка
ГЛАВА 4. ЖИЗНЕННЫЙ ЦИКЛ
• расщепление, переход к анализу;
• принятие решения (контрольная точка (b) на общем участке этапов анализа и конструирования);
• планирование срока или будущей итерации реализации.
4.4.4. Особенности первой итерации Модель жизненного цикла с мини-циклами обработки требований адекватно описывает процесс поставленной разработки проекта программного обеспечения в его стационарный период. Однако она не учитывает тот факт, что первая итерация всегда является особой. При ее выполнении закладываются основы сопутствующих проекту системы типов требований, глоссария и других составляющих поддержки процесса трансформации требований, которые в стационарный период используются.
Первую итерацию обычно характеризует следующее:
1. Разработчики еще не достигли достаточно глубокого понимания проблем предметной области, ее приоритетов и критериев;
2. Круг инициаторов работ, а значит, потенциальных консультантов сформировался далеко не окончательно. Следовательно, есть опасность начать делать не ту систему;
3. Мало информации о том, достаточно ли полон набор требований для объективного принятия проектных решений. Приходится работать на уровне гипотез (важное следствие предыдущих тезисов);
4. Еще не сформированы базовые элементы декомпозиции системы, которые должны стать точками последующего итеративного роста. Они являются первыми, а значит, пробными для реализации компонентами;
5. Если команда разработчиков формируется для данного проекта, то расстановка кадров может быть далеко не оптимальной;
6. Часто разработчики в начале проекта не вполне владеют методами, инструментами и т. п., как следствие, работа на первой итерации имеет учебный аспект.
4.4. ТРЕБОВАНИЯ К ПРОГРАММНОМУ ИЗДЕЛИЮ И ЖИЗНЕННЫЙ ЦИКЛ
Можно выделить и другие особенности первой итерации, которые обусловлены тем, что она задает направление развития проекта на все время будущей жизни программы. Неудачен выбор направления — осуществимость продуктивного итеративного наращивания возможностей программной системы сомнительна. Удачный выбор — это минимизация затрат на последующие переделки, реальная возможность использования принципа итеративного наращивания, облегчение решения задачи отслеживания связей и др.Для первой итерации с ее ближайшей проектной задачей роль этапов анализа и конструирования очень высока. Высока и цена ошибочных решений.
Осознание этого приводит к разработке специальных методов и подходов, которые целесообразно применять на первой итерации, а точнее, когда велика степень неопределенности выбора. Эти методы не только не исключают, но и предполагают переделку проектных решений, переписывание программного кода и т. д., т. е. отчасти нарушают основные каноны итеративного проектирования.
Отклонением от канонов на первой итерации следует признать и частичный возврат к традиционному принципу проектирования, постулированному для последовательно развиваемых программных проектов: не приступать к программированию, пока все требования к системе не будут переработаны в ее спецификации. Разница лишь в трактовке слов «все требования к системе». Для первой итерации итеративного проектирования эти слова означают предварительное накопление достаточного базового набора требований, который позволяет обеспечить надежную основу дальнейшего итеративного наращивания возможностей. Именно этот набор определяет первую ближайшую задачу, решаемую в начале развития проекта, с точки зрения архитектуры системы в целом.
Большинство требований на уровне анализа выражается в виде сценариев, которые надо реализовывать на данной итерации. Это в полной мере относится и к первой итерации. Но здесь исходный комплект сценариев играет две дополнительные роли:
• он рассматривается как один из способов изучения прикладной области. Разработчики согласуют реализуемые сценарии с инициаторами работ и, тем самым, уточняют свое понимание задач проекта, назначение системы;
• являясь аналитическим выражением базового набора требований, исходный комплект должен представлять этот набор репрезентативно, т. е.
так, чтобы обеспечивать надежное развитие проекта.
ГЛАВА 4. ЖИЗНЕННЫЙ ЦИКЛ
Очень важна еще одна особенность первой итерации: к оценке ее результатов нельзя подходить с позиций утилитарной полезности получаемых программных продуктов. Как следствие, смещаются критерии качества: на первый план выступают задачи демонстрации осуществимости проекта, продуктивности выбранного подхода и полноты базового набора требований с точки зрения итеративного наращивания. Иными словами, по указанным выше причинам трудно ожидать, что первая итерация приведет к реально работоспособному программному изделию, но правомерно требовать от нее доказательства того, что данная команда, используя данный метод в конкретных условиях, в дальнейшем приведет проект к успешным результатам. Это мнение должно сложиться у заказчиков, руководства и, что не менее важно, у работников в коллективе исполнителей.Большую часть особенностей первой итерации выразить в модели жизненного цикла не представляется возможным. Они влияют на выбор методов ведения проектов на первой итерации. В свою очередь, эти методы можно проецировать на модели жизненного цикла. В качестве примера одной из таких моделей ниже приводится схема, описывающая организацию начальных работ, которая принята в Центре объектно-ориентированных технологий фирмы IBM. Данный метод получил название «Сначала в глубину», что соответствует выбранной стратегии.
Суть метода состоит в том, что разработка первой итерации проводится мини-циклами реализации выбираемых сценариев. Используется два критерия отбора сценариев для мини-циклов:
• реализацию можно осуществить быстро и • получаемые результаты можно продемонстрировать наглядно и убедительно.
Полнота базового набора требований в методе «Сначала в глубину» достигается за счет анализа последовательно выполняемых мини-циклов для выделенных сценариев. Если в какой-то момент обнаруживается, что для полноты необходимо расширение исходного комплекта сценариев, то комплект пополняется.
На рис. 4.16 представлена еще одна модификация гантеровской модели жизненного цикла, отражающая развитие работ на первой итерации методом «Сначала в глубину». Модель модифицирована в следующих отношениях:
1. По сравнению со стационарным периодом время, отводимое для анализа и конструирования, существенно увеличивается за счет этапа проТРЕБОВАНИЯ К ПРОГРАММНОМУ ИЗДЕЛИЮ И ЖИЗНЕННЫЙ ЦИКЛ проекта Мини-циклы разработки сценариев Контрольные Совместная работа Модели сценариев построены 3’ Рис. 4.16. Фазовое измерение модели жизненного цикла при объектноориентированном развитии проекта методом «Сначала в глубину»
ГЛАВА 4. ЖИЗНЕННЫЙ ЦИКЛ
граммирования (конкретные временные соотношения зависят от особенностей выполняемого проекта и от условий его выполнения);2. На общей части этапов анализа и конструирования (контрольные точки 2, 3) выделяется явно работа по определению и утверждению базового набора требований и сценариев для реализации (группа сценариев обозначена овалом с внутренними незакрашенными кружками). Таким образом, появляется контрольная точка 2 ;
3. Этапы анализа и конструирования для выбранных сценариев выполняются совместно (жирная стрелка между овалами). В результате строятся модели сценариев (контрольная точка 3, модели обозначены закрашенными кружками), которые рассматриваются в качестве исходных данных для спецификации реализуемых компонентов (контрольная точка 5);
4. Для каждого из сценариев образуется мини-цикл его разработки. Разработка мини-циклов включает в себя перекрывающиеся этапы автономной работы и интеграции сценариев (контрольные точки 3, 5 и 5, 5. Фиксируется событие готовности результатов всех мини-циклов (контрольная точка 5 ), которое означает завершение формирования базового набора требований;
6. Интеграция сценариев предполагает ревизию (возможно и переписывание кода, и даже перепроектирование реализации каких-либо из сценариев). Она должна быть закончена к началу этапа пополнения базового окружения проекта в рамках оценки (контрольная точка 7);
7. Вместо подготовки к распространению системы для прикладного использования проводятся демонстрационные испытания, после завершения которых (контрольная точка 9) осуществляется переход к следующей итерации;
8. Прерывания процесса выполнения первой итерации для обработки дополнительных требований не допускаются. По существу это означает, что остается единственный вариант работы с такими требованиями: откладывание их анализа и реализации до последующих итераций.
4.4. ТРЕБОВАНИЯ К ПРОГРАММНОМУ ИЗДЕЛИЮ И ЖИЗНЕННЫЙ ЦИКЛ
Организация работ методом «Сначала в глубину» не предполагает предварительной подготовки элементов системы поддержки процесса разработки, которые зависят от прикладной области. Инструментальная часть этой системы вполне может быть универсальной, в частности, когда разработчики выполняют совместно не первый проект, это, скорее всего, так и будет, но информационная часть системы всегда определяется областью применения программного изделия.В ходе первой итерации к моменту выбора сценариев для реализации должна быть составлена предварительная, гипотетическая система типов требований, которая меняется под воздействием сведений, получаемых при выполнении мини-циклов, а также при интеграции сценариев. Итоговая система типов требований есть один из результатов этапа пополнения базового окружения проекта. Ситуация с глоссарием аналогична, с той лишь разницей, что нет необходимости до этого этапа фиксировать гипотетические положения о прикладной области, о составляемых моделях и т. п. Предварительно (до начала мини-циклов разрабоки сценариев) в глоссарий следует включить сведения о стратегии разработки, соглашения о технологических регламентах, т. е. все то, что носит универсальный характер.
Из приведенной модели видно, что метод «Сначала в глубину» дает хорошее представление о том, как происходит итеративное проектирование. Разработчики могут рассматривать мини-циклы в качестве прототипа итеративного наращивания, а поскольку каждый из мини-циклов обозрим, к концу первой итерации достигается решение задачи, о которой шла речь выше: доказательство того, что данная команда, используя данный метод в конкретных условиях, в дальнейшем приведет проект к успешным результатам.
4.4.5. Фаза завершения Завершение проекта редко описывают в моделях жизненного цикла структурно. Обычно этот период только обозначается, а его работы лишь классифицируются. Возможно, что разнообразие вариантов организации эксплуатационной поддержки препятствует систематическому их изучению. Не стимулирует изучение этих работ также неявное и не соответствующее действительности положение о том, что требования к системе, возникающие на фазе завершения, относятся уже к другому проекту. В то же время промышленная разработка программных систем всегда нуждается в организации как можно более скорых откликов на пользовательские запросы: рекламации, пожелания и требования развития.
ГЛАВА 4. ЖИЗНЕННЫЙ ЦИКЛ
В контексте изучения жизненного цикла с точки зрения обработки требований задачу моделирования фазы завершения можно описать в стиле, который был использован при учете трассировки требований. Цели этого моделирования следующие.a) Во-первых, это систематизация действий, которые необходимо выполнять в качестве реакции на пользовательские запросы.
b) Во-вторых, это явное разграничение реакций, относящихся:
i) к текущей версии, ii) к одной из следующих версий, iii) к другому проекту.
Для развития итеративных проектов такое разграничение очень важно из-за необходимости осуществлять одновременно и поддержку разных версий, и наращивание возможностей системы10.
Основой моделирования фазы завершения проекта (итерации) является обратная связь с пользователями системы. Это, для коммерческого софта, обычные сообщения о ходе эксплуатации: мнения, рекламации, пожелания, нарекания, претензии и т. п. Для открытого софта необходимо также добавить программные наработки и программные усовершенствования пользователей. Все подобные сообщения могут быть классифицированы, ранжированы по степени важности для развиваемого (на данной фазе — обслуживаемого) проекта. Но это обстоятельство в модели не учитывается: считается, что из пользовательских сообщений извлекаются требования к проекту в целом или к его итерации. Сообщения, а значит, и требования могут поступать в ходе эксплуатации в течение всего периода использования системы или ее версии. Требования нуждаются в трассировке, о которой шла речь выше. По этой причине в качестве отправного момента моделирования фазы завершения проекта (итерации) служит модель жизненного цикла, учитывающая трассировку.
В частности, в связи с необходимостью сочетания этих двух, часто концептуально противоречивых, требований, в UNIX-подобных системах появилась концепция совместимости с точностью до ошибок и уникальная система компоновки служебных программ разных версий для поддержки данного конкретного продукта. Этому способствует модульная система организации программного обеспечения в UNIX и LINUX, контрастирующая, например, с централизованной системой Windows, которая начинает саморазрушаться при одновременном использовании программ, требующих разных версий стандартных утилит и системных библиотек.
4.4. ТРЕБОВАНИЯ К ПРОГРАММНОМУ ИЗДЕЛИЮ И ЖИЗНЕННЫЙ ЦИКЛ
В представленной на рис. 4.17 модели описываются операционные маршНачальная Итеративное зацикливание Завершение проекта (итерации) группировка требований Решение о реализации требований принято (c) руты, возникающие в связи с обработкой одного требования в ходе эксплуатации программной системы. Для упрощения предполагается, что операционные маршруты не зависят от накапливаемой информации. Это заведомо огрубляющее предположение в том смысле, что принятие решений о требовании фактически делается не только на основании первичных установок, но и с использованием знаний о системе, пользователях, о текущем представлении о приоритетах и предпочтениях. Можно считать, что подобные сведения используются в точках разветвления операционных маршрутов, но то, как осуществляется такое использование, моделью не описывается.Аналогично организации мини-цикла для трассировки требований, в модели периода эксплуатации началом обработки является поступление сообщения о ходе эксплуатации системы, которое можно трактовать как содержащее требования (контрольная точка (a)). Это событие может возникать в любой момент периода сопровождения, т. е. обсуждаемая модель является естественным продолжением модели с обработкой требования в мини-цикле (см. § 4.4.3). Так как отобразить весь поток сообщений невозможно, рассматривается операционный маршрут только одного сообщения, при этом постулируется, что все сообщения обрабатываются подобно:
• поступление сообщения (контрольная точка (a));
ГЛАВА 4. ЖИЗНЕННЫЙ ЦИКЛ
• первичный анализ, в ходе которого из сообщения извлекаются требования. Этот период должен быть максимально кратким, ибо пользователю необходимо знать о реакции разработчиков на его сообщение.Результатом первичного анализа является принятие решения о требовании (контрольная точка (b), в которой происходит расщепление линии жизненного цикла). Возможны следующие не взаимоисключающие друг друга варианты такого решения:
– немедленная реакция — действия, направленные на быстрое устранение замечания (либо принятие предложения, в случае открытого софта), если это возможно, либо указание пользователю сроков, когда и как будут учтены поступившие претензии или предложения, либо указание путей (возможно, временных) преодоления трудностей. Немедленная реакция выполняется всегда, в том числе совместно с другими решениями;
– требование отклоняется — действия, указывающие пользователю причины отклонения требований и пути преодоления трудностей;
– реализация требования или предложения в текущей версии — если претензии обоснованы, а устранение замечаний, ошибок и т. п.
возможно в рамках обслуживаемой версии, то организуется мини-цикл обработки сообщения в итерации;
– реализация требования или предложения в одной из следующих версий — если устранение замечаний в рамках обслуживаемой версии невозможно или нецелесообразно, то сообщение передается для исполнения на одной из следующих итераций проекта;
– реализация требования или предложения в другом проекте — если выясняется, что в данном проекте выполнить требование или учесть предложение невозможно или нецелесообразно, то, быть может, оно станет одним из аргументов в пользу организации нового проекта.
• мини-цикл обработки сообщения начинается с анализа, цели которого обычны для итеративного развития проектов. В частности, определяется осуществимость реализации на данной итерации или целесообразность переноса ее на другую итерацию, образуется группа требований, которые должны быть реализованы совместно. Выработка этих решений о стратегии реализации требований приурочивается к контрольной точке (c). Особенностью анализа в данном случае является то, что он проводится, как возобновленный процесс, т. к. основные работы итерации уже выполнены;
• реализация отобранных требований или предложений на данной итерации осуществляется по обычной схеме, включающей конструирование и программирование и оценку. В качестве специфики следует указать на особую роль проверочных работ — дополнительный этап проверки реализации, который вкладывается в этап оценки. Эти работы обязательно должны включать повторение проверки того, что было отлажено ранее. Таким образом, пополнение базового окружения проекта приобретает дополнительное содержание: накопление тестовой базы проекта;
• распространение изменений (контрольная точка 10) — деятельность, направленная на то, чтобы сделанные исправления стали доступны для всех пользователей обслуживаемой версии. При массовом использовании программного изделия эта работа может потребовать значительных ресурсов.
Фаза завершения итерации включает этап окончания работ, содержание которого сводится к сворачиванию деятельности с данной версией программного изделия. Предварительное оповещение о наступлении этапа (контрольная точка 11) важно для пользователей, чтобы они смогли либо перестроиться, либо найти аргументы (в частности, ресурсы) в пользу продолжения сопровождения изделия. Как показывает практика, чтобы не потерять своих пользователей, очень часто приходится продолжать поддерживать весьма старые разработки, вкладывая в это солидные ресурсы.
ИТОГИ И ПЕРСПЕКТИВЫ
§ 4.5.Обсуждение жизненного цикла представлено в настоящей главе как последовательное развитие и уточнение понятий под влиянием потребностей развивающихся методов и технологий программирования. Однако из этого не должно складываться впечатление, что столь же прямолинейна историческая линия развития представления о том, какие этапы и как проходятся в течение жизни программы. Напротив, начиная с семидесятых годов XX столетия, когда сформировалась потребность в изучении жизненных циклов, и
ГЛАВА 4. ЖИЗНЕННЫЙ ЦИКЛ
до наших дней варианты их моделей все множатся и множатся. Причина тому — особенности проектов, требующие учета и организационно-технологической поддержки. В качестве иллюстрации этого тезиса далее упоминается лишь некоторые особенности, которые нашли свое отражение в реальных моделях жизненного цикла:• совместная разработка программного обеспечения и оборудования (общего или специального) назначения, • разработка программного обеспечения для встроенных систем, • разработка программного обеспечения по уже существующему прототипу, • разработка, включающая быстрое предварительное построение прототипа, • построение сетевых комплексов, • решение задач переиспользования программного обеспечения, • решение задачи развития программного обеспечения сообществом специалистов без формальной организационной структуры, • учет требований повышенной надежности разрабатываемых программно-аппаратных систем, • разработка адаптивных систем, • разработка систем с настраиваемым интерфейсом, • конструирование программных инструментов, • использование технологических сред для разработки программных систем (различные варианты моделей).
Этот список может быть продолжен. Так, обнаруживаются и отражаются в моделях особенности жизненного цикла долго и быстро живущих программ, длительных, средних и коротких проектов. Как показывает анализ моделей, предлагаемых для разных ситуаций, они лишь уточняют и дополняют общие положения, отслеживанию которых было уделено внимание в предыдущих разделах.
Среди всех мотивов моделирования жизненного цикла особое место занимает систематизация работ, выполняемых при разработке программного обеспечения. Систематизация — первый шаг на пути автоматизации любого производства, и в частности, производства программ. Следующие шаги определение технологических маршрутов деятельности работников данного производства, выявление узких мест, доступных для автоматизации, и разработка инструментов для них. Далее процесс развивается вширь и вглубь:
охватываются автоматизацией другие части технологических маршрутов, совершенствуются ранее построенные инструменты, формируются методы их эффективного применения. Последнее означает формирование новых технологий, и, как следствие, появляется потребность автоматизации новых видов деятельности, обусловленных данными технологиями. Наконец, наступает момент, когда совокупность потребностей в автоматизации, связанных, хотя и не обязательно напрямую, с первоначальной систематизацией, формирует качественно иную потребность в комплексной автоматизации. Это время появления стандартов и стандартных решений, интеграции сложившихся технологий и доработка того, что не вписывается в интегральную схему.
В предыдущем абзаце представлен эскиз формирования произвольного автоматизированного технологичного производства. Не является исключением и процесс технологизации производства программного обеспечения.
Есть, конечно, специфика, но пока еще не ясно, столь ли она значительна, чтобы считать данное производство чем-то исключительным11.
Первая стадия автоматизации программирования связана с поддержкой этапа программирования. Здесь проявляется и специфика: систематизация работ по производству программного обеспечения осуществлялась после осознания того, что поддержка кодирования, хотя и способствует росту производительности труда, но не является достаточным для промышленного конструирования программ. Появление на этой стадии систем программирования и всевозможных средств помощи в наборе текстов предшествовало периоду, когда начинали внедряться разного рода отладочные средства. Именно в это время (т. е. лишь к концу шестидесятых годов) в ответ на потребность в разработке больших и сложных программ было осознано понятие жизненного цикла.
Сразу же обнаружилось узкое место программирования как производХотя уже отмечены некотрые принципиальные отличия программистских задач от стандартных инженерных задач: в частности, для программирования отказывают отработанные на инженерных задачах методики решения проблем, такие, как ТРИЗ.
ГЛАВА 4. ЖИЗНЕННЫЙ ЦИКЛ
ства: неразвитость методологий этапа конструирования, с одной стороны, а с другой — невозможность сведения оценочных работ к тестированию. По существу это две стороны одной медали: нечеткость постановок задач на программирование влечет за собой большую часть трудностей этапа проверки.В результате сформулированной потребности в строгих спецификациях проекта появилось осознание того, что этап конструирования может быть технологически регламентирован. И удачные организационные технологии стали появляться. Чаще это специализированные технологии, предназначенные для разработки программ особого рода, но иногда и общие технологии, некоторые из них впоследствии стали автоматизированными (в качестве конкретного примера из этого ряда уместно указать на IDEF-технологию). Позднее стали формироваться регламентирующие методики работы с требованиями на этапе анализа. И хотя формализованных технологических процедур, допускающих полные автоматические проверки, в аналитической части добиться так и не удалось (что свидетельствует об объективной трудности данной области), прогресс заметен: сегодня можно говорить о поддержке накопления первичных требований и их систематизации, об отслеживании связей между требованиями и их реализациями в проекте и др.
Понятно, что описанный процесс не столь прям, как он представлен выше. В огромной мере на него влияли языкотворчество и тщетные надежды на то, что появление очередного “самого хорошего” языка или “самой прогрессивной” техенологии приведет к “решению всех проблем”. Для становления технологий производственного программирования наиболее заметными оказались методология структурного программирования, объектно-ориентированное программирование и GNU технология движения Open Source. Первая из них позволила опять осознать ограниченность способностей человека, на этот раз в связи с разработкой больших программ. Вторая — дала толчок к разработке методов декомпозиции, приспособленных для преодоления сложности. Третья — показала, как можно организовать технологию работы без привычной для производства централизации (это, видимо, громадный общепроизводственный и общекультурный вклад современной информатики в развитие общества, пока еще недооцениваемый в других отраслях производства). Итеративная разработка привела к необходимости модернизации основополагающих принципов проектирования программ и, в частности, к новому понятию жизненного цикла.
Но, несмотря на это и другие влияния, стадия комплексной автоматизации технологий программирования стала возможной только при соответствующем уровне развития техники, который позволил эффективно примеИТОГИ И ПЕРСПЕКТИВЫ нять выразительные графические возможности при выполнении технологических процедур конструирования программного обеспечения и поддерживать надежные и хорошо структурированные депозитарии понятий. Немаловажным обстоятельством, позволившим перейти к комплексной автоматизации, стало осознание того, что нельзя говорить реально о промышленном программировании без поддержки технологических функций на всех этапах жизни программ. Во 80-х годах XX века появился термин CASE-технология (Computer Aided Software Engineering — компьютерная поддержка разработки программ), которым стали обозначать использование систем, обладающих комплексными автоматизированными средствами поддержки разработки и сопровождения программ.
Замечено, что, впрочем, вполне объяснимо, что наиболее удачным оказалось использование CASE-систем в тех специальных областях, в которых уже были успехи и опыт технологичной практической работы, пусть даже лишь на организационном уровне, а также в тех случаях, когда специальная область уже была обеспечена надежной теоретической базой. В первую очередь здесь следует упомянуть о CASE-системах разработки баз данных в развитых реляционных СУБД (к примеру, Oracle Designer 2000 в системе Oracle). Успехи CASE-систем общего назначения скромнее, скорее всего по причине отсутствия универсальных методов, пригодных для развития любых проектов. Поскольку представление о модели жизненного цикла всегда является основой технологии, это еще раз подтверждает правомерность построения разнообразных моделей.
Сегодня универсальные CASE-системы строятся из расчета не всеобщего назначения, а в рамках применения развитых, но все-таки специальных методологий. Несомненный прогресс в данной сфере достигнут для проектирования, ориентированного на моделирование на этапах анализа и конструирования. В рамках объектно-ориентированного подхода разработан унифицированный язык моделирования UML (Unied Modeling Language), который претендует на роль основы проектирования в методологии итеративного наращивания возможностей программных систем (и является таковой для нынешнего ООП). На базе этого языка построен ряд CASE-систем общего назначения с развитыми средствами. Наиболее используемой из них является Rational Rose фирмы Rational Software, предложившей на рынок не только инструментарий для использования UML, но и комплексную методику производства систем — Rational Unied Process (RUP). Данная методика, конечно же, претендует на охват всех аспектов технологий современного программирования, но Вы уже знаете, как необходимо относиться к таким претензиГЛАВА 4. ЖИЗНЕННЫЙ ЦИКЛ ям. Уместно отметить, что в качестве CASE-системы Rational Rose обладает множеством средств, полезных для поддержки связи первых этапов проектирования с этапом составления программ (кодирования), а также с этапом оценки. В частности, проверяется, что моделирование на разных этапах согласовано, что модельные соглашения, определения классов, других элементов моделей и их взаимосвязи непротиворечивы. Уровень автоматического анализа высок настолько, что в принципе позволяет строить по моделям так называемые реализации по умолчанию. Это заготовки программного кода, включающие в себя описания классов и их методов в том виде, который можно извлечь из моделей. Программист дополняет заготовки фрагментами, детализирующими конкретную реализацию.
Построение реализации по умолчанию — не нововведение Rational Rose.
До этой системы оно активно применялось и в рамках систем визуального программирования, и еще раньше в специализированных CASE-системах, используемых, например, в развитых СУБД. Последнее примечательно: именно для СУБД удалось связать реализацию по умолчанию с графическими моделями информационных систем (ER-диаграммы). В Rational Rose и других UML CASE-системах поддерживается построение реализаций по умолчанию по моделям общего, а не специального назначения.
Реализация по умолчанию является лишь одним из приемов поддержки связей между этапами жизненного цикла разработки программного обеспечения с использованием Rational Rose. Именно идея комплексной поддержки связанности рабочих продуктов разных этапов, а не отдельные приемы, которые появлялись и ранее, — главное для данной CASE-системы. Программное воплощение этой идеи, пусть даже с существенными недоработками, следует отнести к явным достоинствам данного инструментария.
Проанализируем теперь отрицательные следствия рекламных претензий RUP на охват «всех рациональных технологий». Делается попытка механического объединения средств, инструментов и методов довольно многих «рациональных» подходов, но это приводит к эклектике, а для пользователя — к нефиксированной технологии, что по сути своей означает одно — отсутствие технологии. Применяя данную систему, пользователь обязан выстроить свои регламенты: когда, как и в каком качестве будут применяться те или иные средства, методы, инструменты. Если эти регламенты окажутся технологичными, то можно рассчитывать на поддержку Rational Rose, но, к сожалению, не в части проверки принимаемых для формируемой технологии соглашений. Затуманивается принципиальное ограничение, отделяющее области, где ентировано прежде всего на задачи, где заказчику важнее форма, чем содержание, и где успех проекта зависит в первую очередь от упаковки, и лишь во вторую — от качества начинки.
Вопросы, которые затрагивались в настоящей главе, освещены в многочисленных публикациях, посвященных технологии программирования. Большинство из них соответствуют скорее текущей конъюнктуре, чем сути проблемы. В качестве приятного исключения, как классическую работу, выдержавшую испытание времением, можно указать на книгу Ф. Брукса “The Mythical Man-Month. Essay on Software Engineering” (русский перевод первого издания, вышедшего в 1975г., см. в [14], юбилейного издания 1995г. — в [15]). Эта монография по праву считается одной из лучших книг не только по данной тематике, но и по программированию вообще. Сопоставление двух ее изданий явно показывает, что проблемы, которые приходится решать при управлении программными проектами, почти не изменились со времени перфокарт. Меняется только техническая поддержка.
Из ранних работ, не потерявших своей актуальности, прежде всего следует обратить внимание на монографию Гантера [23], содержащую, кроме представленной выше модели, много полезной информации для организации работ над программными проектами. Систематизированные сведения о понятии жизненного цикла и его применении в промышленном программировании можно найти в книге [52], которая к тому же дает представление о состоянии дел в этой области в СССР к началу восьмидесятых годов. Весьма обстоятельное исследование задач и методов проектирования и разработки программного обеспечения выполнено Боэмом. Его книга [11] постоянно цитируется и в наши дни.
Современное представление о технологии проектирования программных систем прочно связано с методологией объектно-ориентированного программирования. Всестороннее изложение данного подхода, его концепций, а также общих методов разработки проектов в объектно-ориентированном стиле можно найти в книге Буча [16]. UML и методы его использования в практической разработке программных проектов хорошо изложены авторами этого языка в монографии [17]. Понятия, связанные с CASE-технологиями, достаточно четко излагаются в работах [64, 18]. В частности, в последней из упомянутых публикаций достаточно подробно освещаются вопросы CASEтехнологий, связанных проектированием информационных систем.
Следующие ссылки помогут получить сведения об упомянутых выше конкретных разработках. Книга [70] дает наиболее полное представление о СУБД Oracle, в частности, об Oracle Designer 2000 и его месте в системе. IDEF-техГЛАВА 4. ЖИЗНЕННЫЙ ЦИКЛ нология хорошо представлена в документе [85]. Информацию о RUP в целом и Rational Rose в частности можно найти на сайте [88].
1. Модели традиционного представления о жизненном цикле: мотивация разных моделей, содержание этапов. Общепринятая и классическая итерационная модели.
2. Каскадная модель и ее мотивация. Понятия подтверждения, обзора, верификации и аттестации. Строгая каскадная модель.
3. Модель фазы-функции Гантера, ее мотивация и особенности. Понятие технологической функции и интенсивности ее выполнения. Расщепление линии жизненного цикла.
4. Принципы итеративного проектирования в сопоставлении с последовательным подходом. Этапы жизненного цикла при итеративном проектировании.
5. Модификация модели фазы-функции и ее мотивация. Особенности начальной и завершающей фаз. Переиспользование рабочих продуктов проекта. Интенсивности технологических функций при объектно-ориентированном проектировании.
6. Параллельное выполнение итераций. Виды технологического параллелизма. Области совмещения работ. Спиралевидные модели жизненного 7. Проблемы определения требований. Трассировка требований и понятие трансформации требований. Схема трассировки требований. Типизация требований. Глоссарий проекта.
8. Учет трассировки требований в модели жизненного цикла. Результаты анализа требований.
9. Особенности начальной итерации. Метод проектирования «Сначала в глубину» и его модель.
10. Фаза завершения проекта (итерации) и моделирование обработки требований в период эксплуатации системы.
11. Мотивация множественности моделей жизненного цикла. Модель жизненного цикла как основа построения технологии проектирования.
12. Специальные и универсальные модели и технологии. CASE-системы.
Понятие инструментальной поддержки технологии. Язык UML и методология RUP.
Структуры программирования Целью работы программиста всегда является создание программы, описывающей некоторый процесс. Программа есть структурное объединение своих составляющих: выражений, операторов и др. Но отношение “быть составленным из” — лишь формально-лингвистическая основа структуры программы. В дополнение к ней нужно рассматривать, в частности, содержательную структуру, когда структурные единицы, выделяемые в программе, отражают разбиение решаемой задачи на подзадачи. В этом случае говорят о декомпозиции программы. Не менее важно для работы с программой структурирование процесса ее выполнения, т. е. выделение в нем взаимодействующих статически заданных и динамически возникающих структурных единиц. При таком структурировании появляются процессы, вызовы процедур, экземпляры объектов и т. д. Наконец, еще одним измерением, с которым приходится иметь дело при составлении программ, является структурирование данных, перерабатываемых в ходе выполнения программы.
Все эти виды структур взаимосвязаны, при программировании они планируются совместно, и далеко не всегда можно разделить работу программиста, относящуюся к разным сторонам создаваемой системы. Если не брать в расчет декомпозицию задачи, которая относится к уровню проектирования, то с формальной точки зрения лингвистическое структурирование программы является первичным — программа строится как набор конструкций, иерархически соподчиненных и соединенных для выполнения процесса. В подавляющем большинстве случаев программа задается до того, как процесс будет запущен, и она определяет, какие входные данные и как будут перерабатываться в выходные, какие промежуточные данные при этом будут порождаться.
Не так уж редки (и концептуально важны) схемы вычислений, при которых используются вычислительные процессы, порождающие программы для дальнейшей обработки. Самый наглядный пример — компилятор, который воспринимает текст на языке программирования, перерабатывает его в последовательность команд конкретного компьютера, которая затем уже перерабатывает данные. В этом примере текст на языке программирования — это структура данных для компилятора, в процессе исполнения программы обрабатываемая до поступления других данных. Но отношение человека к программе и к другим данным соврешенно различно. Поэтому говорится о вычислителе, исполняющем программу на языке, отвлекаясь от того, что для обработки основной части данных строится другая, рабочая, программа. Это естественная идеализация вычислительного процесса, позволяющая раздельно обсуждать две структуры: программы и данных. Встречаются и такие, кажущиеся экзотическими обычному программисту, случаи (например, в Рефал, PROLOG, LISP), когда программа для обработки данных может строиться в ходе переработки части основных данных, и в зависимости от этих данных может появляться та или иная конкретизация программы дальнейшей обработки. Этому подходу уделено внимание в соответствующем месте (см. § 13.1, 13.2), а в данной части сосредоточим внимание на структурах, которые за десятилетия практики работы программистов стали общеупотребительными. При этом подчеркивается назначение каждой лингвистической структурной единицы и ее связи с лругими задачами, возникающими в ходе структурирования. Тем самым программистский опыт переводится на уровень знаний и метода с уровня умений и композиций эмпирических рецептов.
Методически обоснованной отправной точкой реализации указанной установки традиционно считается рассмотрение конструкций структурного стиля программирования. Нет никаких причин в данном курсе отказываться от этой традиции, тем более что так называемые структурные конструкции лежат в основании большинства других стилей, пусть даже в несколько ином понимании. Альтернативные стили и подходящие для них средства, которым отводится место в следующей части, удобно рассматривать на базе всестороннего изучения наиболее распространенного в современной практике стиля структурного программирования.
В данной части подробно разбирается построение программ в структурном стиле. Для обеспечения перекрестных ссылок и в соответствии с одной из главных наших задач: показать взаимосвязи и дать системную картину, в необходимых случаях затрагиваются модификации и понятия, естественно принадлежащие другим стилям.
Так как выражения являются основным строительным материалом программ данного стиля, изложение начинается с них. Затем рассматриваются основные структуры управления во взаимосвязи с другими компонентами программы, т. е. данными, потоками информации и призраками. Структура информационного пространства подробно разбирается там, где накопленный материал дает достаточные основания для разбора и дальнейшее продвижение без анализа этой структуры невозможно: в конце главы, посвященной циклам, и в начале главы, посвященной подпрограммам.
В конце суммируется материал, посвященный структурам данных.
Глава Выражения В современых алгоритмических языках важнейшую роль играют выражения: понятия, семантический смысл которых состоит в том, что они вырабатывают значения. Выражений в этом смысле слова нет лишь в некоторых языках, последовательно поддерживающих нетрадиционные стили (например, в Рефале). Простейшими выражениями являются имена и литералы.
Например, все осмысленные составные части следующей записи на языке C являются выражениями.
Рассмотрим подробнее структуру выражений. При этом, как всегда, мы сопоставляем абстрактно-синтаксическую и конкретно-синтаксическую структуру. Первая из них описывает текстовое строение выражения, а вторая — вычислительные аспекты. В обоих представлениях в качестве основного «строительного блока» выражений выделяются операции с их операндами.
Для большей части примеров программ в данном разделе использован язык С (точнее, соответствующее С подмножество языка С++). Переписывание программ на любой другой традиционный язык не вызвало бы никаких затруднений, но новых качеств изложению материала это бы не добавило. Вместе с тем, семантика конструкций С и, скажем, Pascal’я различается, и это подчеркивается в пояснительном тексте. Для освоения материала важно всегда вычленять суть абстрактных вычислений примеров, отделяя ее от прагматических наслоений. Именно этим обусловлен выбор С, который, являясь широко используемым, весьма прагматичным и непоследовательным языком, предоставляет много поводов для обсуждения решений, принимаемых в нем и в других языках.
ОПЕРАЦИИ
§ 5.1.Новые выражения составляются из более простых выражений посредством операций.
Определение 5.1.1. Операция — лексема языка, которая в абстрактно-синтаксическом представлении является такой составляющей некоторого выражения V, что остальные составляющие V сами являются выражениями, называемыми ее операндами.
Среди операций различаются префиксные — стоящие в конкретно-синтаксическом выражении перед операндами; постфиксные — стоящие после своих операндов; и инфиксные — стоящие между операндами.
Конец определения 5.1.1.
Пример 5.1.2. В выражении (5.1) вхождение ++ в x++ является постфиксом, в ++y — префиксом, из двух идущих подряд звездочек ** первая звездочка интерпретируется как инфикс, вторая — как префикс.
Конец примера 5.1.2.
Префиксные и постфиксные операции определяются в конкретно-синтаксической структуре выражения. Если же рассмотреть абстрактно-синтаксическую, то различия префиксных, постфиксных и инфиксных операций почти исчезают. В современных языках программирования префиксные и постфиксные операции одноместны. Но в трансляторах и системах символьных преобразований часто используются промежуточные2 представления выражений, которые включают многоместные префиксы или многоместные постфиксы. В таких представлениях все остальные типы операций исключаются. Если в Оговорка «почти» связана с тем, что операции ++ и языков C и Java выполняются по-разному в зависимости от того, применены они как префиксные или как постфиксные. В первом случае увеличение операнда производится до вычисления выражения, во втором — после него. В сочетании с совместностью вычисления операндов ‘нормальных’ операций, таких, как двуместный +, это может привести к двусмысленности записи, т. е. к тому, что ее значение не будет определено ни стандартом языка, ни даже наиболее распространенными реализациями, и целиком оставлено на “добрую” волю оптимизаторов.
Поэтому приведенный нами пример является примером плохого программирования, и на практике принято, что при записи выражений на C/C++ нужно иметь в операторе лишь одно присваивание.
Промежуточными представлениями либо значениями обычно называются такие, которые не видны ни на входе, ни на выходе системы.
представлении выражений все операции являются префиксами, оно называется польской записью, или прямой польской записью, если все операции — постфиксы, то обратной (инверсной) польской записью. Пример 5.1.3. Формула в польской записи выглядит как Можно строго доказать, что в случае операций с фиксированным числом аргументов польская запись всегда позволяет обойтись без скобок. Конечно, это уже не так, если, например, у нас есть одноместный и двуместный минус.
Конец примера 5.1.3.
Прямая и в особенности обратная польская запись есть конкретно-синтаксическое представление дерева вычислений выражения. Тем самым она максимально приближена к абстрактно-синтаксической структуре (если игнорировать коммутативность и ассоциативность обычных алгебраических операций). Поэтому она применялась в некоторых языках, претендовавших на прямое соответствие текстов программ их представлению в объектносм коде.
В частности, язык АЛМО использовал обратную польскую запись для представления выражений. В практически забытом сейчас языке APL,5 где конкретно-синтаксическое представление программы увязано со структурой набираемого на пиВпервые такую запись применили польские логики львовско-варшавской школы для логических формул, где в то время (20-е гг. XX века) еще не было груза традиций внешней формы.
АЛМО — русская попытка (1967 г.) создать то, чем затем стал язык C: язык высокого уровня, максимально приближенный к архитектуре машины. АЛМО до середины 80-х гг.
использовался как язык-посредник некоторых семейств трансляторов: программы сначала транслировались на АЛМО, а уже затем в машинные коды.
APL — язык, достаточно широко использовавшийся для написания небольших программ программистами-одиночками в системах разделения времени вплоть до середины 80-х гг.
XX в. В APL было множество операций, в записи часто представлявшихся наложением нескольких машинописных символов друг на друга при помощи возврата каретки; операции над элементами автоматически распространялись до операций над массивами; многие операции были совмещены с присваиваниями (оставшиеся в C рудименты этого, в частности, упоминавшиеся выше ++ и) и т. п. В итоге APL породил одно из древнейших племен хакеров — совершенно непонятные однострочечники — писавших любую программу в виде одной строки на APL, разобраться в которой было почти невозможно.
шущей машинке текста, принято другое, столь же последовательное, как и в польской записи, решение, также прямо увязывавшее последовательность операндов с вычислением выражения. Операции не имеют никаких приоритетов, они выполняются одна за другой в том порядке, в котором они напечатаны, и единственное средство изменить порядок вычислений — скобки. Например, выражение A*B+C*D понималось как математическая формула Простейшие операции практически во всех современных языках програмA B + C) D.
мирования имеют стандартное изображение. Это +,,, которые применяются и для действительных, и для целых чисел, и интерпретируются единообразно.
Операцию деления стоит рассмотреть внимательнее. Интерпретация деления для действительных и целых чисел в подавляющем большинстве языков программирования принципиально различается. Для действительных чисел вычисляется приближение к частному двух чисел. Для целых чисел обычно производится деление нацело, причем для положительных чисел все ясно: берется целая часть частного, а вот с какой стороны будет приближение для отрицательных чисел... тут может быть самое безумное решение. Коегде (например, в языках Pascal и Алгол-68) явно разделяется целочисленное деление и деление с действительным результатом. Обычно для действительного деления остается символ операции /, а для целочисленного вводится новая операция, например, div. Поэтому результаты вычислений операторов где x, a, b — целые переменные, могут быть различны.
Аналогичный эффект можно получить и в языке С, но здесь всплывает в явном виде еще одно важное понятие современных языков.
В первом присваивании явно указано, что аргументы переводятся в действительную форму, и, соответственно, выполняется деление действительных чисел с действительным результатом. Затем этот результат переводится обратно в форму целого числа, при этом обычно он усекается отбрасыванием дробной части. Как и всегда, единства здесь нет. Кое-где он округляется, кое-где дробная часть просто Операции, семантика которых состоит в переводе значения из одной формы в другую, называются приведениями. Операции приведения могут быть как явными (в таком случае практически во всех современных языках они имеют синтаксическую форму TYPE(), берущую начало из Алгола-68), либо неявными. Например, если у нас есть присваивание где d —переменная типа double, x — типа oat, n — типа int, то сначала целое n будет преобразовано в действительное одинарной точности, а затем результат умножения — в действительное число двойной точности. Таким образом, на абстрактно-синтаксических структурах операторов должны появляться еще и новые вершины — приведения значений к нужному типу.
Наряду с целочисленным делением вводится и операция взятия остатка, но она также может быть самым непоследовательным образом определена для различных комбинаций отрицательного делимого и отрицательного делителя. В C операция взятия остатка обозначается %, а в языке Pascal — Операция возведения в степень также практически везде, где она предуmod.
смотрена, обозначается, но ее интерпретация сильно зависит и от языка программирования, и от конкретной его реализации. Например, возведение действительного числа в целую степень в некоторых реализациях делается через умножение, а в других — показатель переводится в действительную форму и применяются стандартные функции exp и log. Существовали7 даже такие реализации, в которых возведение целого числа в целую степень делалось при помощи перевода агументов в действительную форму, и, соответственно, вычисление (1) n могло привести к ошибке. Этот пример показателен: стремление к универсализации не всегда благо. Так что возведением в степень там, где эта операция предусмотрена, во избежание неприятностей лучше пользоваться лишь для целых чисел и для возведения положительных действительных чисел в целую степень.8 В языках C и Pascal от операции возведения в степень просто отказались.
Некоторые операции определяются лишь через конкретные машинные отбрасывается. Определяться это может или в стандарте языка, или в описании конкретной среды программирования. Особенно внимательными надо быть в тех случаях, когда делитель либо делимое отрицательны.
А, может быть, существуют и сейчас.
Например, из стандарта языка FORTRAN-90 нельзя понять, чему равно выражение 0**0.
Разные трансляторы генерируют код, приводящий к разным результатам его вычисления.
представления операндов. Таковы, в частности, поразрядные логические операции в общераспространенных языках программирования. В языке C это операции &, |, ~, ^. Для их понимания нужно помнить, что С рассчитан только на такие машины, в которых целые числа (да и другие объекты) имеют машинное представление в виде последовательности битов. Результат операции & — число, каждый двоичный разряд которого получается обычной булевской конъюнкцией соответствующих разрядов операндов. Соответственно, | означает поразрядную дизъюнкцию, ~ — поразрядное отрицание (инвертирование всех битов), ^ — поразрядное исключающее или.
Операции над машинным представлением особенно эффективны в тех случаях, когда число на самом деле используется как ящик, куда засунуто несколько независимых значений, каждое из которых занимает несколько двоичных разрядов.
Аналогичные операции в языке Pascal обозначаются and, or, not, xor.
Язык C отличается фантастической непоследовательностью в употреблении символов операций. Один и тот же символ операций имеет абсолютно разный смысл, когда он употребляется как бинарная операция и как унарная.
Например, * как бинарная операция означает умножение, а как унарная — взятие значения по данному адресу. & как бинарная операция означает поразрядную конъюнкцию машинных представлений своих операндов, а как унарная — получение адреса стоящего после нее имени. Даже столь безобидная операция, как унарный +, может привести к изменению значения изза неявного преобразования в другой тип.
В языках C++, Ada и Алгол-68 все операции, имеющиеся для некоторого типа, могут переноситься на любой другой тип данных, определенный в программе. Для этого необходимо явно определить их смысл для аргументов нового типа. В Алголе-68 имеется даже внутренняя система определения новых операций.
ЛОГИЧЕСКИЕ ВЫРАЖЕНИЯ
§ 5.2.Рассмотрение начнем с примера. Пусть требуется написать программу, которая печатает «Да», если точка принадлежит области S, и «Нет» — в противном случае. Область S образована пересечением следующих «простых»
областей (см. рис. 5.1, где данная область выделена штриховкой):
Точка принадлежит области, если она принадлежит пересечению областей O1, O2 и O3.
Таким образом, задачу можно свести к следующим действиям:
1. проверить принадлежность точки области O1;
2. если свойство выполняется, то проверить его для O2;
3. если опять свойство выполняется, то проверить его для O3;
4. напечатать «Да», если опять получится истина;
5. во всех остальных случаях печатать «Нет».
Понятно, что порядок проверок может быть и другим — результат от него не зависит.
Прежде всего надо договориться, как будет представляться точка в программе. Типа данных «Точка» в языке С нет, следовательно, его надо моделировать имеющимися средствами. Представим точку парой вещественных чисел X и Y. Эти числа — декартовы координаты точки на плоскости. Представление областей в программе — это задание соответствующих им определяющих отношений над координатами, а проверка принадлежности точки области — условие соответствующего условного оператора.
Программа 5.2. \* Проверка принадлежности области *\ #include int main() printf( "Введите координаты в виде :");
scanf( "%f% %f", &x, &y );
В этом описании нашли свое отражение следующие свойства нашего конечного автомата (за основу взята таблица конечного автомата из § 10.2.3):
• Конечный автомат (тег ) включает в себя теги — перечень всех состояний в любом порядке а также теги — действие и — ссылка на исходное состояние13.
• Каждый содержит атрибут name, чтобы на него можно было ссылаться, и набор условий: один тег и любое количество ( 0) тегов (означающих “else if”). Эти два тега также можно было бы заменить одним универсальным, тем более что структура их потомков не различается, но опять же этого не сделано по соображениям облегчения форматирования.
• Каждый или включает в себя три тэга: — собственно условие и уже описанные теги и — действие при выполненном условии и ссылка на следующее состояние.
• Теги и содержат специальный тег для включения строк на языке C++.
Можно было бы оформить эти тэги с помощью особого состояния Init, но при этом мы бы потеряли универсальность тэга, т. к. состояние Init, например, не нуждается в чтении символа, не содержит условий и должно быть всегда первым. А, как будет понятно в последствии, универсальность составных частей модели сильно упрощает работу с ней.
ГЛАВА 10. МЕТОДЫ ПРОГРАММИРОВАНИЯ ОТ СОСТОЯНИЙ
В описание не включено состояние Exit. Это сделано (а точнее не сделано) по причине того, что эта часть является статичной (одинаковой у различных автоматов), а потому нецелесообразно ее индивидуализировать и явно описывать.Данное описание является основой для построения различных визуализаций автомата с помощью XSL. Например, достаточно просто строится табличная визуализация, практически не отличающаяся от ранее составленной таблицы:
Программа 10.5. Следующая визуализация — это автоматическое преобразование XML основы в программу на языке С/С++. Стоит обратить внимание на то, что результирующий текст оказывается практически тем же самым, что и в программе 10.2.4). Причины тому глубже, чем простота именно такой интерпретациипреобразования основы. Сама осуществимость локального (без использования контекстно-зависимой информации) описания следует из структуры конечного автомата, не требующей для указания перехода ничего вне состояния. И именно это свойство предопределяет простоту локального описания.
Программа 10.5.
ГЛАВА 10. МЕТОДЫ ПРОГРАММИРОВАНИЯ ОТ СОСТОЯНИЙ
Как легко видеть, разница этой и предыдущей визуализаций только в том, что там, где в таблице вставляются графические элементы, которые изображают рамки, образующие саму таблицу, здесь находятся части синтаксиса языка С++/C# и отступы для лучшей читаемости получаемой программы.Итак, благодаря использованию формата представления данных XML, мы получили возможность автоматически создавать программы для данной задачи путем простой замены стилевых таблиц!
Отметим теперь еще несколько интересных возможностей, которые мы бы могли использовать.
Хотя данная технология позволяет легко создавать С++/С# программы, основой для них остается язык XML и чтобы составить новый автомат, программист должен как минимум знать синтаксис этого языка. Следующим естественным шагом будет исключение этого звена: требуется скрыть внутреннее представление данных от конечного пользователя, оставив только интуитивно понятное представление в виде таблицы. Но для этого в первую очередь необходимо выбрать:
• преобразование таблица => XML представление и • средство для удобного редактирования таблиц.
Естественным будет редактировать таблицы там же, где мы их уже научились генерировать — в окне браузера. Доступ к редактированию этих таблиц может предоставить упомянутый не стр. 786 DOM (стандарт, реализованный в браузерах Internet Explorer 5.0 и Netscape Navigator 6.0). Изменения, добавления и другие редактирующие действия определяются довольно просто.
10.5. ПРОГРАММНЫЕ ПРЕДСТАВЛЕНИЯ ГРАФА СОСТОЯНИЙ Например, на языке Java script добавление новой ячейки в таблицу можно описать следующим образом:
var oRow;
var oCell;
oRow = oTable.insertRow();
oCell = oRow.insertCell();
oCell.innerHTML = "This cell is new."
Точно так же можно создавать таблицы, строки и ячейки в них. Реализация же самого интерфейса (кнопочек, средств выделения, полей ввода) зависит только от вашей фантазии.
Тот же DOM, точно таким же образом, может работать с XML, реплицируя все действия конечного пользователя с таблицей в XML представление для последующей записи последнего в виде готового файла со структурой нового конечного автомата.
Еще одним шагом в развитии проекта, использующим язык XML может быть формализация используемого представления: ведь как только определены все теги представления, правила их вложения и способы задания, тем самым получился новый язык (по аналогии с современными языками, построенными таким же образом, мы можем назвать его “automatML”). Пока теги и элементы XML используются исключительно ради удобства для вашего собственного проекта (как если бы вы использовали CSS на своей домашней страничке), то не имеет никакого значения, что вы даете этим элементам и тегам имена, смысл которых отличается от стандартного и известен только вам. Если же, с другой стороны, вы хотите предоставлять данные внешнему миру и получать информацию от других людей, то это обстоятельство приобретает огромное значение. Элементы и атрибуты должны употребляться вами точно так же, как и всеми остальными людьми, или, по крайней мере, вы должны документировать то, что делаете.
Для этого придется использовать определения типов документов (Document Type Denition, DTD). Хранимые в начале файла XML или внешним образом в виде файла *.DTD, эти определения описывают информационную структуру документа. DTD перечисляют возможные имена элементов, определяют имеющиеся атрибуты для каждого типа элементов и описывают сочетаемость одних элементов с другими.
Каждая строка в определении типа документа может содержать декларацию типа элемента, именовать элемент и определять тип данных, которые
ГЛАВА 10. МЕТОДЫ ПРОГРАММИРОВАНИЯ ОТ СОСТОЯНИЙ
элемент может содержать. Она имеет следующий вид:Например, декларация определяет элемент с именем action, содержащий символьные данные (т. е.
текст). Декларация определяет элемент с именем special_report, содержащий подэлементы state_1, state _2 и state_3 в указанном порядке, например:
XML: время пришло XML превосходит самое себя Управление сетями и системами с помощью XML После определения элементов DTD могут также определять атрибуты с помощью команды !ATTLIST. Она указывает элемент, именует связанный с ним атрибут и затем описывает его допустимые значения. !ATTLIST позволяет управлять атрибутами и многими другими способами: задавать значения по умолчанию, подавлять пробелы и т. д. DTD могут также содержать декларации !ENTITY, где определяются ссылки на объекты, а также декларации !NOTATION, указывающие, что делать с двоичными файлами не в формате Серьезное и несколько удивительное ограничение DTD состоит в том, что XML.
они не допускают типизации данных, т. е. ограничивают данные конкретным форматом (таким, как дата, целое число или число с плавающей точкой). Как вы, вероятно, уже заметили, DTD используют иной синтаксис, нежели XML, и не очень-то интуитивно понятны. По названным причинам DTD будут, видимо, заменены более мощными и простыми в использовании схемами XML, работа над которыми ведется в настоящее время.
Возможно, вам приходилось слышать определения ‘правильно составленный’ (well-formed) и ‘действительный’ (valid) применительно к документам XML. Документ является правильно составленным, если для каждого открывающего тега имеется соответствующий закрывающий тег, а накладывающиеся теги отсутствуют. (Таким образом, большая часть документов HTML составлена неправильно.) Документ является действительным, если он содержит DTD и соответствует его правилам.
ПЕРЕХОД ОТ ДАННЫХ К КОНЕЧНОМУ АВТОМАТУ
§ 10.6.Таблицы переходов и состояний являются методом программирования не только для задач, сводящихся к конечным автоматам. При обсуждении синтаксических таблиц и XML/XSL подхода к задаче стандартизованного представления таблиц переходов были указаны возможности применения методики оперирования со структурными представлениями данных и программ для более широкого класса алгоритмов.
Однако мы пока не решали задачи, когда какое-либо представление алгоритма зависит от входных данных. Эта задача расклассифицирована для автоматов как задача динамического порождения автомата (см. § 10.5, пункт 3 на стр. 613). Конечно же, под таким углом зрения можно рассматривать трансляцию: текстовый файл на входном языке есть часть данных, фиксирующая план обработки другой части данных, которая предъявляется для решения конкретной задачи. Задача специализации универсальной программы (см. § 3.9.3) также может рассматриваться как уточнение общего плана, исходя из частичного знания обрабатываемых данных. Упомянутые случаи характеризуются тем, что представление алгоритма, зависящее от части входных данных, строится из заранее определенных заготовок. Например, для трансляции такими заготовками являются алгоритмы выполнения абстрактно-синтаксического представления программы.
В данном разделе показан иной метод построения алгоритма, зависящего от входных данных. Его идея не в компиляции некоего объектного кода из заготовок, комбинируемых по принципу домино, а прямом составлении такого представления алгоритма, которое допускает непосредственную интерпретацию. Естественный путь демонстрации метода — взять за основу известный класс алгоритмов, конкретный представитель которого выбирается, исходя из знания о входных данных.
Будем решать задачу, которая для каждого конкретного случая решается с помощью конечного автомата специального вида (как и всегда, выбор конкретного представления сильно влияет на сложность и другие характеристиГЛАВА 10. МЕТОДЫ ПРОГРАММИРОВАНИЯ ОТ СОСТОЯНИЙ ки программы, и автоматическое применение ранее использованных представлений в других задачах не рекомендуется).
Пусть требуется подсчитать, сколько раз каждое из вводимых слов встречается в некотором большом файле (теперь слово — это любая последовательность символов). 1, 2,..., n — вводимые слова; i = a1 a2... aki — слово. Напечатать:
Число вхождений 1 =, Число вхождений 2 =, Число вхождений n = где — полное число вхождений слова k в файл с учетом возможного перекрытия слов (например, в строке “*МАМАМА{}” два вхождения слова “МАМА”).
Для заданных заранее слов легко построить граф, каждая вершина которого представляет символы внутри слов. Его вершины помечены символом.
Из такой вершины исходят две дуги: первая указывает на вершину, к которой следует переходить, когда очередной читаемый символ совпадает с пометкой вершины, а вторая — на вершину, которая должна стать преемником данной в случае несовпадения. Легко видеть, что это одна из форм представления конечного автомата, каждое состояние которого кодирует множество всех вершин, связанных дугами второго вида, а состояния-преемники определяются дугами первого вида исходного графа. Чтобы этот автомат работал, т.е. решал поставленную за-дачу нужно снабдить его действиями, которые сводятся к увеличению счетчиков, соответ-ствующих найденным словам, а также определить начальное и конечное состояния. Мы не будем заниматься переделкой исходного графа, поскольку такая его форма удобнее для интерпретации.
Если дуги первого вида изображать стрелками, исходящими в горизонтальном на-правлении, дуги второго вида - вертикальными стрелками, а действия со счетчиками - соответствующими пометками при дугах, то, например, для множества слов 1) МАМА, 2) МАШИНА, 3) ШИНА, 4) МАТ, 5) НА может быть построен граф, показанный на рис. 10.6.