Как составить структурную схему программы

Процесс проектирования
сложного программного обеспечения
начинают с уточнения его

структуры, т. е.
определения структурных компонентов
и связей между ними. Результат уточнения
структуры может быть представлен в виде
структурной и/или функциональной схем
и описания (спецификаций) компонентов.

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

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

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

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

Структурная схема
п р о г р а м м н о г о к о м п л е к с а
демонстрирует передачу управления от
программы-диспетчера соответствующей
программе.

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

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

Функциональная
схема.
Функциональная
схема или схема данных (ГОСТ 19.701-90) –
схема взаимодействия компонентов
программного обеспечения с описанием
информационных потоков, состава данных
в потоках и указанием используемых
файлов и устройств. Для изображения
функциональных схем используют
специальные обозначения, установленные
стандартом.

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

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

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

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

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

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

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

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

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

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

• не отделять
операции инициализации и завершения
от соответствующей обработки, так как
модули инициализации и завершения имеют
плохую связность (временную) и сильное
сцепление(по управлению);

• не проектировать
слишком специализированных или слишком
универсальных модулей, так как
проектирование излишне специальных
модулей увеличивает их количество, а
проектирование излишне универсальных
модулей повышает их сложность;

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

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

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

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

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

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

  1. Структурные
    карты Константайна: назначение, типы
    вызов модулей-последовательный,
    параллельный, вызов сопрограммы; особые
    условия вызова-циклический, условный,
    однократный; диаграммы реализации
    параллельного вызова и вызова сопрограммы;
    типы связи – по данным, по управлению.

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

Различают
четыре типа вершин:

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

а-модуль б-подсистема
в-библиотека г-область данных

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


а-послед
б-паралл в-сопрограммы

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

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

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


­­——ожидание
__выполнение

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

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


а-циклич-й
б-усл-й в-однокр-й

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

Структурные
карты Константайна позволяют наглядно
представить результат декомпозиции
программы на модули и оценить ее качество,
т. е. соответствие рекомендациям
структурного программирования (сцепление
и связность).


а
– по данным б – по управлению

  1. Проектирование
    структур данных: представление данных
    в оперативной памяти – векторная
    структура, списковые структуры;
    представление данных во внешней памяти
    – способы организации данных с
    последовательным и прямым доступом.

Под проектированием
структур данных понимают разработку
их представлений в памяти.

Основными
параметрами, которые необходимо учитывать
при проектировании структур данных,
являются:

• вид хранимой
информации каждого элемента данных; •
связи элементов данных и вложенных
структур; • время хранения данных
структуры («время жизни»); • совокупность
операции над элементами данных, вложенными
структурами и структурами в целом.

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

• целые и вещественные
числа различных форматов; • символы; •
булевские значения: true и false; а также
некоторые структурные типы данных,
например: • строки; • записи; • специально
объявленные классы.

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

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

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

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


векторная
стр-ра памяти

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

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

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

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


а-лин.односв.список
б-двусв в-п-св

Однако при
использовании списковых структур
следует помнить, что:

• для хранения
указателей необходима дополнительная
память;

• поиск информации
в линейных списках осуществляется
последовательно,
а потому требует больше времени;

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

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

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

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

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

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

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

• в оперативной
памяти размещают данные, к которым
необходим быстрый доступ как для чтения,
так и для их изменения;

• во внешней —
данные, которые должны сохраняться
после завершения программы.

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

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #

На чтение 13 мин Просмотров 41 Опубликовано 11 апреля 2023 Обновлено 11 апреля 2023

Содержание

  1. Проектирование программного обеспечения
  2. Диаграмма вариантов использования (use case diagram)
  3. Диаграмма классов (class diagram)
  4. Диаграмма состояний (statechart diagram)
  5. Диаграмма последовательности (sequence diagram)
  6. Диаграмма кооперации (collaboration diagram)
  7. Диаграмма компонентов (component diagram)
  8. Диаграмма развертывания (deployment diagram)
  9. Функциональная схема программного обеспечения
  10. Функциональная схема приложения

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

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

  • Анализ предметной области и создание ТЗ (взаимодействия с заказчиком)
  • Проектирование структуры программы
  • Кодирование (набор программного кода согласно проектной документации)
  • Тестирование и отладка
  • Внедрение программы
  • Сопровождение программы
  • Утилизация

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

