7.2. МОДУЛЬ И ОСНОВНЫЕ ПРИНЦИПЫ СТРУКТУРНОГО ПОДХОДА
7.2. МОДУЛЬ И ОСНОВНЫЕ ПРИНЦИПЫ СТРУКТУРНОГО ПОДХОДА
7.2.1. Понятие модуля
Если программа разбивается на подпрограммы, то для представления результатов и аргументов часто приходится вводить новые переменные и таким образом устанавливать связь между подпрограммами. Такие переменные следует вводить и описывать на том этапе разработки, на котором они потребовались. Более того, детализация описания процесса может сопровождаться детализацией описания структуры используемых переменных. Следовательно, в языке должны быть средства для отражения иерархической структуры данных. Из сказанного видно, какую важную роль играет при пошаговой разработке программы понятие процедуры, локальности процедур и данных, структурирования данных.
Проектирование начинается с фиксации внешних спецификаций. На основании внешних спецификаций составляется описание внутреннего алгоритма программы, обязательно со структурой внутренних данных. Далее крупные функции разбиваются на подфункции до достижения подфункции размера модуля — подпрограммы (процедуры или функции) языка программирования, к которым предъявляются особые дополнительные требования.
Модуль — фундаментальное понятие и функциональный элемент технологии структурного программирования.
Модуль — это подпрограмма, но оформленная в соответствии с особыми правилами.
Правило 1. Модуль должен иметь один вход и один выход и выполнять строго однозначную функцию, которая описывается простым распространенным предложением естественного (русского) языка или даже предложением без сказуемого.
Правило 2. Модуль должен обеспечивать компиляцию, независимую от других модулей, с "забыванием" всех внутренних обозначений модулей.
Правило 3. Модуль может вызывать другие модули по их именам.
Правило 4. Хороший модуль не использует глобальные переменные для общения с другим модулем, так как потом трудно отыскать модуль, который портит данные. Если все же используются глобальные переменные, то нужно четко комментировать те модули, которые только читают, и те модули, которые могут менять данные.
Правило 5. Модуль кодируется только стандартными структурами и тщательно комментируется.
В понятие структуры программы включаются состав и описание связей всех модулей, которые реализуют самостоятельные функции программы и описание носителей данных, участвующих в обмене как между отдельными подпрограммами, так и вводимые и выводимые с/на внешних устройств.
В случае сложной, большой программы необходимо овладеть специальными приемами получения рациональной структуры программы. Рациональная структура программы обеспечивает почти двукратное сокращение объема программирования и многократное сокращение объемов и сроков тестирования, а следовательно, принципиально снижает затраты на разработку.
Подчиненность модулей удобно изображать схемой иерархии. Схема иерархии отражает только подчиненность подпрограмм, но не порядок их вызова или функционирование программы. Схема иерархии может иметь вид, показанный на рис. 7.1.
Рис 7.1. Схема иерархии простой программы
Такая схема иерархии обычно дополняется расшифровкой функций, выполняемых модулями.
До составления схемы иерархии целесообразно составить внешние спецификации программы и составить функциональные описания программы вместе с описанием переменных-носителей данных. Особое внимание надо уделить иерархии типов структурированных данных и их комментированию. Декомпозиция программы на подпрограммы производится по принципу от общего к частному, более детальному. Вообще процесс составления функционального описания и составления схемы иерархии является итерационным. Выбор наилучшего варианта является многокритериальным.
Расчленение должно обеспечивать удобный порядок ввода частей в эксплуатацию.
Реализация программы (кодирование, сборка, тестирование) должна вестись по разработанному заранее плану и начинаться с верхних модулей схемы иерархии. Недостающие модули нижних уровней заменяются заглушками, которые представляют собой простейшие подпрограммы: либо без действий; либо выводящие в файл отладки входные данные; либо возвращающие в вышестоящие модули тестовые данные (которые обычно присваиваются внутри заглушки); либо содержащие комбинацию этих действий.
Структурный подход к программированию представляет собой как методологию, так и технологию создания программ. В свое время его внедрение обеспечило повышение производительности труда программистов при написании и отладке программ; получение программ, которые состоят из модулей и пригодны для сопровождения; создание программ коллективом разработчиков; окончание создания программы в заданный срок.
Структурный подход к программированию воспринял и использует многие методы из области проектирования сложных технических систем. Среди них блочно-иерархический подход к проектированию сложных систем, стадийность создания программ, нисходящее проектирование, методы оценки и планирования.
Структурный подход рекомендует соблюдать следующие принципы при создании программного изделия:
— модульность программ;
— структурное кодирование модулей программ;
— нисходящее проектирование рациональной иерархии модулей программ;
— нисходящая реализация программы с использованием заглушек;
— осуществление планирования на всех стадиях проекта;
— сквозной структурный контроль программных комплексов в целом и составляющих их модулей.
Модульность программ характеризуется тем, что вся программа состоит из модулей. Некоторые смысловые группы модулей сосредоточиваются в отдельных файлах. Например, в отдельных файлах (Unit) могут быть сосредоточены модули текстового редактора и модули иерархического меню.
Структурное кодирование модулей программ заключается в особом оформлении их текстов. У модуля должен быть легко различимый заголовок с комментарием, поясняющим функциональное назначение модуля. Имена переменных должны быть мнемоническими. Суть переменных и порядок размещения в них информации должны быть пояснены комментариями, а код модуля закодирован с использованием типовых алгоритмических структур с использованием отступов.
Нисходящее проектирование рациональной иерархии модулей программ заключается в выделении первоначально модулей самого верхнего уровня иерархии, а затем подчиненных модулей.
Нисходящая реализация программы состоит в первичной реализации группы модулей верхних уровней, которые называются ядром программы, и далее постепенно, в соответствии с планом, реализуются модули нижних уровней. Необходимые для линковки программы, недостающие модули имитируются заглушками.
Осуществление планирования на всех стадиях проекта позволяет первоначально спланировать как состав стадий, так и продолжительность всех этапов работ. Такое планирование позволяет завершить разработку в заданный срок при заданных затратах на разработку. Далее планируются порядок и время интеграции модулей во все расширяющееся ядро. Планируются мероприятия по тестированию программы от ранних до заключительных этапов.
Сквозной структурный контроль заключается в соблюдении заранее намеченного плана тестирования, который охватывает период от разработки внешних спецификаций, далее внутренних спецификаций и их корректировку в периоде реализации вплоть до приемо-сдаточных испытаний. Составляющие программу модули тестируются как во время написания их кода, так и при автономном тестировании, инспекции их исходного кода, при тестировании сразу по подключению к ядру.
7.2.2. Понятие заглушки модуля
При структурном программировании программа в основном реализуется (собирается и тестируется) сверху вниз. Сначала из 20–30 модулей пишется ядро. Чтобы начать тестировать, недостающие модули нижних уровней заменяются заглушками. По окончании тестирования ядра, заглушки заменяются новыми готовыми модулями, но если программа еще не закончена, то для успешной ее линковки понадобятся все новые заглушки недостающих модулей. Теперь можно приступать к тестированию собранной части и т. д.
Заглушка — это макет модуля. Самая простая заглушка — это подпрограмма или функция без действий. Более сложная заглушка может выводить сообщение о том, что отработал такой-то модуль. Еще более сложные заглушки могут выводить входную информацию в какой-нибудь файл отладки. Наконец, еще более сложные заглушки выдают на выход тестовую информацию, необходимую для проверки уже реализованных модулей.
Написание заглушек — "лишняя" работа, но требуется искусство проектировщика, чтобы максимальное количество заглушек были простыми, а тестирование уже собранной части программы было бы полным.
7.2.3. Средства изменения топологии иерархии программы
В процессе нисходящего проектирования рациональной иерархии модулей программы необходимо получить оптимальную подчиненность.
Схеме иерархии можно придать любой топологический рисунок. Так, схеме иерархии, изображенной на рис. 7.2, а, можно придать вид, изображенный на рис. 7.2, б.
Фрагменты с вертикальными вызовами могут быть преобразованы в вызовы с одного уровня посредством введения дополнительного модуля, который может не выполнять никаких полезных функций с точки зрения алгоритма программы. Функция нового модуля может состоять лишь в мониторинге, т. е. вызове других модулей в определенном порядке.
Рис. 7.2. Схема иерархии одной и той же программы: а — с вертикальными вызовами; б — с горизонтальными вызовами
Фрагменты с горизонтальными вызовами на одном уровне могут быть преобразованы в вертикальные вызовы модулей разных уровней посредством введения дополнительных переменных. Эти переменные не могут быть получены путем декомпозиции функционального описания на подфункции. Эти дополнительные переменные обычно имеют тип целый или логический и называются флагами (семафорами, ключами) событий. Их смысл обычно характеризуется фразой: "В зависимости от предыстории действий, выполнить такие-то действия".
В процессе проектирования нужно сделать несколько проектных итераций, генерируя каждый раз новую схему иерархии, и сравнить эти иерархии по критериям оценки качества схемы иерархии.
7.2.4. Критерии оценки качества схемы иерархии
Проектированию структуры программы предшествует разработка внешних функциональных описаний. Функциональные описания (алгоритмы выполнения программы) для достижения их восприятия должны быть декомпозированы от общего к частному. Также они должны включать описания форм представления и объема внутренних данных.
Итак, сначала имеется первый вариант схемы иерархии, полученный путем простого членения функций программы на подфункции с указанием переменных, необходимых для размещения данных на разных шагах обработки. Скорее этот вариант не является оптимальным, и требуются проектные итерации (обычно выполняются методом "проб и ошибок") для улучшения топологии схемы.
Каждый новый вариант сравнивается с предшествующим вариантом по описанным здесь критериям. Генерация вариантов прекращается при невозможности дальнейших улучшений.
Фонд критериев оптимальности схем иерархии является необходимым подспорьем при оптимизации схем иерархии и состоит из 13 критериев.
Первый — полнота выполнения специфицированных функций.
Второй — возможность быстрого и дешевого пополнения новыми, ранее не специфицированными функциями.
Третий, вытекающий из блочно-иерархического подхода, — обозримость (понятность) для проектировщика составных частей программы.
Четвертый критерий оценки качества структуры — максимальная независимость по данным отдельных частей программы.
Пятый — возможность связывания программы с обширной многоуровневой схемой иерархии конкретным редактором связей (линковщиком). Если начинаете работать над новой программой, то очень полезно выполнить на ЭВМ ее модель в виде пустых заглушек модулей, которые не содержат никаких действий.
Шестой — достаточность оперативной памяти. Здесь рассматриваются варианты с описанием особенно структурированных статических и динамических переменных на разных уровнях схемы иерархии. Проверка удовлетворения данного критерия осуществляется расчетами с некоторыми машинными экспериментами.
Седьмой — оценка влияния топологии схемы иерархии на скорость выполнения программы при использовании оверлеев (динамической загрузки программы) и механизма подкачки страниц при разработке программы, которая целиком не может быть размещена в оперативной памяти.
Восьмой — отсутствие разных модулей, выполняющих похожие действия. В идеале — один и тот же модуль вызывается на разных уровнях схемы иерархии.
Девятый — достижение при реализации программы такого сетевого графика работы коллектива программистов, который обеспечивает равномерную загрузку коллектива по ключевым датам проекта.
Десятый — всемерное сокращение затрат на тестирование уже собранного ядра программы по ключевым датам сетевого графика реализации. Характеризуется простотой используемых заглушек и качеством тестирования по всем вычислительным маршрутам модулей. Достигается первичной реализацией сверху вниз модулей ввода и вывода программы с отсрочкой реализации остальных ветвей схемы иерархии. Обычно затраты на тестирование как по срокам, так и деньгам составляют около 60 % стоимости всего проекта. Хорошая схема иерархии сокращает затраты на тестирование по сравнению с первоначальным вариантом в 2–5 раз и более.
Одиннадцатый — использование в данном проекте как можно большего числа разработанных в предшествующих проектах модулей и библиотек при минимальном объеме изготавливаемых заново частей.
Двенадцатый — удачное распределение модулей по компилируемым файлам программы и библиотекам.
Тринадцатый — накопление уже готовых модулей и библиотек модулей для использования их во все новых разработках.
Итак, хорошая структура программы обеспечивает сокращение общего объема текстов в 2 или 3 раза, что соответственно удешевляет проект; на несколько порядков удешевляет тестирование (на тестирование обычно приходится не менее 60 % от общих затрат проекта), а также облегчает и удешевляет сопровождение программы.
7.2.5. Рекомендации по организации процесса разработки схемы иерархии
Как правило, составление внешних, затем внутренних функциональных описаний и далее структуры программы осуществляет группа от двух до семи квалифицированных программистов — системных аналитиков.
Отдельные варианты структуры программы разрабатываются до достижения возможности их сравнения. При этом используются следующие документы:
• описание алгоритма (функционирования) программы или методов решения задачи вместе с описанием данных;
• схема иерархии модулей программы с расшифровкой обозначений и функций модулей;
• паспорта модулей.
На основании этих документов готовятся описание алгоритма программы с учетом модульного деления и сетевой график реализации и тестирования программы с тестами, составленными до программирования.
Паспорт модуля — внутренний документ проекта, представляющий собой конверт с именем модуля. Внутри конверта содержатся описания: прототипы вызова самого модуля и модулей, вызываемых модулем данных; расшифровки входных и выходных переменных модуля; описания функции, выполняемой модулем; принципов реализации алгоритма модуля с описанием основных структур данных. В паспорте модуля могут находиться копии литературных источников с описаниями основных идей алгоритма. В процессе выполнения проекта паспорт модуля корректируется до технического задания на разработку этого модуля, а после реализации — до описания модуля.
Группа системных аналитиков проверяет общую однозначность этих описаний и генерирует все новые варианты схем иерархии. При реализации эта группа тестирует уже собранное ядро программы по все новым ключевым датам сетевого графика проекта. В ходе тестирования ядра программы с использованием заглушек уточняются диапазоны данных. В случае необходимости системные аналитики корректируют паспорта модулей перед программированием модулей по результатам уточнения диапазонов данных.
Самое главное в схеме иерархии — минимизация усилий по сборке и тестированию программы. При использовании заглушек можно хорошо тестировать сопряжения модулей, но не сами модули. Тестирование самих модулей потребует изощренных сложных заглушек и астрономического количества тестов. Выход — до интеграции модулей тестировать модули с использованием ведущих программ. Также рекомендуется осуществлять реализацию с некоторым нарушением принципа "сверху — вниз". Рекомендуется сначала с соблюдением принципа "сверху — вниз" реализовать ветвь схемы иерархии, отвечающей за ввод информации с проверкой ее корректности, заглушив ветви расчетов и вывода на самом верхнем уровне. Далее реализуется ветвь вывода информации и в последнюю очередь — ветвь расчетов (функционирования программы). Если функций программы много, то можно сначала реализовать модули выбора функций, заглушив модули самих функций, и далее реализовывать ветвь каждой функции последовательно с соблюдением принципа "сверху — вниз".
Схема иерархии должна включать максимальное количество модулей из других разработок. Многие модули можно использовать в других разработках, однако это не относится к вычислительным модулям, для которых из-за погрешности счета могут не подойти диапазоны данных.
Здесь очень важным является составление удобного графика работы с учетом планирования общего числа кодировщиков программ и их равномерной загрузки по срокам проекта, а также окончание проекта в назначенный срок.
Схема иерархии должна отражаться на файлы с исходными текстами программ таким образом, чтобы каждый файл содержал как можно большее количество готовых функций с общим назначением. Это желательно для облегчения их использования в последующих разработках.
Таким образом, помимо получения денег от заказчика за разработку, программист обязан повышать свой интеллектуальный капитал тоже за деньги заказчика.
Существует очень много автоматизированных систем по формированию декомпозиции схем иерархии, например HIPO, SADT, R-TRAN.