Справочник от Автор24
Поделись лекцией за скидку на Автор24

Технология программирования. Проектирование программного обеспечения

  • ⌛ 2014 год
  • 👀 511 просмотров
  • 📌 484 загрузки
  • 🏢️ ФГБОУ ВПО «СГГА»
Выбери формат для чтения
Загружаем конспект в формате pdf
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Конспект лекции по дисциплине «Технология программирования. Проектирование программного обеспечения» pdf
Министерство образования и науки Российской Федерации Федеральное государственное бюджетное образовательное учреждение высшего профессионального образования «Сибирская государственная геодезическая академия» (ФГБОУ ВПО «СГГА») Кафедра прикладной информатики и информационных систем КОНСПЕКТ ЛЕКЦИЙ УЧЕБНОЙ ДИСЦИПЛИНЫ ТЕХНОЛОГИЯ ПРОГРАММИРОВАНИЯ ДЛЯ направления подготовки дипломированного специалиста специальность – 230400 Информационные системы и технологии квалификация – Бакалавр Новосибирск 2014 г. Технология программирования Бугаков П.Ю. ОГЛАВЛЕНИЕ ВВЕДЕНИЕ ...................................................................................................................... 5 РАЗДЕЛ I. ТЕХНОЛОГИИ ПРОЕКТИРОВАНИЯ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ ...................................................................................................................... 9 1.1. Основные понятия ................................................................................................ 9 1.1.1. Программное обеспечение как изделие........................................................ 9 1.1.2. Надежность программного обеспечения .................................................... 12 1.2. Жизненный цикл программного обеспечения ................................................. 12 1.2.1. Процессы ЖЦ ПО ......................................................................................... 12 1.2.2. Основные процессы ЖЦ ПО: ...................................................................... 14 1.2.3. Вспомогательные процессы ЖЦ ПО: ......................................................... 19 1.2.4. Организационные процессы ЖЦ ПО: ......................................................... 23 1.2.5. Программное обеспечение с малой и большой длительностью ЖЦ ...... 25 1.2.6. Стадии и этапы ЖЦ ПО ............................................................................... 30 1.2.7. Модели жизненного цикла ПО .................................................................... 33 1.3. Анализ и разработка требований к программному обеспечению ................. 37 1.3.1. Классификация требований ......................................................................... 37 1.3.2. Разработка требований ................................................................................. 38 1.4. Проектирование программного обеспечения .................................................. 43 1.4.1. Общие принципы разработки программ .................................................... 46 1.4.2. Основные принципы проектирования ........................................................ 48 1.4.3. Сквозная функциональность ....................................................................... 49 1.4.4. Сцепление и связность ................................................................................. 49 1.4.5. Признаки плохого проекта ........................................................................... 51 1.4.6. Системный подход и программирование ................................................... 51 1.4.7. Общесистемные принципы создания программ ........................................ 52 1.4.8. Особенности программных разработок...................................................... 53 1.4.9. Стандарты и программирование ................................................................. 54 1.4.10. Унифицированный язык моделирования (UML) .................................... 55 РАЗДЕЛ II. ТЕХНОЛОГИЯ И СТИЛЬ ПРОГРАММИРОВАНИЯ ......................... 57 2.1. Архитектурные стили ......................................................................................... 57 2.1.1. Объектно-ориентированная архитектура ................................................... 57 2.1.2. Компонентная архитектура.......................................................................... 58 2.1.3. Многослойная архитектура ......................................................................... 59 2.1.4. Проектирование на основе предметной области ....................................... 60 2.1.5. Архитектура клиент-сервер ......................................................................... 60 2 Технология программирования Бугаков П.Ю. 2.1.6. Многоуровневая/трехуровневая архитектура ............................................ 62 2.1.7. Сервисно-ориентированная архитектура ................................................... 62 2.1.8. Шина сообщений .......................................................................................... 63 2.2. Методы проектирования ПО ............................................................................. 65 2.2.1. Модульное программирование.................................................................... 65 2.2.2. Основные характеристики программного модуля .................................... 65 2.2.3. Методы разработки структуры программы ............................................... 67 2.3. Тестирование и отладка программного средства ............................................ 71 2.3.1. Принципы и виды отладки программного средства ................................. 71 2.3.2 Заповеди отладки программного средства.................................................. 73 2.3.3. Автономная отладка программного средства ............................................ 73 2.3.4. Комплексная отладка программного средства .......................................... 74 2.4. Документирование программных средств ....................................................... 75 2.4.1. Документация, создаваемая и используемая в процессе разработки программных средств. .................................................................................................... 75 2.4.2. Пользовательская документация программных средств .......................... 76 2.4.3. Документация по сопровождению программных средств ....................... 77 2.5. Структурное программирование ....................................................................... 78 2.6. Метод функционального моделирования SADT ............................................. 79 2.6.1. Общие сведения ............................................................................................ 79 2.6.2. Моделирование потоков данных (процессов). Общие сведения ............. 80 2.7. Объектно-ориентированное программирование ............................................. 84 РАЗДЕЛ III СТРУКТУРЫ ДАННЫХ ......................................................................... 87 3.1. Классификация структур данных ...................................................................... 87 3.2. Простые, базовые структуры ............................................................................. 88 3.3. Статические структуры ...................................................................................... 89 3.3.1. Массивы ......................................................................................................... 89 3.3.2. Записи............................................................................................................. 90 3.3.3. Стеки .............................................................................................................. 91 3.3.4. Очереди .......................................................................................................... 94 3.3.5. Деки ................................................................................................................ 96 3.3.6. Строки ............................................................................................................ 97 3.4. Динамические структуры данных ..................................................................... 98 3.4.1. Связные списки ............................................................................................. 98 3.4.2. Связные линейные списки ........................................................................... 99 3.4.3. Машинное представление связных линейных списков ............................ 99 3 Технология программирования Бугаков П.Ю. 3.5. Нелинейные структуры данных ...................................................................... 101 3.5.1. Нелинейные разветвленные списки .......................................................... 101 3.5.2. Графы ........................................................................................................... 102 3.5.3. Деревья ......................................................................................................... 104 3.5.4. Логическое представление и изображение деревьев .............................. 105 РАЗДЕЛ IV АЛГОРИТМЫ СОРТИРОВКИ............................................................. 107 4.1. Сортировка массивов ........................................................................................ 107 4.2. Внутренняя сортировка ................................................................................ 109 4.2.1. Сортировка массива простым выбором ................................................... 109 4.2.2. Сортировка массива простыми включениями ......................................... 110 4.2.3. Сортировка массива простым обменом (метод "пузырька") ................. 112 4.3. Внешняя сортировка ......................................................................................... 113 4.3.1. Общий алгоритм сортировки слиянием ................................................... 114 4.3.2. Сортировка простым слиянием ................................................................. 114 4.3.3. Сортировка естественным слиянием ........................................................ 115 РАЗДЕЛ V АЛГОРИТМЫ ПОИСКА ....................................................................... 117 5.1. Алгоритмы поиска в линейных структурах ................................................... 117 5.2. Последовательный (линейный) поиск ............................................................ 117 5.3. Бинарный (двоичный) поиск ........................................................................... 119 5.4. Алгоритмы поиска на основе деревьев ........................................................... 120 5.4.1. Двоичные (бинарные) деревья .................................................................. 121 5.4.2. Двоичные упорядоченные деревья ........................................................... 121 5.4.3. Случайные деревья ..................................................................................... 122 5.4.4. Оптимальные деревья................................................................................. 123 5.4.5. Сбалансированные по высоте деревья ..................................................... 123 5.4.6. Деревья цифрового (поразрядного) поиска ............................................. 125 5.5. Хеширование ..................................................................................................... 127 5.5.1. Методы хэширования для поиска в основной памяти ............................ 128 5.5.2. Коллизии при хэшировании и способы их разрешения ......................... 128 5.5.3. Линейное зондирование ............................................................................. 129 5.5.4. Двойное хэширование ................................................................................ 131 5.5.5. Использование цепочек переполнения ..................................................... 132 РАЗДЕЛ VI. ВЫЧИСЛИТЕЛЬНЫЕ АЛГОРИТМЫ ............................................... 133 6.1. Определение сложности программ ................................................................. 133 Литература ................................................................................................................... 140 4 Технология программирования Бугаков П.Ю. ВВЕДЕНИЕ Проектирование информационных систем (ИС) — логически сложная, трудоемкая и длительная работа, требующая высокой квалификации участвующих в ней специалистов. Однако до настоящего времени проектирование ИС нередко выполняется на интуитивном уровне неформализованными методами, включающими в себя элементы искусства, практический опыт, экспертные оценки и дорогостоящие экспериментальные проверки качества функционирования ИС. Кроме того, в процессе создания и функционирования ИС информационные потребности пользователей постоянно изменяются или уточняются, что еще более усложняет разработку и сопровождение таких систем. В начале 70-х гг. в США был отмечен кризис программирования (software crisis). Это выражалось в том, что большие проекты стали выполняться с отставанием от графика или с превышением сметы расходов, разработанный продукт не обладал требуемыми функциональными возможностями, производительность его была низка, качество получаемого программного обеспечения не устраивало потребителей. Так, например, в 1995 г. компания Standish Group проанализировала работу 364 американских корпораций и итоги выполнения более 23 тыс. проектов, связанных с разработкой ПО, и сделала следующие выводы. 16,2% проектов завершились в срок, не превысили запланированный бюджет и реализовали все требуемые функции и возможности; 52,7% проектов завершились с опозданием, расходы превысили запланированный бюджет, требуемые функции не были реализованы в полном объеме; 31,1% проектов были аннулированы до завершения. Для проектов, которые завершились с опозданием или были аннулированы до завершения, бюджет среднего проекта оказался превышенным на 89%, а срок выполнения - на 122%. В 1998 г. процентное соотношение проектов лишь немного изменилось в лучшую сторону (26%, 46% и 28% соответственно). В числе причин возможных неудач фигурируют: нечеткая и неполная формулировка требований к ПО, недостаточное вовлечение пользователей в работу над проектом, отсутствие необходимых ресурсов, неудовлетворительное планирование, частое изменение требований и спецификаций, новизна используемой технологии для организации, отсутствие грамотного управления проектом, недостаточная поддержка со стороны высшего руководства. В последнее время ведущие зарубежные аналитики отмечают как одну из причин многих неудач тот факт, что множество проектов выполняется в экстремальных условиях. В англоязычной литературе с легкой руки Эдварда Йордана, одного из ведущих мировых специалистов в области программирования инженерии, утвердилось выражение "death march", буквально - "смертельный марш". Под ним понимается такой проект, параметры которого отклоняются от нормальных значений по крайней мере на 50%. По отношению к проектам создания ПО это означает наличие, как минимум, одного из следующих ограничений: • план проекта сжат более чем наполовину по сравнению с нормальным расчетным планом, т. е. работа, требующая в нормальных условиях 12 календарных 5 Технология программирования Бугаков П.Ю. месяцев, должна быть выполнена за 6 месяцев или менее. Жесткая конкуренция на мировом рынке делает такую ситуацию наиболее распространенной; • количество разработчиков уменьшено более чем наполовину в сравнении с действительно необходимым для проекта данного размера и масштаба, как правило по причине сокращения штатов компании в результате кризиса, реорганизации, реинжиниринга и т. д.; • бюджет и связанные с ним ресурсы урезаны наполовину (результат сокращения компании и других противозатратных мер или конкурентной борьбы за выгодный контракт), что влечет за собой уменьшение числа нанимаемых разработчиков или привлечение малооплачиваемых неопытных молодых разработчиков; • требования к функциям, возможностям, производительности и другим техническим характеристикам вдвое превышают значения, которые они могли бы иметь в нормальных условиях. Потребность контролировать процесс разработки ПО, прогнозировать и гарантировать стоимость разработки, сроки и качество результатов привела в конце 70-х гг. к необходимости перехода от кустарных к индустриальным способам создания ПО и появлению совокупности инженерных методов и средств создания ПО, объединенных общим названием "программная инженерия"(software engineering). Освоение и правильное применение методов и средств создания ПО позволят повысить качество ИС, обеспечить управляемость процесса проектирования ИС и увеличить срок ее жизни. Для успешной реализации проекта объект проектирования (ПО ИС) должен быть прежде всего адекватно описан, т.е. должны быть построены полные и непротиворечивые модели архитектуры ПО, обусловливающей совокупность структурных элементов системы и связей между ними, поведение элементов системы в процессе их взаимодействия, а также иерархию подсистем, объединяющих структурные элементы. Разработка модели архитектуры системы ПО промышленного характера на стадии, предшествующей ее реализации или обновлению, в такой же мере необходима, как и наличие проекта для строительства большого здания. Это утверждение справедливо как в случае разработки новой системы, так и при адаптации типовых продуктов класса R/3 или BAAN, в составе которых также имеются собственные средства моделирования. Хорошие модели являются основой взаимодействия участников проекта и гарантируют корректность архитектуры. Поскольку сложность систем повышается, важно располагать эффективными методами моделирования. Хотя имеется много других факторов, от которых зависит успех проекта, наличие строгого стандарта языка моделирования является весьма существенным. Язык моделирования должен включать: элементы модели - фундаментальные концепции моделирования и их семантику; нотацию —визуальное представление элементов моделирования; руководство по использованию — правила применения элементов в рамках построения тех или иных типов моделей ПО. Очевидно, что конечная цель разработки ПО — это не моделирование, а получение работающих приложений (кода). Диаграммы в конечном счете — это всего лишь наглядные изображения, поэтому, используя графические языки моделирования, очень важно понимать, чем они помогут при написании кода программ. Использование графических языков моделирования целесообразно в ряде случаев: 6 Технология программирования Бугаков П.Ю. • при изучении методов проектирования. Множество людей отмечает наличие серьезных трудностей, связанных, например, с освоением объектно-ориентированных методов и в первую очередь со сменой парадигмы. Графические средства облегчают решение этой проблемы; • при общении с экспертами организации. Графические модели представляют архитектуру системы и объясняют, что эта система будет делать; • при получении общего представления о системе. Графические модели показывают, какого рода абстракции существуют в системе и какие ее части нуждаются в дальнейшем уточнении. В 70-80-х гг. при разработке ПО достаточно широко применялись структурные методы, базирующиеся на строгих формализованных методах описания ПО и принимаемых технических решений (в настоящее время такое же распространение получают объектно-ориентированные методы). Эти методы основаны на использовании наглядных графических моделей: для описания архитектуры ПО в различных аспектах (как статической структуры, так и динамики поведения системы) используются схемы и диаграммы. Наглядность и строгость средств структурного и объектно-ориентированного анализа позволяют разработчикам и будущим пользователям системы с самого начала неформально участвовать в ее создании, обсуждать и закреплять понимание основных технических решений. Однако широкое применение этих методов и следование их рекомендациям при разработке конкретных ИС сдерживалось отсутствием адекватных инструментальных средств, поскольку при неавтоматизированной (ручной) разработке все их преимущества практически сведены к нулю. Действительно, вручную очень трудно разработать и графически представить строгие формальные спецификации системы, проверить их на полноту и непротиворечивость и тем более изменить. Если все же удается создать строгую систему проектных документов, то ее переработка при появлении серьезных изменений практически неосуществима. Ручная разработка обычно порождала следующие проблемы: неадекватная спецификация требований, неспособность обнаруживать ошибки в проектных решениях, низкое качество документации, снижающее эксплуатационные характеристики, затяжной цикл и неудовлетворительные результаты тестирования. При этом разработчики ИС исторически всегда стояли последними в ряду тех, кто использовал компьютерные технологии для повышения качества, надежности и производительности в своей собственной работе (феномен "сапожник без сапог"). Перечисленные проблемы породили потребность в программнотехнологических средствах специального класса — CASE-средствах, реализующих CASE-технологию создания и сопровождения ПО ИС. Термин CASE (Computer Aided Software Engineering) имеет весьма широкое толкование. Первоначально значение термина CASE ограничивалось вопросами автоматизации разработки только лишь программного обеспечения, а в настоящее время оно приобрело новый смысл и охватывает процесс разработки сложных ИС в целом. Таким образом, к концу 80-х гг. назрела необходимость в CASE-технологиях и CASE-средствах и возникли предпосылки для их появления: было проведено много исследований в области программирования (разработка и внедрение языков высокого уровня, методов структурного и модульного программирования, языков проектирования и средств их поддержки, формальных и неформальных языков 7 Технология программирования Бугаков П.Ю. описания системных требований и спецификаций и т. д.). Кроме того, были обеспечены: • подготовка аналитиков и программистов, восприимчивых к концепциям модульного и структурного программирования; • широкое внедрение и постоянный рост производительности компьютеров, позволившие использовать эффективные графические средства и автоматизировать большинство этапов проектирования; • внедрение сетевой технологии, предоставившей возможность объединения усилий отдельных исполнителей в единый процесс проектирования путем использования разделяемой базы данных, содержащей необходимую информацию о проекте. CASE-технология представляет собой совокупность методов проектирования ИС, а также набор инструментальных средств, позволяющих в наглядной форме моделировать предметную область, анализировать эту модель на всех стадиях разработки и сопровождения ИС и разрабатывать приложения в соответствии с информационными потребностями пользователей. Большинство существующих CASE-средств основано на методах структурного или объектно-ориентированного анализа и проектирования, использующих спецификации в виде диаграмм или текстов для описания внешних требований, связей между моделями системы, динамики поведения системы и архитектуры программных средств. 8 Технология программирования Бугаков П.Ю. РАЗДЕЛ I. ТЕХНОЛОГИИ ПРОЕКТИРОВАНИЯ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ 1.1. Основные понятия За последние несколько лет информатика как исследование автоматической обработки информации сформировалась в самостоятельную науку. Еще не все применяемые в информатике понятия определены точно, тем не менее особых разногласий в их применении нет. В рамках дисциплины «Технология и методы программирования» мы будем использовать следующие понятия. Программа - это тексты любых программ на языке программирования или в объектном коде, пригодные для исполнения на электронно-вычислительных машинах. Комплекс программ (КП) - это совокупность взаимосвязанных программ для электронно-вычислительных машин, в основном как объект разработки конечного программного продукта на различных этапах его создания, однако еще не достигшего завершенного состояния, пригодного для тиражирования и эксплуатации с определенными качественными показателями. Программное обеспечение (ПО), или программные средства (ПС), или программное изделие (ПИ), или программный продукт (ПП) - это совокупность программ и связанных с ними данных определенного назначения, пригодных для исполнения на электронно-вычислительных машинах, прошедших испытания с зафиксированными показателями качества и снабженных комплектом документации. В процессе разработки программного обеспечения преимущественно будем использовать термин «Комплекс программ» (или просто «Программа») и только после успешного его завершения, испытания и внедрения — термин «Программное обеспечение» («Программные средства», «Программное изделие», «Программный продукт»). Следует заметить, что понятие «Программа» используется программистами в самом широком диапазоне значений: от примитивной, простой программы (быть может, еще не до конца написанной) до готового программного продукта. И, если это не будет усложнять понимание смысла, мы для простоты изложения также будем употреблять его в различных ситуациях по-разному. Но в основном будем придерживаться вышеописанных определений. С появлением новых технологий в разработку программного обеспечения (визуальные и объектно-ориентированные) были введены и новые понятия, такие как проект и приложение (приложение для пользователя). Разработку приложения стали называть ее проектированием. 1.1.1. Программное обеспечение как изделие Современные программы решают задачи, самые различные по содержанию и отраслевому значению. В научно-исследовательских институтах, в высших и средних специальных учебных заведениях во многих случаях программы создаются в единственном экземпляре для решения частных исследовательских задач, для ускорения вычислений, моделирования процессов, обработки экспериментального материала и т. д. 9 Технология программирования Бугаков П.Ю. Такие программы не имеют массового применения и доступны для использования только тем, кто их разработал. Они становятся объектами научно-технического творчества и редко — промышленными изделиями. Совершенно иным классом программ являются индустриальные программные средства, которые можно квалифицировать как продукцию производственнотехнического назначения. Они представляют собой программы на носителях данных с технической (эксплуатационной и технологической) документацией, разработанные в соответствии с действующими стандартами и прошедшие государственные, межведомственные или ведомственные испытания. Программные средства, принятые в производство, изготавливаются по утвержденной в установленном порядке технологии. Под программным изделием (ПИ) понимается универсальное программное обеспечение, которое предназначается для широкого круга пользователей, быть может даже не известных заранее, и должно рекламироваться, поддерживаться в работоспособном состоянии, расширяться на протяжении длительного периода времени. Программное изделие — это собственно программы плюс документация, гарантия качества, рекламные материалы, обучение, распространение и сопровождение. Отдельная машинная программа или совокупность программ и программное изделие далеко не одно и то же. Программное изделие есть продукт тщательного планирования и целенаправленной разработки, сопровождаемый четкой документацией, прошедший все необходимые испытания, описанный в соответствующих технических публикациях, размноженный в требуемом количестве экземпляров, обслуживаемый и контролируемый поставщиком по заранее продуманному плану. По вопросам разработки систем программного обеспечения (но не программных изделий) можно сделать следующие предположения: ♦ разработчик создает программное обеспечение для себя или, по крайней мере, организационно связан с пользователями разрабатываемого программного обеспечения; ♦ пользователь формулирует свои требования непосредственно разработчику, если последний сам не является одновременно пользователем; ♦ пользователь активно участвует в разработке или в обслуживании программного обеспечения; ♦ программное обеспечение создается обычно для конкретной конфигурации комплекса технических и программных средств и работает в ограниченном диапазоне данных; ♦ разработчик сам вводит в действие программное обеспечение пользователю; ♦ проблемы, возникшие при использовании программного обеспечения, решаются пользователем совместно с разработчиком или с персоналом, осуществляющим его техническое обслуживание (сопровождение); ♦ программы не имеют массового применения и доступны для использования только тем, кто их разработал; ♦ использование программы прекращается после получения результата. 10 Технология программирования Бугаков П.Ю. При разработке программного изделия (за исключением особого случая разработки программного обеспечения по контракту для единственного пользователя) можно сделать следующие предположения: ♦ разработчик не знаком с пользователем; ♦ требования пользователя либо формируются разработчиком, либо передаются ему посреднической организацией (например, поставляющей программное обеспечение); ♦ пользователи не участвуют в рассмотрении и согласовании проектных решений, если не считать редких случаев, когда их интересы представлены посредниками; ♦ программное обеспечение должно сохранять работоспособность в широком диапазоне конфигураций вычислительных комплексов и при самых различных системных программных средствах; ♦ пользователи вводят программное обеспечение в действие либо сами, либо с посторонней помощью, но эта помощь исходит не от разработчика; ♦ проблемы, возникшие при использовании программного обеспечения, разрешаются путем переписки, а иногда через посредника; ♦ программное изделие предназначено для широкого круга пользователей; ♦ программное обеспечение используется многократно и длительное время. Технология (с греческого: ремесло + наука) — совокупность знаний о способах и средствах проведения производственных процессов. В одном случае один человек осуществляет поэтапную разработку программы со своего компьютера. Естественно, он создает сравнительно небольшую программу, не требующую особой оценки. В другом случае разрабатывается очень сложное программное обеспечение, предназначенное для функционирования в реальном масштабе времени и требующее трудозатрат объемом в тысячи человеко-часов. Эти две взаимно противоположные ситуации характеризуются различной степенью формализации и проведения процесса разработки программных средств. Степень формализованное™ и проведения процесса разработки программного обеспечения напрямую зависит от целей его создания, его величины, численности группы разработчиков и других факторов. От того, насколько правильно и удачно, с точки зрения технологии разработки программного обеспечения, построено приложение, зависит качество и жизнеспособность конечного продукта. Под технологией разработки программного обеспечения (ТРПО) понимается совокупность обобщенных и систематизированных знаний об оптимальных способах (приемах) проведения процесса разработки программного обеспечения, обеспечивающего в заданных условиях получение программной продукции с заданными свойствами. ТРПО представляет собой инженерный подход к разработке программных средств ЭВМ, охватывающий методологию программирования, проблемы обеспечения надежности программ, оценки рабочих характеристик и качества проектов. ТРПО рассматривает вопросы управления проектированием систем программного обеспечения с использованием средств и стандартов разработки программ. ТРПО определяет некоторую профессиональную культуру работы специалистов (не только программистов), обеспечивающую заданный уровень производительности труда и качества получаемой в результате программной продукции. 11 Технология программирования Бугаков П.Ю. ТРПО охватывает все этапы разработки программного обеспечения (жизненный цикл ПО). 1.1.2. Надежность программного обеспечения В программном обеспечении имеется ошибка, если оно не выполняет того, что пользователю разумно от него ожидать. Отказ программного обеспечения — это проявление ошибки в нем. Слово «разумно» употреблено в определении для того, чтобы иссключить ситуации, когда, например, к терминалу информационнопоисковой системы публичной библиотеки подходит человек и просит определить объем своего вклада в местном банке. Ошибки в ПО не являются внутренним его свойством. Это значит, что, как бы долго и пристально мы не разглядывали (или тестировали, или доказывали) программу, мы никогда не сможем найти в ней все ошибки, а лишь только обнаружить некоторые. Рис. 1. Зависимость стоимость и вероятности исправления ошибки от времени Надежность программного обеспечения есть вероятность его работы без отказов в течение определенного периода времени, рассчитанная с учетом стоимости для пользователя каждого отказа. Слово «вероятность» в определении, по существу, означает вероятность того, что пользователь не введет в систему некоторый конкретный набор данных, выводящий систему из строя. Надежность также не является внутренним свойством программы. Она во многом связана с тем, как программа используется. Надежность программного обеспечения существенно отличается от надежности аппаратуры. Программы не изнашиваются, поломка программы невозможна. Таким образом, надежность программного обеспечения есть следствие исключения ошибок проектирования, т. е. ошибок, внесенных в процессе разработки ПО. Надежность является составной частью более общего понятия — качества. Качественная программа, например, не только надежна, но и компактна, совместима с другими программами, эффективна, удобна в сопровождении, вполне понятна. Среди прочих характеристик качества программ надежность стоит на первом месте и поэтому дальнейшие вопросы разработки ПО рассматриваются через призму надежности. 1.2. Жизненный цикл программного обеспечения 1.2.1. Процессы ЖЦ ПО 1) Понятие жизненного цикла программного обеспечения (ЖЦ ПО) является одним из базовых в программной инженерии. Жизненный цикл программного обеспечения определяется как период времени, который начинается с момента принятия решения о необходимости создания ПО и заканчивается в момент его полного изъятия из эксплуатации. 12 Технология программирования Бугаков П.Ю. 2) Под жизненным циклом ПО понимают весь период его разработки и эксплуатации (использования), начиная от момента возникновения замысла ПО и кончая прекращением всех видов его использования. Жизненный цикл включает все процессы создания и использования ПО (software process). Основным нормативным документом, регламентирующим состав процессов ЖЦ ПО, является международный стандарт ISO/IEC 12207: 1995 "Information Technology Software Life Cycle Processes" (ISO - International Organization for Standardization Международная организация по стандартизации, IEC – International Electrotechnical Commission — Международная комиссия по электротехнике). Он определяет структуру ЖЦ, содержащую процессы, действия и задачи, которые должны быть выполнены во время создания ПО. В данном стандарте ПО (или программный продукт) определяется как набор компьютерных программ, процедур и, возможно, связанной с ними документации и данных. Процесс определяется как совокупность взаимосвязанных действий, преобразующих некоторые входные данные в выходные. Каждый процесс характеризуется определенными задачами и методами их решения, исходными данными, полученными от других процессов, и результатами. Каждый процесс разделен на набор действий, каждое действие — на набор задач. Каждый процесс, действие или задача инициируется и выполняется другим процессом по мере необходимости, причем не существует заранее определенных последовательностей выполнения (естественно, при сохранении связей по входным данным). В соответствии со стандартом ISO/IEC 12207 все процессы ЖЦ ПО разделены на три группы (рис. 2): • пять основных процессов (приобретение, поставка, разработка, эксплуатация, сопровождение); • восемь вспомогательных процессов, обеспечивающих выполнение основных процессов (документирование, управление конфигурацией, обеспечение качества, верификация, аттестация, совместная оценка, аудит, разрешение проблем); • четыре организационных процесса (управление, создание инфраструктуры, усовершенствование, обучение). Рис. 2. Процессы ЖЦ ПО 13 Технология программирования Бугаков П.Ю. 1.2.2. Основные процессы ЖЦ ПО: Процесс приобретения (acquisition process). Он состоит из действий и задач заказчика, приобретающего ПО. Данный процесс охватывает следующие действия: 1) инициирование приобретения; 2) подготовку заявочных предложений; 3) подготовку и корректировку договора; 4) надзор за деятельностью поставщика; 5) приемку и завершение работ. Инициирование приобретения включает следующие задачи: • определение заказчиком своих потребностей в приобретении, разработке или усовершенствовании системы, программных продуктов или услуг; • анализ требований к системе; • принятие решения относительно приобретения, разработки или усовершенствования существующего ПО; • проверку наличия необходимой документации, гарантий, сертификатов, лицензий и поддержки в случае приобретения программного продукта; • подготовку и утверждение плана приобретения, включающего требования к системе, тип договора, ответственность сторон и т. д. Заявочные предложения должны содержать: • требования к системе; • перечень программных продуктов; • условия и соглашения; • технические ограничения (например, среда функционирования системы). Заявочные предложения направляются выбранному поставщику (или нескольким поставщикам в случае проведения тендера). Поставщик это организация, которая заключает договор с заказчиком на поставку системы, ПО или программной услуги на условиях, оговоренных в договоре. Подготовка и корректировка договора включают следующие задачи: • определение заказчиком процедуры выбора поставщика, включающей критерии оценки предложений возможных поставщиков; • выбор конкретного поставщика на основе анализа предложений; • подготовку и заключение договора с поставщиком; • внесение изменений (при необходимости) в договор в процессе его выполнения. Надзор за деятельностью поставщика осуществляется в соответствии с действиями, предусмотренными в процессах совместной оценки и аудита. В процессе приемки подготавливаются и выполняются необходимые тесты. Завершение работ по договору осуществляется в случае удовлетворения всех условий приемки. Процесс поставки (supply process). Он охватывает действия и задачи, выполняемые поставщиком, который снабжает заказчика программным продуктом или услугой. 14 Технология программирования Бугаков П.Ю. Данный процесс включает следующие действия: 1) инициирование поставки; 2) подготовку ответа на заявочные предложения; 3) подготовку договора; 4) планирование; 5) выполнение и контроль; 6) проверку и оценку; 7) поставку и завершение работ. Инициирование поставки заключается в рассмотрении поставщиком заявочных предложений и принятии решения согласиться с выставленными требованиями и условиями или предложить свои. Планирование включает следующие задачи: • принятие решения поставщиком относительно выполнения работ своими силами или с привлечением субподрядчика; • разработку поставщиком плана управления проектом, содержащего организационную структуру проекта, разграничение ответственности, технические требования к среде разработки и ресурсам, управление субподрядчиками и др. Процесс разработки (development process). Он предусматривает действия и задачи, выполняемые разработчиком, и охватывает работы по созданию ПО и его компонентов в соответствии с заданными требованиями, включая оформление проектной и эксплуатационной документации, подготовку материалов, необходимых для проверки работоспособности и соответствующего качества программных продуктов, материалов, необходимых для организации обучения персонала, и т. д. Процесс разработки включает следующие действия: 1) подготовительную работу; 2) анализ требований к системе; 3) проектирование архитектуры системы; 4) анализ требований к ПО; 5) проектирование архитектуры ПО; 6) детальное проектирование ПО; 7) кодирование и тестирование ПО; 8) интеграцию ПО; 9) квалификационное тестирование ПО; 10) интеграцию системы; 11) квалификационное тестирование системы; 12) установку ПО; 13) приемку ПО. Подготовительная работа начинается с выбора модели ЖЦ ПО, соответствующей масштабу, значимости и сложности проекта (см. разд. 1.2). Действия и задачи процесса разработки должны соответствовать выбранной модели. Разработчик должен выбрать, адаптировать к условиям проекта и использовать 15 Технология программирования Бугаков П.Ю. согласованные с заказчиком стандарты, методы и средства разработки, а также составить план выполнения работ. Анализ требований к системе подразумевает определение ее функциональных возможностей, пользовательских требований, требований к надежности и безопасности, требований к внешним интерфейсам и т. д. Требования к системе оцениваются исходя из критериев реализуемости и возможности проверки при тестировании. Проектирование архитектуры системы на высоком уровне заключается в определении компонентов ее оборудования, ПО и операций, выполняемых эксплуатирующим систему персоналом. Архитектура системы должна соответствовать требованиям, предъявляемым к системе, а также принятым проектным стандартам и методам. Анализ требований к ПО предполагает определение следующих характеристик для каждого компонента ПО: • функциональных возможностей, включая характеристики производительности и среды функционирования компонента; • внешних интерфейсов; • спецификаций надежности и безопасности; • эргономических требований; • требований к используемым данным; • требований к установке и приемке; • требований к пользовательской документации; • требований к эксплуатации и сопровождению. Требования к ПО оцениваются исходя из критериев соответствия требованиям к системе, реализуемости и возможности проверки при тестировании. Проектирование архитектуры ПО включает следующие задачи (для каждого компонента ПО): • трансформацию требований к ПО в архитектуру, определяющую на высоком уровне структуру ПО и состав его компонентов; • разработку и документирование программных интерфейсов ПО и баз данных; • разработку предварительной версии пользовательской документации; • разработку и документирование предварительных требований к тестам и плана интеграции ПО. Архитектура компонентов ПО должна соответствовать требованиям, предъявляемым к ним, а также принятым проектным стандартам и методам. Детальное проектирование ПО включает следующие задачи: • описание компонентов ПО и интерфейсов между ними на более низком уровне, достаточном для их последующего самостоятель? ного кодирования и тестирования; • разработку и документирование детального проекта базы данных; • обновление (при необходимости) пользовательской документации; • разработку и документирование требований к тестам и плана тестирования компонентов ПО; 16 Технология программирования • обновление плана интеграции ПО. Бугаков П.Ю. Кодирование и тестирование ПО охватывают следующие задачи: • разработку (кодирование) и документирование каждого компонента ПО и базы данных, а также совокупности тестовых процедур и данных для их тестирования; • тестирование каждого компонента ПО и базы данных на соответствие предъявляемым к ним требованиям. Результаты тестирования компонентов должны быть документированы; • обновление (при необходимости) пользовательской документации; • обновление плана интеграции ПО. Интеграция ПО предусматривает сборку разработанных компонентов ПО в соответствии с планом интеграции и тестирование агрегированных компонентов. Для каждого из агрегированных компонентов разрабатываются наборы тестов и тестовые процедуры, предназначенные для проверки каждого из квалификационных требований при последующем квалификационном тестировании. Квалификационное требование — это набор критериев или условий, которые необходимо выполнить, чтобы квалифицировать программный продукт как соответствующий своим спецификациям и готовый к использованию в условиях эксплуатации. Квалификационное тестирование ПО проводится разработчиком в присутствии заказчика (по возможности) для демонстрации того, что ПО удовлетворяет своим спецификациям и готово к использованию в условиях эксплуатации. Квалификационное тестирование выполняется для каждого компонента ПО по всем разделам требований при широком варьировании тестов. При этом также проверяются полнота технической и пользовательской документации и ее адекватность самим компонентам ПО. Интеграция системы заключается в сборке всех ее компонентов, включая ПО и оборудование. После интеграции система, в свою очередь, подвергается квалификационному тестированию на соответствие совокупности требований к ней. При этом также производятся оформление и проверка полного комплекта документации на систему. Установка ПО осуществляется разработчиком в соответствии с планом в той среде и на том оборудовании, которые предусмотрены договором. В процессе установки проверяется работоспособность ПО и баз данных. Если устанавливаемое ПО заменяет существующую систему, разработчик должен обеспечить их параллельное функционирование в соответствии с договором. Приемка ПО предусматривает оценку результатов квалификационного тестирования ПО и системы и документирование результатов оценки, которые проводятся заказчиком с помощью разработчика. Разработчик выполняет окончательную передачу ПО заказчику в соответствии с договором, обеспечивая при этом необходимое обучение и поддержку. 17 Технология программирования Бугаков П.Ю. Процесс эксплуатации (operation process). Он охватывает действия и задачи оператора — организации, эксплуатирующей систему. Данный процесс включает следующие действия: 1) подготовительную работу; 2) эксплуатационное тестирование; 3) эксплуатацию системы; 4) поддержку пользователей. Подготовительная работа включает проведение оператором следующих задач: • планирование действий и работ, выполняемых в процессе эксплуатации, и установку эксплуатационных стандартов; • определение процедур локализации и разрешения проблем, возникающих в процессе эксплуатации. Эксплуатационное тестирование осуществляется для каждой очередной редакции программного продукта, после чего она передается в эксплуатацию. Эксплуатация системы выполняется в предназначенной для этого среде в соответствии с пользовательской документацией. Поддержка пользователей заключается в оказании помощи и консультаций при обнаружении ошибок в процессе эксплуатации ПО. Процесс сопровождения (maintenance process). Он предусматривает действия и задачи, выполняемые сопровождающей организацией (службой сопровождения). Данный процесс активизируется при изменениях (модификациях) программного продукта и соответствующей документации, вызванных возникшими проблемами или потребностями в модернизации либо адаптации ПО. В соответствии со стандартом IEEE-90 под сопровождением понимается внесение изменений в ПО в целях исправления ошибок, повышения производительности или адаптации к изменившимся условиям работы или требованиям. Изменения, вносимые в существующее ПО, не должны нарушать его целостность. Процесс сопровождения включает перенос ПО в другую среду (миграцию) и заканчивается снятием ПО с эксплуатации. Процесс сопровождения охватывает следующие действия: 1) подготовительную работу; 2) анализ проблем и запросов на модификацию ПО; 3) модификацию ПО; 4) проверку и приемку; 5) перенос ПО в другую среду; 6) снятие ПО с эксплуатации. Подготовительная работа службы сопровождения включает следующие задачи: • планирование действий и работ, выполняемых в процессе сопровождения; • определение процедур локализации и разрешения проблем, возникающих в процессе сопровождения. 18 Технология программирования Бугаков П.Ю. Анализ проблем и запросов на модификацию ПО, выполняемый службой сопровождения, включает следующие задачи: • анализ сообщения о возникшей проблеме или запроса на модификацию ПО относительно его влияния на организацию, существующую систему и интерфейсы с другими системами. При этом определяются следующие характеристики возможной модификации: • тип (корректирующая, улучшающая, профилактическая или адаптирующая к новой среде); • масштаб (размеры модификации, стоимость и время ее реализации); • критичность (воздействие на производительность, надежность или безопасность); • оценка целесообразности проведения модификации и возможных вариантов ее проведения; • утверждение выбранного варианта модификации. Модификация ПО предусматривает определение компонентов ПО, их версий и документации, подлежащих модификации, и внесение необходимых изменений в соответствии с правилами процесса разработки. Подготовленные изменения тестируются и проверяются по критериям, определенным в документации. При подтверждении корректности изменений в программах производится корректировка документации. Проверка и приемка заключаются в проверке целостности модифицированной системы и утверждении внесенных изменений. При переносе ПО в другую среду используются имеющиеся или разрабатываются новые средства переноса, затем выполняется конвертирование программ и данных вновую среду. С целью облегчить переход предусматривается параллельная эксплуатация ПО в старой и новой среде в течение некоторого периода, когда проводится необходимое обучение пользователей работе в новой среде. Снятие ПО с эксплуатации осуществляется по решению заказчика при участии эксплуатирующей организации, службы сопровождения и пользователей. При этом программные продукты и соответствующая документация подлежат архивированию в соответствии с договором. Аналогично переносу ПО в другую среду с целью облегчить переход к новой системе предусматривается параллельная эксплуатация старого и нового ПО в течение некоторого периода, когда выполняется необходимое обучение пользователей работе с новой системой. 1.2.3. Вспомогательные процессы ЖЦ ПО: Процесс документирования (documentation process). Он предусматривает формализованное описание информации, созданной в течение ЖЦ ПО. Данный процесс состоит из набора действий, с помощью которых планируют, проектируют, разрабатывают, выпускают, редактируют, распространяют и сопровождают документы, необходимые для всех заинтересованных лиц, таких, как руководство, технические специалисты и пользователи системы. 19 Технология программирования Процесс документирования включает следующие действия: 1) подготовительную работу; 2) проектирование и разработку; 3) выпуск документации; 4) сопровождение. Бугаков П.Ю. Процесс управления конфигурацией (configuration management process). Он предполагает применение административных и технических процедур на всем протяжении ЖЦ ПО для определения состояния компонентов ПО в системе, управления модификациями ПО, описания и подготовки отчетов о состоянии компонентов ПО и запросов на модификацию, обеспечения полноты, совместимости и корректности компонентов ПО, управления хранением и поставкой ПО. Согласно стандарту IEEE-90 под конфигурацией ПО понимается совокупность его функциональных и физических характеристик, установленных в технической документации и реализованных в ПО. Процесс управления конфигурацией включает следующие действия: 1) подготовительную работу; 2) идентификацию конфигурации; 3) контроль конфигурации; 4) учет состояния конфигурации; 5) оценку конфигурации; 6) управление выпуском и поставку. Подготовительная работа заключается в планировании управления конфигурацией. Идентификация конфигурации устанавливает правила, с помощью которых можно однозначно идентифицировать и различать компоненты ПО и их версии. Кроме того, каждому компоненту и его версиям соответствует однозначно обозначаемый комплект документации. В результате создается база для однозначного выбора и манипулирования версиями компонентов ПО, использующая ограниченную и упорядоченную систему символов, идентифицирующих различные версии ПО. Контроль конфигурации предназначен для систематической оценки предполагаемых модификаций ПО и координированной их реализации с учетом эффективности каждой модификации и затрат на ее выполнение. Он обеспечивает контроль состояния и развития компонентов ПО и их версий, а также адекватность реально изменяющихся компонентов и их комплектной документации. Учет состояния конфигурации представляет собой регистрацию состояния компонентов ПО, подготовку отчетов обо всех реализованных и отвергнутых модификациях версий компонентов ПО. Совокупность отчетов обеспечивает однозначное отражение текущего состояния системы и ее компонентов, а также ведение истории модификаций. 20 Технология программирования Бугаков П.Ю. Оценка конфигурации заключается в оценке функциональной полноты компонентов ПО, а также соответствия их физического состояния текущему техническому описанию. Управление выпуском и поставка охватывают изготовление эталонных копий программ и документации, их хранение и поставку пользователям в соответствии с порядком, принятым в организации. Процесс обеспечения качества (quality assurance process). Он обеспечивает соответствующие гарантии того, что ПО и процессы его ЖЦ соответствуют заданным требованиям и утвержденным планам. Под качеством ПО понимается совокупность свойств, которые характеризуют способность ПО удовлетворять заданным требованиям. Для получения достоверных оценок создаваемого ПО процесс обеспечения его качества должен происходить независимо от субъектов, непосредственно связанных с разработкой ПО. При этом могут использоваться результаты других вспомогательных процессов, таких, как верификация , аттестация, совместная оценка, аудит и разрешение проблем. Процесс обеспечения качества включает следующие действия: 1) подготовительную работу; 2) обеспечение качества продукта; 3) обеспечение качества процесса; 4) обеспечение прочих показателей качества системы. Подготовительная работа заключается в координации с другими вспомогательными процессами и планировании самого процесса обеспечения качества с учетом используемых стандартов, методов, процедур и средств. Обеспечение качества продукта подразумевает гарантирование полного соответствия программных продуктов и их документации требованиям заказчика, предусмотренным в договоре. Обеспечение качества процесса предполагает гарантирование соответствия процессов ЖЦ ПО, методов разработки, среды разработки и квалификации персонала условиям договора, установленным стандартам и процедурам. Обеспечение прочих показателей качества системы осуществляется соответствии с условиями договора и стандартом качества ISO 9001. в Процесс верификации (verification process). Он состоит в определении того, что программные продукты, являющиеся результатами некоторого действия, полностью удовлетворяют требованиям или условиям, обусловленным предшествующими действиями (верификация в узком смысле означает формальное доказательство правильности ПО). Данный процесс может включать анализ, оценку и тестирование. Верификация может проводиться с различными степенями независимости. Степень независимости может варьироваться от выполнения верификации самим 21 Технология программирования Бугаков П.Ю. исполнителем или другим специалистом данной организации до ее выполнения специалистом другой организации с различными вариациями. Если процесс верификации осуществляется организацией, не зависящей от поставщика, разработчика, оператора или службы сопровождения, то он называется процессом независимой верификации. Процесс верификации включает следующие действия: 1) подготовительную работу; 2) верификацию. В процессе верификации проверяются следующие условия: • непротиворечивость требований к системе и степень учета потребностей пользователей; • возможности поставщика выполнить заданные требования; • соответствие выбранных процессов ЖЦ ПО условиям договора; • адекватность стандартов, процедур и среды разработки процессам ЖЦ ПО; • соответствие проектных спецификаций ПО заданным требованиям; • корректность описания в проектных спецификациях входных и выходных данных, последовательности событий, интерфейсов, логики и т.д.; • соответствие кода проектным спецификациям и требованиям; • тестируемость и корректность кода, его соответствие принятым стандартам кодирования; • корректность интеграции компонентов ПО в систему; • адекватность, полнота и непротиворечивость документации. Процесс аттестации (validation process). Он предусматривает определение полноты соответствия заданных требований и созданной системы или программного продукта их конкретному функциональному назначению. Под аттестацией обычно понимается подтверждение и оценка достоверности проведенного тестирования ПО. Аттестация должна гарантировать полное соответствие ПО спецификациям, требованиям и документации, а также возможность его безопасного и надежного применения пользователем. Аттестацию рекомендуется выполнять путем тестирования во всех возможных ситуациях и использовать при этом независимых специалистов. Аттестация может проводиться на начальных стадиях ЖЦ ПО или как часть работы по приемке ПО. Аттестация, так же как и верификация, может осуществляться с различными степенями независимости. Если процесс аттестации выполняется организацией, не зависящей от поставщика, разработчика, оператора или службы сопровождения, то он называется процессом независимой аттестации. Процесс аттестации включает следующие действия: 1) подготовительную работу; 2) аттестацию. Процесс совместной оценки (joint review process). Он предназначен для оценки состояния работ по проекту и ПО, создаваемого при выполнении данных работ 22 Технология программирования Бугаков П.Ю. (действий). Он сосредоточен в основном на контроле планирования и управления ресурсами, персоналом, аппаратурой и инструментальными средствами проекта. Оценка применяется как на уровне управления проектом, так и на уровне технической реализации проекта и проводится в течение всего срока действия договора. Данный процесс может выполняться двумя любыми сторонами, участвующими в договоре, при этом одна сторона проверяет другую. Процесс совместной оценки включает следующие действия: 1) подготовительную работу; 2) оценку управления проектом; 3) техническую оценку. Процесс аудита (audit process). Он представляет собой определение соответствия требованиям, планам и условиям договора. Аудит может выполняться двумя любыми сторонами, участвующими в договоре, когда одна сторона проверяет другую. Аудит — это ревизия (проверка), проводимая компетентным органом (лицом) в целях обеспечения независимой оценки степени соответствия ПО или процессов установленным требованиям. Аудит служит для установления соответствия реальных работ и отчетов требованиям, планам и контракту. Аудиторы (ревизоры) не должны иметь прямой зависимости от разработчиков ПО. Они определяют состояние работ, использование ресурсов, соответствие документации спецификациям и стандартам, корректность тестирования. Процесс аудита включает следующие действия: 1) подготовительную работу; 2) аудит. Процесс разрешения проблем (problem resolution process). Он предусматривает анализ и решение проблем (включая обнаруженные несоответствия) независимо от их происхождения или источника, которые обнаружены в ходе разработки, эксплуатации, сопровождения или других процессов. Каждая обнаруженная проблема должна быть идентифицирована, описана, проанализирована и разрешена. Процесс разрешения проблем включает следующие действия: 1) подготовительную работу; 2) разрешение проблем. 1.2.4. Организационные процессы ЖЦ ПО: Процесс управления (management process). Он состоит из действий и задач, которые могут выполняться любой стороной, управляющей своими процессами. Данная сторона (менеджер) отвечает за управление выпуском продукта, управление проектом и управление задачами соответствующих процессов, таких, как приобретение, поставка, разработка, эксплуатация, сопровождение и др. Процесс управления включает следующие действия: 1) инициирование и определение области управления; 2) планирование; 23 Технология программирования 3) выполнение и контроль; 4) проверку и оценку; 5) завершение. Бугаков П.Ю. При инициировании менеджер должен убедиться, что необходимые для управления ресурсы (персонал, оборудование и технология) имеются в его распоряжении в достаточном количестве. Планирование подразумевает выполнение, как минимум, следующих задач: • составление графиков выполнения работ; • оценку затрат; • выделение требуемых ресурсов; • распределение ответственности; • оценку рисков, связанных с конкретными задачами; • создание инфраструктуры управления. Процесс создания инфраструктуры (infrastructure process). Он охватывает выбор и поддержку (сопровождение) технологии, стандартов и инструментальных средств, выбор и установку аппаратных и программных средств, используемых для разработки, эксплуатации или сопровождения ПО. Инфраструктура должна модифицироваться и сопровождаться в соответствии с изменениями требований к соответствующим процессам. Инфраструктура, в свою очередь, является одним из объектов управления конфигурацией. Процесс создания инфраструктуры включает следующие действия: 1) подготовительную работу; 2) создание инфраструктуры; 3) сопровождение инфраструктуры. Процесс усовершенствования (improvement process). Он предусматривает оценку, измерение, контроль и усовершенствование процессов ЖЦ ПО. Данный процесс включает следующие действия: 1) создание процесса; 2) оценку процесса; 3) усовершенствование процесса. Усовершенствование процессов ЖЦ ПО направлено на повышение производительности труда всех участвующих в них специалистов за счет совершенствования используемой технологии, методов управления, выбора инструментальных средств и обучения персонала. Усовершенствование основано на анализе достоинств и недостатков каждого процесса. Такому анализу в большой степени способствует накопление в организации исторической, технической, экономической и иной информации по реализованным проектам. Процесс обучения (training process). Он охватывает первоначальное обучение и последующее постоянное повышение квалификации персонала. Приобретение, поставка, разработка, эксплуатация и сопровождение ПО в значительной степени 24 Технология программирования Бугаков П.Ю. зависят от уровня знаний и квалификации персонала. Например, разработчики ПО должны пройти необходимое обучение методам и средствам программной инженерии. Содержание процесса обучения определяется требованиями к проекту. Оно должно учитывать необходимые ресурсы и технические средства обучения. Должны быть разработаны и представлены методические материалы, необходимые для обучения пользователей в соответствии с учебным планом. Процесс обучения включает следующие действия: 1) подготовительную работу; 2) разработку учебных материалов; 3) реализацию плана обучения. 1.2.5. Программное обеспечение с малой и большой длительностью ЖЦ Жизненный цикл — совокупность взаимосвязанных процессов создания и последовательного изменения состояния продукции от формирования к ней исходных требований до окончательной ее эксплуатации или потребления. Программы с малой длительностью жизненного цикла создаются для разового решения научных и иных задач. Их жизненный цикл — от нескольких дней до нескольких месяцев. Ранее такие программы не имели удобного интерфейса, так как затраты на его разработку еще недавно в несколько раз превосходили затраты на разработку вычислительной части. Жизненный цикл программных изделий показан на рис 3. Рис. 3. Жизненный цикл программного обеспечения Каждая программа начинается с какой-либо неудовлетворенной потребности и, осознав ее, необходимо провести системный анализ для выявления целей будущего программного изделия и требований к нему. Следующим этапом будет внешнее специфицирование, предназначенное для создания «идеологии» программы — общей направленности в последующем проектировании, вплоть до внешнего вида программы 25 Технология программирования Бугаков П.Ю. и инструкции пользования программой. На этапе проектирования программное изделие специфицируется в полном объеме от постановки задачи до рабочего проекта с описанием внутренней структуры программы и плана разработки частей программы. Затем происходит кодировка и тестирование, в результате чего выходит готовая версия программы. Программа выпускается в тираж и сопровождается производителем. Сопровождение заключается как в устранении обнаруживаемых в процессе эксплуатации ошибок и выпуске исправленных версий, так и в усовершенствовании базовой версии программы, что зачастую приводит к пере проектированию программы и выпуску радикально обновленных версий. Окончание жизненного цикла обусловливается прекращением эксплуатации разработки. Однако идеи, выдвинутые в процессе эксплуатации программы, обычно используются при разработке последующего, более совершенного и современного изделия. По длительности жизненного цикла программные изделия можно разделить на два класса: с малым временем жизни и большим. Этим классам программ соответствуют гибкий (мягкий) подход к их созданию и использованию и жесткий промышленный подход регламентированного проектирования и эксплуатации программных изделий. В научных организациях и вузах, например, преобладают разработки программ первого класса, а в проектных и промышленных организациях — второго. Программные изделия с малой длительностью эксплуатации создаются в основном для решения научных и инженерных задач, для получения конкретных результатов вычислений. Такие программы обычно относительно невелики. Они разрабатываются одним специалистом или маленькой группой. Главная идея такой программы обсуждается одним программистом и конечным пользователем. Некоторые детали заносятся на бумагу, и проект реализуется в течение нескольких дней или недель. Он не предназначен для тиражирования и передачи для последующего использования в другие коллективы. По существу такие программы являются частью научно-исследовательской работы и не могут рассматриваться как отчуждаемые программные изделия. Их жизненный цикл состоит из длительного интервала системного анализа и формализации проблемы, значительного этапа проектирования программ и относительно небольшого времени эксплуатации и получения результатов. Требования, предъявляемые к функциональным и конструктивным характеристикам, как правило, не формализуются, отсутствуют оформленные испытания программ. Показатели их качества контролируются только разработчиками в соответствии с их неформальными представлениями. Сопровождение и модификация таких программ не обязательны, и их жизненный цикл завершается после получения результатов вычислений. Основные затраты в жизненном цикле таких программ приходятся на этапы системного анализа и проектирования, которые продолжаются от месяца до одного-двух лет, в результате чего жизненный цикл программного изделия редко превышает 3 года. Программные изделия с большой длительностью эксплуатации создаются для регулярной обработки информации и управления. Структура таких программ сложная. Их размеры могут изменяться в достаточно широких пределах, однако все они обладают свойствами познаваемости и возможностью их модификации. 26 Технология программирования Бугаков П.Ю. Программные изделия этого класса допускают тиражирование, они сопровождаются документацией как промышленные изделия и представляют собой отчуждаемые от разработчика программные продукты. Их проектированием и эксплуатацией занимаются большие коллективы специалистов, для чего необходима формализация программной системы, а также формализованные испытания и определение достигнутых показателей качества конечного продукта. Их жизненный цикл составляет 10-20 лет, из которых 70-90 % приходится на эксплуатацию и сопровождение. Поэтому совокупные затраты в процессе эксплуатации и сопровождения таких программных изделий значительно превышают затраты на системный анализ и проектирование. Все последующее изложение ориентировано на разработку крупных (сложных) программных средств управления и обработку информации. Обобщенная модель жизненного цикла программного изделия может выглядеть так. I. Системный анализ: а) исследования; б) анализ осуществимости: — эксплуатационной; — экономической; — коммерческой. II. Проектирование программного обеспечения: а) конструирование: — функциональная декомпозиция системы, ее архитектура, — внешнее проектирование программного обеспечения — проектирование базы данных; — архитектура программного обеспечения; б) программирование: — внутреннее проектирование программного обеспечения, — внешнее проектирование программных модулей, — внутреннее проектирование программных модулей, — кодирование, — отладка программ, — компоновка программ; в) отладка программного обеспечения. III. Оценка (испытания) программного обеспечения. IV. Использование программного обеспечения: а) эксплуатация; б) сопровождение. I. Системный анализ. В начале разработки программного обеспечения проводят системный анализ (предварительное его проектирование), в ходе которого определяются потребность в нем, его назначение и основные функциональные характеристики. Оцениваются затраты ц возможная эффективность применения будущего программного изделия. На этом этапе составляется перечень требований, т. е. четкое определение того, что пользователь ожидает от готового продукта. Здесь же определяются цели и задачи, которые ставятся перед окончательным результатом и ca^iM проектом. 27 Технология программирования Бугаков П.Ю. В фазе системного анализа можно выделить два направления: исследование и анализ осуществимости. Исследования начинаются с того момента, когда руководитель разработки осознает потребность в программном обеспечении. Работа состоит в планировании и координации действий, необходимых для подготовки формального рукописного перечня требований к разрабатываемому программному изделию. Исследования заканчиваются тогда, когда требования сформированы и при необходимости могут быть модифицированы и одобрены ответственным руководителем . Анализ осуществимости есть техническая часть исследований. Назначается руководитель проекта, организующий проектирование и распределение ресурсов (рабочей силы). Работа заключается в исследовании предполагаемого программного изделия с целью получения практической оценки возможности реализации проекта, В частности определяются: ♦ осуществимость эксплуатационная: будет ли изделие достаточно удобно для практического использования? ♦ осуществимость экономическая: приемлема ли стоимость разрабатываемого изделия? Какова эта стоимость? Будет ли изделие экономически эффективно для пользователя? ♦ осуществимость коммерческая: будет ли изделие привлекательным, пользоваться спросом, легко устанавливаемым, приспособленным к обслуживанию, простым в освоении? Эти и другие вопросы необходимо решать главным образом при рассмотрении указанных выше требований. Анализ осуществимости заканчивается, когда все требования собраны и одобрены. Прежде чем продолжить дальнейшую работу над проектом, необходимо удостовериться, что вся необходимая информация получена. Эта информация должна быть точной, понятной и осуществимой. Она должна представлять собой полный комплекс требований удовлетворенности пользователя к разрабатываемому программному продукту, оформляемый в виде спецификации. При несоблюдении данного требования можно значительно замедлить реализацию проекта в будущем вследствие многократного повторного обращения к пользователю за уточнением неверно трактованных деталей, неоговоренных условий и, как следствие, переделке уже разработанных его частей. Часто в период системного анализа принимается решение о прекращении дальнейшей разработки программного обеспечения. II. Проектирование программного обеспечения. Проектирование является основной и решающей фазой жизненного цикла программного обеспечения, во время которого создается и на 90 % приобретает свою окончательную форму программное изделие. Эта фаза жизни охватывает различные виды деятельности проекта и может быть разделена на три основных этапа: конструирование, программирование и отладку программного изделия. Конструирование программного обеспечения обычно начинается как только оказываются зафиксированными на бумаге некоторые предварительные цели и требования к нему. К моменту утверждения требований работа в фазе конструирования будет в самом разгаре. 28 Технология программирования Бугаков П.Ю. На этом этапе осуществляют: — функциональную декомпозицию решаемой задачи, на основе которой определяется архитектура функциональной системы; — внешнее проектирование ПО, выражающееся в форме внешнего взаимодействия его с пользователем; — проектирование базы данных, если это необходимо; — проектирование архитектуры программного обеспечения, т. е. определение объектов, модулей и их сопряжений. Программирование начинается уже в фазе конструирования, как только станут доступными основные спецификации на отдельные компоненты программного изделия, но не ранее утверждения соглашения о требованиях. На этом этапе выполняется работа, связанная со сборкой программного изделия. Она состоит в подробном внутреннем конструировании программного продукта, в разработке внутренней логики каждого модуля системы, которая затем выражается текстом конкретной программы. Фаза программирования завершается, когда разработчики закончат документирование, отладку и компоновку отдельных частей программного изделия. Отладка программного обеспечения осуществляется после того, когда все его компоненты будут отлажены по отдельности и собраны в единый программный продукт. III. Оценка (испытания) программного обеспечения. В этой фазе программное изделие подвергается строгому системному испытанию группой лиц, не являющихся разработчиками. Это делается для гарантии того, что готовое программное изделие удовлетворяет всем требованиям в спецификациях, может быть использовано в среде пользователя, свободно от каких-либо дефектов и содержит полную и точную документацию. Фаза оценки начинается, как только все компоненты (модули) собраны вместе и испытаны, т. е. после полной отладки готового программного продукта. Она заканчивается после получения подтверждения, что программное изделие прошло все испытания и готово к эксплуатации. Она продолжается так же долго, как и программирование. IV. Использование программного обеспечения. Во время использования программного изделия исправляются ошибки, вкравшиеся в процессе его проектирования. Фаза использования программного изделия начинается с момента передачи изделия в систему распределения. Это то время, в течение которого изделие находится в действии и используется эффективно. Тогда же происходят обучение персонала, внедрение, настройка, сопровождение и, возможно, расширение программного изделия - так называемое продолжающееся проектирование. Фаза использования заканчивается, когда изделие изымается из употребления и названные выше действия прекращаются. Отметим, однако, что программное изделие может долго применяться кем-либо еще и после того, как фаза использования в том виде, как она определена здесь, завершится. Потому что этот некто может плодотворно использовать программное изделие у себя даже без помощи разработчика. 29 Технология программирования Бугаков П.Ю. Использование программного продукта определяется его эксплуатацией и сопровождением. Эксплуатация программного изделия заключается в исполнении, функционировании его на ЭВМ для обработки информации и в получении результатов, являющихся целью его создания, а также в обеспечении достоверности и надежности выдаваемых данных. Сопровождение программного обеспечения состоит в эксплуатационном обслуживании, развитии функциональных возможностей и повышении эксплуатационных характеристик программного изделия, в тиражировании и переносе программного изделия на различные типы вычислительных средств. В процессе функционирования программного обеспечения возможно обнаружение ошибок в программах и тогда появляется необходимость их модификации и расширения функций. Эти доработки, как правило, ведутся одновременно с эксплуатацией текущей версии программного изделия. После проверки проведенных корректировок на одном из экземпляров программ очередная версия программного изделия заменяет ранее эксплуатировавшиеся или некоторые из них. При этом процесс эксплуатации программного изделия может быть практически непрерывным, так как замена версии программного изделия является кратковременной. Это приводит к тому, что процесс эксплуатации версии программного изделия обычно идет параллельно и независимо от этапа сопровождения. Возможны и обычно желательны перекрытия между разными фазами жизненного цикла программного изделия. Однако не должно быть никакого перекрытия между несмежными процессами. Рис. 4. Перекрытие между смежными процессами ЖЦ ПО Возможна обратная связь между фазами. Например, если во время одного из шагов внешнего проектирования обнаружены погрешности в формулировке целей, то нужно немедленно вернуться и исправить их. Рассмотренная модель жизненного цикла программного изделия с некоторыми изменениями может служить моделью и для малых проектов. Например, когда проектируется единственная программа, то часто отсутствует процесс проектирования архитектуры системы и проектирования базы данных; процессы исходного и детального внешнего проектирования зачастую сливаются воедино и т. п. 1.2.6. Стадии и этапы ЖЦ ПО Под моделью ЖЦ ПО понимается структура, определяющая последовательность выполнения и взаимосвязи процессов, действий и задач на протяжении ЖЦ. Модель 30 Технология программирования Бугаков П.Ю. ЖЦ зависит от специфики, масштаба и сложности проекта и специфики условий, в которых система создается и функционирует. Модель ЖЦ любого конкретного ПО определяет характер процесса его создания, который представляет собой совокупность упорядоченных во времени, взаимосвязанных и объединенных в стадии работ, выполнение которых необходимо и достаточно для создания ПО, соответствующего заданным требованиям. Под стадией создания ПО понимается часть процесса создания ПО, ограниченная некоторыми временными рамками и заканчивающаяся выпуском конкретного продукта (моделей ПО, программных компонентов, документации), определяемого заданными для данной стадии требованиями. Различают следующие стадии жизненного цикла ПО (рис 5): разработку ПО, производство программных изделий (ПИ) и эксплуатацию ПО. Рис. 5. Стадии и фазы жизненного цикла ПО. Стадия разработки (development) ПС состоит из этапа его внешнего описания, этапа конструирования ПС, этапа кодирования (программирование в узком смысле) ПС и этапа аттестации ПС. Всем этим этапам сопутствуют процессы документирования и управление (management) разработкой ПС. Этапы конструирования и кодирования часто перекрываются, иногда довольно сильно. Это означает, что кодирование некоторых частей программного средства может быть начато до завершения этапа конструирования. Внешнее описание (Requirements document) ПС является описанием его поведения с точки зрения внешнего по отношению к нему наблюдателю с фиксацией требований относительно его качества. Внешнее описание ПС начинается с определения требований к ПС со стороны пользователей (заказчика). Конструирование (design) ПС охватывает процессы: разработку архитектуры ПС, разработку структур программ ПС и их детальную спецификацию. Кодирование (coding ) создание текстов программ на языках программирование, их отладку с тестированием ПС. На этапе аттестации ПС производится оценка качества ПС, после успешного завершения которого разработка ПС считается законченной. 31 Технология программирования Бугаков П.Ю. Программное изделие (ПИ) - экземпляр или копия, снятая с разработанного ПС. Изготовление ПИ - это процесс генерации и/или воспроизведения (снятия копии) программ и программных документов ПС с целью их поставки пользователю для применения по назначению. Производство ПИ - это совокупность работ по обеспечению изготовления требуемого количества ПИ в установленные сроки [3.1]. Стадия производства ПС в жизненном цикле ПС является, по-существу, вырожденной (не существенной), так как представляет рутинную работу, которая может быть выполнена автоматически и без ошибок. Этим она принципиально отличается от стадии производства различной техники. В связи с этим в литературе эту стадию, как правило, не включают в жизненный цикл ПС. Стадия эксплуатации ПС охватывает процессы хранения, внедрения и сопровождения ПС, а также транспортировки и применения (operation) ПИ по своему назначению. Она состоит из двух параллельно проходящих фаз: фазы применения ПС и фазы сопровождения ПС. Применение (operation) ПС - это использование ПС для решения практических задач на компьютере путем выполнения ее программ. Сопровождение (maintenance) ПС - это процесс сбора информации о его качестве в эксплуатации, устранения обнаруженных в нем ошибок, его доработки и модификации, а также извещения пользователей о внесенных в него изменениях. Стадия формирования требований к ПО. Она является одной из важнейших, поскольку определяет успех всего проекта. Данная стадия включает следующие этапы: • планирование работ, предваряющее работы над проектом. Основными задачами этапа являются: определение целей разработки, предварительная экономическая оценка проекта, построение плана-графика выполнения работ, создание и обучение совместной рабочей группы; • проведение обследования деятельности автоматизируемого объекта (организации), в рамках которого осуществляются: • предварительное выявление требований к будущей системе; • определение структуры организации; • определение перечня целевых функций организации; • анализ распределения функций по подразделениям и сотрудникам; выявление функциональных взаимодействий между подразделениями, информационных потоков внутри подразделений и между ними, внешних по отношению к организации объектов и внешних информационных взаимодействий; • анализ существующих средств автоматизации деятельности организации; • построение моделей деятельности организации, предусматривающее обработку материалов обследования и построение двух видов моделей: • модели "AS-1S" ("как есть"), отражающей существующее на момент обследования положение дел в организации и позволяющей понять, каким образом функционирует данная организация, а также выявить узкие места и сформулировать предложения по улучшению ситуации; • модели "ТО-ВЕ" ("как должно быть"), отражающей представление о новых технологиях работы организации. 32 Технология программирования Бугаков П.Ю. Каждая из моделей включает в себя полную функциональную и информационную модель деятельности организации, а также, в случае необходимости, модель, описывающую динамику поведения организации. Переход от модели "AS-IS" к модели "ТО-ВЕ" может выполняться двумя способами: 1) совершенствованием существующих технологий на основе оценки их эффективности; 2) радикальным изменением технологий и перепроектированием бизнеспроцессов (реинжиниринг бизнес-процессов). Построенные модели имеют самостоятельное практическое значение. Например, модель "AS-IS" позволяет выявлять узкие места в существующих технологиях и предлагать рекомендации по решению проблем независимо от того, предполагается на данном этапе дальнейшая разработка ПО или нет. Кроме того, модель облегчает обучение сотрудников конкретным направлениям деятельности организации за счет использования наглядных диаграмм (известно, что "одна картинка стоит тысячи слов"). Стадия проектирования. Она, как правило, включает следующие этапы: • разработка системного проекта. На этом этапе дается ответ на вопрос: "Что должна делать будущая система?", а именно: определяются архитектура системы, ее функции, внешние условия функционирования, интерфейсы и распределение функций между пользователями и системой, требования к программным и информационным компонентам, состав исполнителей и сроки разработки. Основу системного проекта составляют модели проектируемой ИС, которые строятся на основе модели "ТО-ВЕ". Документальным результатом этапа является техническое задание; • разработка технического проекта. На этом этапе на основе системного проекта осуществляется собственно проектирование системы, включающее проектирование архитектуры системы и детальное проектирование. Таким образом, дается ответ на вопрос: "Как построить систему, чтобы она удовлетворяла предъявленным к ней требованиям?". Модели проектируемой ИС при этом уточняются и детализируются до необходимого уровня. Содержание последующих стадий совпадает в основном с соответствующими процессами ЖЦ ПО. 1.2.7. Модели жизненного цикла ПО К настоящему времени наибольшее распространение получили следующие две основные модели ЖЦ ПО: каскадная модель (1970 — 1985 гг.) и спиральная модель (1986 - 1990 гг.). В однородных ИС 70-х и 80-х гг. прикладное ПО представляло собой единое целое. Для разработки такого типа ПО применялся каскадный подход (другое название — водопад (waterfall)) (рис. 6). 33 Технология программирования Бугаков П.Ю. Рис. 6. Каскадная схема разработки ПО Принципиальной особенностью каскадного подхода является следующее: переход на следующую стадию осуществляется только после того, как будет полностью завершена работа на текущей стадии, и возвратов на пройденные стадии не предусматривается. Каждая стадия заканчивается получением некоторых результатов, которые служат в качестве исходных данных для следующей стадии. Требования к разрабатываемому ПО, определенные на стадии формирования требований, строго документируются в виде технического задания и фиксируются на все время разработки проекта. Каждая стадия завершается выпуском полного комплекта документации, достаточной для того, чтобы разработка могла быть продолжена другой командой разработчиков. Критерием качества разработки при таком подходе является точность выполнения спецификаций технического задания. При этом основное внимание разработчиков сосредоточивается на достижении оптимальных значений технических характеристик разрабатываемого ПО: производительности, объема занимаемой памяти и др. П р е и м у щ е с т в а применения каскадного способа заключаются в следующем: • на каждой стадии формируется законченный набор проектной документации, отвечающий критериям полноты и согласованности; • выполняемые в логичной последовательности стадии работ позволяют планировать сроки завершения всех работ и соответствующие затраты. В то же время этот подход обладает рядом н е д о с т а т к о в , вызванных прежде всего тем, что реальный процесс создания ПО никогда полностью не укладывался в такую жесткую схему. Процесс создания ПО носит, как правило, итерационный характер: результаты очередной стадии часто вызывают изменения в проектных решениях, выработанных на более ранних стадиях. Таким образом, постоянно возникает 34 Технология программирования Бугаков П.Ю. потребность в возврате к предыдущим стадиям и уточнении или пересмотре ранее принятых решений. В результате реальный процесс создания ПО принимает иной вид (рис. 7). Рис. 7. Реальный процесс разработки ПО Изображенную на рис. 7 схему часто относят к отдельной модели, так называемой модели с промежуточным контролем, в которой межстадийные корректировки обеспечивают большую надежность по сравнению с каскадной моделью, хотя и увеличивают весь период разработки. Основным недостатком каскадного подхода являются существенное запаздывание с получением результатов и, как следствие, достаточно высокий риск создания системы, не удовлетворяю.щей изменившимся потребностям пользователей. Практика показывает, что на начальной стадии проекта полностью и точно сформулировать все требования к будущей системе не удается. Это объясняется двумя причинами: 1) пользователи не в состоянии сразу изложить все свои требования и не могут предвидеть, как они изменятся в ходе разработки; 2) за время разработки могут произойти изменения во внешней среде, которые повлияют на требования к системе. В рамках каскадного подхода требования к ИС фиксируются в виде технического задания на все время ее создания, а согласование получаемых результатов с пользователями производится только в точках, планируемых после завершения каждой стадии (при этом возможна корректировка результатов по замечаниям пользователей, если они не затрагивают требования, изложенные в техническом 35 Технология программирования Бугаков П.Ю. задании). Таким образом, пользователи могут внести существенные замечания только после того, как работа над системой будет полностью завершена. В случае неточного изложения требований или их изменения в течение длительного периода создания ПО пользователи получают систему, не удовлетворяющую их потребностям. В результате приходится начинать новый проект, который может постигнуть та же участь. Для преодоления перечисленных проблем в середине 80-х гг. была предложена спиральная модель ЖЦ (рис. 8). Ее принципиальной особенностью является следующее: прикладное ПО создается не сразу, как в случае каскадного подхода, а по частям с использованием метода прототипирования. Под прототипом понимается действующий программный компонент, реализующий отдельные функции и внешние интерфейсы разрабатываемого ПО. Создание прототипов осуществляется в несколько итераций, или витков спирали. Каждая итерация соответствует созданию фрагмента или версии ПО, на ней уточняются цели и характеристики проекта, оценивается качество полученных результатов и планируются работы следующей итерации. На каждой итерации производится тщательная оценка риска превышения сроков и стоимости проекта, чтобы определить необходимость выполнения еще одной итерации, степень полноты и точности понимания требований к системе, а также целесообразность прекращения проекта. Спиральная модель избавляет пользователей и разработчиков ПО от необходимости полного и точного формулирования требований к системе на начальной стадии, поскольку они уточняются на каждой итерации. Таким образом, углубляются и последовательно конкретизируются детали проекта, и в результате выбирается обоснованный вариант, который доводится до реализации. Разработка итерациями отражает объективно существующий спиральный цикл создания системы. Неполное завершение работ на каждой стадии позволяет переходить на следующую стадию, не дожидаясь полного завершения работы на текущей. При итеративном способе разработки недостающую работу можно будет выполнить на следующей итерации. Главная же задача — как можно быстрее показать пользователям системы работоспособный продукт, тем самым активизируя процесс уточнения и дополнения требований. Спиральная модель не исключает использования каскадного подхода на завершающих стадиях проекта в тех случаях, когда требования к системе оказываются полностью определенными. Основная проблема спирального цикла — определение момента перехода на следующую стадию. Для ее решения необходимо ввести временные ограничения на каждую из стадий жизненного цикла. Переход осуществляется в соответствии с планом, даже если не вся запланированная работа закончена. План составляется на основе статистических данных, полученных в предыдущих проектах, и личного опыта разработчиков. 36 Технология программирования Бугаков П.Ю. Рис. 8. Спиральная модель жизненного цикла программного обеспечения 1.3. Анализ и разработка требований к программному обеспечению 1.3.1. Классификация требований Подробно вопросы анализа и разработки требований к ПО рассматриваются в государственных стандартах, работах [6, 17, 49] и др. В начале процесса создания ПО необходимо четко описать те действия, которые должна выполнять система. Требования — это описание функциональных возможностей и ограничений системы. Разработкой требований (requirements engineering) называется процесс формирования, анализа, документирования и проверки функциональных возможностей системы. Каждое требование должно быть четко выражено, быть полным, не вступать в противоречие с другими требованиями, а также сопровождаться тестами, подтверждающими его выполнение. Все возможные требования к ПО делятся на функциональные и нефункциональные. Функциональные требования — это перечень сервисов, которые должна предоставлять ПС. При этом должны быть описана реакция системы на входные данные, поведение системы в тех или иных ситуациях и т.д. 37 Технология программирования Бугаков П.Ю. Нефункциональные требования описывают характеристики системы и ее окружения, а не ее поведение. Нефункциональные требования к системе тесно связаны с обеспечением качества ПО. Например, нефункциональными являются требования к производительности системы, быстрому восстановлению после сбоев, интерфейсу, надежности, доступности и т.д. Особенность нефункциональных требований состоит в том, что их выполнение трудно проверить. В идеале нефункциональные требования должны иметь количественные показатели. В действительности четкой границы между» этими типами требований не существует, и данное разбиение требований на два класса в значительной степени условно. При разработке и анализе требований можно выделить два основных уровня: 1. Пользовательские требования. Должны описывать функциональные и нефункциональные системные требования естественным языком с использованием простых таблиц, а также наглядных и понятных диаграмм. Эти требования должны определять только внешнее поведение системы. 2. Системные требования. Содержат детализированное описание пользовательских требований. Они включают в себя максимально полную спецификацию системы в целом. Системные требования определяют, что должна делать система, не показывая при этом механизмов реализации. В документе, содержащем требования к системе, желательно отделять пользовательские требования от более детализированных системных требований, поскольку понимание последних требует определенных профессиональных знаний и навыков. 1.3.2. Разработка требований Разработка требований включает в себя мероприятия, необходимые для создания и утверждения документа, содержащего спецификацию системных требований. Различают четыре основных этапа процесса разработки требований: 1) анализ технической осуществимости создания системы; 2) формирование и анализ требований; 3) специфицирование требований и создание соответствующей документации; 4) аттестация требований и процесс управления изменениями системных требований. Этап 1. Анализ технической осуществимости создания с и с т е м ы . Процесс разработки требований начинается с описания и анализа создаваемой системы и ее назначения. Для этого определяются источники информации (менеджеры отделов, разработчики программного обеспечения, технологи, конечные пользователи и т.д.), затем производится сбор и анализ информации о будущей системе, определяются решаемые задачи и выбираются технологии реализации. В соответствии с ГОСТ Р ИСО/МЭК 12207: 1) заказчик инициирует процесс заказа, описывая концепцию или потребность в заказе, разработке или модернизации системы, программного продукта или программной услуги; 2) заказчик определяет и анализирует требования к системе. 38 Технология программирования Бугаков П.Ю. Требования к системе должны охватывать функциональные, коммерческие, организационные и потребительские аспекты системы, а также требования безопасности, защиты и другие критические требования наряду с требованиями к проектированию, тестированию и соответствующим стандартам и процедурам. В результате создается итоговый документ, содержащий требования, отвечающие бизнес-целям организации-заказчика и организации- разработчика, результаты анализа осуществимости проекта в пределах выделенных средств и сроков, а также результаты анализа возможности интеграции с существующими эксплуатируемыми системами заказчика. Должен быть предложен обоснованный бюджет и график работ по созданию системы. Важно подчеркнуть, что анализ осуществимости выработанных требований необходим в целях минимизации риска срыва всего проекта. Э т а п 2 . Ф о р м и р о в а н и е и а н а л и з т р е б о в а н и й . На этом этапе команда разработчиков ПО совместно с заказчиком и конечными пользователями системы определяют области применения, системные сервисы, режимы работы системы и ее характеристики, аппаратные ограничения и т.д. В формировании и анализе требований принимают участие различные группы людей: пользователи, инженеры-разработчики, бизнес-менеджеры, специалисты в предметной области. В связи с этим сложно сформулировать общие требования, потому что каждая сторона выражает свою точку зрения и интересы. К тому же многие участники процесса могут выдвигать трудноосуществимые требования, так как не могут адекватно оценить стоимость их реализации. Одним из результатов анализа требований является достижение понимания и согласия относительно функциональных возможностей ПО. Как правило, процесс формирования и анализа требований проходит через ряд этапов [49]. 1. Анализ предметной области. Аналитики должны изучить предметную область и среду, в которой будет эксплуатироваться система. 2. Сбор требований. Это процесс формирования требований, в котором участвуют все заинтересованные лица. При этом продолжается анализ предметной области. 3. Классификация требований. На этом этапе весь набор требований преобразуется в логически связанные группы. 4. Разрешение противоречий. Требования лиц, занятых в процессе их формирования, могут быть противоречивыми. На этом этапе определяются и разрешаются обнаруженные противоречия. 5. Назначение приоритетов. На этом этапе требованиям назначаются приоритеты в соответствии с важностью задач для бизнес- целей заказчика. 6. Проверка требований. На этом этапе определяется полнота, последовательность и непротиворечивость требований. К формированию и анализу требований не существует универсального подхода. Ниже приводятся три метода, облегчающих процесс формирования требований. 1. Метод, основанный на множестве опорных точек зрения. При разработке требований выявляются общие интересы и точки зрения участников процесса создания ПО. При таком подходе происходит идентификация опорных точек зрения и соответствующих им сервисов. Один и тот же сервис может быть соотнесен с несколькими точками зрения. Присутствие сервисов, которым не было сопоставлено ни одной опорной точки зрения, означает, что на начальном этапе некоторые опорные 39 Технология программирования Бугаков П.Ю. точки зрения не были идентифицированы. Такой подход позволяет эффективно выявлять противоречия в требованиях, предложенных различными лицами. Далее происходит обобщение мнений, связанных с каждой опорной точкой зрения, и разрешение возникших противоречий. 2. Метод построения сценариев. Как правило, воспринимать абстрактное описание системы гораздо сложнее, чем сценарий взаимодействия с ней. В процессе обсуждения сценариев формулируются новые требования и детализируются уже существующие. Обычно сценарий состоит из начального описания состояния системы, описания нормального протекания событий, описания исключительных ситуаций и способов их обработки, информации относительно других действий, которые можно осуществлять во время выполнения сценария, а также описания состояния системы после завершения сценария. Существуют различные мнения относительно наилучшего способа описания сценариев: некоторые предпочитают использовать диаграммы прецедентов (use case diagram), другие предпочитают неформальное описание сценариев на естественном языке. 3. Этнографический метод. Учет социальных и организационных требований при создании ПО часто имеет большое значение для успеха системы на рынке. При использовании этнографического подхода разработчики требований погружаются в среду, в которой будет эксплуатироваться создаваемое ПО, с целью выявления неявных требований к системе, которые отражают реальные аспекты ее эксплуатации, а не формальные умозрительные процессы. Этнографический подход позволяет детализировать требования для критических систем, чего не всегда можно добиться другими методами разработки требований. Однако этот метод ориентирован на конечного пользователя, он не может охватить всех требований предметной области и требований организационного характера, поэтому должен использоваться совместно с другими подходами. В соответствии с ГОСТ Р ИСО/МЭК 12207 разработчик должен установить и документально оформить следующие требования к программным средствам: • функциональные и технические требования, включая производительность, физические характеристики и окружающие условия, под которые должно быть создано ПО; • требования к внешним интерфейсам ПО; • квалификационные требования; • требования безопасности; • требования защиты; • эргономические требования; • требования к определению данных и базе данных; • требования по вводу в действие и приемке поставляемого ПО на объектах эксплуатации и сопровождения; • требования к документации пользователя; • требования к эксплуатации ПО пользователем; • требования к обслуживанию пользователя. Этап 3. Специфицирование требований и создание с о о т в е т с т в у ю щ е й д о к у м е н т а ц и и . Результатом анализа требований является официальный документ для разработчиков ПО, который в соответствии с ГОСТ 19.102 — 77 называют техническим заданием (см. подразд. 1.2) или спецификацией требований к программному обеспечению (Software Requirements Specification, 40 Технология программирования Бугаков П.Ю. SRS) [6]. Он содержит совокупность формализованных требований к ПО и используется как основа для разработки программной системы. Ниже приводятся состав и содержание требований к АС в соответствии со стандартом ГОСТ 34.602 — 89. 1. Требования к системе в целом: • требования к структуре и функционированию системы; • требования к численности и квалификации персонала системы и режиму его работы; • показатели назначения; • требования к надежности; • требования безопасности; • требования к эргономике и технической эстетике; • требования к транспортабельности для подвижных АС; • требования к эксплуатации, техническому обслуживанию, ремонту и хранению компонентов системы; • требования к защите информации от несанкционированного доступа; • требования по сохранности информации при авариях; • требования к защите от влияния внешних воздействий; • требования к патентной чистоте; • требования по стандартизации и унификации; • дополнительные требования. 2. Требования к функциям {задачам), выполняемым системой: • по каждой подсистеме перечень функций и задач (в том числе обеспечивающих взаимодействие частей системы), подлежащих автоматизации; • при создании системы в две или более очереди — перечень функциональных подсистем, отдельных функций или задач, вводимых в действие в первой и последующих очередях; • временной регламент реализации каждой функции, задачи (или комплекса задач); • требования к качеству реализации каждой функции (задачи или комплекса задач), форме представления выходной информации, необходимой точности и времени выполнения, а также одновременности выполнения группы функций и достоверности выдачи результатов; • перечень и критерии отказов для каждой функции, по которой задаются требования по надежности. 3. К требованиям к видам обеспечения относятся: требования к математическому, информационному, лингвистическому, программному, техническому, метрологическому, организационному, методическому и другим видам обеспечения системы. Более подробно создание необходимой документации изложено в [Ю]. Для документирования требований, анализа системы и ее модификации могут использоваться модели системы. Модель является абстракцией системы, опускает детали и легче поддается анализу, чем любое другое представление этой системы. Она является связующим звеном между процессом анализа исходной задачи и процессом проектирования системы. Модели, как правило, используются в качестве иллюстративного материала для представления системы в различных аспектах: 41 Технология программирования Бугаков П.Ю. 1. Внешнее представление, когда моделируется окружение или рабочая среда системы (модели системного окружения). 2. Описание поведения системы, когда моделируется ее поведение (поведенческие модели). 3. Описание структуры системы, когда моделируется системная архитектура или структуры данных, обрабатываемых системой (модели данных, объектные модели, модели наследования). Объектное моделирование соединяет в себе поведенческое и структурное моделирование. Такие структурные методы, как структурный анализ систем и объектно-ориентированный анализ, обеспечивают основу для детального моделирования системы как части процесса формулировки и анализа требований. Для поддержки структурных методов существуют различные CASE-средства, представляющие собой пакет программных средств, который поддерживает отдельные этапы процесса разработки ПО. В этот пакет могут входить: • редакторы диаграмм потоков данных; • средства проектирования, анализа, проверки ПО; • средства генерирования отчетов; • средства создания форм. Для разработки диаграмм, поясняющих требования, может использоваться унифицированный язык моделирования ( Unified Modeling Language, UML). Кроме того, могут применяться специализированные диаграммы, такие как диаграммы модели сущность-связь {entity-relationship diagram, ERD) и диаграммы потоков данных (Data Flow Diagrams, DFD). Для формализованного описания системных требований может использоваться язык описания программ (Program Description Language, PDL). В качестве PDL может использоваться любой универсальный язык программирования. Этап 4. Аттестация требований и процесс управ ления и з м е н е н и я м и с и с т е м н ы х т р е б о в а н и й . Аттестация должна подтвердить, что разработанные на предыдущих этапах требования действительно определяют систему, необходимую заказчику. Проверка требований важна, так как ошибки в спецификации требований могут привести к последующей переделке системы, а следовательно, к большим материальным и временным затратам. В соответствии с ГОСТ Р ИСО/МЭК 12207 — 99 разработчик должен оценить требования по следующим критериям (при этом результаты оценок должны быть документально оформлены): • учет требований к системе и проекту системы; • внешняя согласованность с требованиями к системе; • внутренняя согласованность требований к ПО между собой; • тестируемость требований; • выполнимость программного проекта; • возможность эксплуатации и сопровождения. После успешного проведения оценки должно быть документально зафиксировано состояние требований к ПО. Существует ряд методов аттестации требований, которые можно использовать совместно или каждый в отдельности. 1. Обзор требований — это процесс просмотра системной спецификации для нахождения неточных описаний и ошибок. Обзор требований может быть 42 Технология программирования Бугаков П.Ю. неформальным и формальным. Неформальный обзор — это простое обсуждение требований с большим количеством лиц, участвующих в их формировании. При формальном обзоре группа разработчиков должна обосновать причину включения каждого требования в спецификацию. При этом проверяется непротиворечивость требований и их полнота. 2. Прототипирование. Прототип системы может создаваться для уточнения неясных требований. Прототип предоставляется конечным пользователям и заказчику, которые могут провести с ним эксперименты, позволяющие уточнить требования. 3. Генерация тестовых сценариев. Тесты для требований разрабатываются как часть процесса аттестации, что позволяет обнаружить проблемы в спецификации. Если такие тесты сложно или невозможно разработать, то обычно это означает, что требования трудно выполнить, и поэтому необходимо их пересмотреть. 1.4. Проектирование программного обеспечения [В. А Камаев, В.В. Костерин Технологии программирования] Проект — совокупность проектных документов в соответствии с установленным перечнем, которая представляет результат проектирования. Проектирование — это процесс создания описания (спецификации), необходимого для построения в заданных условиях еще несуществующего объекта на основе первичного описания этого объекта. Результатом проектирования является проектное решение или совокупность проектных решений, удовлетворяющих заданным требованиям. Заданные требования обязательно должны включать форму представления решения. Проектным документом называют документ, выполненный по заданной форме, в котором представлено какое-либо проектное решение. Проектной ситуацией называется реальность (ситуация), в которой ведется проектирование. Источником первопричиной всякой проектной деятельности является субъект — человек или группа людей, испытывающий дискомфорт в существующей ситуации. Лежа на теплой печи (находясь в комфортной ситуации) можно мечтать о решении мировых проблем и ничего не делать. Однако страх перед грядущим дискомфортом (замерзание, голод) вернет мечтателя в реальную ситуацию и потребует нахождения способа решения и самого решения проблемы дальнейшего его существования (заготовка дров и продуктов). Дискомфорт субъекта может быть конкретизирован в виде потребности, удовлетворение которой снимает его. Для удовлетворения потребности нужен некоторый объект (в нашем случае программный продукт), наличие которого, его свойства и состояние удовлетворяют потребность субъекта. Ответив на вопрос, какими свойствами должен обладать объект, мы тем самым подготовим исходные данные для следующего вопроса: как должен быть устроен объект, чтобы иметь такие свойства? Для решения этой задачи также необходимо раскрыть исходную ситуацию. Причем такое раскрытие требуется на разных уровнях конкретизации объекта. Таким образом, получается, что процедура раскрытия проектной ситуации может повторяться многократно и на разных этапах решения общей проектной задачи. При этом все решения взаимосвязаны: решения, принятые на 43 Технология программирования Бугаков П.Ю. одном этапе, должны быть учтены при выполнении других. Как-то формализовать и учесть это влияние затруднительно, поэтому процесс решения носит итерационный характер. Для удовлетворения потребности должна быть реализована некоторая деятельность, конечным результатом которой (целью) и будет создание объекта и (или) приведение его в желаемое (целевое) состояние. Эта деятельность тоже является объектом, требующим проектирования. По отношению к ней также должна решаться аналогичная задача. Иначе говоря, мы получаем, что и сам процесс проектирования является объектом проектирования. Важными при разработке процессов проектирования являются такие понятия, как стратегия, тактика, метод, методика. Стратегия — наука, искусство генерации наиболее существенных общих целей и тактик их достижения. Тактика — совокупность средств и приемов для достижения намеченной цели и искусство их применения. Метод — способ практического осуществления чего-нибудь. Методика — совокупность методов практического выполнения чего-нибудь. Методология (от греч. «учение о методах») — система принципов и способов организации и построения теоретической и практической деятельности, а также учение об этой системе. Современная методология проектирования позволила довести методы проектирования до технологий с набором методик (технология проектирования — апробированная стратегия проектирования). Методики излагаются в виде описаний проектных процедур, которые содержат алгоритмы (только для тривиальных нетворческих операций) и эвроритмы (которыми излагаются эвристические операции). Эвроритм может включать алгоритмы. Термин эвроритм образован от легендарного возгласа Архимеда «эврика!», что в переводе с греческого — «нашел, открыл». Эвристика — наука, раскрывающая природу мыслительных операций человека при решении конкретных задач независимо от их конкретного содержания. В более узком смысле, эвристика — это догадки, основанные на опыте решения родственных задач. Технологии программирования — это апробированные стратегии создания программ, которые излагаются в виде методик с информационными фондами, описаниями проектных процедур и проектных операций. Под проектной процедурой понимают формализованную совокупность действий, выполнение которых оканчивается проектным решением. Например, проектной процедурой является процедура раскрытия проектной ситуации, процедура разработки структуры программы. Действие или формализованная совокупность действий, составляющие часть проектной процедуры, алгоритм которых остается неизменным для ряда проектных процедур, называется проектной операцией. Например, вычерчивание схемы, дифференцирование функции. Проектные процедуры могут включать другие проектные процедуры и т.д., до проектных операций. 44 Технология программирования Бугаков П.Ю. В реальных проектных ситуациях необходим синтез рациональной стратегии каждого конкретного проекта. Достаточно часто этот синтез осуществляется на основе одной, двух и даже трех технологий. Перед нами также стоит вопрос, который уже ранее был упомянут: «Как определить, достигнута ли цель, привела ли деятельность к желаемому результату: тот ли объект создан, который нам нужен, обладает ли он нужными свойствами?» Для этого используются показатели качества (иногда их называют критериями) — величины, свойства, понятия, характеризующие систему с точки зрения субъекта, позволяющие оценить степень удовлетворения его потребностей. На начальном этапе проектирования анализ потребностей позволяет определить вид объекта, его функции (что объект делает при функционировании), свойства и состояние, при которых удовлетворяются потребности. При проектировании решаются задачи синтеза и анализа. Синтез — это процесс построения описания системы по заданному функционированию. Анализ — процесс определения функционирования по заданному описанию системы. Одним из определений задачи оптимизации разработки программ является нахождение разумного компромисса между достигаемой целью и затрачиваемыми на это ресурсами, что может приводить как к пересмотру целей разработки, так и к изменению лимита ресурсов. Решение этой задачи особенно важно, так как программы являются дорогостоящим продуктом, и правильно проведя разработку, можно добиться значительного ее удешевления. Любая задача характеризуется необходимостью преобразования некоторой исходной ситуации в ситуацию, называемую решением. Говоря о любой задаче, мы всегда имеем ее информационные элементы: — информацию об условии (условие задачи) — что задано; — информацию о решении (признаки исходной ситуации)— что требуется получить; — информацию о технологии преобразования условия в решение — как решить. Проектная задача характеризуется неопределенностью априори информации: что требуется получить, что задано. Более того, получение способа решения задачи является объектом проектирования. И наконец, решение проектной задачи должно быть найдено в рамках ограничений внешней среды: доступных денежных средств, заранее заданных сроков, возможностями технических средств, состоянием теории программирования, программных заделов и т. д. Рассмотрим пример. Необходимо в уме сложить числа 4 и 3. Ответ, разумеется, 7. Необходимо в уме перемножить числа 7 и 9. Ответ, конечно, известен — 63. Но если вы не знаете таблицы умножения, то надо выполнить нестандартное преобразование в виде многократного сложения. Трудно ли оно для вас? Необходимо в уме перемножить числа 289 и 347. Если вы не феноменальный счетчик, то хватит ли в вашей голове оперативной памяти? Однако если выполнить методическое преобразование, известное еще со школы, с использованием ручки и бумаги, то вы; скорее всего, справитесь и с этой задачей. Более того, полученное вами на бумаге решение сможет проверить любой заурядный человек, который владеет необходимым методическим обеспечением в виде стандартного школьного алгоритма сложения. 45 Технология программирования Бугаков П.Ю. Обычный нормальный человек со средними способностями может одновременно в своей голове удержать не более семи мыслей. В школе задачи с шестью действиями считаются задачами повышенной сложности и помечаются символом «*». В армиях разных стран, времен и народов производилось деление на десятки, сотни, тысячи. У командиров в подчинении находилось либо десять воинов, либо десять младших командиров. Программа — очень сложный объект, содержащий до нескольких сотен тысяч мыслей. Поэтому важным элементом проектирования является оформление проектных решений в виде программной документации. Различают внешнюю программную документацию, которая согласуется с заказчиком, и внутреннюю промежуточную документацию проекта, которая необходима самим программистам для их работы. Сложность программного обеспечения — отнюдь не случайное его свойство, скорее необходимое. Его сложность определяется четырьмя основными причинами: сложностью задач и, сложностью управления процессом разработки, сложностью описания поведения отдельных подсистем, сложностью обеспечения гибкости конечного программного продукта. 1.4.1. Общие принципы разработки программ Программы различаются по назначению, выполняемым функциям, формам реализации. Однако можно полагать, что существуют некоторые общие принципы, которые следует использовать при разработке программ. Частотный принцип. Принцип основан на выделении в алгоритмах и данных особых групп по частоте использования. Для действий, наиболее часто встречающихся при работе программ, создаются условия их быстрого выполнения. К часто используемым данным обеспечивается наиболее быстрый доступ. «Частые» операции стараются делать более короткими. Следует отметить, что лишь не более 5 % операторов программы оказывают ощутимое влияние на скорость выполнения программы. Этот факт позволяет значительную часть операторов программы кодировать без учета скорости вычислений, обращая основное внимание при этом на «красоту» и наглядность текстов. Принцип модульности. Под модулем в данном контексте понимают функциональный элемент рассматриваемой системы, имеющий оформление, законченное и выполненное в пределах требований системы, и средства сопряжения с подобными элементами или элементами более высокого уровня данной или другой системы. Способы обособления составных частей программ в отдельные модули могут различаться существенно. В значительной степени разделение системы на модули определяется используемым методом проектирования программ. Принцип функциональной избирательности. Этот принцип является логическим продолжением частотного и модульного принципов и используется при проектировании программ. В программах выделяется некоторая часть важных модулей, которые постоянно должны быть в состоянии готовности для эффективной организации вычислительного процесса. Эту часть в программах называют ядром или монитором. При формировании состава монитора требуется учесть два противоречивых требования. В состав монитора, помимо чисто управляющих модулей, должны войти наиболее часто используемые модули. Количество модулей должно быть таким, чтобы объем памяти, занимаемой монитором, был не слишком большим. Программы, входящие' в состав монитора, постоянно хранятся в 46 Технология программирования Бугаков П.Ю. оперативной памяти. Остальные части программ постоянно хранятся во внешних запоминающих устройствах и загружаются в оперативную память только при необходимости, перекрывая друг друга также при необходимости. Принцип генерируемости. Основное положение этого принципа определяет такой способ исходного представления программы, который бы позволял осуществлять настройку на конкретную конфигурацию технических средств, круг решаемых проблем, условия работы пользователя. Принцип функциональной избыточности. Этот принцип учитывает возможность проведения одной и той же работы различными средствами. Особенно важен учет этого принципа при разработке пользовательского интерфейса для выдачи одних и тех же данных разными способами вызова из-за психологических различий в восприятии информации. Принцип «по умолчанию». Применяется для облегчения организации связей с системой как на стадии генерации, так и при работе с уже готовыми программами. Принцип основан на хранении в системе некоторых базовых описаний структур, модулей, конфигураций оборудования и данных, определяющих условия работы с программой. Эту информацию программа использует в качестве заданной, если пользователь забудет или сознательно не конкретизирует ее. В данном случае программа сама установит соответствующие значения. Проектирование является чрезвычайно сложной задачей, свидетельством чему служит огромное количество книг и статей, посвященных данному вопросу. Основная проблема проектирования состоит в сложном характере зависимостей между составными частями ПС. Основной способ проектирования, позволяющий бороться со сложностью, состоит в применении известного принципа «разделяй и властвуй», который в отношении ПС состоит в разбиении задач (системных функций, подсистем, модулей и т.д.) на более простые подзадачи. Такая рекурсивная декомпозиция проводится до получения требуемого уровня детализации, обеспечивая возможность независимой работы разработчиков над различными частями системы. Проектирование системы на самом высоком уровне называется архитектурным проектированием. Вслед за архитектурным проектированием выполняется детальное проектирование, целью которого является уточнение архитектуры системы до необходимого уровня детализации, позволяющего в дальнейшем программистам выполнить свою работу. Главным результатом процесса проектирования является архитектура системы. Несмотря на широкое использование термина «архитектура», для него до сих пор не существует общепризнанного определения. Филипп Крачтен, Гради Буч, Курт Биттнер и Рич Рейт- ман предложили следующее определение архитектуры [70]: «Архитектура ПО заключает в себе ряд важных решений об организации программной системы, среди которых: выбор структурных элементов и их интерфейсов, составляющих и объединяющих систему в единое целое; поведение, обеспечиваемое совместной работой этих элементов; организация этих структурных и поведенческих элементов в более крупные подсистемы, а также архитектурный стиль, которого придерживается данная организация. Выбор архитектуры ПО влияет на функциональность, удобство использования, устойчивость, производительность, повторное использование, понятность, экономические и технологические ограничения, эстетическое восприятие и поиск компромиссов». 47 Технология программирования Бугаков П.Ю. Важно отметить, что архитектура касается только внешних интерфейсов программной системы, внутренние детали реализации не являются архитектурными. Создание архитектуры приложения — это процесс формирования структурированного решения, отвечающего всем системным требованиям и обеспечивающего оптимальные показатели качества, такие как производительность, безопасность, удобство сопровождения и др. Для конкретной системы может существовать несколько подходящих архитектур, из которых необходимо выбрать лучшую. Неправильное определение ключевых сценариев, неправильное проектирование или неспособность выявить долгосрочные последствия основных решений могут поставить под угрозу весь процесс создания системы. Необходимо помнить, что архитектура должна: • раскрывать структуру системы, скрывая детали реализации; • охватывать все варианты использования и сценарии системы; • отвечать требованиям различных заинтересованных сторон; • соответствовать функциональным требованиям и обеспечивать требуемые показатели качества. В зависимости от типа разрабатываемого приложения можно использовать те или иные существующие архитектурные наработки и паттерны (см. ниже). Выбор типа приложения зависит от системных требований и ограничений среды. В процессе детального проектирования определяются интерфейсы подсистем, компонент, классов, распределяется ответственность между ними и определяется порядок их взаимодействия. После проведения детального проектирования с большей точностью может быть проведена оценка стоимости проекта графики работ могут быть разбиты вплоть до конкретных задач, которые, в свою очередь, могут быть распределены между разработчиками. Проектировщики никогда сразу не получают окончательного варианта архитектуры системы. Как правило, дизайн изменяется и дорабатывается в ходе реализации системы по мере выявления новых сведений, а также в ходе тестирования системы на соответствие требованиям реального окружения. В процессе проектирования архитекторы создают различные документы, модели, диаграммы, отражающие те или иные аспекты архитектуры системы. Созданные проектировщиками артефакты используются в дальнейшем программистами и группой сопровождения. 1.4.2. Основные принципы проектирования Вне зависимости от типа приложения необходимо помнить о следующих принципах проектирования: • Разделение функций. Необходимо разделить приложение на отдельные компоненты с минимальным перекрытием функциональности, Неверное разделение может привести к сильному сцеплению и сложностям при взаимодействии компонент, несмотря на слабое перекрытие их функциональности. • Принцип персональной ответственности. Каждый компонент или модуль должен отвечать только за одно конкретное свойство/ функцию или совокупность связанных функций. • Принцип минимального знания, также известный как Закон Деметера (Law of Demeter, LoD). Компоненте или объекту не должны быть известны внутренние детали реализации других компонент или объектов. 48 Технология программирования Бугаков П.Ю. • DRY (Don't Repeat Yourself). Функциональность должна быть реализована только в одном месте и не должна дублироваться ни в одном другом компоненте. Данный принцип был подробно рассмотрен ранее в теме «Экстремальное программирование». • YAGNI ( You Ain't Gonna Need If). Если требования к приложению четко не определены или существует вероятность изменения дизайна в будущем, не следует тратить силы на проектирование наперед. Данный принцип был подробно рассмотрен ранее в теме «Экстремальное программирование». Цель архитектора ПО при проектировании приложения или системы — максимальное упрощение дизайна через его разбиение на функциональные области. 1.4.3. Сквозная функциональность Сквозная функциональность представляет собой важную область проектирования приложения, которая обычно не связана с системными функциями, реализуемыми приложением. К сквозной функциональности могут относиться следующие аспекты реализации приложений: механизмы протоколирования, механизмы аутентификации и авторизации, инфраструктура управления исключениями, инфраструктура кэширования. Смешивание кода, относящегося к реализации сквозной функциональности с основным кодом приложения, может привести к созданию проекта, который будет сложно расширять и обслуживать. В этом случае внесение изменений в сквозную функциональность может потребовать переработки и проверки кода, реализующего системные функции. Для упрощения реализации сквозной функциональности рекомендуется воспользоваться существующими методами аспектноориентированного программирования [61]. 1.4.4. Сцепление и связность Сцепление' (связанность, coupling) и связность (cohesion) являются важнейшими параметрами, характеризующими зависимости модулей программной системы (классов, библиотек, подсистем и т.п.). Стоит отметить, что сцепление и связность являются общесистемными характеристиками, применимыми при синтезе любых систем. Сцепление характеризует степень зависимости модулей. При проектировании необходимо стремиться к минимальной зависимости модулей друг от друга, т.е. к обеспечению их слабого сцепления (low coupling), при котором использование модуля осуществляется через простой и стабильный интерфейс, не заботясь о деталях реализации других модулей. Связано это с тем, что возможности повторного использования сильно сцепленных модулей сильно ограничены. Выделяют следующие основные виды сцепления модулей [40]: • сцепление по данным — модули взаимодействуют через передачу параметров, при этом каждый параметр является элементарным информационным объектом. Это наиболее предпочтительный тип сцепления; • сцепление по структуре данных — модули взаимодействуют через передачу составных информационных объектов (структур); 49 Технология программирования Бугаков П.Ю. • сцепление по управлению — модуль посылает другому модулю информационный объект — флаг, предназначенный для управления его внутренней логикой; • сцепление по общей памяти — модули взаимодействуют через одну и ту же область глобальных данных. Данный вид сцепления является нежелательным, так как ошибка в одном модуле может неожиданно проявиться в другом. Кроме того, использование глобальных данных существенно усложняет понимание взаимодействия модулей; • сцепление по содержимому — возникает в том случае, если один из них ссылается внутрь другого. Это недопустимый тип сцепления, противоречащий принципу модульности, т.е. рассмотрению модуля в виде «черного ящика». На практике допустимо использовать сцепление по данным, структуре данных и управлению. Связность модуля — внутренняя характеристика модуля, характеризующая меру внутренней взаимосвязи между его частями. Связность модуля характеризует степень его «плотности» и направленности на решение определенной задачи. При проектировании нужно стремиться к высокой связности (high cohesion), так как чем выше связность модуля, тем уже его ответственность. Низкая связность (low ' Слова «связанность» и «связность» похожи по звучанию, поэтому вместо связанности будет использоваться термин «сцепление». Стоит отметить, что в некоторых источниках в качестве синонима связности используется термин «сцепление». cohesion) модуля свидетельствует о том, что его зона ответственности размыта или чрезвычайно широка, что приводит к тому, что этот модуль сложно понимать и использовать повторно. Если сцепление является характеристикой системы в целом, то связность характеризует отдельно взятый модуль. Выделяют следующие виды связности модулей [40]: • функционально связный модуль — содержит объекты, предназначенные для решения одной единственной задачи; • последовательно связной модуль — объекты в этом модуле охватывают подзадачи, для которых выходные данные одной из подзадач являются входными для другой; • информационно связный модуль — содержит объекты, использующие одни и те же входные или выходные данные; • процедурно связный модуль — это такой модуль, объекты которого включены в различные (возможно, несвязанные) подзадачи, в которых управление переходит от одной подзадачи к следующей. В отличие от последовательно связанного модуля, в котором осуществляется передача данных, в процедурно связанном модуле выполняется передача управления; • модуль с временной связностью — это такой модуль, в котором объекты привязаны к конкретному промежутку времени. Элементы данного модуля почти не связаны друг с другом за исключением того, что должны выполняться в определенном порядке; • модуль с логической связностью — это такой модуль, объекты которого содействуют решению одной общей подзадачи; • модуль со связностью по совпадению содержит объекты, которые слабо связаны друг с другом. 50 Технология программирования Бугаков П.Ю. В программных системах должны присутствовать модули, имеющие следующие три вида связности: функциональная, последовательная и информационная, так как другие виды связности являются крайне нежелательными и осложняют понимание и сопровождение системы. 1.4.5. Признаки плохого проекта В результате изменений программный проект может начать «загнивать». Признаками «загнивания» программного проекта является обнаружение одного из следующих признаков [35]. 1. Закрепощенность. Система с трудом поддается изменениям, поскольку одно изменение влечет за собой каскад изменений в зависимых модулях. Чем больше модулей подвержено изменениям, тем более закрепощенным является проект. Изменение закрепощенного кода приводит к непредвиденным ошибкам и занимает намного больше времени, чем планировалось изначально. 2. Неустойчивость. В результате осуществляемых изменений система разрушается в тех местах, которые не имеют прямого отношения к непосредственно изменяемому компоненту. Довольно часто подобные проблемы связаны с наличием скрытого дублирования, которое приводит к проблемам при изменении требований. 3. Неподвижность. Достаточно трудно разделить систему на компоненты, которые могли бы повторно использоваться в других системах. Связано это с тем, что для использования компонента требуется «вытянуть» все его зависимости. 4. Вязкость. Сделать что-то правильно намного сложнее, чем выполнить какиелибо некорректные действия. Вязкость может проявляться в двух формах: по отношению к ПО и по отношению к среде. Вязкость кода выражается в том, что правильное решение, диктуемое проектом, сложнее, чем применение хакерских приемов. Вязкость среды может выражаться, например в низкой скорости выполнения тестов, что приводит к тому, что они запускаются недостаточно часто. 5. Неоправданная сложность. Проект включает в себя элементы, которые не используются в настоящий момент времени и не несут непосредственной выгоды. Причиной появления неоправданной сложности является попытка разработчиков предвидеть потенциальные будущие изменения системы, которые в итоге никогда не случаются. 6. Неоправданные повторения. Проект содержит повторяющиеся структуры, которые могут унифицироваться с применением простой абстракции. Дублирование приводит к серьезным проблемам при необходимости внесения изменений и исправлении ошибок. 7. Неопределенность. Проект трудно читать и понимать. Без постоянной переработки эволюционирующий проект с течением времени становится все более неопределенным. 1.4.6. Системный подход и программирование Системный подход — общенаучный обобщенный эвроритм, предусматривающий всестороннее исследование сложного объекта с использованием компонентного, структурного, функционального, параметрического и генетического видов анализа. Компонентный анализ — рассмотрение объекта, включающего в себя составные элементы и входящего, в свою очередь, в систему более высокого ранга. 51 Технология программирования Бугаков П.Ю. Структурный анализ — выявление элементов объекта и связей между ними. Функциональный анализ — рассмотрение объекта как комплекса выполняемых им полезных и вредных функций. Параметрический анализ — установление качественных пределов развития объекта — физических, экономических, экологических и др. Применительно к программам параметрами могут быть: время выполнения какого-нибудь алгоритма, размер занимаемой памяти и т. д. При этом выявляются ключевые технические противоречия, мешающие дальнейшему развитию объекта, и ставится задача их устранения за счет новых технических решений. Генетический анализ — исследование объекта на его соответствие законам развития программных систем. В процессе анализа изучается история развития (генезис) исследуемого объекта: конструкции аналогов и возможных частей, технологии изготовления, объемы тиражирования, языки программирования и т.д. При блочно-иерархическом подходе (частном эвроритме системного подхода, который используется часто в технике и программировании) процесс проектирования и представления о самом объекте расчленяется на уровни. На высшем уровне используется наименее детализированное представление, отражающее самые общие черты и особенности проектируемой системы. На каждом новом последовательном уровне разработки степень подробности рассмотрения возрастает, при этом система рассматривается не в целом, а отдельными блоками. Методология блочно-иерархического подхода базируется на трех концепциях: разбиения и локальной оптимизации, абстрагирования, повторяемости. Концепция разбиения позволяет сложную задачу проектирования объекта или системы свести к решению более простых задач с учетом их взаимосвязи. Локальная оптимизация подразумевает улучшение параметров внутри каждой простой задачи. Абстрагируемость заключается в построении моделей, отражающих только значимые в данных условиях свойства объектов. Повторяемость — в использовании существующего опыта проектирования. Блочно-иерархический подход позволяет на каждом уровне решать задачи приемлемой сложности. Разбиение на блоки должно быть таким, чтобы документация на любом уровне была обозрима и воспринимаема одним человеком. Главным недостатком блочно-иерархического подхода является то, что на верхних уровнях имеют дело с неточными моделями объекта, и решения принимаются в условиях недостаточной информации. Следовательно, при этом подходе высока вероятность проектных ошибок. 1.4.7. Общесистемные принципы создания программ При создании и развитии программного обеспечения (ПО) рекомендуется применять следующие общесистемные принципы: 1) принцип включения, предусматривающий, что требования к созданию, функционированию и развитию ПО определяются со стороны более сложной, включающей его в себя системы; 2) принцип системного единства, состоящий в том, что на всех стадиях создания, функционирования и развития ПО его целостность будет обеспечиваться связями между подсистемами, а также функционированием подсистемы управления; 52 Технология программирования Бугаков П.Ю. 3) принцип развития, предусматривающий в ПО возможность его наращивания и совершенствования компонентов и связей между ними; 4) принцип комплексности, заключающийся в том, что ПО обеспечивает связанность обработки информации как отдельных элементов, так и всего объема данных в целом на всех стадиях обработки; 5) принцип информационного единства, т. е. во всех подсистемах, средствах обеспечения и компонентах ПО используются единые термины, символы, условные обозначения и способы представления; 6) принцип совместимости, состоящий в том, что язык, символы, коды и средства программного обеспечения согласованы, обеспечивают совместное функционирование всех подсистем и сохраняют открытой структуру системы в целом; 7) принцип инвариантности, предопределяющий, что подсистемы и компоненты ПО инвариантны к обрабатываемой информации, т. е. являются универсальными или типовыми. 1.4.8. Особенности программных разработок В программировании существуют различные концепции языков (парадигмы), которые при написании программ могут приводить как к одним и тем же, так и радикально различным подходам. Более того, для ряда языков необходим «свой» тип мышления, особые технологии разработки, особая школа обучения. Большинство программистов используют в работе один два языка программирования в рамках одной парадигмы. Иногда программисту бывает трудно понять чью-то программу, реализованную в непривычной для него парадигме. В противовес изменению цели проекта под используемый язык в ряде проектных случаев рационально избрать иной язык программирования. Приемы и способы программирования конкретного программиста определяются используемым языком. Часто в стороне остаются альтернативные подходы к цели, а следовательно, не используются оптимальные решения в выборе парадигмы, соответствующей решаемой задаче. Ниже дан список основных парадигм программирования вместе с присущими им видами абстракций: — процедурно-ориентированные — алгоритмы; — объектно-ориентированные — классы и объекты; — логически-ориентированные — цели, выраженные в исчислении предикатов; — ориентированные на правила — правила «если..., то...»; — ориентированные на ограничения — инвариантные соотношения; — параллельное программирование — потоки данных. Существуют и другие парадигмы. Почему же их столько? Отчасти потому, что программирование — сравнительно новая дисциплина, а отчасти — из-за желания людей решать разные задачи. Кроме того, наиболее популярная в данный момент компьютерная архитектура не является единственной. В настоящее время проводится большое число экспериментов с машинами, имеющими нестандартные архитектуры, многие из которых рассчитаны на применение других парадигм программирования, например числа Фибоначчи. Общая природа цифровых машин позволяет с большей или меньшей эффективностью моделировать одну архитектуру с помощью другой. Из архитектур наиболее удачны те, в которых за счет аппаратуры и программного обеспечения достигнута наивысшая скорость и простота использования. 53 Технология программирования Бугаков П.Ю. Невозможно назвать какую-либо парадигму наилучшей во всех областях практического применения. Например, для проектирования баз знаний более пригодна парадигма, ориентированная на правила. Объектно-ориентированная парадигма является наиболее приемлемой для широкого круга задач, связанных с большими промышленными системами, в которых основной проблемой является сложность. 1.4.9. Стандарты и программирование Стандарты давно используются в технике и программировании. Создание сложной системы немыслимо без стандартов. Они нужны для борьбы с хаосом и неразберихой, но вместе с этим стандарт не должен быть слишком «узким» и мешать техническому прогрессу. Государственные стандарты отслеживают тенденции развития программирования и дают обязательные рекомендации по их соблюдению. Помимо государственных стандартов (ГОСТ) действуют отраслевые стандарты (ОСТ), стандарты предприятий (СТП). Группа стандартов ГОСТ «Единая система программной документации» (ЕСПД) претерпела мало изменений с момента ее создания, пережила несколько поколений ЭВМ и революционных изменений технологий разработки программ. При этом она до настоящего времени никогда не затрудняла новаций. Помимо вышеизложенных стандартов де-юре имеются стандарты де-факто. Ряд стандартов устанавливается де-факто ведущими фирмами-разработчиками программ и вычислительной техники. Стандарты де-факто появляются на основе идей какой-то широко известной разработки. Выгодно делать продукты в стиле разработки какой-то фирмы, так как пользователи уже имеют навыки работы с меню в стиле «Lotus», электронными таблицами, текстовыми редакторами. Обычно стандартом де-факто определяются используемые операционные системы, трансляторы с языков программирования, организация файлов и средний уровень качества, достигаемый по окончании тестирования программ. Конкретному разработчику выгодно следовать таким стандартам. В области программирования общепризнанной ведущей организацией по разработке стандартов является институт ANSI (Американский национальный институт стандартов). Данный институт является лидером по установке стандартов языков программирования, кодовых таблиц клавиш и символов, выводимых на экран, и еще многих других. Необходимо также отметить стандарты ISO. К сожалению, самое благородное дело стандартизации — достижение всеобщей унификации и взаимозаменяемости — может также стать тормозом развития. Вводя новый стандарт, надо учитывать последствия ввода, особенно если стандарт является опережающим и опережает практику развития или если стандарт является слишком «узким» и тормозит эволюцию прогресса. Во всем мире руководствуются следующим отношением к стандартам: или полностью им следуй, или делай свой собственный стандарт. Стандарты дают дополнительные ограничения. Программист должен уметь не только использовать готовые стандарты, но и разрабатывать новые. Так, например, правила однотипного оформления исходного текста программы определяются стандартом проекта, который может быть изменен при начале разработки нового проекта. Однако в течение выполнения одного проекта оформление всех частей программы должно быть однотипным. Поэтому зачастую 54 Технология программирования Бугаков П.Ю. перед началом нового проекта конкретным программистам следует разрабатывать свои стандарты, которые не нарушают ГОСТ, ОСТ и СТП и действуют в пределах конкретного проекта. 1.4.10. Унифицированный язык моделирования (UML) В 1990-х гг. Гради Буч, Джеймс Рамбо и Ивар Якобсон решили объединить различные методы объектно-ориентированного анализа для разработки унифицированного языка моделирования ( Unified Modeling Language, UML). Разработанный ими язык стал фактическим стандартом моделирования объектов. Текущая версия UML 2.0 опубликована в августе 2005 г. UML версии 1.4.2 принят в качестве международного стандарта ISO/IEC 19501:2005. UML является графическим языком объектного моделирования, предназначенным для разработки требований, проектирования и документирования в основном программных систем. Некоторые UML-редакторы поддерживают кодогенерацию на основе диаграмм. UML также может использоваться для моделирования бизнес- процессов и отображения организационных структур. UML предлагает большое количество графических нотаций для статических и динамических типов моделей. Ниже дается характеристика наиболее популярным видам диаграмм [57]. Диаграмма классов (class diagram) — статическая структурная диаграмма, описывающая структуру системы. Диаграммы классов показывают классы системы их зависимости, атрибуты и методы. Существуют разные точки зрения на построение диаграмм классов в зависимости от целей их применения: • концептуальная точка зрения — диаграмма классов описывает модель предметной области, в ней присутствуют только классы прикладных объектов; • точка зрения спецификации — диаграмма классов применяется при проектировании информационных систем; • точка зрения реализации — диаграмма классов содержит классы, используемые непосредственно в программном коде (при использовании объектноориентированных языков программирования). Компонентная диаграмма (component diagram) — статическая структурная диаграмма, показывающая разбиение программной системы на структурные компоненты и связи (зависимости) между компонентами. В качестве физических компонент могут выступать файлы, библиотеки, модули, исполняемые файлы, пакеты и т. п. Диаграмма композитной/составной структуры (composite structure diagram) — статическая структурная диаграмма, демонстрирующая внутреннюю структуру классов и, по возможности, взаимодействие элементов (частей) внутренней структуры класса. Диаграммы композитной структуры могут использоваться совместно с диаграммами классов. Диаграмма развертывания (deployment diagram) служит для моделирования развертывания артефактов (программных компонент и т. п.) на работающих узлах (аппаратных средствах). Диаграмма объектов (object diagram) демонстрирует полный или частичный снимок моделируемой системы в заданный момент времени. На диаграмме объектов 55 Технология программирования Бугаков П.Ю. отображаются объекты системы с указанием текущих значений их атрибутов и связей между объектами. Диаграмма пакетов (package diagram) — структурная диаграмма, основным содержанием которой являются пакеты и отношения между ними. Диаграммы пакетов служат в первую очередь для организации элементов диаграмм в группы по какому-либо признаку с целью упрощения структуры и организации работы с моделью системы. Диаграмма деятельности (activity diagram) — диаграмма, на которой показано разложение некоторой деятельности на ее составные части. Диаграммы деятельности используются при моделировании бизнес-процессов, технологических процессов, последовательных и параллельных вычислений. Диаграмма состояний (диаграмма конечного автомата, state machine diagram) — динамическая диаграмма, на которой представлен конечный автомат с простыми и составными состояниями, а также переходами между ними по определенным сигналам. Диаграмма прецедентов (диаграмма вариантов использования, use case diagram) — диаграмма, на которой отражены отношения, существующие между актерами (actor) и вариантами использования системы. Диаграммы прецедентов дают возможность заказчику, конечному пользователю и разработчику совместно обсуждать функциональность и поведение системы. Диаграммы коммуникации (communication diagram) и последовательности (sequence diagram) выражают взаимодействие, но показывают его различными способами и с достаточной степенью точности могут быть преобразованы одна в другую. На диаграмме последовательности изображается упорядоченное во времени взаимодействие объектов. В частности, на ней изображаются участвующие во взаимодействии объекты и последовательность сообщений, которыми они обмениваются. На диаграмме коммуникации изображаются взаимодействия между частями составной структуры или ролями кооперации. В отличие от диаграммы последовательности, на диаграмме коммуникации явно указываются отношения между элементами (объектами), а вместо времени применяются порядковые номера вызовов. Диаграмма синхронизации (timing diagram) — альтернативное представление диаграммы последовательности, явным образом показывающее изменения состояния на линии жизни с заданной шкалой времени. Может применяться при разработке приложений реального времени. Более подробно с языком UML можно познакомиться в [9]. UML позволяет стандартизированным образом описать практически все возможные аспекты поведения системы. Кроме того, UML является расширяемым языком и позволяет вводить собственные текстовые и графические стереотипы. Не следует использовать UML в качестве формального языка для создания полного проекта системы, так как UML не имеет точной семантики и полноты по Тьюрингу. Кроме того, не следует пытаться создавать диаграммы для каждого аспекта разрабатываемой системы. UML-диаграммы — это форма документации, которая требует усилий на поддержание ее в актуальном состоянии. 56 Технология программирования Бугаков П.Ю. РАЗДЕЛ II. ТЕХНОЛОГИЯ И СТИЛЬ ПРОГРАММИРОВАНИЯ 2.1. Архитектурные стили Архитектурные стили (парадигмы) — это повторно используемые высокоуровневые обобщенные шаблоны и принципы, обеспечивающие решение часто встречающихся при проектировании проблем [70]. Архитектура программной системы практически никогда не ограничена лишь одним архитектурным стилем, зачастую она является сочетанием архитектурных стилей. Архитектурные стили описывают различные аспекты проектирования: одни описывают схемы развертывания, другие — вопросы структурирования, третьи — аспекты организации распределенного взаимодействия (табл. 1.1). 2.1.1. Объектно-ориентированная архитектура Объектно-ориентированная архитектура — это парадигма проектирования, основанная на разделении ответственностей системы на самостоятельные пригодные для повторного использования объекты, каждый из которых включает относящиеся к нему данные и Т а б л и ц а 1.1. Архитектурные стили Категория Описание Структура Объектно-ориентированная, компонентная, многослойная архитектура Предметная область Проектирование на основе предметной области Развертывание Клиент-сервер, многоуровневая/трехуровневая архитектура Связь Сервисно-ориентированная архитектура (SOA), шина сообщений поведение. Система рассматривается не как набор подпрограмм и процедурных команд, а как набор взаимодействующих объектов. Объекты обособлены, независимы и слабо связаны; взаимодействие между ними происходит через интерфейсы путем вызова методов, свойств и использования событий объектов. Объекты являются экземплярами классов, задающих интерфейс и реализацию поведения объектов. Основными принципами объектно-ориентированного архитектурного стиля проектирования являются [70]: • Инкапсуляция. Объекты предоставляют функциональность только через методы, свойства и события, скрывая внутренние детали реализации от других объектов. При обеспечении совместимого интерфейса изменение и замена объектов может быть выполнена без оказания влияния на клиентов. • Наследование. Наследование позволяет построить новые типы на основе существующих классов. Производные типы получают функциональность своих базовых классов и возможность переопределения их поведения. Наследование упрощает обслуживание и обновление, поскольку изменения, вносимые в базовый класс, автоматически распространяются на все производные классы. В статически 57 Технология программирования Бугаков П.Ю. типизированных языках наследование является наиболее сильной связью между классами, которая не может быть изменена в процессе выполнения программы. • Полиморфизм. Позволяет переопределять поведение базового типа путем реализации производных типов. Объекты производных типов могут использоваться всюду, где ожидается появление объектов базового типа. • Композиция. Объекты могут быть образован ы из других объектов, которые используются для реализации поведения. Объекты могут скрывать наличие внутренних объектов или предоставлять их через простые интерфейсы. • Абстракция. Конкретные типы объектов могут быть изолированы от потребителя путем введения абстрактного интерфейса. Клиенты используют объекты только через введенный интерфейс, который реализуется объектами конкретных типов. Это позволяет обеспечивать альтернативные реализации абстракции, не оказывая влияния на ее потребителей. Ниже в отдельном разделе рассматриваются принципы объектноориентированного программирования классов (принципы SOLID). К основным преимуществам объектно-ориентированной архитектуры относятся: • Понятность. Понятность обеспечивается близким соответствием приложения реальным объектам предметной области. • Возможность повторного использования. Применение полиморфизма и абстракции обеспечивает возможность повторного использования. • Тестируемость. Инкапсуляция позволяет улучшить тестируемость приложения. • Расширяемость. Инкапсуляция, полиморфизм и абстракция гарантируют, что изменения в представлении данных не повлияют на интерфейсы, предоставляемые объектами. • Высокая связность. Размещая в объекте только функционально близкие методы и используя для разных наборов функций разные объекты, можно достичь высокого уровня связности. Более подробно вопросы объектно-ориентированного проектирования рассматриваются в работах [8, 35, 70]. 2.1.2. Компонентная архитектура В компонентной архитектуре приложение структурируется в виде повторно используемых функциональных или логических компонент, имеющих четко определенные интерфейсы, содержащие методы, события и свойства. Использование компонент обеспечивает более высокий уровень абстракции, чем объектноориентированный подход. Компоненты используются через предоставляемые ими интерфейсы, позволяющие вызывающей стороне использовать их функциональность, не полагаясь на подробности внутренней реализации. Основные преимущества компонентного стиля заключаются в следующем [70]: • Упрощение развертывания. Компоненты проектируются с минимальными внешними зависимостями, что позволяет легко развернуть их в любой подходящей среде. Существующие версии компонентов могут заменяться новыми совместимыми версиями, не оказывая влияния на другие компоненты или систему в целом. • Уменьшение стоимости. Использование компонент сторонних производителей позволяет уменьшить затраты на разработку и обслуживание системы. 58 Технология программирования Бугаков П.Ю. • Возможность повторного использования. Как правило, компоненты проектируются с обеспечением возможности их повторного использования в разных контекстах различных приложений. Для обеспечения независимости от контекста такие сведения, как данные о состоянии, должны не извлекаться компонентом самостоятельно, а передаваться в него клиентами. Повторное использование ранее созданных компонент упрощает создание на их базе новых приложений. • Обеспечение компонентной инфраструктурой дополнительных сервисов. Компонентная инфраструктура (например, Component Object Model, СОМ) упрощает использование компонент, предоставляя такие дополнительные сервисы, как активация, управление жизненным циклом, организация очереди вызовов методов, обработка событий и транзакции. 2.1.3. Многослойная архитектура При использовании многослойной архитектуры функциональность приложения группируется в слои, выстраиваемые поверх друг друга. При строгом разделении на слои компоненты одного слоя могут взаимодействовать только с компонентами того же слоя или компонентами слоя, расположенного прямо под данным слоем. При использовании более свободного разделения на слои допускается взаимодействие компонент слоя с компонентами того же и всех нижележащих слоев. Верхние слои посылают запросы нижним слоям и могут реагировать на события, возникающие в этих слоях, обеспечивая возможность передачи данных между слоями вверх и вниз. Необходимо отметить, что слои могут быть распределены по разным узлам вычислительной сети. Разделение на слои помогает организовать строгое разделение функциональности, что в свою очередь обеспечивает гибкость, а также удобство и простоту обслуживания. Многослойная архитектура обладает следующими достоинствами [70]: • Изоляция. Изменение внутренней реализации слоя при сохранении его интерфейса не оказывает влияния на другие слои. При проектировании нет необходимости делать какие-либо предположения о типах данных, методах и свойствах или реализации, поскольку все эти детали скрыты за интерфейсом слоя. • Управляемость. Разделение на слои помогает четче идентифицировать зависимости и организовать код в модули (библиотеки, сборки и т.п.), что повышает управляемость. • Производительность. Распределение слоев по нескольким физическим уровням может улучшить масштабируемость, отказоустойчивость и производительность. • Повторное использование. Отсутствие зависимостей между нижними и верхними слоями обеспечивает возможность их повторного использования в других сценариях. • Тестируемость. Улучшение тестируемости является результатом наличия строго определенных интерфейсов слоев, а также возможности переключения между их разными реализациями. • Высокая связность. Четко определенные границы ответственности для каждого слоя и гарантированное включение в слой только функциональности, напрямую связанной с его задачами, обеспечивает максимальную связность в рамках слоя. 59 Технология программирования Бугаков П.Ю. • Слабое сцепление. Использование абстракций и событий помогает добиться слабого сцепления между слоями. 2.1.4. Проектирование на основе предметной области Проектирование на основе предметной области (Domain Driven Design, DDD) — это объектно-ориентированный подход к проектированию ПО, основанный на создании модели предметной области приложения, выраженной на языке специалистов этой области [39]. При создании модели предметной области группа разработки нередко работает в сотрудничестве со специалистами в данной области, вырабатывая специализированный язык, ориентированный на предметную область. В некоторых случаях могут разрабатываться предметно-ориентированные языки программирования (Domain Specific Language, DSL), используемые в дальнейшем для разработки системы непосредственно в терминах предметной области. В качестве ядра ПО выступает модель предметной области, которая является прямой проекцией выработанного языка. Модель предметной области позволяет специалистам выявлять пробелы в ПО. Процесс DDD имеет целью не только реализацию используемого языка, но также улучшение и уточнение языка предметной области. Основными преимуществами стиля DDD являются [70, 39]: • Обмен информацией. Все участники группы разработки могут использовать модель предметной области и описываемые ею сущности для передачи сведений и требований предметной области с помощью общего языка предметной области, не прибегая к техническому жаргону. • Расширяемость. Модель предметной области часто является модульной и гибкой, что упрощает обновление и расширение при изменении условий и требований. • Удобство тестирования. Объекты модели предметной области характеризуются слабым сцеплением и высокой связностью, что облегчает процесс тестирования. DDD следует применять лишь для сложных предметных областей, для которых процессы моделирования и лингвистического анализа обеспечивают безусловные преимущества при обмене сложной для понимания информацией и формулировании общего видения предметной области. 2.1.5. Архитектура клиент-сервер Клиент-серверная архитектура описывает распределенные приложения, в которых система состоит из клиента, сервера и соединяющей их сети. Простейшая форма системы клиент-сервер, называемая двухуровневой архитектурой, — это серверное приложение, к которому напрямую обращаются множество клиентов. В более общем случае архитектурный стиль клиент-сервер описывает отношения между клиентом и одним или более серверами. Клиент инициирует один или более запросов, ожидает от них ответы и обрабатывает их при получении. Обычно сервер авторизует пользователя и затем проводит обработку, необходимую для получения результата. Для связи с клиентом сервер может использовать широкий диапазон протоколов и форматов данных. Выделяют два основных подхода к организации распределения логики работы приложения между клиентом и сервером. 60 Технология программирования Бугаков П.Ю. 1. Модель тонкого клиента. Вся логика приложения и управление данными выполняются на сервере. Клиент отвечает только за реализацию представления данных пользователю. Большинство web- приложений организовано в соответствии с данным подходом. 2. Модель толстого клиента. Сервер выступает в роли хранилища данных (во многих случаях в роли сервера выступает СУБД — система управления базами данных). Логика работы системы и взаимодействие с пользователем реализуются на стороне клиента. Главный недостаток модели тонкого клиента — большая загруженность сервера и сети, а также неиспользование вычислительных ресурсов клиентов. Недостатком толстого клиента является усложнение процедуры развертывания приложения. Как правило, смешанный подход к организации распределения логики между клиентом и сервером значительно сложнее в применении. К другим разновидностям стиля клиент-сервер относятся: • Системы «клиент — очередь — клиент». Этот подход позволяет клиентам обмениваться друг с другом данными через очередь на сервере. • Одноранговые {Peer-to-Peer, Р2Р) приложения. Созданный на базе «клиент—очередь —клиент», стиль Р2Р позволяет клиенту и серверу обмениваться ролями с целью распределения и синхронизации файлов и данных между множеством клиентов. Эта схема расширяет стиль клиент/сервер, добавляя множественные ответы на запросы, совместно используемые данные, обнаружение ресурсов и устойчивость при удалении участников сети. • Серверы приложений (application server). Приложения и сервисы размещаются и выполняются на сервере. Тонкий клиент выполняет доступ к ним через браузер или специальное ПО. Основные преимущества архитектурного стиля клиент-сервер состоят в следующем [70]. • Контроль доступа. Сервер может централизованно контролировать доступ клиентов к хранимым данным. • Централизованный доступ к данным. Данные в системе хранятся только в одном месте, поэтому упрощается решение таких важных административных задач как резервное копирование. • Простота обслуживания. Серверы могут быть прозрачно заменены, что упрощает процедуру их обслуживания (ремонт, обновление, перемещение). Традиционная двухуровневая архитектура клиент-сервер имеет множество недостатков, включая возможное тесное сцепление данных и логики на сервере, что может иметь негативное влияние на расширяемость и масштабируемость системы. Для решения этих проблем архитектурный стиль клиент-сервер был развит в более универсальный трехуровневый (или многоуровневый) архитектурный стиль, описываемый ниже. При использовании распределенных архитектур всегда следует иметь в виду, что удаленные вызовы осуществляются на несколько порядков медленнее локальных. Кроме того, следует прикладывать дополнительные усилия для устранения проблем безопасности, связанных с передачей запросов по открытым телекоммуникационным сетям. 61 Технология программирования Бугаков П.Ю. 2.1.6. Многоуровневая/трехуровневая архитектура Многоуровневая (TV-уровневая) и трехуровневая архитектура описывают организацию развертывания приложения, функциональность которого четко разделена на сегменты (уровни), физически размещаемые на разных компьютерах. Данные архитектурные стили созданы на базе компонентной и многослойной архитектуры. Компоненты каждого уровня абсолютно независимы от остальных, кроме тех, с которыми они непосредственно соседствуют. Уровню N требуется лишь знать, как обрабатывать запрос от I уровня, как передавать этот запрос на TV- I уровень (если таковой имеется), и как обрабатывать результаты запроса. Для обеспечения лучшей масштабируемости связь между уровнями обычно асинхронная. При использовании трехуровневой архитектуры предполагается наличие трех уровней: клиентское приложение, подключенное к серверу приложений, который в свою очередь подключен к СУБД. Основными преимуществами многоуровневого/трехуровневого архитектурного стиля являются: • Простота обслуживания. Уровни не зависят друг от друга, что позволяет выполнять обновление или изменение уровней, не оказывая влияния на приложение в целом. • Масштабируемость. Архитектура позволяет развертывать уровень на нескольких параллельно работающих серверах. • Гибкость. Управление каждым уровнем может выполняться независимо, что повышает гибкость. • Доступность. Изолированность уровней позволяет быстро и простыми средствами переконфигурировать систему при возникновении сбоев или при плановом обслуживании на одном из уровней. • Контроль доступа. На каждом уровне можно организовать контроль доступа, что повышает безопасность приложения. 2.1.7. Сервисно-ориентированная архитектура Сервисно-ориентированная архитектура (Service-Oriented Architecture, SOA) рассматривает приложение как комбинацию слабо сцепленных сервисов, взаимодействие с которыми организуется на основе строго определенных платформенно- и языково-независимых протоколов (например, SOAP, WSDL). При взаимодействии используются стандартные протоколы и сообщения, контракты и обмен метаданными. С помощью метаданных осуществляется публикация и обнаружение служб, что позволяет определить клиентам их возможности и способ взаимодействия с ними. Как правило, сервисы работают в контексте (отдельном приложении), отличном от контекста потребителя, что уменьшает сцепление между ними. Клиенты пользуются услугами сервисов в терминах предоставляемых ими контрактов, что позволяет использовать для реализации сервисов различные языки программирования, технологии и инфраструктуры, операционные системы, API и т.д. Использование стандартных протоколов позволяет приложениям, работающим на одних платформах, пользоваться услугами сервисов, работающих на других платформах. SOA предоставляет гибкий и элегантный способ комбинирования и 62 Технология программирования Бугаков П.Ю. многократного повторного использования служб для построения сложных распределенных ПС. Реализация служб и их клиентов может быть существенно упрощена за счет изоляции многих аспектов реализации, связанных с управлением параллельной обработкой, надежностью доставки, обеспечения безопасности, управления транзакциями и т.д. Настройка указанных аспектов может осуществляться декларативно, что позволяет разработчикам больше времени уделять непосредственно реализации функционала приложения, а не вспомогательной инфраструктуре. Выделяют следующие основные принципы архитектурного стиля SOA [31, 70]: • Сервисы автономны. Обслуживание, разработка, развертывание и контроль версий каждого сервиса происходит независимо от других служб и клиентов. • Сервисы могут быть распределены. Сервисы могут размещаться в любом месте сети, локально или удаленно, если сеть поддерживает необходимые протоколы связи. • Сервисы слабо сцеплены. Каждый сервис совершенно не зависит от остальных и может быть заменен или обновлен без влияния на приложения, его использующие, при условии предоставления совместимого интерфейса. • Сервисы совместно используют схему и контракт, но не класс. При обмене данными сервисы совместно используют контракты и схемы, но не внутренние классы. • Совместимость сервисов основана на политике. Политика в данном случае означает описание характеристик, таких как транспорт, протокол и безопасность. К основным преимуществам SOA-архитектуры относятся [70]: • Возможность взаимодействия. Поскольку протоколы и форматы данных основываются на отраслевых стандартах, поставщик и потребитель сервиса могут создаваться и развертываться на разных платформах. • Возможность обнаружения. Сервисы могут предоставлять описания, что позволяет другим приложениям и сервисам обнаруживать их и автоматически определять интерфейс. • Автономность. Сервисы являются автономными, доступ к ним осуществляется по формальному контракту, что обеспечивает слабое сцепление и абстракцию. • Повторное использование. Сервисы обеспечивают определенную функциональность, устраняя необходимость ее дублирования в приложениях. 2.1.8. Шина сообщений Архитектура, основанная на шине сообщений, определяет принцип построения программной системы, которая может принимать и отправлять сообщения по одному или более каналам связи, обеспечивая, таким образом, возможность взаимодействия без необходимости знания конкретных деталей друг о друге. Взаимодействие между частями приложения осуществляются путем передачи (обычно асинхронной) сообщений через общую шину. В типовых реализациях архитектуры, основанной на шине сообщений, используется либо маршрутизатор сообщений, либо шаблон проектирования Publish/ Subscribe либо системы обмена сообщениями (Message Queuing). Использование шины сообщений обеспечивает следующие возможности [70]. 63 Технология программирования Бугаков П.Ю. • Все взаимодействие между приложениями основывается на сообщениях, использующих известные схемы. • Сложные операции могут выполняться как часть многошагового процесса путем сочетания ряда операций, каждая из которых обеспечивает решение определенных задач. • Взаимодействие через шину обеспечивает возможность вставки или удаления приложений, что позволяет изменить логику обработки сообщений и улучшить масштабируемость системы путем подключения к шине нескольких экземпляров одного и того же приложения. • Использование модели связи посредством сообщений, основанной на общих стандартах, позволяет взаимодействовать приложениям, разработанным с использованием различных инфраструктур, таких как Microsoft. NET и Java. К разновидностям шины сообщений относятся: • Сервисная шина предприятия (Enterprise Service Bus, ESB). ESB основывается на шине сообщений и использует сервисы для обмена данными между шиной и компонентами, подключенными к шине. ESB обеспечивает сервисы для преобразования одного формата в другой, обеспечивая возможность связи между клиентами, использующими несовместимые форматы сообщений. • Шина интернет-сервисов (Internet Service Bus, ISB). Подобна сервисной шине предприятия, но приложения размещаются не в сети предприятия, а в так называемом облаке. Основная идея ISB — использование унифицированных идентификаторов ресурсов (Uniform Resource Identifiers, URIs) и политик, управляющих логикой маршрутизации через приложения и сервисы в облаке. Основными преимуществами архитектуры, основанной на шине сообщений, являются: • Расширяемость. Возможность добавления или удаления приложений с шины без влияния на существующие приложения. • Невысокая сложность. Приложения упрощаются, потому что каждому из них необходимо знать лишь, как обмениваться данными с шиной. • Гибкость. Многие изменения в приложении могут быть внесены путем внесения изменений в конфигурацию или параметры, управляющие маршрутизацией. • Слабое сцепление. Кроме предоставляемого приложением интерфейса для связи с шиной сообщений, отсутствуют другие зависимости, что обеспечивает возможность изменения, обновления и замены его другим приложением, предоставляющим такой же интерфейс. • Масштабируемость. Возможность подключения к шине множества экземпляров одного приложения для обеспечения одновременной обработки множества запросов. • Простота. Несмотря на то, что реализация шины сообщений усложняет инфраструктуру, каждому приложению приходится поддерживать лишь одно подключение к шине сообщений, а не множество подключений к другим приложениям. 64 Технология программирования 2.2. Методы проектирования ПО Бугаков П.Ю. 2.2.1. Модульное программирование Приступая к разработке каждой программы ПС, следует иметь в виду, что оно, как правило, является большой системой, поэтому мы должны принять меры для ее упрощения. Для этого такую программу разрабатывают по частям, которые называются программными модулями. А сам такой метод разработки программ называют модульным программированием. Программный модуль  это любой фрагмент описания процесса, оформляемый как самостоятельный программный продукт, пригодный для использования в описаниях процесса. Это означает, что каждый программный модуль программируется, компилируется и отлаживается отдельно от других модулей программы, и тем самым, физически разделен с другими модулями программы. Более того, каждый разработанный программный модуль может включаться в состав разных программ, если выполнены условия его использования, декларированные в документации по этому модулю. Таким образом, программный модуль может рассматриваться и как средство борьбы со сложностью программ, и как средство борьбы с дублированием в программировании (т.е. как средство накопления и многократного использования программистских знаний). 2.2.2. Основные характеристики программного модуля Не всякий программный модуль способствует упрощению программы [7.2]. Выделить хороший с этой точки зрения модуль является серьезной творческой задачей. Для оценки приемлемости выделенного модуля используются некоторые критерии. Так, Хольт [7.4] предложил следующие два общих таких критерия:  хороший модуль снаружи проще, чем внутри;  хороший модуль проще использовать, чем построить. Майерс [7.5] предлагает для оценки приемлемости программного модуля использовать более конструктивные его характеристики:  размер модуля,  прочность модуля,  сцепление с другими модулями,  рутинность модуля (независимость от предыстории обращений к нему). Размер модуля измеряется числом содержащихся в нем операторов или строк. Модуль не должен быть слишком маленьким или слишком большим. Маленькие модули приводят к громоздкой модульной структуре программы и могут не окупать накладных расходов, связанных с их оформлением. Большие модули неудобны для изучения и изменений, они могут существенно увеличить суммарное время повторных трансляций программы при отладке программы. Обычно рекомендуются программные модули размером от нескольких десятков до нескольких сотен операторов. Прочность модуля  это мера его внутренних связей. Чем выше прочность модуля, тем больше связей он может спрятать от внешней по отношению к нему части программы и, следовательно, тем больший вклад в упрощение программы он может внести. Модуль может быть выделен, например, при обнаружении в разных местах программы повторения одной и той же последовательности операторов, которая и оформляется в отдельный модуль. 65 Технология программирования Бугаков П.Ю. Два высших по прочности класса модулей рекомендуются для использования. Эти классы мы и рассмотрим подробнее. Функционально прочный модуль  это модуль, выполняющий (реализующий) одну какую-либо определенную функцию. При реализации этой функции такой модуль может использовать и другие модули. Такой класс программных модулей рекомендуется для использования. Информационно прочный модуль  это модуль, выполняющий (реализующий) несколько операций (функций) над одной и той же структурой данных (информационным объектом), которая считается неизвестной вне этого модуля. Для каждой из этих операций в таком модуле имеется свой вход со своей формой обращения к нему. Такой класс следует рассматривать как класс программных модулей с высшей степенью прочности. Информационно прочный модуль может реализовывать, например, абстрактный тип данных. (ФУНКЦИЯ, ПРОЦЕДУРА). Сцепление модуля  это мера его зависимости по данным от других модулей. Характеризуется способом передачи данных. Чем слабее сцепление модуля с другими модулями, тем сильнее его независимость от других модулей. Для оценки степени сцепления Майерс предлагает [7.5] упорядоченный набор из шести видов сцепления модулей. Худшим видом сцепления модулей является сцепление по содержимому. Таким является сцепление двух модулей, когда один из них имеет прямые ссылки на содержимое другого модуля (например, на константу, содержащуюся в другом модуле). Такое сцепление модулей недопустимо. Не рекомендуется использовать также сцепление по общей области  это такое сцепление модулей, когда несколько модулей используют одну и ту же область памяти. Единственным видом сцепления модулей, который рекомендуется для использования современной технологией программирования, является параметрическое сцепление (сцепление по данным по Майерсу [7.5])  это случай, когда данные передаются модулю либо при обращении к нему как значения его параметров, либо как результат его обращения к другому модулю для вычисления некоторой функции. Такой вид сцепления модулей реализуется на языках программирования при использовании обращений к процедурам (функциям). Рутинность модуля  это его независимость от предыстории обращений к нему. Модуль будем называть рутинным, если результат (эффект) обращения к нему зависит только от значений его параметров (и не зависит от предыстории обращений к нему). Модуль будем называть зависящим от предыстории, если результат (эффект) обращения к нему зависит от внутреннего состояния этого модуля, изменяемого в результате предыдущих обращений к нему. Майерс [7.5] не рекомендует использовать зависящие от предыстории (непредсказуемые) модули, так как они провоцируют появление в программах хитрых (неуловимых) ошибок. Однако такая рекомендация является неконструктивной, так как во многих случаях именно зависящий от предыстории модуль является лучшей реализаций информационно прочного модуля. Поэтому более приемлема следующая (более осторожная) рекомендация:  всегда следует использовать рутинный модуль, если это не приводит к плохим (не рекомендуемым) сцеплениям модулей;  зависящие от предыстории модули следует использовать только в случае, когда это необходимо для обеспечения параметрического сцепления;  в спецификации зависящего от предыстории модуля должна быть четко сформулирована эта зависимость таким образом, чтобы было возможно 66 Технология программирования Бугаков П.Ю. прогнозировать поведение (эффект выполнения) данного модуля при разных последующих обращениях к нему. 2.2.3. Методы разработки структуры программы Как уже отмечалось выше, в качестве модульной структуры программы принято использовать древовидную структуру, включая деревья со сросшимися ветвями. В узлах такого дерева размещаются программные модули, а направленные дуги (стрелки) показывают статическую подчиненность модулей, т.е. каждая дуга показывает, что в тексте модуля, из которого она исходит, имеется ссылка на модуль, в который она входит. Другими словами, каждый модуль может обращаться к подчиненным ему модулям, т.е. выражается через эти модули. При этом модульная структура программы, в конечном счете, должна включать и совокупность спецификаций модулей, образующих эту программу. Спецификация программного модуля содержит  синтаксическую спецификацию его входов, позволяющую построить на используемом языке программирования синтаксически правильное обращение к нему (к любому его входу),  функциональную спецификацию модуля (описание семантики функций, выполняемых этим модулем по каждому из его входов). Обычно в литературе обсуждаются два метода разработки структуры программы: метод восходящей разработки и метод нисходящей разработки. Метод восходящей разработки заключается в следующем. Сначала строится модульная структура программы в виде дерева. Затем поочередно программируются модули программы, начиная с модулей самого нижнего уровня (листья дерева модульной структуры программы), в таком порядке, чтобы для каждого программируемого модуля были уже запрограммированы все модули, к которым он может обращаться. После того, как все модули программы запрограммированы, производится их поочередное тестирование и отладка в принципе в таком же (восходящем) порядке, в каком велось их программирование. Такой порядок разработки программы на первый взгляд кажется вполне естественным: каждый модуль при программировании выражается через уже запрограммированные непосредственно подчиненные модули, а при тестировании использует уже отлаженные модули. Однако, современная технология не рекомендует такой порядок разработки программы. Во-первых, для программирования какого-либо модуля совсем не требуется наличия текстов используемых им модулей  для этого достаточно, чтобы каждый используемый модуль был лишь специфицирован (в объеме, позволяющем построить правильное обращение к нему), а для тестирования его возможно (и даже, как мы покажем ниже, полезно) используемые модули заменять их имитаторами (заглушками). Во-вторых, при восходящей разработке глобальная информация для модулей нижних уровней еще не ясна в полном объеме (например, глобальная структура данных). В-третьих, при восходящем тестировании для каждого модуля (кроме головного) приходится создавать ведущую программу (модуль), которая должна подготовить для тестируемого модуля необходимое состояние информационной среды и произвести 67 Технология программирования Бугаков П.Ю. требуемое обращение к нему. Это приводит к большому объему «отладочного» программирования и в то же время не дает никакой гарантии, что тестирование модулей производилось именно в тех условиях, в которых они будут выполняться в рабочей программе. Метод нисходящей разработки заключается в следующем. Как и в предыдущем методе сначала строится модульная структура программы в виде дерева. Затем поочередно программируются модули программы, начиная с модуля самого верхнего уровня (головного), переходя к программированию какого-либо другого модуля только в том случае, если уже запрограммирован модуль, который к нему обращается. После того, как все модули программы запрограммированы, производится их поочередное тестирование и отладка в таком же (нисходящем) порядке. При этом первым тестируется головной модуль программы, который представляет всю тестируемую программу и поэтому тестируется при «естественном» состоянии информационной среды, при котором начинает выполняться эта программа. При этом те модули, к которым может обращаться головной, заменяются их имитаторами (так называемыми заглушками). Каждый имитатор модуля представляется весьма простым программным фрагментом, который, в основном, сигнализирует о самом факте обращения к имитируемому модулю, производит необходимую для правильной работы программы обработку значений его входных параметров (иногда с их распечаткой) и выдает, если это необходимо, заранее запасенный подходящий результат. Особенностью рассмотренных методов восходящей и нисходящей разработок (которые мы будем называть классическими) является требование, чтобы модульная структура программы была разработана до начала программирования (кодирования) модулей. Ниже предлагаются конструктивный и архитектурный подходы к разработке программ, в которых модульная структура формируется в процессе программирования (кодирования) модулей. Спецификация программы (головного модуля) Текст головного модуля Спецификация 1-ой подзадачи Спецификация 3-ей подзадачи Спецификация 2-ой подзадачи Рис. 9. Первый шаг формирования модульной структуры программы при конструктивном подходе. 68 Технология программирования Бугаков П.Ю. Конструктивный подход к разработке программы представляет собой модификацию нисходящей разработки, при которой модульная древовидная структура программы формируется в процессе программирования модулей. Разработка программы при конструктивном подходе начинается с программирования головного модуля, исходя из спецификации программы в целом. При этом спецификация программы принимается в качестве спецификации ее головного модуля, который полностью берет на себя ответственность за выполнение функций программы. В процессе программирования головного модуля, в случае, если эта программа достаточно большая, выделяются подзадачи (внутренние функции), в терминах которых программируется головной модуль. Это означает, что для каждой выделяемой подзадачи (функции) создается спецификация реализующего ее фрагмента программы, который в дальнейшем может быть представлен некоторым поддеревом модулей. Спецификация программы (головного модуля) Текст головного модуля Спецификация 2-ой подзадачи Спецификация 1-ой подзадачи Спецификация 3-ей подзадачи Текст головного модуля 1-ой подзадачи Текст головного модуля 3-ей подзадачи Спецификация 2-ой подзадачи Текст головного модуля 2-ой подзадачи Спецификация 2.1-ой подзадачи Спецификация 2.2-ой подзадачи Рис. 10. Второй шаг формирования модульной структуры программы при конструктивном подходе. Архитектурный подход к разработке программы представляет собой модификацию восходящей разработки, при которой модульная структура программы формируется в процессе программирования модуля. Но при этом ставится существенно другая цель разработки: повышение уровня используемого языка программирования, а не разработка конкретной программы. Это означает, что для заданной предметной области выделяются типичные функции, каждая из которых 69 Технология программирования Бугаков П.Ю. может использоваться при решении разных задач в этой области, и специфицируются, а затем и программируются отдельные программные модули, выполняющие эти функции. Так как процесс выделения таких функций связан с накоплением и обобщением опыта решения задач в заданной предметной области, то обычно сначала выделяются и реализуются отдельными модулями более простые функции, а затем постепенно появляются модули, использующие ранее выделенные функции. Так как такие структуры могут многократно использоваться в разных конкретных программах, то архитектурный подход может рассматриваться как путь борьбы с дублированием в программировании. В связи с этим программные модули, создаваемые в рамках архитектурного подхода, обычно параметризуются для того, чтобы усилить применимость таких модулей путем настройки их на параметры. Методы разработки структуры программ Восходящие Нисходящие Классический подход Классический подход Классическая нисходящая разработка Классическая восходящая разработка (не рекомендуется) Классическая нисходящая реализация Классическая восходящая реализация (не рекомендуется) Конструктивный подход Архитектурный подход Конструктивная разработка Архитектурная разработка Конструктивная реализация Архитектурная реализация Целенаправленная конструктивная реализация Рис. 11. Классификация методов разработки структуры программ. 70 Технология программирования Бугаков П.Ю. Возможны и другие варианты обхода дерева. Так, при конструктивной реализации для обхода дерева программы целесообразно следовать идеям Фуксмана, которые он использовал в предложенном им методе вертикального слоения [7.8]. Сущность такого обхода заключается в следующем. В рамках конструктивного подхода сначала реализуются только те модули, которые необходимы для самого простейшего варианта программы, которая может нормально выполняться только для весьма ограниченного множества наборов входных данных, но для таких данных эта задача будет решаться до конца. Вместо других модулей, на которые в такой программе имеются ссылки, в эту программу вставляются лишь их имитаторы, обеспечивающие, в основном, сигнализацию о выходе за пределы этого частного случая. Затем к этой программе добавляются реализации некоторых других модулей (в частности, вместо некоторых из имеющихся имитаторов), обеспечивающих нормальное выполнение для некоторых других наборов входных данных. И этот процесс продолжается поэтапно до полной реализации требуемой программы. Таким образом, обход дерева программы производится с целью кратчайшим путем реализовать тот или иной вариант (сначала самый простейший) нормально действующей программы. В связи с этим такая разновидность конструктивной реализации получила название метода целенаправленной конструктивной реализации. Достоинством этого метода является то, что уже на достаточно ранней стадии создается работающий вариант разрабатываемой программы. Психологически это играет роль допинга, резко повышающего эффективность разработчика. Поэтому этот метод является весьма привлекательным. Подводя итог сказанному, на рис. 11 представлена общая классификация рассмотренных методов разработки структуры программы. 2.3. Тестирование и отладка программного средства Отладка ПС  это деятельность, направленная на обнаружение и исправление ошибок в ПС с использованием процессов выполнения его программ. Тестирование ПС  это процесс выполнения его программ на некотором наборе данных, для которого заранее известен результат применения или известны правила поведения этих программ. Указанный набор данных называется тестовым или просто тестом. Таким образом, отладку можно представить в виде многократного повторения трех процессов: тестирования, в результате которого может быть констатировано наличие в ПС ошибки, поиска места ошибки в программах и документации ПС и редактирования программ и документации с целью устранения обнаруженной ошибки. Другими словами: Отладка = Тестирование + Поиск ошибок + Редактирование. 2.3.1. Принципы и виды отладки программного средства Успех отладки ПС в значительной степени предопределяет рациональная организация тестирования. При отладке ПС отыскиваются и устраняются, в основном, те ошибки, наличие которых в ПС устанавливается при тестировании. Как было уже отмечено, тестирование не может доказать правильность ПС [10.9], в лучшем случае оно может продемонстрировать наличие в нем ошибки. Другими словами, нельзя гарантировать, что тестированием ПС практически выполнимым набором тестов 71 Технология программирования Бугаков П.Ю. можно установить наличие каждой имеющейся в ПС ошибки. Поэтому возникает две задачи. Первая задача: подготовить такой набор тестов и применить к ним ПС, чтобы обнаружить в нем по возможности большее число ошибок. Однако чем дольше продолжается процесс тестирования (и отладки в целом), тем большей становится стоимость ПС. Отсюда вторая задача: определить момент окончания отладки ПС (или отдельной его компоненты). Признаком возможности окончания отладки является полнота охвата пропущенными через ПС тестами (т.е. тестами, к которым применено ПС) множества различных ситуаций, возникающих при выполнении программ ПС, и относительно редкое проявление ошибок в ПС на последнем отрезке процесса тестирования. Последнее определяется в соответствии с требуемой степенью надежности ПС, указанной в спецификации его качества. Проектирование тестов можно начинать сразу же после завершения этапа внешнего описания ПС. Возможны разные подходы к выработке стратегии проектирования тестов, которые можно условно графически разместить (см. рис. 12) между следующими двумя крайними подходами. Левый крайний подход заключается в том, что тесты проектируются только на основании изучения спецификаций ПС (внешнего описания, описания архитектуры и спецификации модулей). Строение модулей при этом никак не учитывается, т.е. они рассматриваются как черные ящики. Фактически такой подход требует полного перебора всех наборов входных данных, так как в противном случае некоторые участки программ ПС могут не работать при пропуске любого теста, а это значит, что содержащиеся в них ошибки не будут проявляться. Однако тестирование ПС полным множеством наборов входных данных практически неосуществимо. Правый крайний подход заключается в том, что тесты проектируются на основании изучения текстов программ с целью протестировать все пути выполнения каждой программ ПС. Если принять во внимание наличие в программах циклов с переменным числом повторений, то различных путей выполнения программ ПС может оказаться также чрезвычайно много, так что их тестирование также будет практически неосуществимо. Тестирование по отношению к спецификациям Тестирование по отношению к текстам программ Оптимальная стратегия Рис. 12. Спектр подходов к проектированию тестов. Оптимальная стратегия проектирования тестов расположена внутри интервала между этими крайними подходами, но ближе к левому краю. Она включает проектирование значительной части тестов по спецификациям, но она требует также проектирования некоторых тестов и по текстам программ. При этом в первом случае эта стратегия базируется на принципах:  на каждую используемую функцию или возможность  хотя бы один тест, 72 Технология программирования Бугаков П.Ю.  на каждую область и на каждую границу изменения какой-либо входной величины  хотя бы один тест,  на каждую особую (исключительную) ситуацию, указанную в спецификациях,  хотя бы один тест. Во втором случае эта стратегия базируется на принципе: каждая команда каждой программы ПС должна проработать хотя бы на одном тесте. 2.3.2 Заповеди отладки программного средства Ниже приводятся рекомендации по организации отладки в форме заповедей [10.1, 10.8]. Заповедь 1. Считайте тестирование ключевой задачей разработки ПС, поручайте его самым квалифицированным и одаренным программистам; нежелательно тестировать свою собственную программу. Заповедь 2. Хорош тот тест, для которого высока вероятность обнаружить ошибку, а не тот, который демонстрирует правильную работу программы. Заповедь 3. Готовьте тесты как для правильных, так и для неправильных данных. Заповедь 4. Документируйте пропуск тестов через компьютер; детально изучайте результаты каждого теста; избегайте тестов, пропуск которых нельзя повторить. Заповедь 5. Каждый модуль подключайте к программе только один раз; никогда не изменяйте программу, чтобы облегчить ее тестирование. Заповедь 6. Пропускайте заново все тесты, связанные с проверкой работы какойлибо программы ПС или ее взаимодействия с другими программами, если в нее были внесены изменения (например, в результате устранения ошибки). В нашей стране различаются [8] два основных вида отладки (включая тестирование): автономную и комплексную отладку ПС. Автономная отладка ПС означает последовательное раздельное тестирование различных частей программ, входящих в ПС, с поиском и исправлением в них фиксируемых при тестировании ошибок. Она фактически включает отладку каждого программного модуля и отладку сопряжения модулей. Комплексная отладка означает тестирование ПС в целом с поиском и исправлением фиксируемых при тестировании ошибок во всех документах (включая тексты программ ПС), относящихся к ПС в целом. К таким документам относятся определение требований к ПС, спецификация качества ПС, функциональная спецификация ПС, описание архитектуры ПС и тексты программ ПС. 2.3.3. Автономная отладка программного средства При автономной отладке ПС каждый модуль на самом деле тестируется в некотором программном окружении, кроме случая, когда отлаживаемая программа состоит только из одного модуля. Это окружение состоит [10.8] из других модулей, часть которых является модулями отлаживаемой программы, которые уже отлажены, а часть  модулями, управляющими отладкой (отладочными модулями, см. ниже). При восходящем тестировании (см. лекцию 7) это окружение будет содержать только один отладочный модуль (кроме случая, когда отлаживается последний модуль отлаживаемой программы), который будет головным в тестируемой программе. Такой отладочный модуль называют ведущим (или драйвером [10.1]). Ведущий отладочный модуль подготавливает информационную среду для тестирования отлаживаемого модуля (т. е. формирует ее состояние, требуемое для тестирования 73 Технология программирования Бугаков П.Ю. этого модуля, в частности, путем ввода некоторых тестовых данных), осуществляет обращение к отлаживаемому модулю и после окончания его работы выдает необходимые сообщения. При отладке одного модуля для разных тестов могут составляться разные ведущие отладочные модули. При нисходящем тестировании (см. лекцию 7) окружение отлаживаемого модуля в качестве отладочных модулей содержит отладочные имитаторы (заглушки) некоторых еще не отлаженных модулей. К таким модулям относятся, прежде всего, все модули, к которым может обращаться отлаживаемый модуль. Некоторые из этих имитаторов при отладке одного модуля могут изменяться для разных тестов. К достоинствам восходящего тестирования относятся:  простота подготовки тестов,  возможность полной реализации плана тестирования модуля. Это связано с тем, что тестовое состояние информационной среды готовится непосредственно перед обращением к отлаживаемому модулю (ведущим отладочным модулем). Недостатками восходящего тестирования являются следующие его особенности:  тестовые данные готовятся, как правило, не в той форме, которая рассчитана на пользователя (кроме случая, когда отлаживается последний, головной, модуль отлаживаемой программ);  большой объем отладочного программирования (при отладке одного модуля приходится составлять много ведущих отладочных модулей, формирующих подходящее состояние информационной среды для разных тестов);  необходимость специального тестирования сопряжения модулей. К достоинствам нисходящего тестирования относятся следующие его особенности:  большинство тестов готовится в форме, рассчитанной на пользователя;  во многих случаях относительно небольшой объем отладочного программирования (имитаторы модулей, как правило, весьма просты и каждый пригоден для большого числа, нередко  для всех, тестов);  отпадает необходимость тестирования сопряжения модулей. Недостатком нисходящего тестирования является то, что тестовое состояние информационной среды перед обращением к отлаживаемому модулю готовится косвенно  оно является результатом применения уже отлаженных модулей к тестовым данным или данным, выдаваемым имитаторами. Это, во-первых, затрудняет подготовку тестов и требует высокой квалификации тестовика (разработчика тестов), а во-вторых, делает затруднительным или даже невозможным реализацию полного плана тестирования отлаживаемого модуля. 2.3.4. Комплексная отладка программного средства Как уже было сказано выше, при комплексной отладке тестируется ПС в целом, причем тесты готовятся по каждому из документов ПС [10.8]. Тестирование этих документов производится, как правило, в порядке, обратном их разработке. Тестирование архитектуры ПС. Целью тестирования является поиск несоответствия между описанием архитектуры и совокупностью программ ПС. 74 Технология программирования Бугаков П.Ю. Тестирование внешних функций. Целью тестирования является поиск расхождений между функциональной спецификацией и совокупностью программ ПС. Несмотря на то, что все эти программы автономно уже отлажены, указанные расхождения могут быть, например, из-за несоответствия внутренних спецификаций программ и их модулей (на основании которых производилось автономное тестирование) функциональной спецификации ПС. Как правило, тестирование внешних функций производится так же, как и тестирование модулей на первом шаге, т.е. как черного ящика. Тестирование качества ПС. Целью тестирования является поиск нарушений требований качества, сформулированных в спецификации качества ПС. Это наиболее трудный и наименее изученный вид тестирования. Тестирование документации по применению ПС. Целью тестирования является поиск несогласованности документации по применению и совокупностью программ ПС, а также выявление неудобств, возникающих при применении ПС. Этот этап непосредственно предшествует подключению пользователя к завершению разработки ПС (тестированию определения требований к ПС и аттестации ПС), поэтому весьма важно разработчикам сначала самим воспользоваться ПС так, как это будет делать пользователь [10.1]. Все тесты на этом этапе готовятся исключительно на основании только документации по применению ПС. Тестирование определения требований к ПС. Целью тестирования является выяснение, в какой мере ПС не соответствует предъявленному определению требований к нему. Особенность этого вида тестирования заключается в том, что его осуществляет организация-покупатель или организация-пользователь ПС [10.1. Обычно это тестирование производится с помощью контрольных задач  типовых задач, для которых известен результат решения. 2.4. Документирование программных средств 2.4.1. Документация, создаваемая и используемая в процессе разработки программных средств. При разработке ПС создается и используется большой объем разнообразной документации. Она необходима как средство передачи информации между разработчиками ПС, как средство управления разработкой ПС и как средство передачи пользователям информации, необходимой для применения и сопровождения ПС. На создание этой документации приходится большая доля стоимости ПС. Эту документацию можно разбить на две группы [13.1]:  Документы управления разработкой ПС.  Документы, входящие в состав ПС. Документы управления разработкой ПС (software process documentation) управляют и протоколируют процессы разработки и сопровождения ПС, обеспечивая связи внутри коллектива разработчиков ПС и между коллективом разработчиков и менеджерами ПС (software managers)  лицами, управляющими разработкой ПС. Эти документы могут быть следующих типов [13.1]:  Планы, оценки, расписания. Эти документы создаются менеджерами для прогнозирования и управления процессами разработки и сопровождения ПС.  Отчеты об использовании ресурсов в процессе разработки. Создаются менеджерами. 75 Технология программирования Бугаков П.Ю.  Стандарты. Эти документы предписывают разработчикам, каким принципам, правилам, соглашениям они должны следовать в процессе разработки ПС. Эти стандарты могут быть как международными или национальными, так и специально созданными для организации, в которой ведется разработка ПС.  Рабочие документы. Это основные технические документы, обеспечивающие связь между разработчиками. Они содержат фиксацию идей и проблем, возникающих в процессе разработки, описание используемых стратегий и подходов, а также рабочие (временные) версии документов, которые должны войти в ПС.  Заметки и переписка. Эти документы фиксируют различные детали взаимодействия между менеджерами и разработчиками. Документы, входящие в состав ПС (software product documentation), описывают программы ПС как с точки зрения их применения пользователями, так и с точки зрения их разработчиков и сопроводителей (в соответствии с назначением ПС). Эти документы образуют два комплекта с разным назначением:  Пользовательская документация ПС (П-документация).  Документация по сопровождению ПС (С-документация). 2.4.2. Пользовательская документация программных средств Пользовательская документация ПС (user documentation) объясняет пользователям, как они должны действовать, чтобы применить разрабатываемое ПС [13.1, 13.2.]. Она необходима, если ПС предполагает какое-либо взаимодействие с пользователями. К такой документации относятся документы, которыми должен руководствоваться пользователь при инсталляции ПС (при установке ПС с соответствующей настройкой на среду применения ПС), при применении ПС для решения своих задач и при управлении ПС (например, когда разрабатываемое ПС будет взаимодействовать с другими системами). Эти документы частично затрагивают вопросы сопровождения ПС, но не касаются вопросов, связанных с модификацией программ. В связи с этим следует различать две категории пользователей ПС: ординарных пользователей ПС и администраторов ПС. Ординарный пользователь ПС (end-user) использует ПС для решения своих задач (в своей предметной области). Это может быть инженер, проектирующий техническое устройство, или кассир, продающий железнодорожные билеты с помощью ПС. Он может и не знать многих деталей работы компьютера или принципов программирования. Администратор ПС (system administrator) управляет использованием ПС ординарными пользователями и осуществляет сопровождение ПС, не связанное с модификацией программ. Например, он может регулировать права доступа к ПС между ординарными пользователями, поддерживать связь с поставщиками ПС или выполнять определенные действия, чтобы поддерживать ПС в рабочем состоянии, если оно включено как часть в другую систему. Типичный состав пользовательской документации для достаточно больших ПС:  Общее функциональное описание ПС. Дает краткую характеристику функциональных возможностей ПС. Предназначено для пользователей, которые должны решить, насколько необходимо им данное ПС.  Руководство по инсталляции ПС. Предназначено для администраторов ПС. Оно должно детально предписывать, как устанавливать системы в конкретной среде, в 76 Технология программирования Бугаков П.Ю. частности, должно содержать описание компьютерно-считываемого носителя, на котором поставляется ПС, файлы, представляющие ПС, и требования к минимальной конфигурации аппаратуры.  Инструкция по применению ПС. Предназначена для ординарных пользователей. Содержит необходимую информацию по применению ПС, организованную в форме удобной для ее изучения.  Справочник по применению ПС. Предназначен для ординарных пользователей. Содержит необходимую информацию по применению ПС, организованную в форме удобной для избирательного поиска отдельных деталей.  Руководство по управлению ПС. Предназначено для администраторов ПС. Оно должно описывать сообщения, генерируемые, когда ПС взаимодействует с другими системами, и как должен реагировать администратор на эти сообщения. Кроме того, если ПС использует системную аппаратуру, этот документ может объяснять, как сопровождать эту аппаратуру. Как уже говорилось ранее (см. лекцию 4), разработка пользовательской документации начинается сразу после создания внешнего описания. Качество этой документации может существенно определять успех ПС. Она должна быть достаточно проста и удобна для пользователя (в противном случае это ПС, вообще, не стоило создавать). Поэтому, хотя черновые варианты (наброски) пользовательских документов создаются основными разработчиками ПС, к созданию их окончательных вариантов часто привлекаются профессиональные технические писатели. Кроме того, для обеспечения качества пользовательской документации разработан ряд стандартов (см. например, [13.2]), в которых предписывается порядок разработки этой документации, формулируются требования к каждому виду пользовательских документов и определяются их структура и содержание. 2.4.3. Документация по сопровождению программных средств Документация по сопровождению ПС (system documentation) описывает ПС с точки зрения ее разработки. Эта документация необходима, если ПС предполагает изучение того, как оно устроена (сконструирована), и модернизацию его программ. Документация по сопровождению ПС можно разбить на две группы: (1) документация, определяющая строение программ и структур данных ПС и технологию их разработки; (2) документацию, помогающую вносить изменения в ПС. Документация первой группы содержит итоговые документы каждого технологического этапа разработки ПС. Она включает следующие документы:  Внешнее описание ПС (Requirements document).  Описание архитектуры ПС (description of the system architecture), включая внешнюю спецификацию каждой ее программы (подсистемы).  Для каждой программы ПС  описание ее модульной структуры, включая внешнюю спецификацию каждого включенного в нее модуля.  Для каждого модуля  его спецификация и описание его строения (design description).  Тексты модулей на выбранном языке программирования (program source code listings). 77 Технология программирования Бугаков П.Ю.  Документы установления достоверности ПС (validation documents), описывающие, как устанавливалась достоверность каждой программы ПС и как информация об установлении достоверности связывалась с требованиями к ПС. Документация второй группы содержит  Руководство по сопровождению ПС (system maintenance guide), которое описывает особенности реализации ПС (в частности, трудности, которые пришлось преодолевать) и как учтены возможности развития ПС в его строении (конструкции). В нем также фиксируются, какие части ПС являются аппаратно- и программно-зависимыми. Литература 1. Ian Sommerville. Software Engineering. - Addison-Wesley Publishing Company, 1992. P. 2. 3. ANSI/IEEE Std 1063-1988, IEEE Standard for Software User Documentation. 4. ANSI/IEEE Std 830-1984, IEEE Guide for Software Requirements Specification. 5. ANSI/IEEE Std 1016-1987, IEEE Recommended Practice for Software Design Description. 6. ANSI/IEEE Std 1008-1987, IEEE Standard for Software Unit Testing. 7. ANSI/IEEE Std 1012-1986, IEEE Standard for Software Verification and Validation Plans. 8. ANSI/IEEE Std 983-1986, IEEE Guide for Software Quality Assurance Planning. 9. ANSI/IEEE Std 829-1983, IEEE Standard for Software Test Documentation. 2.5. Структурное программирование На сегодняшний день в программной инженерии существуют два основных подхода к разработке ПО, принципиальное различие между которыми обусловлено разными способами декомпозиции систем. Первый подход называют функционально-модульным или структурным. В его основу положен принцип функциональной декомпозиции, при которой структура системы описывается в терминах иерархии ее функций и передачи информации между отдельными функциональными элементами. Второй, объектно-ориентированный подход использует объектную декомпозицию. При этом структура системы описывается в терминах объектов и связей между ними, а поведение системы описывается в терминах обмена сообщениями между объектами. Итак, сущность структурного подхода к разработке ПО заключается в его декомпозиции (разбиении) на автоматизируемые функции: система разбивается на функциональные подсистемы, которые, в свою очередь, делятся на подфункции, те — на задачи и так далее до конкретных процедур. При этом автоматизируемая система сохраняет целостное представление, в котором все составляющие компоненты взаимоувязаны. Все наиболее распространенные методы структурного подхода базируются на ряде общих принципов. 78 Технология программирования Бугаков П.Ю. Базовыми принципами являются: • принцип "разделяй и властвуй"; • принцип иерархического упорядочения — принцип организации составных частей системы в иерархические древовидные структуры с добавлением новых деталей на каждом уровне. Основными из этих принципов являются: • принцип абстрагирования - выделение существенных аспектов системы и отвлечение от несущественных; • принцип непротиворечивости — обоснованность и согласованность элементов системы; • принцип структурирования данных — данные должны быть структурированы и иерархически организованы. В структурном подходе используются в основном две группы средств, описывающих функциональную структуру системы и отношения между данными. Каждой группе средств соответствуют определенные виды моделей (диаграмм), наиболее распространенными среди которых являются: • SADT(Structured Analysis and Design Technique — метод структурного анализа и проектирования,) — модели и соответствующие функциональные диаграммы; • DFD (Data Flow Diagrams) - диаграммы потоков данных; На стадии формирования требований к ПО SADT-модели и DFD используются для построения модели "AS-IS" и модели "ТО-ВЕ", отражая, таким образом существующую и предлагаемую структуру бизнес-процессов организации и взаимодействие между ними (использование SADT-моделей, как правило, ограничивается только данной стадией, поскольку они изначально не предназначались для проектирования ПО). С помощью ERD выполняется описание используемых в организации данных на концептуальном уровне, не зависимом от средств реализации базы данных (СУБД). На стадии проектирования DFD используются для описания структуры проектируемой системы ПО, при этом они могут уточняться, расширяться и дополняться новыми конструкциями. Аналогично ERD уточняются и дополняются новыми конструкциями, описывающими представление данных на логическом уровне, пригодном для последующей генерации схемы базы данных. Данные модели могут дополняться диаграммами, отражающими системную архитектуру ПО, структурные схемы программ, иерархию экранных форм и меню и др. 2.6. Метод функционального моделирования SADT 2.6.1. Общие сведения Метод SADT представляет собой совокупность правил и процедур, предназначенных для построения функциональной модели объекта какой-либо предметной области. Функциональная модель SADT отображает функциональную структуру объекта, т.е. производимые им действия и связи между этими действиями. Основные элементы этого метода основываются на следующих концепциях: 79 Технология программирования Бугаков П.Ю. • графическое представление блочного моделирования. Графика блоков и дуг SADT-диаграммы отображает функцию в виде блока, а интерфейсы входа-выхода представляются дугами, соответственно входящими в блок и выходящими из него. Взаимодействие блоков друг с другом описывается посредством интерфейсных дуг, выражающих "ограничения", которые, в свою очередь, определяют, когда и каким образом функции выполняются и управляются; • строгость и точность. Выполнение правил SADT требует достаточной строгости и точности, не накладывая в то же время чрезмерных ограничений на действия аналитика. Правила SADT включают: ограничение количества блоков на каждом уровне декомпозиции (правило 3—6 блоков), связность диаграмм (номера блоков), уникальность меток и наименований (отсутствие повторяющихся имен), синтаксические правила для графики (блоков и дуг), разделение входов и управлений (правило определения роли данных); • отделение организации от функции, т.е. исключение влияния административной структуры организации на функциональную модель. , Рис. 13. Структура SADT-модели. Декомпозиция диаграмм 2.6.2. Моделирование потоков данных (процессов). Общие сведения Диаграммы потоков данных (DFD) являются основным средством моделирования функциональных требований к проектируемой системе. С их помощью эти требования представляются в виде иерархии функциональных компонентов (процессов), связанных потоками данных. Главная цель такого представления — 80 Технология программирования Бугаков П.Ю. продемонстрировать, как каждый процесс преобразует свои входные данные в выходные, а также выявить отношения между этими процессами. Диаграммы потоков данных известны очень давно. В фольклоре упоминается следующий пример использования DFD для реорганизации переполненного клерками офиса, относящийся к 20-м гг. Осуществлявший реорганизацию консультант обозначил кружком каждого клерка, а стрелкой - каждый документ, передаваемый между ними. Используя такую диаграмму, он предложил схему реорганизации, в соответствии с которой два клерка, обменивающихся множеством документов, были посажены рядом, а клерки с малым взаимодействием были посажены на большом расстоянии друг от друга. Так появилась первая модель, представляющая собой потоковую диаграмму — предвестника DFD. В соответствии с данными методами модель системы определяется как иерархия диаграмм потоков данных, описывающих асинхронный процесс преобразования информации от ее ввода в систему до выдачи пользователю. Диаграммы верхних уровней иерархии (контекстные диаграммы) определяют основные процессы или подсистемы с внешними входами и выходами. Они детализируются при помощи диаграмм нижнего уровня. Такая декомпозиция продолжается, создавая многоуровневую иерархию диаграмм, до тех пор, пока не будет достигнут уровень декомпозиции, на котором процессы становятся элементарными и детализировать их далее невозможно. Источники информации (внешние сущности) порождают информационные потоки (потоки данных), переносящие информацию к подсистемам или процессам. Те, в свою очередь, преобразуют информацию и порождают новые потоки, которые переносят информацию к другим процессам или подсистемам, накопителям данных или внешним сущностям — потребителям информации. Рис. 14. Графическое изображение процесса В настоящее время существует два способа написания программ: снизу вверх и сверху вниз. При написании программы снизу вверх приступить к отладке программы невозможно, не написав полностью всю программу. При написании программы сверху вниз на любом этапе написания программы она может быть оттранслирована и выполнена, при этом можно отследить все алгоритмические действия программы, написанные к этому времени. Основная идея структурного программирования заключаются в том, что существует только четыре структурных оператора. Используя эти структурные операторы можно построить сколь угодно сложную программу. Первый структурный оператор называется линейная цепочка операторов. Любая задача может быть разбита на несколько подзадач. Выполнение подзадач может быть поручено подпрограмме, в названии которой можно (и нужно) отразить 81 Технология программирования Бугаков П.Ю. подзадачу, которую должна решать эта подпрограмма. На момент написания алгоритма (и программы) верхнего уровня нас не интересует, как будет решаться эта задача, поэтому вместо настоящей подпрограммы поставим подпрограммузаглушку. Язык программирования С Алгоритмическое изображение оператора Рис. 15. Линейный алгоритм Второй структурный оператор называется условный оператор. Достаточно часто одна или другая задачи должны исполняться в зависимости от определённого условия, которое зависит от результатов выполнения предыдущей программы или от внешних устройств. Каждая из таких задач называется плечом условного оператора. Язык программирования С Алгоритмическое изображение оператора Рис. 16. Разветвляющийся алгоритм (полный вариант) Условный оператор может использоваться в неполном варианте, когда одно из плеч алгоритма отсутствует: Алгоритмическое изображение оператора Язык программирования С 82 Технология программирования Бугаков П.Ю. Рис. 17. Разветвляющийся алгоритм (неполный вариант) Третий структурный оператор - это оператор цикла с проверкой условия после тела цикла. Такой оператор легко реализуется на языке программирования ассемблер при помощи команды условного или безусловного перехода. Отличие от условного оператора заключается в том, что передача управления осуществляется не вперёд, а назад. На языках программирования высокого уровня такой оператор входит в состав языка (оператор do..while в языке программирования C или оператор repeat..until в языке программирования PASCAL). Алгоритмическое изображение оператора Язык программирования С Рис. 18. Цикл с постусловием 83 Технология программирования Бугаков П.Ю. Четвёртый структурный оператор - это оператор цикла с проверкой условия до тела цикла. В отличие от предыдущего оператора тело цикла в этом операторе может ни разу не выполниться, если условие цикла сразу же выполнено. Этот оператор как и условный оператор невозможно реализовать на одной машинной команде. Алгоритмическое изображение оператора Язык программирования С Рис. 19. Цикл с предусловием 2.7. Объектно-ориентированное программирование Принципиальное различие между структурным и объектно-ориентированным подходом заключается в способе декомпозиции системы. Объектно-ориентированный подход использует объектную декомпозицию, при этом статическая структура системы описывается в терминах объектов и связей между ними, а поведение системы описывается в терминах обмена сообщениям и между объектами. Каждый объект системы обладает своим собственным поведением, моделирующим поведение объекта реального мира. Основные понятия объектно-ориентированного подхода – объект и класс. Объект определяется как осязаемая реальность (tangible entity) — предмет или явление, имеющие четко определяемое поведение. Объект обладает состоянием, поведением и индивидуальностью; структура и поведение схожих объектов определяют общий для них класс. Состояние объекта характеризуется перечнем всех возможных (статических) свойств данного объекта и текущими значениями (динамическими) каждого из этих свойств. Поведение характеризует воздействие объекта на другие объекты и наоборот относительно изменения состояния этих объектов и передачи сообщений. Иначе говоря, поведение объекта полностью определяется его действиями. Объект представляется как совокупность данных, характеризующих его состояние, и функций их обработки, моделирующих его поведение. Вызов функции на выполнение часто называют посылкой сообщения объекту. При создании объектно-ориентированной программы предметная область представляется в виде совокупности объектов. Выполнение программы состоит в том, что объекты обмениваются сообщениями. 84 Технология программирования Бугаков П.Ю. Класс — это множество объектов, связанных общностью структуры и поведения. Любой объект является экземпляром класса. Определение классов и объектов — одна из самых сложных задач объектно-ориентированного проектирования. Определенное воздействие одного объекта на другой с целью вызвать соответствующую реакцию называется операцией. Как правило, в объектных и объектно-ориентированных языках операции, выполняемые над данным объектом, называются методами и являются составной частью определения класса. Термины "экземпляр класса" и "объект"являются эквивалентными. Концептуальной основой объектно-ориентированного подхода является объектная модель. Основными ее элементами являются: • абстрагирование (abstraction); • инкапсуляция (encapsulation); • модульность (modularity); • иерархия (hierarchy). Кроме основных имеются еще три дополнительных элемента, не являющихся в отличие от основных строго обязательными: • типизация (typing); • параллелизм (concurrency); • устойчивость (persistence); • Индивидуальность • Полиморфизм • наследование Абстрагирование — это выделение существенных характеристик некоторого объекта, которые отличают его от всех других видов объектов и, таким образом, четко определяют его концептуальные границы относительно дальнейшего рассмотрения и анализа. Абстрагирование концентрирует внимание на внешних особенностях объекта и позволяет отделить самые существенные особенности его поведения от деталей их реализации. Выбор правильного набора абстракций для заданной предметной области представляет собой главную задачу объектно-ориентированного проектирования. Инкапсуляция — это процесс отделения друг от друга отдельных элементов объекта, определяющих его устройство и поведение. Инкапсуляция служит для того, чтобы изолировать интерфейс объекта, отражающий его внешнее поведение, от внутренней реализации объекта. Объектный подход предполагает, что собственные ресурсы, которыми могут манипулировать только методы самого класса, скрыты от внешней среды. Абстрагирование и инкапсуляция являются взаимодополняющими операциями: абстрагирование фокусирует внимание на внешних особенностях объекта, а инкапсуляция (или, иначе, ограничение доступа) не позволяет объектампользователям различать внутреннее устройство объекта. Модульность — это свойство системы, связанное с возможностью ее декомпозиции на ряд внутренне связных, но слабо связанных между собой модулей. Инкапсуляция и модульность создают барьеры между абстракциями. Иерархия — это ранжированная или упорядоченная система абстракций, расположение их по уровням. Основными видами иерархических структур применительно к сложным системам являются структура классов (иерархия по номенклатуре) и структура объектов (иерархия по составу). Примерами иерархии 85 Технология программирования Бугаков П.Ю. классов являются простое и множественное наследование (один класс использует структурную или функциональную часть соответственно одного или нескольких других классов), а иерархии объектов - агрегация. Типизация — это ограничение, накладываемое на класс объектов и препятствующее взаимозаменяемости различных классов (или сильно сужающее ее возможность). Типизация позволяет защититься от использования объектов одного класса вместо другого или по крайней мере управлять таким использованием. Параллелизм — свойство объектов находиться в активном или пассивном состоянии и различать активные и пассивные объекты между собой. Устойчивость — свойство объекта существовать во времени (вне зависимости от процесса, породившего данный объект) и/или в пространстве (при перемещении объекта из адресного пространства, в котором он был создан). Индивидуальность —это свойства объекта, отличающие его от всех других объектов. Понятие полиморфизма может быть интерпретировано как способность класса принадлежать более чем одному типу. Наследование означает построение новых классов на основе существующих с возможностью добавления или переопределения данных и методов. Важное значение имеет возможность многократного использования кода. Для объекта можно определить наследников, корректирующих или дополняющих его поведение. Наследование применяется для:  исключения из программы повторяющихся фрагментов кода;  упрощения модификации программы;  упрощения создания новых программ на основе существующих. Достоинства ООП  использование при программировании понятий, близких к предметной области;  возможность успешно управлять большими объемами исходного кода благодаря инкапсуляции, то есть скрытию деталей реализации объектов и упрощению структуры программы;  возможность многократного использования кода за счет наследования;  сравнительно простая возможность модификации программ;  возможность создания и использования библиотек объектов. Недостатки ООП  некоторое снижение быстродействия программы, связанное с использованием виртуальных методов;  идеи ООП не просты для понимания и в особенности для практического использования;  для эффективного использования существующих объектно-ориентированных систем требуется большой объем первоначальных знаний;  неграмотное применение ООП может привести к значительному ухудшению характеристик разрабатываемой программы. 86 Технология программирования Бугаков П.Ю. РАЗДЕЛ III СТРУКТУРЫ ДАННЫХ 3.1. Классификация структур данных Под СТРУКТУРОЙ ДАННЫХ в общем случае понимают множество элементов данных и множество связей между ними. Такое определение охватывает все возможные подходы к структуризации данных, но в каждой конкретной задаче используются те или иные его аспекты. Поэтому вводится дополнительная классификация структур данных, направления которой соответствуют различным аспектам их рассмотрения. Прежде чем приступать к изучению конкретных структур данных, дадим их общую классификацию по нескольким признакам. Понятие "ФИЗИЧЕСКАЯ структура данных" отражает способ физического представления данных в памяти машины и называется еще структурой хранения, внутренней структурой или структурой памяти. Рассмотрение структуры данных без учета ее представления в машинной памяти называется абстрактной или ЛОГИЧЕСКОЙ структурой. В общем случае между логической и соответствующей ей физической структурами существует различие, степень которого зависит от самой структуры и особенностей той среды, в которой она должна быть отражена. Вследствие этого различия существуют процедуры, осуществляющие отображение логической структуры в физическую и, наоборот, физической структуры в логическую. Эти процедуры обеспечивают, кроме того, доступ к физическим структурам и выполнение над ними различных операций, причем каждая операция рассматривается применительно к логической или физической структуре данных. Различаются ПРОСТЫЕ (базовые, примитивные) структуры (типы) данных и ИНТЕГРИРОВАННЫЕ (структурированные, композитные, сложные). Простыми называются такие структуры данных, которые не могут быть расчленены на составные части, большие, чем биты. С точки зрения физической структуры важным является то обстоятельство, что в данной машинной архитектуре, в данной системе программирования мы всегда можем заранее сказать, каков будет размер данного простого типа и какова структура его размещения в памяти. С логической точки зрения простые данные являются неделимыми единицами. Интегрированными называются такие структуры данных, составными частями которых являются другие структуры данных - простые или в свою очередь интегрированные. Интегрированные структуры данных конструируются программистом с использованием средств интеграции данных, предоставляемых языками программирования. В зависимости от отсутствия или наличия явно заданных связей между элементами данных следует различать НЕСВЯЗНЫЕ структуры (векторы, массивы, строки, стеки, очереди) и СВЯЗНЫЕ структуры (связные списки). Весьма важный признак структуры данных - ее изменчивость - изменение числа элементов и (или) связей между элементами структуры. В определении изменчивости структуры не отражен факт изменения значений элементов данных, поскольку в этом случае все структуры данных имели бы свойство изменчивости. По признаку изменчивости различают структуры СТАТИЧЕСКИЕ, ПОЛУСТАТИЧЕСКИЕ, ДИНАМИЧЕСКИЕ. Классификация структур данных по признаку изменчивости приведена на рис. 20. Базовые структуры данных, статические, полустатические и 87 Технология программирования Бугаков П.Ю. динамические характерны для оперативной памяти и часто называются оперативными структурами. Файловые структуры соответствуют структурам данных для внешней памяти. Рис. 20. Классификация структур данных Важный признак структуры данных - характер упорядоченности ее элементов. По этому признаку структуры можно делить на ЛИНЕЙНЫЕ И НЕЛИНЕЙНЫЕ структуры. В зависимости от характера взаимного расположения элементов в памяти линейные структуры можно разделить на структуры с ПОСЛЕДОВАТЕЛЬНЫМ распределением элементов в памяти (векторы, строки, массивы, стеки, очереди) и структуры с ПРОИЗВОЛЬНЫМ СВЯЗНЫМ распределением элементов в памяти ( односвязные, двусвязные списки). Пример нелинейных структур - многосвязные списки, деревья, графы. В языках программирования понятие "структуры данных" тесно связано с понятием "типы данных". Любые данные, т.е. константы, переменные, значения функций или выражения, характеризуются своими типами. Информация по каждому типу однозначно определяет : 1) структуру хранения данных указанного типа, т.е. выделение памяти и представление данных в ней, с одной стороны, и интерпретирование двоичного представления, с другой; 2) множество допустимых значений, которые может иметь тот или иной объект описываемого типа; 3) множество допустимых операций, которые применимы к объекту описываемого типа. 3.2. Простые, базовые структуры Целые (54), Вещественные (60), Символьные (58) char – символьные int – целые 88 Технология программирования float – с плавающей точкой double – с плавающей точкой двойной длины void – пустой, не имеющий значения Бугаков П.Ю. signed – знаковый unsigned – беззнаковый long – длинный short – короткий Тип Char Unsigned char Signed char Int Unsigned int Signed int Short int Unsigned short int Signed short int Long int Signed long int Unsigned long int Float Double Long double Размер в байтах (битах) 1 (8) 1 (8) 1 (8) 2 (16) 2 (16) 2 (16) 2 (16) 2 (16) 2 (16) 4 (32) 4 (32) 4 (32) 4 (32) 8 (64) 10 (80) Интервал изменения -128…127 0…255 -128…127 -32768…32767 0…65535 -32768…32767 -32768…32767 0…65535 -32768…32767 -2147483648…2147483647 -2147483648…2147483647 0…4294967295 3.4E-38…3.4E+38 1.7E-308…1.7E+308 3.4E-4932…3.4E+4932 Логический тип bool, используется int (0-ложь, 1-истина) Перечисления – Топп (62) Указатели – Топп (63) 3.3. Статические структуры 3.3.1. Массивы Массив - такая структура данных, которая характеризуется:  фиксированным набором элементов одного и того же типа;  каждый элемент имеет уникальный набор значений индексов;  количество индексов определяют мерность массива, например, два индекса двумерный массив, три индекса - трехмерный массив, один индекс - одномерный массив или вектор;  обращение к элементу массива выполняется по имени массива и значениям индексов для данного элемента. Другое определение: массив - это вектор, каждый элемент которого - вектор. Массив является примером набора данных. Одномерный массив — это конечный, последовательный список элементов одного и того же типа данных — однородный массив (homogeneous array). Последовательность определяет первый 89 Технология программирования Бугаков П.Ю. элемент, второй элемент и так далее. С каждым элементом ассоциирован целый индекс (index), определяющий позицию элемента в списке. Массив имеет оператор индекса, который делает возможным прямой доступ (direct access) к элементам в списке при сохранении или возвращении какого-либо элемента. Рис. 21. Одномерный массив Двумерные массивы: Рис. 22. Двумерные массивы 3.3.2. Записи Запись (record) — это структура, которая связывает элементы различных типов в один объект. Элементы в записи называются полями (fields). Подобно массиву, запись имеет оператор доступа, который делает возможным прямой доступ к каждому полю. Например, Student — это структура записи, содержащая информацию о студенте, посещающем колледж. Эта информация включает имя (Name), адрес (Local Address), возраст (Age), профилирующую дисциплину (academic major) и среднюю успеваемость (grade-point average, GPA). Рис. 23. Запись Поля Name и Local Address содержат строковые данные. Age и GPA являются численными типами, a Major — это тип перечисления. Полагая, что Том — студент, мы получаем доступ к отдельным полям, объединяя записи имени и поля с использованием оператора доступа ".": Tom.Name Tom.Age Tom.GPA Тот.Major 90 Технология программирования Бугаков П.Ю. «Запись позволяет объединять данные различных типов (неоднородные типы — heterogeneous types) в структуре. В отличие от массива, запись описывает единственное значение, а не список значений. ADT Record Данные Элемент, содержащий набор полей неоднородного типа. Каждое поле имеет имя, обеспечивающее прямой доступ к данным в поле. Операции Оператор доступа Вход: Имя записи (recname) и поле Предусловия: Нет Процесс: Доступ к данным в поле Выход: При нахождении данных возвращать значение поля клиенту. Постусловия: При сохранении данных запись изменяется Конец ADT Record 3.3.3. Стеки Рис. 24. Стек Одной из весьма важных и полезных концепций в программировании является концепция стека. В данном разделе мы дадим определение стека как некоторой абстрактной структуры данных. Затем будет рассмотрена конкретная реализация стека на языке С. Стеком называется упорядоченный набор элементов, в котором размещение новых элементов и удаление существующих производится только с одного его конца, называемого вершиной стека. При представлении стека в статической памяти для него выделяется память, как для вектора. В дескрипторе этого вектора кроме обычных для вектора параметров должен находиться также указатель стека – адрес вершины стека. Указатель стека может указывать либо на первый свободный элемент стека, либо на последний записанный в стек элемент. (Все равно, какой из этих двух вариантов выбрать, важно в дальнейшем строго придерживаться его при обработке стека.). Рассмотрим, что означает такое определение. Пусть в стеке имеется два элемента, один из которых расположен выше, чем другой. Мы можем изобразить такой стек графически. Разумеется, стек можно изобразить различными способами. Обычно стек 91 Технология программирования Бугаков П.Ю. изображается так, как показано на рис. 25. Элемент 23 расположен выше, чем все остальные элементы. Элемент 47 расположен выше, чем элементы 1, 21, но ниже, чем элемент 12. Возникает следующий вопрос: как меняется стек? Из определения стека следует, что один конец стека считается его вершиной. В вершину стека может быть помещен новый элемент (в этом случае вершина стека перемещается вверх). Это указывается вертикальными линиями, продолженными в направлении вершины стека. Стек работает по принципу LIFO (Last Input – First Output, т.е. «поступивший последним обслуживается первым»). Рассмотрим теперь стек в динамике, чтобы понять, как он расширяется и сжимается во времени. Первоначально стек пуст. Рис. 25. Стек целых чисел Стрелкой на рис. 26 обозначен указатель стека, который показывает на первую свободную ячейку стека (т.е. на ячейку над вершиной стека). Затем на его «дно» положен элемент 21, затем поочередно добавлены еще четыре элемента, затем верхний элемент взят из стека. При выполнении операции загрузки элемента в стек данные записываются на место, определяемое указателем стека, а сам указатель стека устанавливается таким образом, что задает следующую свободную ячейку блока памяти. Данные изымаются из стека с помощью операции извлечения элемента из стека, при выполнении которой указатель стека возвращается назад на один шаг. Рис. 26. Заполнение стека Основные операции над стеком - включение нового элемента (английское название push - заталкивать) и исключение элемента из стека (англ. pop выскакивать). Полезными могут быть также вспомогательные операции:  определение текущего числа элементов в стеке;  очистка стека;  неразрушающее чтение элемента из вершины стека, которое может быть реализовано, как комбинация основных операций: x:=pop(stack); push(stack,x). Операции, выполняемые над стеком, имеют специальные названия. При добавлении элемента в стек мы говорим, что элемент помещается (вталкивается) в 92 Технология программирования Бугаков П.Ю. стек (Push). Для стека Stack и элемента х определена операция Push(Stack, х), по которой в стек Stack помещается элемент х. Аналогичным образом определяется операция выборки (выталкивания) элемента из стека – Pop(Stack), по которой из стека удаляется верхний элемент и возвращается в качестве значения функции. Следовательно, операция присваивания х = Pop(Stack) удалит элемент из вершины стека и присвоит его значение переменной х. Число элементов в стеке не ограничено, поскольку в определении стека не содержится никаких указаний на это. Добавление нового элемента только увеличивает стек. Однако если стек содержит единственный элемент и этот элемент удаляется, то стек в результате не содержит ни одного элемента и называется пустым стеком. Хотя операция выборки применима к любому элементу стека, она не может быть применена к пустому стеку, поскольку в нем отсутствуют элементы, которые можно извлечь. Следовательно, перед тем как выполнить над стеком операцию выборки, следует убедиться в том, что стек не пустой. Для этого имеется специальная операция Empty(Stack), которая проверяет, является ли стек пустым. Если стек пуст, то операция Empty(Stack) возвращает значение «истина». В противном случае она возвращает значение «ложь». Другой операцией, выполняемой над стеком, является операция определения верхнего элемента стека без его удаления. Эта операция называется Stacktop(Stack). Она возвращает значение верхнего элемента стека. Операция Stacktop(Stack) не является принципиально новой операцией, поскольку она может быть получена комбинацией операций Pop и Push: х = Stacktop(Stack) эквивалентно x = Pop(Stack) Push(Stack, x). Аналогично операции Pop(Stack) операция Stacktop(Stack) не определена для пустого стека. Результатом попытки выполнения операции Pop(Stack) и Stacktop(Stack) над пустым стеком является возникновение ошибки типа underflow (потеря значимости). Такой ситуации следует избегать, и перед выполнением операций Pop(Stack) и Stacktop(Stack) надо выполнить операцию Empty(Stack) и убедиться в том, что стек не пуст. При написании программы с использованием стека необходимо решить, каким образом реализовать стек. Для языка С имеется несколько способов построения стека. Рассмотрим самый простой. Далее будут описаны другие способы представления стека. Стек представляет собой упорядоченный набор данных и в языке С уже имеется тип данных с такой характеристикой – массив. Однако стек и массив представляют собой совершенно различные вещи. Число элементов в массиве фиксировано и устанавливается при объявлении данного массива. В общем случае пользователь не может изменить это число. С другой стороны, стек представляет собой динамическую структуру, размер которой непрерывно изменяется по мере того, как в него добавляются или из него удаляются элементы. Однако, хотя массив и не может быть стеком, он может быть для него некоторой базой. Это означает, что массив может быть объявлен с размером, достаточно большим для перекрытия максимального размера стека. В процессе выполнения программы стек будет увеличиваться и уменьшаться в пределах отведенного пространства. В одном конце массива будет располагаться фиксированное дно стека, а 93 Технология программирования Бугаков П.Ю. вершина стека будет постоянно изменяться по мере удаления и добавления элементов. Поэтому необходима переменная, которая в каждый момент выполнения программы будет отслеживать текущее положение вершины стека. Назовем ее указателем стека и присвоим ей имя top. Рис. 27. Помещение в стек и извлечение из него 3.3.4. Очереди Рис. 28. Работа очереди Очередью называется упорядоченный набор элементов, которые могут удаляться с одного ее конца (называемого началом очереди) и помещаться в другой конец этого набора (называемого концом очереди). На рис. 29 приведена очередь, содержащая три целых числа 45, 12, 34. Элемент 45 расположен в начале очереди, а элемент 34 – в ее конце. На рис. 29, б из очереди был удален один элемент. Поскольку элементы могут удаляться только из начала очереди, то удаляется элемент 45, а в начале очереди теперь находится элемент 12. 94 Технология программирования Бугаков П.Ю. Рис. 29. Добавление и удаление элемента из очереди На рис. 29 в в очередь добавляется один новый элемент – минус 14, который помещен в ее конец. Первый помещаемый в очередь элемент удаляется первым. Очередь в отличие от стека организуется по принципу FIFO – First Input – First Output (Первым пришел – Первым ушел). Основные операции над очередью - те же, что и над стеком - включение, исключение, определение размера, очистка, неразрушающее чтение. Для очереди определены три примитивные операции. Операция Insert(queue, x) помещает элемент х в конец очереди queue. Операция x = Remove(queue) удаляет элемент из начала очереди queue и присваивает его значение переменной x. Третья операция Empty(queue) возвращает значение TRUE или FALSE в зависимости от того, является ли очередь пустой или нет. Очередь на рис. 29 может быть реализована при помощи следующей последовательности операций (предполагается, что изначально она является пустой): Insert(queue, 45) Insert(queue, 12) Insert(queue, 34) [рис. 29, а] x = Remove(queue) [рис. 29, б; переменная х устанавливается в 45] Insert(queue, -14) [рис. 29, в]. Операция Insert может быть выполнена всегда, поскольку на количество элементов, которые может содержать очередь, никаких ограничений не накладывается. Операция Remove применима только к непустой очереди, поскольку невозможно удалить элемент из очереди, не содержащей элементы. Результатом попытки удалить элемент из пустой очереди, является ситуация потеря значимости. Каким образом очередь может быть реализована в языке С? Первое, что приходит в голову, это применение для данной цели массива, в котором будут располагаться данные и двух переменных, используемыми в качестве указателей начала очереди (head) и конца (tail). При выполнении операции добавления элемента в очередь данные записываются в ячейку, определяемую значением переменной tail, и значение переменной tail изменяется таким образом, чтобы она указывала на следующую свободную ячейку массива, доступного для организации очереди. Выполнение операции исключения 95 Технология программирования Бугаков П.Ю. элемента из очереди состоит в чтении данных, местоположение которых определяется значением переменной head, и изменении значения переменной head таким образом, чтобы она указывала на следующий элемент очереди. Очередь оказывается пустой, когда значения переменных head и tail равны. Итак, переменные head и tail содержат позиции массива, занимаемые первым и последним элементами очереди. Изначально tail устанавливается в нуль, а head равен единице. Очередь всегда пуста, если tail < head. Число элементов в очереди в любой момент времени равно значению tail – head +1. 3.3.5. Деки Задачи, требующие структуры дека, встречаются в вычислительной технике и программировании гораздо реже, чем задачи, реализуемые на структуре стека или очереди. Дек – это особый вид очереди. Дек – это последовательная структура данных, в которой как включение, так и исключение элементов может осуществляться с любого из двух концов. Логическая и физическая структуры дека аналогичны логической и физической структуре кольцевой FIFO-очереди. Однако применительно к деку, целесообразно говорить не о начале и конце очереди, а о левом и правом конце дека. Над деком выполняются следующие операции: • очистка дека; • включение элемента справа; • включение элемента слева; • исключение элемента справа; • исключение элемента слева; • определение размера дека. Физическая структура дека в статической памяти идентична структуре кольцевой очереди. Разработать программный пример, иллюстрирующий организацию дека и операции над ним не сложно по образцу операций, реализованных для стека и очереди. Попробуйте это сделать самостоятельно. На рис. 30 в качестве примера показана последовательность состояний дека при включении и удалении пяти элементов. На каждом этапе стрелка указывает с какого конца дека (левого или правого) осуществляется включение или исключение элемента. Элементы соответственно обозначены буквами A, B, C, D, E. Каким образом дек может быть реализован в языке С? Как и для очереди, для реализации дека можно использовать массив. Опишем структуру дека, реализованного с помощью массива. Рис. 30. Состояния дека в процессе изменения 96 Технология программирования 3.3.6. Строки Бугаков П.Ю. Рис. 31. Строка Логическая структура строки Строка - это линейно упорядоченная последовательность символов, принадлежащих конечному множеству символов, называемому алфавитом. Строки обладают следующими важными свойствами:  их длина, как правило, переменна, хотя алфавит фиксирован;  обычно обращение к символам строки идет с какого-нибудь одного конца последовательности, т.е важна упорядоченность этой последовательности, а не ее индексация; в связи с этим свойством строки часто называют также цепочками;  чаще всего целью доступа к строке является на отдельный ее элемент (хотя это тоже не исключается), а некоторая цепочка символов в строке. Говоря о строках, обычно имеют в виду текстовые строки - строки, состоящие из символов, входящих в алфавит какого-либо выбранного языка, цифр, знаков препинания и других служебных символов. Действительно, текстовая строка является наиболее универсальной формой представления любой информации: на сегодняшний день вся сумма информации, накопленной человечеством - от Ветхого Завета до нашего учебного пособия - представлена именно в виде текстовых строк. В наших дальнейших примерах этого раздела будем работать именно с текстовыми строками. Однако, следует иметь в виду, что символы, входящие в строку могут принадлежать любому алфавиту. Так, в языке PL/1, наряду с типом данных "символьная строка" CHAR(n) - существует тип данных "битовая строка" - BIT(n). Битовые строки, составляются из 1-битовых символов, принадлежащих алфавиту: { 0, 1 }. Все строковые операции с равным успехом применимы как к символьным, так и к битовым строкам. Кодирование символов было рассмотрено в главе 2. Отметим, что в зависимости от особенности задачи, свойств применяемого алфавита и представляемого им языка и свойств носителей информации могут применяться и другие способы кодирования символов. В современных вычислительных системах, однако, повсеместно принята кодировка всего множества символов на разрядной сетке фиксированного размера (1 байт). Хотя строки рассматриваются в главе, посвященной полустатическим структурам данных, в тех или иных конкретных задачах изменчивость строк может варьироваться от полного ее отсутствия до практически неограниченных возможностей изменения. Ориентация на ту или иную степень изменчивости строк определяет и физическое представление их в памяти и особенности выполнения операций над ними. В большинстве языков программирования (C, PASCASL, PL/1 и др.) строки представляются именно как полустатические структуры. В зависимости от ориентации языка программирования средства работы со строками занимают в языке более или менее значительное место. Рассмотрим три примера возможностей работы со строками. Язык C является языком системного программирования, типы данных, с которыми работает язык C, максимально приближены к тем типам, с которыми 97 Технология программирования Бугаков П.Ю. работают машинные команды. Поскольку машинные команды не работают со строками, нет такого типа данных и в языке C. Строки в C представляются в виде массивов символов. Операции над строками могут быть выполнены как операции обработки массивов или же при помощи библиотечных (но не встроенных!) функций строковой обработки. Базовыми операциями над строками являются:  определение длины строки;  присваивание строк;  конкатенация (сцепление) строк;  выделение подстроки;  поиск вхождения. 3.4. Динамические структуры данных 3.4.1. Связные списки Динамические структуры по определению характеризуются отсутствием физической смежности элементов структуры в памяти непостоянством и непредсказуемостью размера (числа элементов) структуры в процессе ее обработки. Поскольку элементы динамической структуры располагаются по непредсказуемым адресам памяти, адрес элемента такой структуры не может быть вычислен из адреса начального или предыдущего элемента. Для установления связи между элементами динамической структуры используются указатели, через которые устанавливаются явные связи между элементами. Такое представление данных в памяти называется связным. Элемент динамической структуры состоит из двух полей:  информационного поля или поля данных, в котором содержатся те данные, ради которых и создается структура; в общем случае информационное поле само является интегрированной структурой - вектором, массивом, записью и т.п.;  поле связок, в котором содержатся один или несколько указателей, связывающий данный элемент с другими элементами структуры; Когда связное представление данных используется для решения прикладной задачи, для конечного пользователя "видимым" делается только содержимое информационного поля, а поле связок используется только программистомразработчиком. Достоинства связного представления данных - в возможности обеспечения значительной изменчивости структур;  размер структуры ограничивается только доступным объемом машинной памяти;  при изменении логической последовательности элементов структуры требуется не перемещение данных в памяти, а только коррекция указателей. Вместе с тем связное представление не лишено и недостатков, основные из которых:  работа с указателями требует, как правило, более высокой квалификации от программиста;  на поля связок расходуется дополнительная память;  доступ к элементам связной структуры может быть менее эффективным по времени. 98 Технология программирования Бугаков П.Ю. Последний недостаток является наиболее серьезным и именно им ограничивается применимость связного представления данных. Если в смежном представлении данных для вычисления адреса любого элемента нам во всех случаях достаточно было номера элемента и информации, содержащейся в дескрипторе структуры, то для связного представления адрес элемента не может быть вычислен из исходных данных. Дескриптор связной структуры содержит один или несколько указателей, позволяющих войти в структуру, далее поиск требуемого элемента выполняется следованием по цепочке указателей от элемента к элементу. Поэтому связное представление практически никогда не применяется в задачах, где логическая структура данных имеет вид вектора или массива - с доступом по номеру элемента, но часто применяется в задачах, где логическая структура требует другой исходной информации доступа (таблицы, списки, деревья и т.д.). 3.4.2. Связные линейные списки Связанный список – это структура данных, в произвольно выбранное место которой данные могут как включаться, так и изыматься. Для обеспечения такой гибкости, в каждый элемент добавляется указатель на следующий элемент списка. Списком называется упорядоченное множество, состоящее из переменного числа элементов, к которым применимы операции включения, исключения. Список, отражающий отношения соседства между элементами, называется линейным. Логические списки мы уже рассматривали в главе 4, но там речь шла о полустатических структурах данных и на размер списка накладывались ограничения. Если ограничения на длину списка не допускаются, то список представляется в памяти в виде связной структуры. Линейные связные списки являются простейшими динамическими структурами данных. Графически связи в списках удобно изображать с помощью стрелок. Если компонента не связана ни с какой другой, то в поле указателя записывают значение, не указывающее ни на какой элемент. Такая ссылка обозначается специальным именем nil. 3.4.3. Машинное представление связных линейных списков На рис. 32 приведена структура односвязного списка. На нем поле INF информационное поле, данные, NEXT - указатель на следующий элемент списка. Каждый список должен иметь особый элемент, называемый указателем начала списка или головой списка, который обычно по формату отличен от остальных элементов. В поле указателя последнего элемента списка находится специальный признак nil, свидетельствующий о конце списка. Рис. 32. Структура односвязного списка Однако, обработка односвязного списка не всегда удобна, так как отсутствует возможность продвижения в противоположную сторону. Такую возможность обеспечивает двухсвязный список, каждый элемент которого содержит два указателя: на следующий и предыдущий элементы списка. Структура линейного двухсвязного списка приведена на рис. 33, где поле NEXT - указатель на следующий элемент, поле 99 Технология программирования Бугаков П.Ю. PREV - указатель на предыдущий элемент. В крайних элементах соответствующие указатели должны содержать nil, как и показано на рис. 33. Для удобства обработки списка добавляют еще один особый элемент - указатель конца списка. Наличие двух указателей в каждом элементе усложняет список и приводит к дополнительным затратам памяти, но в то же время обеспечивает более эффективное выполнение некоторых операций над списком. Рис. 33. Структура двухсвязного списка Разновидностью рассмотренных видов линейных списков является кольцевой список, который может быть организован на основе как односвязного, так и двухсвязного списков. При этом в односвязном списке указатель последнего элемента должен указывать на первый элемент; в двухсвязном списке в первом и последнем элементах соответствующие указатели переопределяются, как показано на рис. 34. При работе с такими списками несколько упрощаются некоторые процедуры, выполняемые над списком. Однако, при просмотре такого списка следует принять некоторых мер предосторожности, чтобы не попасть в бесконечный цикл. Рис. 34. Структура кольцевого двухсвязного списка В памяти список представляет собой совокупность дескриптора и одинаковых по размеру и формату записей, размещенных произвольно в некоторой области памяти и связанных друг с другом в линейно упорядоченную цепочку с помощью указателей. Запись содержит информационные поля и поля указателей на соседние элементы списка, причем некоторыми полями информационной части могут быть указатели на блоки памяти с дополнительной информацией, относящейся к элементу списка. Дескриптор списка реализуется в виде особой записи и содержит такую информацию о списке, как адрес начала списка, код структуры, имя списка, текущее число элементов в списке, описание элемента и т.д., и т.п. Дескриптор может находиться в той же области памяти, в которой располагаются элементы списка, или для него выделяется какое-нибудь другое место. Простые операции над линейными списками: 1. Перебор элементов списка. 100 Технология программирования 2. Операция формирования списка. 3. Вставка элемента в список. 4. Удаление элемента. 5. Перестановка элементов списка. 6. Печать односвязного списка. Бугаков П.Ю. 3.5. Нелинейные структуры данных 3.5.1. Нелинейные разветвленные списки Нелинейным разветвленным списком является список, элементами которого могут быть тоже списки. В разделе 5.2 мы рассмотрели двухсвязные линейные списки. Если один из указателей каждого элемента списка задает порядок обратный к порядку, устанавливаемому другим указателем, то такой двусвязный список будет линейным. Если же один из указателей задает порядок произвольного вида, не являющийся обратным по отношению к порядку, устанавливаемому другим указателем, то такой список будет нелинейным. В обработке нелинейный список определяется как любая последовательность атомов и списков (подсписков), где в качестве атома берется любой объект, который при обработке отличается от списка тем, что он структурно неделим. Если мы заключим списки в круглые скобки, а элементы списков разделим запятыми, то в качестве списков можно рассматривать такие последовательности: (a,(b,c,d),e,(f,g)) () ((a)) Первый список содержит четыре элемента: атом a, список (b,c,d) (содержащий в свою очередь атомы b,c,d), атом e и список (f,g), элементами которого являются атомы f и g. Второй список не содержит элементов, тем не менее нулевой список, в соответствии с нашим определением является действительным списком. Третий список состоит из одного элемента: списка (a), который в свою очередь содержит атом а. Другой способ представления, часто используемый для иллюстрации списков, графические схемы, аналогичен способу представления, применяемому при изображении линейных списков. Каждый элемент списка обозначается прямоугольником; стрелки или указатели показывают, являются ли прямоугольники элементами одного и того же списка или элементами подсписка. Пример такого представления дан на рис 35. Рис. 35. Схематическое представление разветвленного списка 101 Технология программирования Бугаков П.Ю. Разветвленные списки описываются тремя характеристиками: порядком, глубиной и длиной. Порядок. Над элементами списка задано транзитивное отношение, определяемое последовательностью, в которой элементы появляются внутри списка. В списке (x,y,z) атом x предшествует y, а y предшествует z. При этом подразумевается, что x предшествует z. Данный список не эквивалентен списку (y,z,x). При представлении списков графическими схемами порядок определяется горизонтальными стрелками. Горизонтальные стрелки истолковываются следующим образом: элемент из которого исходит стрелка,предшествует элементу, на который она указывает. Глубина. Это максимальный уровень, приписываемый элементам внутри списка или внутри любого подсписка в списке. Уровень элемента предписывается вложенностью подсписков внутри списка, т.е.числом пар круглых скобок, окаймляющих элемент. В списке, изображенном на рис. 35), элементы a и e находятся на уровне 1, в то время как оставшиеся элементы - b, c, d, f и g имеют уровень 2. Глубина входного списка равна 2. При представлении списков схемами концепции глубины и уровня облегчаются для понимания, если каждому атомарному или списковому узлу приписать некоторое число l. Значение l для элемента x, обозначаемое как l(x), является числом вертикальных стрелок, которое необходимо пройти для того, чтобы достичь данный элемент из первого элемента списка. На рис. 35 l(a)=0, l(b)=1 и т.д. Глубина списка является максимальным значением уровня среди уровней всех атомов списка. Длина - это число элементов уровня 1 в списке. Например, длина списка на рис. 35 равна 3. Типичный пример применения разветвленного списка - представление последнего алгебраического выражения в виде списка. Алгебраическое выражение можно представить в виде последовательности элементарных двухместных операций вида: < операнд 1 > < знак операции > < операнд 2 > 3.5.2. Графы Граф - это сложная нелинейная многосвязная динамическая структура, отображающая свойства и связи сложного объекта. Многосвязная структура обладает следующими свойствами:  1) на каждый элемент (узел, вершину) может быть произвольное количество ссылок;  2) каждый элемент может иметь связь с любым количеством других элементов;  3) каждая связка (ребро, дуга) может иметь направление и вес. В узлах графа содержится информация об элементах объекта. Связи между узлами задаются ребрами графа. Ребра графа могут иметь направленность, показываемую стрелками, тогда они называются ориентированными, ребра без стрелок - неориентированные. Граф, все связи которого ориентированные, называется ориентированным графом или орграфом; граф со всеми неориентированными связями - неориентированным графом; граф со связями обоих типов - смешанным графом. Обозначение связей: неориентированных - (A,B), ориентированных - . Примеры изображений графов даны на рис. 36. Скобочное представление графов рис: а).((A,B),(B,A)) и б).(< A,B >,< B,A >). 102 Технология программирования Бугаков П.Ю. Рис. 36. Граф неориентированный (а) и ориентированный (б). Для ориентированного графа число ребер, входящих в узел, называется полустепенью захода узла, выходящих из узела -полустепенью исхода. Количество входящих и выходящих ребер может быть любым, в том числе и нулевым. Граф без ребер является нуль-графом. Если ребрам графа соответствуют некоторые значения, то граф и ребра называются взвешенными. Мультиграфом называется граф, имеющий параллельные (соединяющие одни и те же вершины) ребра, в противном случае граф называется простым. Путь в графе - это последовательность узлов, связанных ребрами; элементарным называется путь, в котором все ребра различны, простым называется путь, в котором все вершины различны. Путь от узла к самому себе называется циклом, а граф, содержащий такие пути - циклическим. Два узла графа смежны, если существует путь от одного из них до другого. Узел называется инцидентным к ребру, если он является его вершиной, т.е. ребро направлено к этому узлу. Логически структура-граф может быть представлена матрицей смежности или матрицей инцидентности. Матрицей смежности для n узлов называется квадратная матрица adj порядка n. Элемент матрицы a(i,j) равен 1, если узел j смежен с узлом i (есть путь < i,j >), и 0 -в противном случае (рис. 37). Рис. 37. Графа и его матрица смежности Если граф неориентирован, то a(i,j)=a(j,i), т.е. матрица симметрична относительно главной диагонали. Матрицы смежности используются при построении матриц путей, дающих представление о графе по длине пути: путь длиной в 1 - смежный участок - , путь длиной 2 - (< A,B >,< B,C >), ... в n смежных участков: где n - максимальная длина, равная числу узлов графа. На рис. 38 даны путевые матирцы пути adj2, adj3, adj4 для графа рис. 37. Рис. 38. Матрицы путей 103 Технология программирования Бугаков П.Ю. Матрицы инцидентности используются только для орграфов. В каждой строке содержится упорядоченная последовательность имен узлов, с которыми данный узел связан ориетрированными (исходящими) ребрами. На рис. 39 показана матрица инцидентности для графа рис. 37. Рис. 39. Матрица инцидентности 3.5.3. Деревья Дерево - это граф, который характеризуется следующими свойствами:  1. Cуществует единственный элемент (узел или вершина), на который не ссылается никакой другой элемент - и который называется КОРНЕМ (рис. 40 - A,G,M - корни).  2. Начиная с корня и следуя по определенной цепочке указателей, содержащихся в элементах, можно осуществить доступ к любому элементу структуры.  3. На каждый элемент, кроме корня, имеется единственная ссылка, т.е. каждый элемент адресуется единственным указателем. Название "дерево" проистекает из логической эквивалентности древовидной структуры абстрактному дереву в теории графов. Линия связи между парой узлов дерева называется обычно ВЕТВЬЮ. Те узлы, которые не ссылаются ни на какие другие узлы дерева, называются ЛИСТЬЯМИ (или терминальными вершинами)(рис. 40 - b,k,l,h - листья). Узел, не являющийся листом или корнем, считается промежуточным или узлом ветвления (нетерминальной или внутренней вершиной). Для ориентированного графа число ребер, исходящих из некоторой начальной вершины V, называется ПОЛУСТЕПЕНЬЮ ИСХОДА этой вершины. Число ребер, для которых вершина V является конечной, называется ПОЛУСТЕПЕНЬЮ ЗАХОДА вершины V, а сумма полустепеней исхода и захода вершины V называется ПОЛНОЙ СТЕПЕНЬЮ этой вершины. Рис. 40. Дерево и лес Ниже будет представлен важный класс орграфов - ориентированные деревья - и соответствующая им терминология. Деревья нужны для описания любой структуры с иерархией. Традиционные примеры таких структур: генеалогические деревья, десятичная классификация книг в библиотеках, иерархия должностей в организации, 104 Технология программирования Бугаков П.Ю. алгебраическое выражение, включающее операции, для которых предписаны определенные правила приоритета. Ориентированное дерево - это такой ациклический орграф (ориентированный граф), у которого одна вершина, называемая корнем, имеет полустепень захода, равную 0, а остальные - полустепени захода, равные 1. Ориентированное дерево должно иметь по крайней мере одну вершину. Изолированная вершина также представляет собой ориентированное дерево. Вершина ориентированного дерева, полустепень исхода которой равна нулю, называется КОНЦЕВОЙ (ВИСЯЧЕЙ) вершиной или ЛИСТОМ; все остальные вершины дерева называют вершинами ветвления. Длина пути от корня до некоторой вершины называется УРОВНЕМ (НОМЕРОМ ЯРУСА) этой вершины. Уровень корня ориентированного дерева равен нулю, а уровень любой другой вершины равен расстоянию (т.е. модулю разности номеров уровней вершин) между этой вершиной и корнем. Ориентированное дерево является ациклическим графом, все пути в нем элементарны. Во многих приложениях относительный порядок следования вершин на каждом отдельном ярусе имеет определенное значение. При представлении дерева в ЭВМ такой порядок вводится автоматически, даже если он сам по себе произволен. Порядок следования вершин на некотором ярусе можно легко ввести, помечая одну вершину как первую, другую - как вторую и т.д. Вместо упорядочивания вершин можно задавать порядок на ребрах. Если в ориентированном дереве на каждом ярусе задан порядок следования вершин, то такое дерево называется УПОРЯДОЧЕННЫМ ДЕРЕВОМ. Введем еще некоторые понятия, связанные с деревьями. На рис. 41 показано дерево: Узел X называется ПРЕДКОМ (или ОТЦОМ), а узлы Y и Z называются НАСЛЕДНИКАМИ (или СЫНОВЬЯМИ) их соответственно между собой называют БРАТЬЯМИ. Причем левый сын является старшим сыном, а правый - младшим. Число поддеревьев данной вершины называется СТЕПЕНЬЮ этой вершины. ( В данном примере X имеет 2 поддерева, следовательно СТЕПЕНЬ вершины X равна 2). Рис. 41. Дерево Если из дерева убрать корень и ребра, соединяющие корень с вершинами первого яруса, то получится некоторое множество несвязанных деревьев. Множество несвязанных деревьев называется ЛЕСОМ (рис. 40). 3.5.4. Логическое представление и изображение деревьев Имеется ряд способов графического изображения деревьев. Первый способ заключается в использовании для изображения поддеревьев известного метода диаграмм Венна, второй - метода вкладывающихся друг в друга скобок, третий способ - это способ, применяемый при составлении оглавлений книг. Последний способ, базирующийся на формате с нумерацией уровней, сходен с методами, используемыми 105 Технология программирования Бугаков П.Ю. в языках программирования. При применении этого формата каждой вершине приписывается числовой номер, который должен быть меньше номеров, приписанных корневым вершинам присоединенных к ней поддеревьев. Отметим, что корневые вершины всех поддереьев данной вершины должны иметь один и тот же номер. Метод вложенных скобок (V0(V1(V2(V5)(V6))(V3)(V4))(V7(V8)(V9(V10)))) Рис. 42. Представление дерева : а)- исходное дерево, б)- оглавление книг, в)- граф, г)- диаграмма Венна 106 Технология программирования Бугаков П.Ю. РАЗДЕЛ IV АЛГОРИТМЫ СОРТИРОВКИ Под сортировкой понимают процесс перестановки объектов данного множества в определенном порядке. Цель сортировки - облегчить последующий поиск элементов в отсортированном множестве. В этом смысле элементы сортировки присутствуют во многих задачах прикладного программирования. Зависимость выбора алгоритмов решения задачи от структуры данных - явление довольно частое. В случае сортировки эта зависимость настолько сильна, что методы сортировки обычно разделяют на две категории: • сортировка массивов; • сортировка последовательных файлов. Эти две разновидности сортировок часто называют соответственно внутренней (сортировка массивов) и внешней (сортировка файлов) сортировками. Это объясняется тем, что массивы располагаются во "внутренней" (оперативной) памяти ЭВМ и для нее характерен быстрый произвольный доступ (прямой доступ). Файлы же хранятся в более медленной, но более вместительной "внешней" памяти, т.е. на запоминающих устройствах с механическим передвижением (магнитных дисках и лентах). Указанное существенное различие можно наглядно продемонстрировать на примере сортировки пронумерованных карточек. 1. Представление карточек в виде массива с прямым доступом (рис. 43) означает, что все карточки одновременно видны и равнодоступны. Рис. 43. Произвольный (прямой) доступ 2. Представление карточек в виде последовательного файла (рис. 44) предполагает, что видна и доступна только верхняя карточка. Чтобы добраться до остальных карточек необходимо, например, перекладывать карточки в колоде по одной спереди назад. Очевидно, что такое ограничение приведет к существенному изменению методов сортировки. Рис. 44. Последовательный доступ 4.1. Сортировка массивов Пусть есть последовательность a0, a1... an и функция сравнения, которая на любых двух элементах последовательности принимает одно из трех значений: меньше, больше или равно. Задача сортировки состоит в 107 Технология программирования Бугаков П.Ю. перестановке членов последовательности таким образом, чтобы выполнялось условие: ai <= ai+1, для всех i от 0 до n. Возможна ситуация, когда элементы состоят из нескольких полей: struct element { field x; field y; } Если значение функции сравнения зависит только от поля x, то x называют ключом, по которому производится сортировка. На практике, в качестве x часто выступает число, а поле y хранит какие -либо данные, никак не влияющие на работу алгоритма. Пожалуй, никакая другая проблема не породила такого количества разнообразнейших решений, как задача сортировки. Существует ли некий "универсальный", наилучший алгоритм ? Вообще говоря, нет. Однако, имея приблизительные характеристики входных данных, можно подобрать метод, работающий оптимальным образом. Для того, чтобы обоснованно сделать такой выбор, рассмотрим параметры, по которым будет производиться оценка алгоритмов. 1. Время сортировки основной параметр, характеризующий быстродействие алгоритма. 2. Память - ряд алгоритмов требует выделения дополнительной памяти под временное хранение данных. При оценке используемой памяти не будет учитываться место, которое занимает исходный массив и независящие от входной последовательности затраты, например, на хранение кода программы. 3. Устойчивость - устойчивая сортировка не меняет взаимного расположения равных элементов. Такое свойство может быть очень полезным, если они состоят из нескольких полей, а сортировка происходит по одному из них, например, по x. Рис. 45. Пример работы сортировок 4. Естественность поведения - эффективность метода при обработке уже отсортированных, или частично отсортированных данных. Алгоритм ведет себя естественно, если учитывает эту характеристику входной последовательности и работает лучше. Еще одним важным свойством алгоритма является его сфера применения. Здесь основных позиций две:  внутренние сортировки работают с данным в оперативной памяти с произвольным доступом;  внешние сортировки упорядочивают информацию, расположенную на внешних носителях. Это накладывает некоторые дополнительные ограничения на алгоритм: 108 Технология программирования Бугаков П.Ю.  доступ к носителю осуществляется последовательным образом: в каждый момент времени можно считать или записать только элемент, следующий за текущим;  объем данных не позволяет им разместиться в ОЗУ. Кроме того, доступ к данным на носителе производится намног о медленнее, чем операции с оперативной памятью. Данный класс алгоритмов делится на два основных подкласса: Внутренняя сортировка оперирует с массивами, целиком помещающимися в оперативной памяти с произвольным доступом к любой ячейке. Данные обычно сортируются на том же месте, без дополнительных затрат. Внешняя сортировка оперирует с запоминающими устройствами большого объема, но с доступом не произвольным, а последовательным (сортировка файлов), т.е в данный момент мы 'видим' только один элемент, а затраты на перемотку по сравнению с памятью неоправданно велики . Это приводит к специальным методам сортировки, обычно использующим дополнительное дисковое пространство. Методы внутренней сортировки массивов можно разбить на три основных класса: • сортировка выбором; • сортировка включениями (вставками); • сортировка обменом. В простейшем случае элементы массива будем располагать в порядке не убывания их ключей. 4.2. Внутренняя сортировка 4.2.1. Сортировка массива простым выбором Выбирается элемент с наибольшим значением ключа и меняется местами с последним. Затем то же самое повторяется для s-1 первого элемента, найденный элемент с наибольшим значением ключа меняется местами с предпоследним элементом и т.д. (рис. 46). Рис. 46. Сортировка простым выбором Метод основан на следующем правиле. 1. Выбирается элемент с наибольшим значением ключа. 2. Он меняется местами с последним элементом arr[ 5-i ]. Эти операции затем повторяются с оставшимися первыми s-1 элементами, затем - с s-2 первыми элементами и т.д. до тех пор, пока не останется только один первый элемент наименьший. Пример сортировки массива простым выбором приведен на рис. 47. 3. 109 Технология программирования Бугаков П.Ю. Рис. 47. Сортировка массива простым выбором Эффективность сортировки простым выбором. Число сравнений ключей не зависит от начального порядка ключей. Операция сравнения выполняется в теле цикла с управляющей переменной к и средним числом повторений size!2. Этот цикл, в свою очередь, находится в теле цикла с управляющей переменной L и числом повторений size-1. Таким образом, число сравнений Число пересылок, напротив, зависит от начального порядка ключей. Если принять, что операция сравнения в теле цикла по к дает результат "истина" в половине случаев, то среднее число пересылок в этом цикле равно sizelA. Цикл по Z,, как указывалось выше, выполняется size-1 раз и в теле цикла выполняется три пересылки и цикл по к. С учетом этого число пересылок Получаем, что при сортировке простым выбором и число сравнений, и число пересылок пропорционально size2. 4.2.2. Сортировка массива простыми включениями Элементы разделяются на уже готовую последовательность (упорядоченную) и неупорядоченную (рис. 48). В начале упорядоченная часть содержит только один элемент. Очередной элемент из начала неупорядоченной части вставляется на подходящее место в упорядоченную часть. При этом упорядоченная часть удлиняется на один элемент, а неупорядоченная часть - укорачивается. Сортировка заканчивается при исчезновении неупорядоченной части. Рис. 48. Сортировка простыми включениями При поиске подходящего места удобно чередовать сравнения и пересылки, т.е. как бы "просеивать" сору, сравнивая его с очередным элементом arr[j ] и, либо вставляя сору, либо пересылая arr[j ] направо и передвигаясь налево. Заметим, что "просеивание" может закончиться при двух различных условиях. 1. Найден элемент arr[j ] с ключом, меньшим, чем у сору. 110 Технология программирования Бугаков П.Ю. 2. Достигнут левый конец упорядоченного сегмента и, следовательно, сору нужно вставить в левый конец упорядоченного сегмента. Рис. 49. Пример сортировки массива простыми включениями Это типичный пример цикла с двумя условиями окончания. При записи подобных циклов можно использовать известный прием фиктивного элемента ("барьера"), установив "барьер" слева в упорядоченной части массива агг[ 0 ] = сору (рис. 50). Рис. 50. Использование "барьера" при сортировке массива ростыми включениями Прототип функции сортировки массива простыми включениями, ее определение и пример вызова даны в примере. Теперь наступила пора познакомиться с ними. Обратите внимание на то, что при использовании метода "левого барьера" размер массива, подлежащего сортировке, увеличен на один элемент. При этом элемент массива с нулевым индексом является вспомогательным и не сортируется. Таким образом, в массиве сортируются элементы с индексами 1, 2, ..., sizel-\. По этой причине для сортировки простым выбором используются функции размещения сортируемого массива в динамической памяти, заполнения его значениями из файла и печати значений элементов массива в файл, отличающиеся от аналогичных функций для других методов. Эффективность сортировки. Число С, сравнений ключей при i-ом просеивании составляет самое большее i, а самое меньшее - 1. Число Мt пересылок (присваиваний) элементов при i-ом просеивании равно Это объясняется тем, что тело цикла while выполняется на один раз меньше, чем число проверок условия повтора цикла. Три других пересылки при /-ом просеивании есть: Поэтому общее число сравнений и пересылок есть где (size-2) - число повторов цикла по i, 111 Технология программирования Бугаков П.Ю. CMIN и MMIN имеют место, если элементы массива с самого начала упорядочены, а CMAX и MMAX встречаются, если элементы массива расположены в обратном порядке. 4.2.3. Сортировка массива простым обменом (метод "пузырька") Основная характеристика процесса - обмен местами двух соседних элементов (перестановка), если они расположены не так, как требует отсортированный массив (рис. 51). На приведенном рисунке изображен только один шаг (просмотр). Сортировка массива гарантируется после 5-i просмотра. Рис. 51. Сортировка простым обменом Данный алгоритм основан на принципе сравнения и обмена пары соседних элементов до тех пор, пока не будут отсортированы все элементы массива. Пример сортировки массива методом "пузырька" приведен на рис. 52. Очевидно, что в наихудшем случае, когда минимальное значение ключа элемента имеется у самого правого элемента, число просмотров равно size-1. Прототип функции сортировки массива простым обменом, ее определение и пример вызова даны в примере. Рис. 52. Пример сортировки массива простым обменом Эффективность сортировки. За один проход среднее число сравнений СКСРЕД равно size 12 (на первом проходе - size-1, а на последнем - 1). При этом среднее число 112 Технология программирования Бугаков П.Ю. возможных пересылок А/КСРЕД =1.5 *СКСРЕД (в предположении, что проверяемое условие выполняется в половине случаев). Минимальное количество проходов равно 1, максимальное - size-1, а среднее - size 12. Следовательно, 4.3. Внешняя сортировка Внешние сортировки применяются к данным, которые хранятся во внешней памяти. При выполнении таких сортировок требуется работать с данными, расположенными на внешних устройствах последовательного доступа. Для файлов, расположенных на таких устройствах в каждый момент времени доступен только один компонент последовательности данных, что является существенным ограничением по сравнению с сортировкой массивов, где всегда доступен каждый элемент. Внешняя сортировка – это сортировка данных, которые расположены на внешних устройствах и не вмещающихся в оперативную память. Данные, хранящиеся на внешних устройствах, имеют большой объем, что не позволяет их целиком переместить в оперативную память, отсортировать с использованием одного из алгоритмов внутренней сортировки, а затем вернуть их на внешнее устройство. В этом случае осуществлялось бы минимальное количество проходов через файл, то есть было бы однократное чтение и однократная запись данных. Однако на практике приходится осуществлять чтение, обработку и запись данных в файл по блокам, размер которых зависит от операционной системы и имеющегося объема оперативной памяти, что приводит к увеличению числа проходов через файл и заметному снижению скорости сортировки. К наиболее известным алгоритмам внешних сортировок относятся:  сортировки слиянием (простое слияние и естественное слияние);  улучшенные сортировки (многофазная сортировка и каскадная сортировка). Из представленных внешних сортировок наиболее важным является метод сортировки с помощью слияния. Прежде чем описывать алгоритм сортировки слиянием введем несколько определений. Основным понятием при использовании внешней сортировки является понятие серии. Серия (упорядоченный отрезок) – это последовательность элементов, которая упорядочена по ключу. Количество элементов в серии называется длиной серии. Серия, состоящая из одного элемента, упорядочена всегда. Последняя серия может иметь длину меньшую, чем остальные серии файлов. Максимальное количество серий в файле N (все элементы не упорядочены). Минимальное количество серий одна (все элементы упорядочены). В основе большинства методов внешних сортировок лежит процедура слияния и процедура распределения. Слияние – это процесс объединения двух (или более) упорядоченных серий в одну упорядоченную последовательность при помощи циклического выбора элементов доступных в данный момент. Распределение – это процесс разделения упорядоченных серий на два и несколько вспомогательных файла. Фаза – это действия по однократной обработке всей последовательности элементов. Двухфазная сортировка – это сортировка, в которой отдельно 113 Технология программирования Бугаков П.Ю. реализуется две фазы: распределение и слияние. Однофазная сортировка – это сортировка, в которой объединены фазы распределения и слияния в одну. Двухпутевым слиянием называется сортировка, в которой данные распределяются на два вспомогательных файла. Многопутевым слиянием называется сортировка, в которой данные распределяются на N (N > 2) вспомогательных файлов. 4.3.1. Общий алгоритм сортировки слиянием Сначала серии распределяются на два или более вспомогательных файлов. Данное распределение идет поочередно: первая серия записывается в первый вспомогательный файл, вторая – во второй и так далее до последнего вспомогательного файла. Затем опять запись серии начинается в первый вспомогательный файл. После распределения всех серий, они объединяются в более длинные упорядоченные отрезки, то есть из каждого вспомогательного файла берется по одной серии, которые сливаются. Если в каком-то файле серия заканчивается, то переход к следующей серии не осуществляется. В зависимости от вида сортировки сформированная более длинная упорядоченная серия записывается либо в исходный файл, либо в один из вспомогательных файлов. После того как все серии из всех вспомогательных файлов объединены в новые серии, потом опять начинается их распределение. И так до тех пор, пока все данные не будут отсортированы. Выделим основные характеристики сортировки слиянием:  количество фаз в реализации сортировки;  количество вспомогательных файлов, на которые распределяются серии. Рассмотрим основные и наиболее важные алгоритмы внешних сортировок более подробно. 4.3.2. Сортировка простым слиянием Одна из сортировок на основе слияния называется простым слиянием. Алгоритм сортировки простым слияния является простейшим алгоритмом внешней сортировки, основанный на процедуре слияния серией. В данном алгоритме длина серий фиксируется на каждом шаге. В исходном файле все серии имеют длину 1, после первого шага она равна 2, после второго – 4, после третьего – 8, после k-го шага – 2k. Алгоритм сортировки простым слиянием Шаг 1. Исходный файл f разбивается на два вспомогательных файла f1 и f2. Шаг 2. Вспомогательные файлы f1 и f2 сливаются в файл f, при этом одиночные элементы образуют упорядоченные пары. Шаг 3. Полученный файл f вновь обрабатывается, как указано в шагах 1 и 2. При этом упорядоченные пары переходят в упорядоченные четверки. Шаг 4. Повторяя шаги, сливаем четверки в восьмерки и т.д., каждый раз удваивая длину слитых последовательностей до тех пор, пока не будет упорядочен целиком весь файл (рис. 53). После выполнения i проходов получаем два файла, состоящих из серий длины 2i. Окончание процесса происходит при выполнении условия 2i n. Следовательно, процесс сортировки простым слиянием требует порядка O(log n) проходов по данным. Признаками конца сортировки простым слиянием являются следующие условия: 114 Технология программирования Бугаков П.Ю.  длина серии не меньше количества элементов в файле (определяется после фазы слияния);  количество серий равно 1 (определяется на фазе слияния).  при однофазной сортировке второй по счету вспомогательный файл после распределения серий остался пустым.  Рис. 53. Демонстрация сортировки двухпутевым двухфазным простым слиянием Заметим, что для выполнения внешней сортировки методом простого слияния в оперативной памяти требуется расположить всего лишь две переменные – для размещения очередных элементов (записей) из вспомогательных файлов. Исходный и вспомогательные файлы будут O(log n) раз прочитаны и столько же раз записаны. 4.3.3. Сортировка естественным слиянием В случае простого слияния частичная упорядоченность сортируемых данных не дает никакого преимущества. Это объясняется тем, что на каждом проходе сливаются серии фиксированной длины. При естественном слиянии длина серий не ограничивается, а определяется количеством элементов в уже упорядоченных подпоследовательностях, выделяемых на каждом проходе. Сортировка, при которой всегда сливаются две самые длинные из возможных последовательностей, является естественным слиянием. В данной сортировке объединяются серии максимальной длины. Алгоритм сортировки естественным слиянием Шаг 1. Исходный файл f разбивается на два вспомогательных файла f1 и f2. Распределение происходит следующим образом: поочередно считываются записи ai исходной последовательности (неупорядоченной) таким образом, что если значения ключей соседних записей удовлетворяют условию f(ai) f(ai+1), то они записываются в первый вспомогательный файл f1. Как только встречаются f(ai)>f(ai+1), то записи ai+1 копируются во второй вспомогательный файл f2. Процедура повторяется до тех пор, пока все записи исходной последовательности не будут распределены по файлам. Шаг 2. Вспомогательные файлы f1 и f2 сливаются в файл f, при этом серии образуют упорядоченные последовательности. Шаг 3. Полученный файл f вновь обрабатывается, как указано в шагах 1 и 2. Шаг 4. Повторяя шаги, сливаем упорядоченные серии до тех пор, пока не будет упорядочен целиком весь файл. Символ "`" обозначает признак конца серии. 115 Технология программирования Бугаков П.Ю. Признаками конца сортировки естественным слиянием являются следующие условия:  количество серий равно 1 (определяется на фазе слияния).  при однофазной сортировке второй по счету вспомогательный файл после распределения серий остался пустым. Естественное слияние, у которого после фазы распределения количество серий во вспомогательных файлах отличается друг от друга не более чем на единицу, называется сбалансированным слиянием, в противном случае – несбалансированное слияние. Рис. 54. Демонстрация сортировки двухпутевым двухфазным естественным слиянием Таким образом, число чтений или перезаписей файлов при использовании метода естественного слияния будет не хуже, чем при применении метода простого слияния, а в среднем – даже лучше. Но в этом методе увеличивается число сравнений за счет тех, которые требуются для распознавания концов серий. Помимо этого, максимальный размер вспомогательных файлов может быть близок к размеру исходного файла, так как длина серий может быть произвольной. 116 Технология программирования Бугаков П.Ю. РАЗДЕЛ V АЛГОРИТМЫ ПОИСКА 5.1. Алгоритмы поиска в линейных структурах Поиск – процесс нахождения конкретной информации в ранее созданном множестве данных. Обычно данные представляют собой записи, каждая из которых имеет хотя бы один ключ. Ключ поиска – это поле записи, по значению которого происходит поиск. Ключи используются для отличия одних записей от других. Целью поиска является нахождение всех записей (если они есть) с данным значением ключа. Структуру данных, в которой проводится поиск, можно рассматривать как таблицу символов (таблицу имен или таблицу идентификаторов) – структуру, содержащую ключи и данные, и допускающую две операции – вставку нового элемента и возврат элемента с заданным ключом. Иногда таблицы символов называют словарями по аналогии с хорошо известной системой упорядочивания слов в алфавитном порядке: слово – ключ, его толкование – данные. Поиск является одним из наиболее часто встречаемых действий в программировании. Существует множество различных алгоритмов поиска, которые принципиально зависят от способа организации данных. У каждого алгоритма поиска есть свои преимущества и недостатки. Поэтому важно выбрать тот алгоритм, который лучше всего подходит для решения конкретной задачи. Поставим задачу поиска в линейных структурах. Пусть задано множество данных, которое описывается как массив, состоящий из некоторого количества элементов. Проверим, входит ли заданный ключ в данный массив. Если входит, то найдем номер этого элемента массива, то есть, определим первое вхождение заданного ключа (элемента) в исходном массиве. Таким образом, определим общий алгоритм поиска данных: Шаг 1. Вычисление элемента, что часто предполагает получение значения элемента, ключа элемента и т.д. Шаг 2. Сравнение элемента с эталоном или сравнение двух элементов (в зависимости от постановки задачи). Шаг 3. Перебор элементов множества, то есть прохождение по элементам массива. Основные идеи различных алгоритмов поиска сосредоточены в методах перебора и стратегии поиска. Рассмотрим основные алгоритмы поиска в линейных структурах более подробно. 5.2. Последовательный (линейный) поиск Последовательный (линейный) поиск – это простейший вид поиска заданного элемента на некотором множестве, осуществляемый путем последовательного сравнения очередного рассматриваемого значения с искомым до тех пор, пока эти значения не совпадут. Идея этого метода заключается в следующем. Множество элементов просматривается последовательно в некотором порядке, гарантирующем, что будут просмотрены все элементы множества (например, слева направо). Если в ходе просмотра множества будет найден искомый элемент, просмотр прекращается с 117 Технология программирования Бугаков П.Ю. положительным результатом; если же будет просмотрено все множество, а элемент не будет найден, алгоритм должен выдать отрицательный результат. Алгоритм последовательного поиска Шаг 1. Полагаем, что значение переменной цикла i=0. Шаг 2. Если значение элемента массива x[i] равно значению ключа key, то возвращаем значение, равное номеру искомого элемента, и алгоритм завершает работу. В противном случае значение переменной цикла увеличивается на единицу i=i+1. Шаг 3. Если i= low ){ if (key == x[middle]) found = true; else if (key < x[middle]) high = middle - 1; else low = middle + 1; middle = (high + low) / 2; } return found ? middle : -1 ; } В процессе работы алгоритма бинарного поиска размер фрагмента, где этот поиск должен продолжаться, каждый раз уменьшается примерно в два раза. Это обеспечивает сложность алгоритма пропорциональную O(log n), где n – количество элементов множества. Время выполнения алгоритма бинарного поиска: если функция имеет вещественный аргумент, найти решение с точностью до ε можно за время , а если аргумент дискретен, то поиск решения займет 1 + log n времени. Достоинством данного алгоритма является относительная быстрота выполнения поиска, по сравнению с алгоритмом последовательного поиска. Недостаток заключается в том, что бинарный поиск может применяться только на упорядоченном 5.4. Алгоритмы поиска на основе деревьев Поиск данных, являясь одним из приоритетных направлений работы с данными, предполагает использование соответствующих алгоритмов в зависимости от ряда факторов: способ представления данных, упорядоченность множества поиска, объем данных, расположение их во внешней или во внутренней памяти. Поиск – процесс нахождения конкретной информации в ранее созданном множестве данных. Как правило, данные представляют собой структуры, каждая из которых имеет хотя бы один ключ – значение определенного поля конкретной структуры. Ключ поиска – это поле, по значению которого происходит поиск. Рассмотрим организацию поиска данных, имеющих древовидную структуру. Анализируя дерево только с точки зрения представления данных в виде иерархической структуры, заметим, что выигрыша при организации поиска не 120 Технология программирования Бугаков П.Ю. получится. Сравнение ключа поиска с эталоном необходимо провести для всех элементов дерева. Уменьшить число сравнений ключей с эталоном возможно, если выполнить организацию дерева особым образом, то есть расположить его элементы по определенным правилам. При этом в процессе поиска будет просмотрено не все дерево, а отдельное поддерево. Такой подход позволяет классифицировать деревья в зависимости от правил построения. Выделим некоторые популярные виды деревьев, на основе которых рассмотрим организацию поиска. 5.4.1. Двоичные (бинарные) деревья Двоичные деревья представляют собой иерархическую структуру, в которой каждый узел имеет не более двух потомков. То есть двоичное дерево либо является пустым, либо состоит из данных и двух поддеревьев (каждое из которых может быть пустым). При этом каждое поддерево в свою очередь тоже является деревом. Поиск на таких структурах не дает выигрыша по выполнению по сравнению с линейными структурами того же размера, так как необходимо в худшем случае выполнить обход всего дерева. Поэтому интерес представляют двоичные упорядоченные деревья. 5.4.2. Двоичные упорядоченные деревья Двоичное дерево упорядоченно, если для любой его вершины x справедливы такие свойства (рис. 56):  все элементы в левом поддереве меньше элемента, хранимого в x,  все элементы в правом поддереве больше элемента, хранимого в x,  все элементы дерева различны.  Рис. 56. Двоичное упорядоченное дерево Если в дереве выполняются первые два свойства, но встречаются одинаковые элементы, то такое дерево является частично упорядоченным. В дальнейшем будет идти речь только о двоичных упорядоченных деревьях. Основными операциями, производимыми с упорядоченным деревом, являются:  поиск вершины;  добавление вершины;  удаление вершины;  вывод (печать) дерева;  очистка дерева. 121 Технология программирования Бугаков П.Ю. Алгоритм удаления элемента более трудоемкий, так как надо соблюдать упорядоченность дерева. При удалении может случиться, что удаляемый элемент находится не в листе, то есть вершина имеет ссылки на реально существующие поддеревья. Эти поддеревья терять нельзя, а присоединить два поддерева на одно освободившееся после удаления место невозможно. Поэтому необходимо поместить на освободившееся место либо самый правый элемент из левого поддерева, либо самый левый из правого поддерева. Упорядоченность дерева при этом не нарушится. Удобно придерживаться одной стратегии, например, заменять самый левый элемент из правого поддерева. Нельзя забывать, что при замене вершина, на которую производится замена, может иметь правое поддерево. Это поддерево необходимо поставить вместо перемещаемой вершины. Временная сложность этих алгоритмов (она одинакова для этих алгоритмов, так как в их основе лежит поиск) оценим для наилучшего и наихудшего случая. В лучшем случае, то есть случае полного двоичного дерева, получаем сложность Omin(log n). В худшем случае дерево может выродиться в список. Такое может произойти, например, при добавлении элементов в порядке возрастания. При работе со списком в среднем придется просмотреть половину списка. Это даст сложность Omax(n). 5.4.3. Случайные деревья Случайные деревья поиска представляют собой упорядоченные бинарные деревья поиска, при создании которых элементы (их ключи) вставляются в случайном порядке. При создании таких деревьев используется тот же алгоритм, что и при добавлении вершины в бинарное дерево поиска. Будет ли созданное дерево случайным или нет, зависит от того, в каком порядке поступают элементы для добавления. Примеры различных деревьев, создаваемых при различном порядке поступления элементов, приведены ниже (рис. 57). При поступлении элементов в случайном порядке получаем дерево с минимальной высотой h (рис. 57А), при этом минимизируется время поиска элемента в дереве, которое пропорционально O(log n). При поступлении элементов в упорядоченном виде (рис. 57В) или в порядке с единичными сериями монотонности (рис. 57С) происходит построение вырожденных деревьев поиска (оно вырождено в линейный список), что нисколько не сокращает время поиска, которое составляет O(n). Рис. 57. Случайные деревья поиска 122 Технология программирования Бугаков П.Ю. 5.4.4. Оптимальные деревья В двоичном дереве поиск одних элементов может происходить чаще, чем других, то есть существуют вероятности pk поиска k-го элемента и для различных элементов эти вероятности неодинаковы. Можно предположить, что поиск в дереве в среднем будет более быстрым, если те элементы, которые ищут чаще, будут находиться ближе к корню дерева. Пусть даны 2n+1 вероятностей p1,p2,...,pn, q0,q1,...,qn, где pi – вероятность того, что аргументом поиска является Ki элемент; qi – вероятность того, что аргумент поиска лежит между вершинами Ki и Ki+1; q0 – вероятность того, что аргумент поиска меньше, чем значение элемента K1; qn – вероятность того, что аргумент поиска больше, чем Kn. Тогда цена дерева поиска C будет определяться следующим образом: где – уровень узла j, а – уровень листа K. Дерево поиска называется оптимальным, если его цена минимальна. То есть оптимальное бинарное дерево поиска – это бинарное дерево поиска, построенное в расчете на обеспечение максимальной производительности при заданном распределении вероятностей поиска требуемых данных. Существует подход построения оптимальных деревьев поиска, при котором элементы вставляются в порядке уменьшения частот, что дает в среднем неплохие деревья поиска. Однако этот подход может дать вырожденное дерево поиска, которое будет далеко от оптимального. Еще один подход состоит в выборе корня k таким образом, чтобы максимальная сумма вероятностей для вершин левого поддерева или правого поддерева была настолько мала, насколько это возможно. Такой подход также может оказаться плохим в случае выбора в качестве корня элемента с малым значением pk. Существуют алгоритмы, которые позволяют построить оптимальное дерево поиска. К ним относится, например, алгоритм Гарсия-Воча. Однако такие алгоритмы имеют временную сложность порядка O(n2). Таким образом, создание оптимальных деревьев поиска требует больших накладных затрат, что не всегда оправдывает выигрыш при быстром поиске. 5.4.5. Сбалансированные по высоте деревья В худшем случае, когда дерево вырождено в линейный список, хранение данных в упорядоченном бинарном дереве никакого выигрыша в сложности операций по сравнению с массивом или линейным списком не дает. В лучшем случае, когда дерево сбалансировано, для всех операций получается логарифмическая сложность, что гораздо лучше. Идеально сбалансированным называется дерево, у которого для каждой вершины выполняется требование: число вершин в левом и правом поддеревьях различается не более чем на 1. 123 Технология программирования Бугаков П.Ю. Рис. 58. Сбалансированное по высоте дерево Однако идеальную сбалансированность довольно трудно поддерживать. В некоторых случаях при добавлении или удалении элементов может потребоваться значительная перестройка дерева, не гарантирующая логарифмической сложности. В 1962 году два советских математика: Г.М. Адельсон-Вельский и Е.М. Ландис – ввели менее строгое определение сбалансированности и доказали, что при таком определении можно написать программы добавления и/или удаления, имеющие логарифмическую сложность и сохраняющие дерево сбалансированным. Дерево считается сбалансированным по АВЛ (сокращения от фамилий Г.М. АдельсонВельский и Е.М. Ландис), если для каждой вершины выполняется требование: высота левого и правого поддеревьев различаются не более, чем на 1. Не всякое сбалансированное по АВЛ дерево идеально сбалансировано, но всякое идеально сбалансированное дерево сбалансировано по АВЛ. При операциях добавления и удаления может произойти нарушение сбалансированности дерева. В этом случае потребуются некоторые преобразования, не нарушающие упорядоченности дерева и способствующие лучшей сбалансированности. Рассмотрим такие преобразования. Пусть вершина a имеет правый потомок b. Обозначим через P левое поддерево вершины a, через Q и R – левое и правое поддеревья вершины b соответственно. Упорядоченность дерева требует, чтобы P0) Основным недостатком метода квадратичных проб является то, что для включаемой записи может не найтись свободного элемента массива даже в том случае, когда реально около половины элементов являются свободными. Рис. 64. Двойное хэширование – поиск ключа 131 Технология программирования Бугаков П.Ю. 5.5.5. Использование цепочек переполнения Это один из наиболее очевидных способов разрешения коллизий. Если по смыслу в элементах массива хранятся записи типа T, то в данном случае к записи добавляется поле типа ссылки на T. При возникновении коллизии по причине включения новой записи для нового элемента выделяется динамическая память, и он включается в конец линейного списка, который начинается от первичного элемента массива. Если коллизия возникает при поиске ключа, то список переполнения просматривается либо до момента нахождения требуемого ключа, либо до конца, что означает отсутствие искомой записи (рисунок 65). Рис. 65. Цепочка переполнения Метод цепочек переполнения легко реализуется, понятен, но потенциально приводит к излишним расходам памяти. 132 Технология программирования Бугаков П.Ю. РАЗДЕЛ VI. ВЫЧИСЛИТЕЛЬНЫЕ АЛГОРИТМЫ 6.1. Определение сложности программ Для большинства проблем существует много различных алгоритмов. Какой из них выбрать для решения конкретной задачи? Этот вопрос очень тщательно прорабатывается в программировании. Эффективность программы (кода) является очень важной ее характеристикой. Пользователь всегда предпочитает более эффективное решение даже в тех случаях, когда эффективность не является решающим фактором. Эффективность программы имеет две составляющие: память (или пространство) и время. Пространственная эффективность измеряется количеством памяти, требуемой для выполнения программы. Компьютеры обладают ограниченным объемом памяти. Если две программы реализуют идентичные функции, то та, которая использует меньший объем памяти, характеризуется большей пространственной эффективностью. Иногда память становится доминирующим фактором в оценке эффективности программ. Однако в последний годы в связи с быстрым ее удешевлением эта составляющая эффективности постепенно теряет свое значение. Временная эффективность программы определяется временем, необходимым для ее выполнения. Лучший способ сравнения эффективностей алгоритмов состоит в сопоставлении их порядков сложности. Этот метод применим как к временной, так и пространственной сложности. Порядок сложности алгоритма выражает его эффективность обычно через количество обрабатываемых данных. Например, некоторый алгоритм может существенно зависеть от размера обрабатываемого массива. Если, скажем, время обработки удваивается с удвоением размера массива, то порядок временной сложности алгоритма определяется как размер массива. Порядок алгоритма - это функция, доминирующая над точным выражением временной сложности. Функция f(n) имеет порядок O(g(n)), если имеется константа К и счетчик n0, такие, что f(n)(K*g(n), для n>n0. Например: Известно, что точное время обработки массива 2 Действительное время(Длина массива)= Длина массива +5*Длина массива+100; Вспомогательная функция: Оценка времени(Длина массива)=1,1* Длина массива2 Как видно из рисунка вспомогательная функция доминирует над точной, кроме того вспомогательная функция проще и близка к точной на столько на сколько это возможно. Тогда порядок алгоритма обработки массива будет O(Длина массива2) или O(N2). O-функции выражают относительную скорость алгоритма в зависимости от некоторой переменной (или переменных). Существуют три важных правила для определения сложности. 1. O(k*f)=O(f) 133 Технология программирования Бугаков П.Ю. 2. O(f*g)=O(f)*O(g) или O(f/g)=O(f)/O(g) 3. O(f+g) равна доминанте O(f) и O(g) Здесь k обозначает константу, a f и g - функции. Первое правило, приведенное на рисунке, декларирует, что постоянные множители не имеют значения для определения порядка сложности. 1,5*N=O(N) Из второго правила следует, что порядок сложности произведения двух функций равен произведению их сложностей. O((17*N)*N) = O(17*N)*O(N) = O(N)*O(N)=O(N*N) = O(N2) Из третьего правила следует, что порядок сложности суммы функций определяется как порядок доминанты первого и второго слагаемых, т.е. выбирается наибольший порядок. O(N5+N2)=O(N5) O-сложность алгоритмов. O(1) Большинство операций в программе выполняются только раз или только несколько раз. Алгоритмами константной сложности. Любой алгоритм, всегда требующий независимо от размера данных одного и того же времени, имеет константную сложность. О(N) Время работы программы линейно обычно когда каждый элемент входных данных требуется обработать лишь линейное число раз. О(N2), О(N3), О(Nа) Полиномиальная сложность. О(N2)-квадратичная сложность, О(N3)- кубическая сложность О(Log(N)) Когда время работы программы логарифмическое, программа начинает работать намного медленнее с увеличением N. Такое время работы встречается обычно в программах, которые делят большую проблему в маленькие и решают их по отдельности. O(N*log(N)) Такое время работы имеют те алгоритмы, которые делят большую проблему в маленькие, а затем, решив их, соединяют их решения. O(2N) Экспоненциальная сложность. Такие алгоритмы чаще всего возникают в результате подхода именуемого метод грубой силы. Программист должен уметь проводить анализ алгоритмов и определять их сложность. Временная сложность алгоритма может быть посчитана исходя из анализа его управляющих структур. 134 Технология программирования Бугаков П.Ю. Алгоритмы без циклов и рекурсивных вызовов имеют константную сложность. Если нет рекурсии и циклов, все управляющие структуры могут быть сведены к структурам константной сложности. Следовательно, и весь алгоритм также характеризуется константной сложностью. Определение сложности алгоритма в основном сводится к анализу циклов и рекурсивных вызовов. Например, рассмотрим алгоритм обработки элементов массива. For i:=1 to N do Begin ... End; Сложность этого алгоритма O(N), т.к. тело цикла выполняется N раз, и сложность тела цикла равна O(1). Если один цикл вложен в другой и оба цикла зависят от размера одной и той же переменной, то вся конструкция характеризуется квадратичной сложностью. For i:=1 to N do For j:=1 to N do Begin ... End; Сложность этой программы О(N2). Давайте оценим сложность программы "Тройки Пифагора" Существуют два способа анализа сложности алгоритма: восходящий (от внутренних управляющих структур к внешним) и нисходящий (от внешних и внутренним). 135 Технология программирования Бугаков П.Ю. O(H)=O(1)+O(1)+O(1)=O(1); O(I)=O(N)*(O(F)+O(J))=O(N)*O(доминанты условия)=О(N); O(G)=O(N)*(O(C)+O(I)+O(K))=O(N)*(O(1)+O(N)+O(1))=O(N2); O(E)=O(N)*(O(B)+O(G)+O(L))=O(N)* O(N2)= O(N3); O(D)=O(A)+O(E)=O(1)+ O(N3)= O(N3) Сложность данного алгоритма O(N3). Как правило, около 90% времени работы программы требует выполнение повторений и только 10% составляют непосредственно вычисления. Анализ сложности программ показывает, на какие фрагменты выпадают эти 90% -это циклы наибольшей глубины вложенности. Повторения могут быть организованы в виде вложенных циклов или вложенной рекурсии. Эта информация может использоваться программистом для построения более эффективной программы следующим образом. Прежде всего можно попытаться сократить глубину вложенности повторений. Затем следует рассмотреть возможность сокращения количества операторов в циклах с наибольшей глубиной вложенности. 136 Технология программирования Бугаков П.Ю. Если 90% времени выполнения составляет выполнение внутренних циклов, то 30%ное сокращение этих небольших секций приводит к 90%*30%=27%-му снижению времени выполнения всей программы. Это наиболее простой пример. Анализом эффективности алгоритмов занимается отдельный раздел математики и найти наиболее оптимальную функцию бывает не так - то и просто. Давайте оценим алгоритм бинарного поиска в массиве - дихотомию. Суть алгоритма: идем к середине массива и ищем соответствие ключа значению срединного элемента. Если нам не удается найти соответствия, мы смотрим на относительный размер ключа и значение срединного элемента и затем перемещаемся в нижнюю или верхнюю половину списка. В этой половине снова ищем середину и опять сравниваем с ключом. Если не получается, снова делим на половину текущий интервал. function search(low, high, key: integer): integer; var mid, data: integer; begin while low<=high do begin mid:=(low+high) div 2; data:=a[mid]; if key=data then search:=mid else if key < data then high:=mid-1 else low:=mid+1; end; search:=-1; end; Решение: Первая итерация цикла имеет дело со всем списком. Каждая последующая итерация делит пополам размер подсписка. Так, размерами списка для алгоритма являются n n/21 n/22 n/23 n/24 ... n/2m В конце концов будет такое целое m, что n/2m<2 или n<2m+1 Так как m - это первое целое, для которого n/2m<2, то должно быть верно n/2m-1>=2 или 2m=
«Технология программирования. Проектирование программного обеспечения» 👇
Готовые курсовые работы и рефераты
Купить от 250 ₽
Решение задач от ИИ за 2 минуты
Решить задачу
Помощь с рефератом от нейросети
Написать ИИ
Получи помощь с рефератом от ИИ-шки
ИИ ответит за 2 минуты

Тебе могут подойти лекции

Смотреть все 493 лекции
Все самое важное и интересное в Telegram

Все сервисы Справочника в твоем телефоне! Просто напиши Боту, что ты ищешь и он быстро найдет нужную статью, лекцию или пособие для тебя!

Перейти в Telegram Bot