Но важно не только преодоление сложности, но и заложенные в этом возможности написания правильных программ. Создавать монолитные программы размером до 1000 операторов, в которых отсутствуют ошибки, хотя и можно, но достаточно сложно. С помощью методов структурного модульного программирования можно программу из 1000 операторов записать в виде 20 модулей по 50 операторов в каждом, в виде последовательно выполняющихся частей программы.
При проектировании задачи «сверху вниз» она разбивается на подзадачи, каждой из которых соответствует модуль. При этом необходимо добиться, чтобы:
1) программный модуль не содержал ошибок и не зависел от контекста, в котором он будет использоваться;
2) из модулей можно было формировать большие программы без каких-либо предварительных знаний о внутренней работе модуля.
Все модули программы должны взаимодействовать, не оказывая никаких непредусмотренных действий друг на друга.
Примерное наилучшее ограничение в размере модуля - не более 60 строк (допустимы исключения). Такая длина удобна для восприятия на странице или на дисплее.
Модули по возможности должны быть независимыми. Каждый модуль должен не зависеть: а) от источника входных данных; б) от места назначения выходных данных и в) от предыстории. В противном случае резко возрастает сложность системы. При соблюдении этих условий изменение в одной подпрограмме не повлияет на остальную часть программы. Воздействие изменения в одном модуле на другую часть программы получило название волнового эффекта. Этот эффект сводится к минимуму уменьшением связей между модулями.
Один из способов достижения этой цели - это по возможности избегать использования глобальных переменных и делать модули небольшими. Минимизация связей между модулями (модульное сцепление) производится за счет усиления связей между элементами одного модуля (модульная прочность). Модули будут независимые, если любой модуль можно заменить новым, воспринимающим те же входные данные и выдающим такие же выходные, а на программе это не отразится, это означает, что модули независимые.
Фактор сложности включает три составляющие: функциональную, распределенную и связи. Функциональная сложность обусловлена тем, что один модуль выполняет слишком много функций. Распределенная сложность - это сложность идентификации общей функции, распределенной между несколькими модулями, вследствие чего утрачивается возможность уменьшения сложности всей программы при модульном программировании. Сложность связи определяет сложность взаимодействия модулей при использовании общих данных.
Разбиение на модули должно происходить на стадии проектирования «сверху вниз». Для каждого модуля должны быть определены:
а) алгоритм решения задачи;
б) область допустимых входных значений;
в) область возможных выходных значений;
г) возможные побочные эффекты.
При разбиении программы на составные части необходимо придерживаться следующих рекомендаций:
- каждый сегмент программы должен иметь один вход в начале и один выход в конце (если другие сегменты вызываются внутри какого-либо сегмента, то в них входят через начало, выходят через конец и возвращаются обратно в вызывающий сегмент);
- главная процедура должна принимать все решения по управлению потоком данных для соответствующих обрабатывающих процедур;
- каждый сегмент должен иметь как можно меньше ветвей вычислений (чем меньше размер модуля, тем меньше ветвей, которые надо тестировать); четкость программы в целом в большой степени зависит от четкости структуры каждого модуля.
Таким образом, каждая процедура является замкнутой. Управление передается от главной процедуры к вызываемой. Вызываемая процедура может в свою очередь вызвать другую процедуру, но в любом случае возврат всегда будет происходить в процедуру, являющуюся вызывающей для данной процедуры.
Структурное кодирование - это метод написания хорошо структурированных программ, позволяющий получать программы, более удобные для тестирования, модификации и использования. Суть его состоит в том, что программы произвольного размера и сложности могут быть написаны с использованием ограниченного множества базисных структур.
Этот же прием положен в основу проектирования логических схем, где любая логическая функция может быть реализована из элементарных функций И, ИЛИ, НЕ. В булевой алгебре доказывается соответствующая теорема.
Аналогично структурное кодирование состоит в получении правильной программы из некоторых простых логических структур. Оно основано на строго доказанной теореме о структурировании, утверждающей, что любую правильную программу (с одним входом и одним выходом, без зацикливания и недостижимых команд) можно написать, используя лишь три логические структуры:
1) последовательности операторов;
2) выбора одного из двух операторов (IF THEN ELSE);
3) повторения оператора, пока выполняется некоторое условие.
Каждая структура имеет один вход и один выход. Можно получить программу любой сложности, применяя итерацию и вложение этих основных структур. При использовании только указанных структур отпадает необходимость в безусловных переходах и метках. Применение структурного программирования в значительной мере уменьшает сложность программ. Программу можно читать сверху вниз как печатный текст. Переходов, типичных для программ с оператором GOTO, здесь не будет. Важно заметить, что структурированные программы требуют более детального проектирования до программирования. Иначе невозможно остаться в пределах требуемой структуры, и мы вынуждены будем прибегать к переходам, чтобы реализовать непредусмотренные случаи. Цель структурированного программирования - обеспечить возможность чтения программы от начала до конца, следуя ее логике.
2.4. Технология структурного программирования
Структурное программирование для каждого модуля предполагает следующий цикл разработки: составление спецификации, проектирование, кодирование, тестирование. Начинается оно с составления спецификации и проектирования главного модуля. Во время проектирования каждого модуля выявляются требования к подмодулям, после чего для каждого из подмодулей может быть составлена точная функциональная спецификация.
Функциональная спецификация является внутренним (не регламентированным соответствующим стандартом) документом разработки программы и может иметь произвольную форму. Функциональная спецификация представляет собой краткое описание каждого из модулей программы и может служить формальным заданием для его программирования. Она должна быть достаточно полной и по возможности унифицированной. В ней определяются способы управления модулем, описываются его входные и выходные данные, указываются ссылки на вызываемые и вызывающие модули. Спецификация также должна содержать описание внутренних (локальных) переменных и алгоритма.
Рекомендуемый вариант оформления функциональной спецификации может содержать разделы:
1) имя-заголовок (идентификатор модуля со списком формальных параметров);
2) назначение модуля;
3) неформальное описание (краткое изложение выполняемых функций);
4) ссылки (перечни вызывающих и вызываемых модулей);
5) алгоритм (с использованием псевдокодов или схем алгоритмов);
6) данные (описание формальных параметров, глобальных и локальных переменных с указанием структуры, типов и атрибутов размерности).
Преимущество программирования «сверху вниз», прежде всего, состоит в том, что спецификации каждого модуля фиксируются в процессе проектирования и не нуждаются, как правило, в последующих изменениях. В противоположность этому при программировании «снизу вверх», когда составление спецификации, проектирование, программирование и тестирование модулей начинается с самого нижнего уровня, всегда существует опасность того, что уже разработанные модули нижнего уровня придется изменять в результате неизбежно возникающих проблем или изменений при переходе к более высоким уровням иерархии.
При структурном программировании каждый модуль проверяется индивидуально, но не изолированно. Тестирование производится «сверху вниз». На каждом этапе функции модулей более низкого уровня моделируются при тестировании данного модуля по всем его логическим ветвям.
Благодаря принципу модульности главная программа должна быть короткой и вызывать модули более низкого уровня, которые можно моделировать, создавая так называемые подыгрывающие подпрограммы. Подыгрывающая программа - очень короткая последовательность команд, которая используется как замена, пока не будет создана фактическая программа.
Подыгрывающие программы могут быть двух видов: фиктивные и замещающие модули. Фиктивные модули не выполняют никакой работы, а только возвращают управление вызывающему модулю. Замещающие модули используются для простой обработки до тех пор, пока не окажется возможным программировать более сложный модуль. Использование обращений к фиктивным подпрограммам позволяет производить компилирование, отладку и тестирование на более ранней стадии программирования. После выбора алгоритма вторым основным условием получения успешной программы является хороший проект. Всегда есть стремление быстрее начать программирование, однако без завершения проектирования это ни к чему хорошему не приводит. Кодирование - это минимальная задача. Большинство решений следует принять до того, как начнется программирование, так как потом трудно будет изменить направление работы.