UML — является графическим языком для визуализации, описания параметров, конструирования и документирования различных систем (программ в частности). Диаграммы создаются с помощью специальных CASE средств, например Rational Rose (http://www-01.ibm.com/software/rational/) и Enterprise Architect (http://www.sparxsystems.com.au/). На основе технологии UML строится единая информационная модель. Приведенные выше CASE средства способны генерировать код на различных объектно-ориентированных языках, а так же обладают очень полезной функцией реверсивного инжиниринга. (Реверсивный инжиниринг позволяет создать графическую модель из имеющегося программного кода и комментариев к нему.)

Рассмотрим типы диаграмм для визуализации модели (это must have, хотя типов гораздо больше):

  • Диаграмма вариантов использования (use case diagram)
  • Диаграмма классов (class diagram)
  • Диаграмма состояний (statechart diagram)
  • Диаграмма последовательности (sequence diagram)
  • Диаграмма кооперации (collaboration diagram)
  • Диаграмма компонентов (component diagram)
  • Диаграмма развертывания (deployment diagram)

Диаграмма вариантов использования (use case diagram)

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

Диаграмма классов (class diagram)

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

Диаграмма состояний (statechart diagram)

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

Диаграмма последовательности (sequence diagram)

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

Диаграмма кооперации (collaboration diagram)

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

Диаграмма компонентов (component diagram)

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

Диаграмма развертывания (deployment diagram)

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

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

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

Источник

Функциональная схема программного обеспечения

Функциональная схема или схема данных (ГОСТ 19. 701-90) — схема взаимодействия компонентов программного обеспечения с описанием информационных потоков, состава данных в потоках и указанием используемых файлов и устройств. Для изображения функциональных схем используют специальные обозначения, установленные стандартом.

Функциональные схемы более информативны, чем структурные. На рисуноке- 12. для сравнения приведены функциональные схемы программных комплексов и систем.

Рисунок — 12. Примеры функциональных схем: а — комплекс программ, б — программная система.

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

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

Объектно-ориентированный подход и язык визуального моделирования UML в анализе требований к программному обеспечению предприятия (организации).

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

UML представляет собой объектно-ориентированный язык моделирования, обладающий следующими основными характеристиками:

· является языком визуального моделирования, который обеспечивает разработку репрезентативных моделей для организации взаимодействия заказчика и разработчика ИС, различных групп разработчиков ИС;

· содержит механизмы расширения и специализации базовых концепций языка.

· UML — это стандартная нотация визуального моделирования программных систем, принятая консорциумом Object Managing Group(OMG) осенью 1997 г., и на сегодняшний день она поддерживается многими объектно-ориентированными CASE-продуктами.

· UML включает внутренний набор средств моделирования (модулей?) («ядро»), которые сейчас приняты во многих методах и средствах моделирования. Эти концепции необходимы в большинстве прикладных задач, хотя не каждая концепция необходима в каждой части каждого приложения. Пользователям языка предоставлены возможности:

· строить модели на основе средств ядра, без использования механизмов расширения для большинства типовых приложений;

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

Дата добавления: 2018-02-28 ; просмотров: 3141 ; Мы поможем в написании вашей работы!

Источник

Функциональная схема приложения

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

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

Унифицированный язык объектно-ориентированного моделирования Unified Modeling Language (UML) явился средством достижения компромисса между структурным и функциональным подходами. Существует достаточное количество инструментальных средств, поддерживающих с помощью UML жизненный цикл информационных систем, и, одновременно, UML является достаточно гибким для настройки и поддержки специфики деятельности различных команд разработчиков.

UML представляет собой объектно-ориентированный язык моделирования, обладающий следующими основными характеристиками:

— является языком визуального моделирования, который обеспечивает разработку репрезентативных моделей для организации взаимодействия заказчика и разработчика ИС, различных групп разработчиков ИС;

— содержит механизмы расширения и специализации базовых концепций языка.

— UML – это стандартная нотация визуального моделирования программных систем, принятая консорциумом Object Managing Group(OMG) осенью 1997 г., и на сегодняшний день она поддерживается многими объектно-ориентированными CASE-продуктами.

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

Пользователям языка предоставлены возможности:

— строить модели на основе средств ядра, без использования механизмов расширения для большинства типовых приложений;

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

Стандарт UML версии 1.1, принятый в 1997 г., содержит следующий набор диаграмм:

1. Структурные (structural) модели:

— диаграммы классов (class diagrams) — для моделирования статической структуры классов системы и связей между ними;

— диаграммы компонентов (component diagrams) — для моделирования иерархии компонентов (подсистем) системы;

— диаграммы развертывания (deployment diagrams) — для моделирования физической архитектуры системы.

2. Модели поведения (behavioral):

— диаграммы вариантов использования (use case diagrams) — для моделирования функциональных требований к системе (в виде сценариев взаимодействия пользователей с системой);

— диаграммы взаимодействия (interaction diagrams):

— диаграммы последовательности (sequence diagrams) и диаграммы кооперации (collaboration diagrams) — для моделирования процесса обмена сообщениями между объектами;

— диаграммы состояний (statechart diagrams) — для моделирования поведения объектов системы при переходе из одного состояния в другое;

— диаграммы деятельности (activity diagrams) — для моделирования поведения системы в рамках различных вариантов использования, или потоков управления.

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

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

Диаграмма вариантов использования является самым общим представлением функциональных требований к системе. Для последующего проектирования системы требуются более конкретные детали, которые описываются в документе, называемом «сценарием варианта использования» или «потоком событий» (flow of events). Сценарий подробно документирует процесс взаимодействия действующего лица с системой, реализуемого в рамках варианта использования. Основной поток событий описывает нормальный ход событий (при отсутствии ошибок). Альтернативные потоки описывают отклонения от нормального хода событий (ошибочные ситуации) и их обработку.

Достоинства модели вариантов использования заключаются в том, что она:

— определяет пользователей и границы системы;

— определяет системный интерфейс;

— удобна для общения пользователей с разработчиками;

— используется для написания тестов;

— является основой для написания пользовательской документации;

— хорошо вписывается в любые методы проектирования (как объектно-ориентированные, так и структурные).

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

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

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

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

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

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

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

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

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

Ответьте на контрольные вопросы (ответы пришлите преподавателю):

1. Какие существуют виды схем, описывающих структуру и процесс работы приложения?

2. Что представляет из себя структурная схема?

3. Приведите примеры структурных компонентов программного обеспечения.

4. Какие существуют методики структурного проектирования программного обеспечения?

5. Что такое функциональная схема?

6. В чём преимущество функциональной схемы по сравнению со структурной?

7. При помощи какого языка можно составить описание, моделирующее программную систему?

8. Какие наборы диаграмм используются в UML?

Источник

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

К моему удивлению оказалось, что на вроде бы актуальный вопрос: «Как построить хорошую/красивую архитектуру ПО?» — не так легко найти ответ. Не смотря на то, что есть много книг и статей, посвященных и шаблонам проектирования и принципам проектирования, например, принципам SOLID (кратко описаны тут, подробно и с примерами можно посмотреть тут, тут и тут) и тому, как правильно оформлять код, все равно оставалось чувство, что чего-то важного не хватает. Это было похоже на то, как если бы вам дали множество замечательных и полезных инструментов, но забыли главное — объяснить, а как же «проектировать табуретку».

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

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

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

Критерии хорошей архитектуры

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

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

Гибкость системы. Любое приложение приходится менять со временем — изменяются требования, добавляются новые. Чем быстрее и удобнее можно внести изменения в существующий функционал, чем меньше проблем и ошибок это вызовет — тем гибче и конкурентоспособнее система. Поэтому в процессе разработки старайтесь оценивать то, что получается, на предмет того, как вам это потом, возможно, придется менять. Спросите у себя: «А что будет, если текущее архитектурное решение окажется неверным?», «Какое количество кода подвергнется при этом изменениям?». Изменение одного фрагмента системы не должно влиять на ее другие фрагменты. По возможности, архитектурные решения не должны «вырубаться в камне», и последствия архитектурных ошибок должны быть в разумной степени ограничены. «Хорошая архитектура позволяет ОТКЛАДЫВАТЬ принятие ключевых решений» (Боб Мартин) и минимизирует «цену» ошибок.

Расширяемость системы. Возможность добавлять в систему новые сущности и функции, не нарушая ее основной структуры. На начальном этапе в систему имеет смысл закладывать лишь основной и самый необходимый функционал (принцип YAGNI — you ain’t gonna need it, «Вам это не понадобится») Но при этом архитектура должна позволять легко наращивать дополнительный функционал по мере необходимости. Причем так, чтобы внесение наиболее вероятных изменений требовало наименьших усилии.

Требование, чтобы архитектура системы обладала гибкостью и расширяемостью (то есть была способна к изменениям и эволюции) является настолько важным, что оно даже сформулировано в виде отдельного принципа — «Принципа открытости/закрытости» (Open-Closed Principle — второй из пяти принципов SOLID): Программные сущности (классы, модули, функции и т.п.) должны быть открытыми для расширения, но закрытыми для модификации.

Иными словами: Должна быть возможность расширить/изменить поведение системы без изменения/переписывания уже существующих частей системы.

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

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

Тестируемость. Код, который легче тестировать, будет содержать меньше ошибок и надежнее работать. Но тесты не только улучшают качество кода. Многие разработчики приходят к выводу, что требование «хорошей тестируемости» является также направляющей силой, автоматически ведущей к хорошему дизайну, и одновременно одним из важнейших критериев, позволяющих оценить его качество: «Используйте принцип «тестируемости» класса в качестве «лакмусовой бумажки» хорошего дизайна класса. Даже если вы не напишите ни строчки тестового кода, ответ на этот вопрос в 90% случаев поможет понять, насколько все «хорошо» или «плохо» с его дизайном» (Идеальная архитектура).

Существует целая методология разработки программ на основе тестов, которая так и называется — Разработка через тестирование (Test-Driven Development, TDD).

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

Хорошо структурированный, читаемый и понятный код. Сопровождаемость. Над программой, как правило, работает множество людей — одни уходят, приходят новые. После написания сопровождать программу тоже, как правило, приходится людям, не участвовавшем в ее разработке. Поэтому хорошая архитектура должна давать возможность относительно легко и быстро разобраться в системе новым людям. Проект должен быть хорошо структурирован, не содержать дублирования, иметь хорошо оформленный код и желательно документацию. И по возможности в системе лучше применять стандартные, общепринятые решения привычные для программистов. Чем экзотичнее система, тем сложнее ее понять другим (Принцип наименьшего удивления — Principle of least astonishment. Обычно, он используется в отношении пользовательского интерфейса, но применим и к написанию кода).

Ну и для полноты критерии плохого дизайна:

  1. Его тяжело изменить, поскольку любое изменение влияет на слишком большое количество других частей системы. (Жесткость, Rigidity).
  2. При внесении изменений неожиданно ломаются другие части системы. (Хрупкость, Fragility).
  3. Код тяжело использовать повторно в другом приложении, поскольку его слишком тяжело «выпутать» из текущего приложения. (Неподвижность, Immobility).

Модульная архитектура. Декомпозиция как основа

software architecture

Не смотря на разнообразие критериев, все же главной при разработке больших систем считается задача снижения сложности. А для снижения сложности ничего, кроме деления на части, пока не придумано. Иногда это называют принципом «разделяй и властвуй» (divide et impera), но по сути речь идет об иерархической декомпозиции. Сложная система должна строится из небольшого количества более простых подсистем, каждая из которых, в свою очередь, строится из частей меньшего размера, и т.д., до тех пор, пока самые небольшие части не будут достаточно просты для непосредственного понимания и создания.

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

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

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

  • Масштабируемость (Scalability)
    возможность расширять систему и увеличивать ее производительность, за счет добавления новых модулей.
  • Ремонтопригодность (Maintainability)
    изменение одного модуля не требует изменения других модулей
  • Заменимость модулей (Swappability)
    модуль легко заменить на другой
  • Возможность тестирования (Unit Testing)
    модуль можно отсоединить от всех остальных и протестировать / починить
  • Переиспользование (Reusability)
    модуль может быть переиспользован в других программах и другом окружении
  • Сопровождаемость (Maintenance)
    разбитую на модули программу легче понимать и сопровождать

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

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

«Архитектура — это организация системы, воплощенная в ее компонентах, их отношениях между собой и с окружением.
Система — это набор компонентов, объединенных для выполнения определенной функции.«

Таким образом, хорошая архитектура это, прежде всего, модульная/блочная архитектура. Чтобы получить хорошую архитектуру надо знать, как правильно делать декомпозицию системы. А значит, необходимо понимать — какая декомпозиция считается «правильной» и каким образом ее лучше проводить?

«Правильная» декомпозиция

1. Иерархическая

Не стоит сходу рубить приложение на сотни классов. Как уже говорилось, декомпозицию надо проводить иерархически — сначала систему разбивают на крупные функциональные модули/подсистемы, описывающие ее работу в самом общем виде. Затем, полученные модули, анализируются более детально и, в свою очередь, делятся на под-модули либо на объекты.

Перед тем как выделять объекты разделите систему на основные смысловые блоки хотя бы мысленно. Для небольших приложений двух уровней иерархии часто оказывается вполне достаточно — система вначале делится на подсистемы/пакеты, а пакеты делятся на классы.

software decomposition

Эта мысль, при всей своей очевидности, не так банальна как кажется. Например, в чем заключается суть такого распространенного «архитектурного шаблона» как Модель-Вид-Контроллер (MVC)? Всего навсего в отделении представления от бизнес-логики, то есть в том, что любое пользовательское приложение вначале делится на два модуля — один из которых отвечает за реализацию собственно самой бизнес логики (Модель), а второй — за взаимодействие с пользователем (Пользовательский Интерфейс или Представление). Затем, для того чтобы эти модули могли разрабатываться независимо, связь между ними ослабляется с помощью паттерна «Наблюдатель» (подробно о способах ослабления связей будет рассказано дальше) и мы фактически получаем один из самых мощных и востребованных «шаблонов», которые используются в настоящее время.

Типичными модулями первого уровня (полученными в результате первого деления системы на наиболее крупные составные части) как раз и являются — «бизнес-логика», «пользовательский интерфейс», «доступ к БД», «связь с конкретным оборудованием или ОС».

Для обозримости на каждом иерархическом уровне рекомендуют выделять от 2 до 7 модулей.

2. Функциональная

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

Модуль = Функция + Данные, необходимые для ее выполнения.

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

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

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

3. High Cohesion + Low Coupling
Самым же главным критерием качества декомпозиции является то, насколько модули сфокусированы на решение своих задач и независимы. Обычно это формулируют следующим образом: «Модули, полученные в результате декомпозиции, должны быть максимально сопряженны внутри (high internal cohesion) и минимально связанны друг с другом (low external coupling).«

  • High Cohesion, высокая сопряженность или «сплоченность» внутри модуля, говорит о том, модуль сфокусирован на решении одной узкой проблемы, а не занимается выполнением разнородных функций или несвязанных между собой обязанностей. (Сопряженностьcohesion, характеризует степень, в которой задачи, выполняемые модулем, связаны друг с другом )

    Следствием High Cohesion является принцип единственной ответственности (Single Responsibility Principle — первый из пяти принципов SOLID), согласно которому любой объект/модуль должен иметь лишь одну обязанность и соответственно не должно быть больше одной причины для его изменения.

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

    Это значит, что при правильном проектировании, при изменении одного модуля, не придется править другие или эти изменения будут минимальными. Чем слабее связанность, тем легче писать/понимать/расширять/чинить программу.

Считается, что хорошо спроектированные модули должны обладать следующими свойствами:

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

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

Делая декомпозицию системы желательно проверять ее качество задавая себе вопросы: «Какую функцию выполняет каждый модуль?«, “Насколько модули легко тестировать?”, “Возможно ли использовать модули самостоятельно или в другом окружении?”, “Как сильно изменения в одном модуле отразятся на остальных?

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

Как ослаблять связанность между модулями

Для наглядности, картинка из неплохой статьи «Decoupling of Object-Oriented Systems», иллюстрирующая основные моменты, о которых будет идти речь.

Decoupling architecture

1. Интерфейсы. Фасад

Главным, что позволяет уменьшать связанность системы, являются конечно же Интерфейсы (и стоящий за ними принцип Инкапсуляция + Абстракция + Полиморфизм):

  • Модули должны быть друг для друга «черными ящиками» (инкапсуляция). Это означает, что один модуль не должен «лезть» внутрь другого модуля и что либо знать о его внутренней структуре. Объекты одной подсистемы не должны обращаться напрямую к объектам другой подсистемы
  • Модули/подсистемы должны взаимодействовать друг с другом лишь посредством интерфейсов (то есть, абстракций, не зависящих от деталей реализации) Соответственно каждый модуль должен иметь четко определенный интерфейс или интерфейсы для взаимодействия с другими модулями.

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

Благодаря интерфейсам и полиморфизму, как раз и достигается возможность модифицировать и расширять код, без изменения того, что уже написано (Open-Closed Principle). До тех пор, пока взаимодействие модулей описано исключительно в виде интерфейсов, и не завязано на конкретные реализации, мы имеем возможность абсолютно «безболезненно» для системы заменить один модуль на любой другой, реализующий тот же самый интерфейс, а также добавить новый и тем самым расширить функциональность. Это как в конструкторе или «плагинной архитектуре» (plugin architecture) — интерфейс служит своего рода коннектором, куда может быть подключен любой модуль с подходящим разъемом. Гибкость конструктора обеспечивается тем, что мы можем просто заменить одни модули/«детали» на другие, с такими же разъемами (с тем же интерфейсом), а также добавить сколько угодно новых деталей (при этом уже существующие детали никак не изменяются и не переделываются). Подробнее про Open-Closed Principle и про то, как он может быть реализован можно почитать тут + хорошая статья на английском.

Интерфейсы позволяют строить систему более высокого уровня, рассматривая каждую подсистему как единое целое и игнорируя ее внутреннее устройство. Они дают возможность модулям взаимодействовать и при этом ничего не знать о внутренней структуре друг друга, тем самым в полной мере реализуя принцип минимального знания, являющейся основой слабой связанности. Причем, чем в более общей/абстрактной форме определены интерфейсы и чем меньше ограничений они накладывают на взаимодействие, тем гибче система. Отсюда фактически следует еще один из принципов SOLID — Принцип разделения интерфейса (Interface Segregation Principle), который выступает против «толстых интерфейсов» и говорит, что большие, объемные интерфейсы надо разбивать на более маленькие и специфические, чтобы клиенты маленьких интерфейсов (зависящие модули) знали только о методах, которые необходимы им в работе. Формулируется он следующим образом: «Клиенты не должны зависеть от методов (знать о методах), которые они не используют» или “Много специализированных интерфейсов лучше, чем один универсальный”.

Итак, когда взаимодействие и зависимости модулей описываются лишь с помощью интерфейсов, те есть абстракций, без использования знаний об их внутреннем устройстве и структуре, то фактически тем самым реализуется инкапсуляция, плюс мы имеем возможность расширять/изменять поведения системы за счет добавления и использования различных реализаций, то есть за счет полиморфизма. Из этого следует, что концепция интерфейсов включает в себя и в некотором смысле обобщает почти все основные принципы ООП — Инкапсуляцию, Абстракцию, Полиморфизм. Но тут возникает один вопрос. Когда проектирование идет не на уровне объектов, которые сами же и реализуют соответствующие интерфейсы, а на уровне модулей, то что является реализацией интерфейса модуля? Ответ: если говорить языком шаблонов, то как вариант, за реализацию интерфейса модуля может отвечать специальный объект — Фасад.

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

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

Facade Decoupling architecture

Замечание: Хотя большинство программистов понимают важность интерфейсов при проектировании классов (объектов), складывается впечатление, что идея необходимости использовать интерфейсы также и на уровне модулей только зарождается. Мне встретилось очень мало статей и проектов, где интерфейсы бы применялись для ослабления связанности между модулями/слоями и соответственно использовался бы паттерн «Фасад». Кто, например, видел «Фасад» на схемах уже упоминавшегося «архитектурного шаблона» Модель-Вид-Контроллер, или хотя бы слышал его упоминание среди паттернов, входящих в состав MVC (наряду с Observer и Composite)? А ведь он там должен быть, поскольку Модель это не класс, это модуль, причем центральный. И у создателя MVC Трюгве Реенскауга он, конечно же, был (смотрим «The Model-View-Controller (MVC ). Its Past and Present», только учитываем, что это писалось в 1973 году и то, что мы сейчас называем Представлением — Presentaition/UI тогда называлось Editior). Странным образом «Фасад» потерялся на многие годы и вновь обнаружить его мне удалось лишь недавно, в основном, в обобщенном варианте MVC от Microsoft («Microsoft Application Architecture Guide»). Вот соответствующие слайды:

Facade MVC Model-View-Controller Decoupling

А разработчикам, к сожалению, приходится заново «переоткрывать» идею, что к объектам Модели, отвечающей за бизнес-логику приложения, нужно обращаться не напрямую а через интерфейс, то есть «Фасад», как например, в этой статье, откуда для полноты картины взят еще один слайд:

Facade MVC Model-View-Controller Decoupling

2. Dependency Inversion. Корректное создание и получение зависимостей

Формально, требование, чтобы модули не содержали ссылок на конкретные реализации, а все зависимости и взаимодействие между ними строились исключительно на основе абстракций, то есть интерфейсов, выражается принципом Инвертирования зависимостей (Dependency Inversion — последний из пяти принципов SOLID):

  • Модули верхнего уровня не должны зависеть от модулей нижнего уровня. И те, и другие должны зависеть от абстракций.
  • Абстракции не должны зависеть от деталей. Реализация должна зависеть от абстракции.

У этого принципа не самая очевидная формулировка, но суть его, как и было сказано, выражается правилом: «Все зависимости должны быть в виде интерфейсов». Подробно и очень хорошо принцип инвертирования зависимостей разбирается в статье Модульный дизайн или «что такое DIP, SRP, IoC, DI и т.п.». Статья из разряда must-read, лучшее, что доводилось читать по архитектуре ПО.

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

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

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

Ну а теперь разберем подробнее, как это делается на практике и каким образом модули могут корректно создавать и получать свои «зависимости», не нарушая принципа Dependency Inversion.

Итак, при проектировании модуля должны быть определены следующие ключевые вещи:

  • что модуль делает, какую функцию выполняет
  • что модулю нужно от его окружения, то есть с какими объектами/модулями ему придется иметь дело и
  • как он это будет получать

Крайне важно то, как модуль получает ссылки на объекты, которые он использует в своей работе. И тут возможны следующие варианты:

  1. Модуль сам создает объекты необходимые ему для работы.

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

    «Суть заключается в том, что вместо непосредственного инстанцирования объекта через new, мы предоставляем классу-клиенту некоторый интерфейс для создания объектов. Поскольку такой интерфейс при правильном дизайне всегда может быть переопределён, мы получаем определённую гибкость при использовании низкоуровневых модулей в модулях высокого уровня».

    В случаях, когда нужно создавать группы или семейства взаимосвязанных объектов, вместо Фабричного Метода используется Абстрактная Фабрика (Abstract factory).

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

    Этот подход реализуется шаблоном Локатор Сервисов (Service Locator), основная идея которого заключается в том, что в программе имеется объект, знающий, как получить все зависимости (сервисы), которые могут потребоваться.

    Главное отличие от фабрик в том, что Service Locator не создаёт объекты, а фактически уже содержит в себе инстанцированные объекты (или знает где/как их получить, а если и создает, то только один раз при первом обращении). Фабрика при каждом обращении создает новый объект, который вы получаете в полную собственность и можете делать с ним что хотите. Локатор же сервисов выдает ссылки на одни и те же, уже существующие объекты. Поэтому с объектами, выданными Service Locator, нужно быть очень осторожным, так как одновременно с вами ими может пользоваться кто-то еще.

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

    Вообще говоря, Service Locator иногда называют антипаттерном и не рекомендуют использовать (главным образом потому, что он создает неявные связности и дает лишь видимость хорошего дизайна). Подробно можно почитать у Марка Симана:
    Service Locator is an Anti-Pattern
    Abstract Factory or Service Locator?

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

    Это так и называется — Внедрение Зависимостей (Dependency Injection). Обычно требуемые зависимости передаются либо в качестве параметров конструктора (Constructor Injection), либо через методы класса (Setter injection).

    Такой подход инвертирует процесс создания зависимости — вместо самого модуля создание зависимостей контролирует кто-то извне. Модуль из активного элемента, становится пассивным — не он делает, а для него делают. Такое изменение направления действия называется Инверсия Контроля (Inversion of Control), или Принцип Голливуда — «Не звоните нам, мы сами вам позвоним».

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

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

Более подробно и с примерами о способах создания и получения зависимостей можно почитать, например, в этой статье (только надо иметь ввиду, что хотя автор пишет о Dependency Inversion, он использует термин Inversion of Control; возможно потому, что в русской википедии содержится ошибка и этим терминам даны одинаковые определения). А принцип Inversion of Control (вместе с Dependency Injection и Service Locator) детально разбирается Мартином Фаулером и есть переводы обеих его статей: «Inversion of Control Containers and the Dependency Injection pattern» и “Inversion of Control”.

Не будет преувеличением сказать, что использование интерфейсов для описания зависимостей между модулями (Dependency Inversion) + корректное создание и внедрение этих зависимостей (прежде всего Dependency Injection) являются центральными/базовыми техниками для снижения связанности. Они служат тем фундаментом, на котором вообще держится слабая связанность кода, его гибкость, устойчивость к изменениям, переиспользование, и без которого все остальные техники имеют мало смысла. Но, если с фундаментом все в порядке, то знание дополнительных приемов может быть очень даже полезным. Поэтому продолжим.

3. Замена прямых зависимостей на обмен сообщениями

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

Связь модулей через обмен сообщениями является гораздо более слабой, чем прямая зависимость и реализуется она чаще всего с помощью следующих шаблонов:

  • Наблюдатель (Observer). Применяется в случае зависимости «один-ко-многим», когда множество модулей зависят от состояния одного — основного. Использует механизм рассылки, который заключается в том, что основной модуль просто осуществляет рассылку одинаковых сообщений всем своим подписчикам, а модули, заинтересованные в этой информации, реализуют интерфейс «подписчика» и подписываются на рассылку. Находит широкое применение в системах с пользовательским интерфейсом, позволяя ядру приложения (модели) оставаться независимым и при этом информировать связанные с ним интерфейсы о том что произошли какие-то изменения и нужно обновиться.

    Организация взаимодействия посредством рассылки сообщений имеет дополнительный «бонус» — необязательность существования «подписчиков» на «опубликованные» (т.е. рассылаемые) сообщения. Качественно спроектированная подобная система допускает добавление/удаление модулей в любое время.

  • Посредник (Mediator). Применяется, когда между модулями имеется зависимость «многие ко многим. Медиатор выступает в качестве посредника в общении между модулями, действуя как центр связи и избавляет модули от необходимости явно ссылаться друг на друга. В результате взаимодействие модулей друг с другом («все со всеми») заменяется взаимодействием модулей лишь с посредником («один со всеми»). Говорят, что посредник инкапсулирует взаимодействие между множеством модулей.

    Mediator Посредник

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

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

4. Замена прямых зависимостей на синхронизацию через общее ядро

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

Ядро-посредник может как знать о модулях-клиентах и управлять ими (пример — архитектура apache ), так и может быть полностью, или почти полностью, независимым и ничего о клиентах не знать. В сущности именно этот подход реализован в «шаблоне» Модель-Вид-Контроллер (MVC), где с одной Моделью (являющейся ядром приложение и общим хранилищем данных) могут взаимодействовать множество Пользовательских Интерфейсов, которые работают синхронно и при этом не знают друг о друге, а Модель не знает о них. Ничто не мешает подключить к общей модели и синхронизировать таким образом не только интерфейсы, но и другие вспомогательные модули.

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

5. Закон Деметры (law of Demeter)

Закон Деметры запрещает использование неявных зависимостей: «Объект A не должен иметь возможность получить непосредственный доступ к объекту C, если у объекта A есть доступ к объекту B и у объекта B есть доступ к объекту C«. Java-пример.

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

Закон Деметры реализует уже упоминавшийся «принцип минимального знания», являющейся основой слабой связанности и заключающийся в том, что объект/модуль должен знать как можно меньше деталей о структуре и свойствах других объектов/модулей и вообще чего угодно, включая собственные подкомпоненты. Аналогия из жизни: Если Вы хотите, чтобы собака побежала, глупо командовать ее лапами, лучше отдать команду собаке, а она уже разберётся со своими лапами сама.

6. Композиция вместо наследования

Одну из самых сильных связей между объектами дает наследование, поэтому, по возможности, его следует избегать и заменять композицией. Эта тема хорошо раскрыта в статье Герба Саттера — «Предпочитайте композицию наследованию».

Могу только посоветовать в данном контексте обратить внимание на шаблон Делегат (Delegation/Delegate) и пришедший из игр шаблон Компонет (Component), который подробно описан в книге «Game Programming Patterns» (соответствующая глава из этой книги на английском и ее перевод).

Что почитать

Статьи в интернете:

  • Design patterns for decoupling (небольшая полезная глава из UML Tutorial);
  • Немного про архитектуру;
  • Patterns For Large-Scale JavaScript Application Architecture;
  • Слабое связывание компонентов в JavaScript;
  • Как писать тестируемый код — статья из которой хорошо видно что критерии тестируемости кода и хорошего дизайна совпадают;
  • сайт Мартина Фаулера.

Замечательный ресурс — Архитектура приложений с открытым исходным кодом, где «авторы четырех дюжин приложений с открытым исходным кодом рассказывают о структуре созданных ими программ и о том, как эти программы создавались. Каковы их основные компоненты? Как они взаимодействуют? И что открыли для себя их создатели в процессе разработки? В ответах на эти вопросы авторы статей, собранных в данных книгах, дают вам уникальную возможность проникнуть в то, как они творят«. Одна из статей полностью была опубликована на хабре — «Масштабируемая веб-архитектура и распределенные системы».

Интересные решения и идеи можно найти в материалах, посвященных разработке игр. Game Programming Patterns — большой сайт с подробным описанием многих шаблонов и примерами их применения к задаче создания игр (оказывается, есть уже его перевод — «Шаблоны игрового программирования», спасибо strannik_k за ссылку). Возможно будет полезна также статья «Гибкая и масштабируемая архитектура для компьютерных игр» (и ее оригинал. Нужно только иметь ввиду что автор почему-то композицию называет шаблоном «Наблюдатель»).

По поводу паттернов проектирования:

  • Интересная «Мысль про паттерны проектирования»;
  • Удобный сайт с краткими описаниями и схемами всех шаблонов проектирования — «Обзор паттернов проектирования»;
  • сайт на базе книги банды четырех, где все шаблоны описаны очень подробно — «Шаблоны [Patterns] проектирования».

Есть еще принципы/паттерны GRASP, описанные Крэгом Лэрманом в книге «Применение UML 2.0 и шаблонов проектирования», но они больше запутывают чем проясняют. Краткий обзор и обсуждение на хабре (самое ценное в комментариях).

Ну и конечно же книги:

  • Мартин Фаулер «Архитектура корпоративных программных приложений»;
  • Стив Макконнелл «Совершенный код»;
  • Шаблоны проектирования от банды четырех (Gang of Four, GoF) — «Приемы объектно-ориентированного проектирования. Паттерны проектирования».

Структурная схема — программа

Cтраница 1

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

Структурная схема программы и описание ее работы для общего случая приведены в гл.
 [2]

Структурная схема программы обмена с технологической ЭВМ представлена на рис. 5.35. Запуск ее производится от распределительной программы с частотой, определяемой временем заполнения информационного блока по самому скоростному каналу. Вначале программа формирует массив управляющей информации для передачи ее в технологическую ЭВМ. Массив содержит сведения о количестве блоков, готовых для передачи в технологическую ЭВМ, и блоках, требуемых для передачи из нее в ОВ.
 [3]

На рис. 7.33 приведена структурная схема программы расчета переходного электромеханического процесса.
 [5]

На рис. 11.16 приведена структурная схема программы измерительного преобразователя активной мощности ( ИПАМ) с одной операцией перемножения и цифровой фильтрацией выходного сигнала. Его АЧХ в отличие от показанной на рис. 4.12, 5 имеет первый нуль на удвоенной промышленной частоте.
 [6]

На рис. 9.33 приведена структурная схема программы микропроцессорного быстродействующего элемента сравнения квадратов амплитуд непрерывного действия. При сравнении квадрата амплитуды одной синусоидальной величины ( напряжения или тока) с заданным значением цифровой код квадрата амплитуды второй величины заменяется постоянным заданным числом. Соответственно структурная схема ( см. рис. 9.32, а) и программа микропроцессорного элемента сравнения ( рис. 9.33) упрощаются.
 [7]

На рис. 12.31 приведена структурная схема программы микропроцессорного быстродействующего элемента сравнения квадратов амплитуд непрерывного действия. При сравнении квадрата амплитуды одной синусоидальной величины ( напряжения или тока) с заданным значением цифровой код квадрата амплитуды второй величины заменяется постоянным заданным числом. Соответственно структурная схема ( см. рис, 12.30, а) и программа микропроцессорного элемента сравнения ( рис. 12.31) упрощаются.
 [8]

На рис. 11.6 6 приведена структурная схема программы такого микропроцессорного измерительного преобразователя квадрата амплитуды напряжения.
 [10]

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

При всех видах проверок кроме листинга программы обычно используются спецификации на модуль; структурная схема программы; основные формулы, по которым проводятся вычисления.
 [12]

Реализация смешанного моделирования на двух уровнях ( схемном и функциональном) нашла отражение в структурной схеме программы ПАКЛС, представленной на рис. 7.17, причем в зависимости от объекта исследования и решаемой задачи отдельные части схемы могут описываться либо на одном, либо на другом уровне.
 [13]

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

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

Страницы:  

   1

   2

   3

Понравилась статья? Поделить с друзьями:
  • Как можно найти это музыка
  • Как найти эксцентриситет вершины графа
  • Этот регистрационный номер уже используется властелин колец 2 как исправить
  • Как найти текст который ищешь
  • Как найти вязкость среды