Сторожевое условие
Сторожевое условие (guard condition), если оно есть, всегда записывается в прямых скобках после события-триггера и представляет собой некоторое булевское выражение (выражение, результатом которого является «истина» или «ложь» ).
Установить телефонное соединение (телефонный номер) (телефонное соединение установлено)
Активизация почтовой программы
Загрузка почты с сервера провайдера
Закончить
загрузку
почты
#▼
(почтовый
ящик
на
сервере
пуст),
разорвать
телефонное
соединение
(телефонный
номер)
Рис. 3.52. Диаграмма состояний для моделирования почтовой программы-клиента
Пример диаграммы состояний почтовой программы-клиента показан на рис. 3.52.
Контрольные вопросы
Приведите эксплуатационные требования к ПО.
Перечислите функциональные требования к ПО.
Чем определяется выбор архитектуры ПО?
Л. Охарактеризуйте статические и полустатические структуры данных.
Охарактеризуйте динамические структуры данных.
Приведите понятие модуля. Характеристики модуля. 7 Какие существуют методы разработки модулей?
Что такое спецификации процессов?
Приведите пример диаграммы переходов состояний.
Какие бывают функциональные диаграммы?
Приведите пример диаграммы потоков данных.
Что такое диаграммы «сущность—связь»?
Охарактеризуйте понятие иМ1_.
Опишите варианты использования системы.
Чем описывается поведение системы?
Глава 4
Существуют два стиля проектирования: эволюционное и предварительное проектирование [18].
Методология Extreme Programming (ХР) бросила вызов многим устоявшимся представлениям о разработке программного обеспечения. Пожалуй, наиболее-противоречивой идеей является отказ от предварительного проектирования в пользу более эволюционного подхода. Противники ХР считают, что это возврат к разработкам типа «code and fix» («пишем и правим»). Для приверженцев же новой методологии это отказ от техник проектирования (например, UML), их принципов. Незачем беспокоиться о проектировании, считают они. Достаточно внимательно «вслушиваться» в свой код, и проектирование образуется само собой [17].
В большинстве случаев эволюционное проектирование — это нечто ужасное. В конце концов, все равно вместо дизайна системы вы получаете просто набор из специфических решений, каждое из которых затрудняет дальнейшие изменения в программном коде. Часто это вообще нельзя считать дизайном (и, уж конечно, такой дизайн никак нельзя назвать хорошим). Как говорит Кент, дизайн существует для того, чтобы дать возможность оперативно вносить в систему любые изменения. Если дизайн плох, то такая возможность исчезает. В результате вы будете иметь дело с энтропией программного продукта, и со временем и без того плохой дизайн системы станет еще хуже. Теперь вам будет не только сложнее вносить в систему изменения, но и отыскивать и исправлять ошибки, которые начинают множиться с катастрофической быстротой. Все это — кошмар
ПРОЕКТИРОВАНИЕ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ
разработок в стиле «code and fix», когда с течением времени исправление ошибок обходится все дороже и дороже.
Предварительное проектирование — полная противоположность эволюционному. При разработке ПО проектировщики заранее продумывают все основные вопросы. При этом они не пишут программный код, поскольку не создают программный продукт, а только разрабатывают его дизайн. В своей работе они могут использовать такие техники, как UML, что позволяет им абстрагироваться от некоторых подробностей разработок, относящихся непосредственно к программированию. Как только проектный план готов, его можно передавать в другой отдел (или даже в другую компанию), где будут вестись работы по непосредственному созданию системы. Поскольку проектировщики работают на некотором уровне абстракции, им удается избежать принятия ряда тактических решений, ведущих к энтропии программного продукта. Программисты же могут руководствоваться проектным планом и (если они ему следуют) создавать качественно выстроенную систему [5].
Такой подход к разработке ПО не нов — им активно пользуется множество людей начиная с 1970-х годов. По многим показателям он гораздо лучше, чем эволюционное проектирование в стиле «code and fix», однако и у него есть существенные недостатки. Один из главных недостатков заключается в том, что невозможно заранее продумать все вопросы, с которыми придется столкнуться во время кодирования системы. Таким образом, в ходе работ непременно возникнет ситуация, когда у программистов появятся вопросы относительно спроектированного дизайна. А что, если проектировщики, закончив свою часть работы, уже переключились на другой проект? Тогда программисты начинают самостоятельно решать сложившуюся проблему, отступая от уже принятых проектных решений и внося при этом в программный продукт долю энтропии. И даже если проектировщик еще работает над проектом и может помочь, все равно ему потребуется довольно много времени, чтобы выяснить ситуацию, внести изменения в диаграммы и уже затем менять код. А при разработке, как правило, вопрос времени всегда стоит остро. Отсюда энтропия (опять-таки).
Кроме того, существует еще и проблема культур. Проектировщиками становятся благодаря высокому мастерству и большому опыту в программировании. Однако, став проектировщиком, программист настолько поглощается новой работой что просто не имеет физической возможности заниматься написанием программного кода. При этом инструментарий и материалы программных разработок постоянно меняются. А когда вы перестаете сами писать код, вы не только теряете возможность отслеживать новшества в этой области. Вы теряете уважение тех, кто продолжает заниматься написанием программного кода.
Однако такие проблемы все же можно как-то урегулировать. Может быть, можно что-то сделать с напряженностью в отношениях между людьми. Может быть, можно найти таких проектировщиков, которые могли бы разбираться в большинстве вопросов, и такой дисциплинированный процесс разработки, который позволял бы вносить изменения в диаграммы. Однако остается еще одна проблема — изменяющиеся требования. Именно изменяющиеся требования являются проблемой номер один.
Бороться с изменяющимися требованиями можно по-разному. Один из возможных путей — делать дизайн достаточно гибким, чтобы при изменениях в требованиях его можно было легко менять. Однако для этого требуется заранее знать, какого типа изменения следует ожидать. Да, при проектировании системы можно попытаться угадать те области, в которых наиболее вероятны изменения, и учесть их в дизайне. В этом случае вы, действительно, облегчите себе работу с ожидаемыми изменениями в требованиях, но ничуть не облегчите (а возможно, только ухудшите) ситуацию с изменениями неожиданными. Кроме того, чтобы заранее определить те области, в которых наиболее вероятны изменения, вы должны прекрасно понимать требования, что, по наблюдениям, очень непросто [3].
Впрочем, не все проблемы с изменениями в требованиях возникают из-за их непонимания. Множество людей напряженно работают над разработкой технических требований к системе в надежде, что это убережет их от дальнейших поправок при проектировании. Но и так вы далеко не всегда сможете решить проблему. Многие изменения в требованиях диктуются изменениями в экономике и том виде бизнеса, для которого предназначается система. Такие изменения предугадать невозможно, сколько бы вы ни сидели над разработкой требований.
4.1. Проектирование программного обеспечения при структурном подходе
При проектировании сложного программного обеспечения прежде всего необходимо определить структурные компоненты и связи между ними. Полученная в результате структура ПО должна быть представлена в виде структурной или функциональной схем и спецификаций ее компонентов [1].
4././- Структурная схема разрабатываемого программного обеспечения
Структурной называют схему, отражающую состав и взаимодействие по управлению частей разрабатываемого программного обеспечения.
Структурная схема определяется архитектурой разрабатываемого ПО (см. разд. 3.2).
Разработку структурной схемы программы обычно выполняют методом пошаговой детализации (см. разд. 4.1.3).
Структурные схемы пакетов программ разрабатывают для каждой программы пакета по отдельности, поскольку организация программ в пакеты не предусматривает передачи управления между ними.
Компонентами структурной схемы программной системы или программного комплекса могут служить программы, подсистемы, базы данных, библиотеки ресурсов и т. п.
Пример структурной схемы программного комплекса для решения математических задач изображен на рис. 4.1.
Диспетчер
Блок решения
Блок вывода результатов
Рис. 4.1. Пример структурной схемы программного комплекса
1 - 7888
Как правило, для программных систем разрабатывается функциональная схема, которая дает более полное представление о проектируемом программном обеспечении с точки зрения взаимодействия его компонентов между собой и с внешней средой.
4-/-2- Функциональная схема
Функциональная схема (ГОСТ 19.701—90) — это схема взаимодействия компонентов программного обеспечения с описанием информационных потоков, состава данных в потоках и указанием используемых файлов и устройств [1]. Для изображения функциональных схем используют специальные обозначения, установленные стандартом (табл. 4.1).
Таблица 4.1. Обозначения элементов функциональных схем
|
Название блока |
Обозначение |
Назначение блока |
|||
|
Сохраненные данные |
|
|
|
Для обозначения таблиц и других структур данных, которые должны быть сохранены без уточнения типа устройства |
|
|
Оперативное запоминающее устройство |
|
|
|
Для обозначения таблиц и других структур данных, хранящихся в оперативной памяти |
|
|
Запоминающее устройство с прямым доступом |
|
( ( |
) |
Для обозначения таблиц и других структур данных, хранящихся на магнитных дисках |
|
|
Документ |
|
|
|
Для обозначения таблиц и других структур данных, выводимых на печать |
|
|
Ручной ввод |
|
|
|
Для обозначения ручного ввода данных с клавиатуры |
|
|
Дисплей |
с: |
> |
Для обозначения данных, выводимых на дисплей компьютера |
||
Функциональные схемы более информативны, чем структурные. На рис. 4.2 приведена функциональная схема программно
го комплекса, реализующего различные методы сортировки массивов.
4.7.3. Метод пошаговой детализации при составлении алгоритмов
Метод пошаговой детализации реализует нисходящий подход к программированию и предполагает пошаговую разработку алгоритма. Можно выделить следующие этапы [38]:
Создается описание программы в целом. Определяются основные логические шаги, требуемые для решения задачи, даже если пока неизвестно, как их выполнить. Эти логические шаги могут отражать различные физические способы решения или могут быть удобными групповыми именами для тех действий, выполнение которых представляется довольно смутно. Последовательности шагов, требуемых для решения задачи, записываются на обычном языке или на псевдокоде (см. разд. 3.5.1).
В общих терминах детализируется описание шагов, введенных на этапе 1. В детализированное описание может входить обозначение циклических структур, в то время как действия внутри циклов могут по-прежнему оставаться неясными. Таким образом, выполняются только общие эскизы сложных действий.
На этом и последующих уровнях в виде последовательных итераций производятся те же действия, что описаны на этапе 2.
При каждой новой итерации уточняются детали, оставшиеся неясными после предыдущих итераций, и создаются более определенные описания. По мере выполнения итераций неопределенные детали становятся все проще и проще, так что на каком-то этапе могут быть полностью описаны.
4. Разработка завершена: в модульном виде получено описание требуемой программы. Перевод этого описания в программу на конкретном языке программирования должен быть достаточно простой задачей.
Пример 4.1. Пусть требуется определить наибольшее значение в некотором наборе данных и вывести эти данные, поделенные на наибольшее значение. Скажем, если данные представляют собой последовательность чисел:
5.0, -3.24, 10.0, -1.25, 8.33,
то вывод должен выглядеть следующим образом:
0.5, -0.324, 1, -0.125, 0.833
Уровень 1:
Программа
ввести данные
найти максимум введенных данных вывести результаты Конец.
Детализация 1.1. Ввод данных можно детализировать на псевдокоде следующим образом:
Ввести данные:
определить количество чисел Цикл-пока: не все элементы введены прочитать и запомнить значение элемента
Все-цикл
Детализация 1.2. Отыскание максимума можно детализировать следующим образом: Найти максимум: выбрать в качестве максимума первый элемент данных сравнить все значения с максимумом, заменяя текущий максимум на очередное значение, если оно не превысило его
Детализация 1.3. Вывод результатов можно детализировать следующим образом:
Цикл-пока не все элементы выведены
вывести значение элемента Все-цикл
Уровень 2. Он включает в себя три детализованные выше части, из которых только детализация 1.2 требует дополнительного внимания. Ее можно детализировать на псевдокоде следующим образом:
Найти максимум: выбрать в качестве максимума первый элемент данных Цикл-пока не все элементы проверены сравнить все значения с максимумом Если текущее значение больше максимума Максимум = текущее значение Конец-если Конец-цикл
Задача в приведенном примере проста и не требует разбиения на модули.
При решении реальной задачи может потребоваться написание на псевдокоде многих уровней, чтобы довести все модули до такого состояния, при котором они окажутся готовыми для программирования.
4./.4, Структурные карты Константайна
Методика структурных карт используется на этапе проектирования ПО для того, чтобы продемонстрировать, каким образом программный продукт выполняет системные требования. При этом наиболее часто применяются две техники: структурные карты Константайна (Constantine), предназначенные для описания отношений между модулями, и структурные карты Джексона (Jackson), предназначенные для описания внутренней структуры модулей [39].
Структуру программной системы составляют модули, которые в любом языке программирования имеют следующие общие свойства:
• модуль имеет имя, по которому к нему можно обращаться как к единому фрагменту;
модуль состоит из множества операторов языка програм. мирования, записанных последовательно;
модуль может принимать и/или передавать данные как параметры в вызывающей последовательности или связывать данные через фиксированные ячейки или общие области.
Структурные карты Константайна представляют собой модель отношений между модулями программы. Узлы структурных карт соответствуют модулям и областям данных, потоки изображают межмодульные связи. На диаграмме специальными узлами изображаются циклические и условные вызовы модулей, а потоки проходят через эти специальные узлы. Потоки, изображающие межмодульные связи по данным и управлению, также изображаются на диаграмме специальными узлами, а стрелками указываются направления потоков. На рис. 4.3 приведены основные компоненты структурных карт Константайна.
Имя °—"
а б в
Рис. 4.3. Элементы структурных карт: а — модуль; б — вызов модуля; в — связь по данным; г — связь по управлению
Модуль является базовым элементом структурной карты. Различают следующие типы модулей (рис. 4.4):
модуль (рис. 4.4, я);
подсистема — детализированный модуль или программа. Может использоваться повторно любое число раз (рис. 4.4, б);
библиотека — совокупность подпрограмм, размещенных в модуле отдельно от данной системы (рис. 4.4, <?);
область данных — описывает модули, содержащие исключительно области глобальных/распределенных данных (рис. 4.4, г).
Отдельные части программной системы (программы, подпрограммы) могут вызываться последовательно, параллельно или как сопрограммы (рис. 4.5).