• 7.1. ПОНЯТИЕ СТРУКТУРЫ ПРОГРАММЫ
  • 7.2. МОДУЛЬ И ОСНОВНЫЕ ПРИНЦИПЫ СТРУКТУРНОГО ПОДХОДА
  • 7.3. РЕТРОСПЕКТИВНОЕ ПРОЕКТИРОВАНИЕ ДЕМОНСТРАЦИОННОЙ ПРОГРАММЫ MCALC ФИРМЫ "BORLAND INC."
  • Глава 7

    ТЕХНОЛОГИЯ СТРУКТУРНОГО ПРОГРАММИРОВАНИЯ

    7.1. ПОНЯТИЕ СТРУКТУРЫ ПРОГРАММЫ

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

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

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

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

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

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

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

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

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

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

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

    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.

    7.3. РЕТРОСПЕКТИВНОЕ ПРОЕКТИРОВАНИЕ ДЕМОНСТРАЦИОННОЙ ПРОГРАММЫ MCALC ФИРМЫ "BORLAND INC."

    Согласно ретроспективно проведенного системного анализа (см. гл. 2), фирма "Borland Inc." приняла решение о реализации демонстрационного примера программы электронной таблицы. Вполне возможно сгенерировать множество вариантов реализации электронной таблицы, начиная от варианта со всеми клетками в одном окне и кончая, например, вариантом Excel. Однако фирма "Borland Inc." избрала вариант с прокруткой информации клеток в окне, изменением адресов клеток при вставках строк и столбцов, а также при их удалении. В проект введены требования разработки некоммерческого изделия. Размер таблицы ограничен 100–100 клетками. В программе отсутствует функция копирования клеток. Избранная сложность реализуемого варианта соответствует многофайловому проекту. Программа имеет функции поддержки вывода на дисплей, ввода с клавиатуры; в ней реализован интерпретатор формул с математическими функциями; для сохранения информации таблицы используется файл сложной организации, рассмотренный в гл. 3. Все это позволяет продемонстрировать возможности компилятора.

    Программа Mcalc 1985–1988 гг. (Turbo Pascal 5.0) состоит из следующих файлов:

    • mcalc.pas — файл основной программы;

    • mcvars.pas — файл глобальных описаний;

    • mcdisply.pas — файл подпрограмм работы с дисплеем;

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

    • mcinput.pas — файл подпрограмм ввода данных с клавиатуры;

    • mcommand.pas — файл подпрограмм, обслуживающих систему меню и действий, выбранных посредством меню;

    • mcutil.pas — файл вспомогательных подпрограмм;

    • mcparser.pas — файл интерпретатора арифметических выражений формул клеток.

    Все файлы закодированы с соблюдением развиваемых стандартов оформления. Так, в файлах mcdisply.pas, mcinput.pas описания прототипов подпрограмм выполнены с использованием более раннего синтаксиса языка программирования, что говорит об их заимствовании из программ, написанных ранее; при этом можно выявить их небольшое модифицирование (рефакторинг).

    Хотя фирма "Borland Inc." занимается разработкой компиляторов, файл mcparser.pas также является заимствованным из UNIX YACC utility и лишь частично модифицированным. Остальные файлы являются оригинальными.

    Ассемблерный файл mcmvsmem.asm является искусственно добавленным. Цель его добавления — демонстрация возможности использования ассемблерных вставок. Содержащиеся в нем алгоритмы вполне можно было бы реализовать на языке Pascal. Более того, можно было бы вообще обойтись без реализованных в нем подпрограмм, правда, при этом были бы видны некоторые задержки вывода информации на экран.

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

    Рассмотрим организацию файла mcvars.pas, содержащего в основном описание структуры внутренних данных программы. Файл содержит описания в секции interface. Секция implementation пустая.

    В начале файла содержится код, который в зависимости от наличия сопроцессора транслируется в одном из двух вариантов:

    {$IFOPT N+}

    {Есть встроенный сопроцессор}

    type

    Real = Extended; {Замена типа Real на Extended}

    const

    EXPLIMIT = 11356; {Предельное значение аргумента
    экспоненты}

    SQRLIMIT = 1Е2466;{Предельное значение аргумента
    SQRT}

    ….

    {$ELSE}

    const 

    {Тип Real не переопределен}

    EXPLIMIT = 88; {Предельное значение аргумента
    экспоненты}

    SQRLIMIT = 1E18; {Предельное значение аргумента
    SQRT}

    ….

    {$ENDIF}

    Описания констант содержат следующие блоки:

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

    — блок парных строк текстов меню и "горячих" клавиш выбора тем меню;

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

    MAXCOLS = 100; { Maximum is 702 } {Размер таблицы}

    MAXROWS = 100;

    MINCOLWIDTH = 3; {Минимальная ширина столбца}

    MAXCOLWIDTH = 77; {Максимальная ширина столбца}

    ….

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

    — основные константы, мнемоника имен которых облегчает восприятие текстов программы

    HIGHLIGHT = True; {Подсвеченная текущая клетка}

    NOHIGHLIGHT = False; {Неподсвеченная клетка}

    {Атрибут содержимого клетки}

    ТХТ = 0;

    VALUE = 1;

    FORMULA = 2;

    ….

    {Разрешенные буквы}

    LETTERS: set of Char = ['A'..'Z', 'a'..'z'];

    — коды управляющих клавиш клавиатуры.

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

    Далее следуют описания типа информации содержимого табличной клетки и типа указателя на клетку:

    type

    CellRec = record

    Error: Boolean;

    case Attrib: Byte of

    TXT: (T: IString);

    VALUE: (Value: Real);

    FORMULA: (Fvalue: Real;

    Formula: IString);

    end;

    CellPtr = ^CellRec; {Указатель на клетку}

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

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

    MAXCOLS*MAXROWS*[SizeOf(Istring)+SizeOf(Extened)] = 100*100*[80+10] = 900K

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

    Cell: array [1..MAXCOLS, 1..MAXROWS] of CellPtr;

    CurCell: CellPtr; {Указатель на текущую клетку}

    Format: array [1..MAXCOLS, 1..MAXROWS] of Byte;

    LeftCol, RightCol, TopRow, BottomRow,

    {Позиционирование}

    CurCol, CurRow, LastCol, LastRow: Word;

    ….

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

    Сравните этот проект структуры данных с проектом структуры данных электронной таблицы, представленной в гл. 3.

    Для составления оставшейся проектной документации выполним трассировку программы. После двойного нажатия клавиши <F7> начинает исполняться настроечный код, содержащийся в файлах *.TPU, и далее начинают выполняться операторы основной программы program Mcalc, находящейся в файле mcalc.pas.

    В результате исследований была выявлена схема иерархии модулей программы, изображенная на рис. 7.3–7.5. Расшифровка обозначений схемы иерархии представлена в табл. 7.1.

    Рис. 7.3. Фрагмент схемы иерархии основных модулей программы


    Рис. 7.4. Схема иерархии модуля RedrawScreen


    Рис. 7.5. Сокращенная схема иерархии модуля Run


    Таблица 7.1

    Расшифровка обозначений схемы иерархии

    Имя модуля Файл Назначение модуля
    Act Mclib Обрабатывает информацию введенной строки, занося ее в клетку
    CenterColString Mcutil Рассчитывает X координату центрируемой в поле вывода строки
    ChangeAutoCalc Mclib Устанавливает автоматический и ручной режимы рекалькуляции таблицы
    ChangeFormDisplay Mclib Устанавливает режим видимости значений формул или текста формул
    ClearInput Mcdisplay Очищает на экране поле строки ввода
    ClrScr Crt Очищает информацию в окне экрана
    DisplayCell Mclib Выводит на экран информацию клетки
    DisplayScreen Mclib Отображает на экране внутреннюю информацию таблицы
    EditCell Mcommand Осуществляет редактирование содержимого клетки
    EditString Mcinput Редактор текстовой строки
    EgaInstalled Mcdisplay Функция, определяющая наличие видеокарты EGA
    FillChar Dos Присваивает элементам массива значение символа
    GetCursor Mcdisplay Считывает толщину курсора в переменную
    GetInput Mcinput Получив первый введенный символ, продолжает ввод информации клетки
    GetKey Mcinput Формирует слово расширенного кода клавиши
    GetSetCursor Mcdisplay Считывает толщину курсора в переменную и устанавливает новую толщину курсора
    GotoXY Mcdisplay Перемещает курсор в соответствии с заданными координатами дисплея
    InitColorTable Mcdisplay Инициализирует массив пересчета цветов для монохромного монитора
    InitDisplay Mcdisplay Инициализирует видеокарту на работу в режиме 80-25
    InitVars Mcutil Инициализирует значения основных переменных программы
    Intr Dos Вызывает прерывание MS DOS
    LoadSheet Mcommand Загружает информацию таблицы из файла
    MainMenu Mcommand Реализует выбор тем меню программы
    Mcalc Mcalc Главная программа
    ParamCount Dos Счетчик полей командной строки запуска программы Mcalc
    ParamStr Dos Возвращает значения заданного поля командной строки запуска программы Mcalc
    PrintCol Mcdisplay Выводит значение координаты колонки таблицы
    PrintFreeMem Mcdisplay Выводит на экран значение остатка свободной памяти
    PrintRow Mcdisplay Выводит значение координаты строки таблицы
    ReadKey Mcinput Считывает короткий код одной нажатой клавиши
    Recalc Mclib Осуществляет перерасчет значений формул клеток таблицы
    RedrawScreen Mclib Отображает на экране всю информацию таблицы
    Run Mcalc Главный цикл программы
    Scroll Mcdisplay Прокручивает информацию экрана в указанном направлении; устанавливает цвет фона освободившейся части экрана
    SetBottomRow Mcdisplay Выводит на экран столбец с номерами строк таблицы
    SetColor Mcdisplay Устанавливает цвет вывода строк на экран
    SetCursor Mcdisplay Устанавливает заданную толщину курсора
    SetRightCol Mcdisplay Выводит на экран строку с наименованиями столбцов таблицы
    ShowCellType Mcdisplay Выводит на экран надпись о типе текущей клетки таблицы
    TextMode Dos Переводит экран в указанный текстовый режим
    Window Crt Определяет окно на экране дисплея
    Write - Оператор вывода языка Pascal
    WriteXY Mcdisplay Осуществляет вывод заданного числа символов заданной строки по заданным координатам дисплея

    Рассмотрим функциональное описание основного ядра программы. В файле mcutil.pas исполняется рудиментарный, оставшийся от прежних разработок код:

    HeapError:= @HeapFunc;

    В файле mcdisplay.pas последовательно выполняются подпрограммы: InitDisplay, GetSetCursor, Window, EGAInsalled.

    Процедура InitDisplay инициализирует видеокарту на работу в режиме 80 25 при помощи вызова прерывания 10h и вызовом процедуры InitColorTable инициализирует массив пересчета цветов для монохромного монитора. Последний массив используется при вызовах процедуры SetColor.

    Процедура GetSetCursor при помощи процедуры GetCursor считывает толщину курсора в переменную OldCursor и при помощи процедуры SetCursor устанавливает новую толщину курсора (NOCURSOR).

    Процедура Window определяет окно на экране дисплея для размещения информации всей таблицы. Далее начинает выполняться код главной программы Mcalc.

    Присваиванием CheckBreak:= False запрещается использование клавиши <Ctrl+Break> немедленного завершения программы.

    Вывод начальной заставки осуществляется следующими вызовами подпрограмм. Процедурами SetColor и ClrScr производится очистка окна программы. Двойным вызовом процедур SetColor и WriteXY выводятся две строки начальной заставки. Несмотря на отсутствие курсора, отрабатывается рудиментарный вызов "сокрытия" курсора GotoXY(80,25). При помощи функции GetKey осуществляется ожидание нажатия пользователем любой клавиши.

    Процедурами SetColor и ClrScr производится очистка окна программы.

    Вызовом процедуры InitVars инициализируются значения основных переменных программы. Массивы инициализируются значениями по умолчанию вызова процедуры FillChar.

    Присваиванием Changed:= False указывается факт неизменности информации клеток таблицы после момента инициализации переменных для запрещения срабатывания автосохранения.

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

    Если значение ParamCount = 1, то в командной строке MS DOS вызова программы было указано имя файла таблицы. В этом случае выполняется процедура LoadSheet, которая загружает информацию таблицы из файла с именем файла, полученном при помощи вызова функции ParamStr.

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

    При завершении выполнения программы последовательно производится установка цвета экрана, вызовом TextMode переводится экран в текстовый режим, запомненный в переменной OldMode, и, наконец, вызовом SetCursor восстанавливается толщина курсора, запомненная в переменной OldCursor.

    Работа процедуры RedrawScreen заключается в последовательном выводе на экран информации:

    • процедурой SetRightCol выводится на экран строка с наименованиями столбцов таблицы;

    • процедурой SetBottomRow выводится на экран колонка с номерами строк таблицы;

    • процедурами GotoXY и Write выводятся надписи в верхней строке экрана, хотя имеется более удобная процедура WriteXY;

    • выводится число остатка байт памяти;

    • процедурой DisplayScreen отображается на экране внутренняя информация таблицы.

    Внешний вид программы Mcalc приведен на рис. 7.6.

    Работа процедуры Run начинается с установления переменной главного цикла Stop:= False и выполнения процедуры Clearlnput. Главный цикл программы выполняется до изменения значения переменной Stop на True. Такое изменение возможно лишь при выборе пользователем темы меню Quit — завершение работы с программой.

    Внутри главного цикла последовательно выполняются следующие действия:

    — при помощи процедуры DisplayCell выводится на экран подсвеченная клеточным курсором текущая клетка (клетка А1 на рис. 7.6);

    — при помощи процедуры ShowCellType выводится в нижнем левом углу экрана надпись типа текущей клетки таблицы (см. рис. 7.6);

    — оператором Input:= GetKey в переменную Input вводится код символа клавиши, нажатой пользователем;

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

    Действия отработки клавиши, нажатой пользователем, представляют собой цепочку альтернативных действий, реализованную структурой ВЫБОР. Сначала отрабатываются действия "горячих" клавиш. В секции default (если клавиша не была "горячей") вызовом процедуры Getlnput начинается занесение информации в текущую клетку таблицы. Процедура Getlnput, занеся символ Input в редактируемую строку, первоначально вызывает EditString — редактор текстовой строки информации клетки и затем вызывает процедуру Act, которая обрабатывает информацию введенной строки, занося ее в клетку.

    Рис. 7.6. Внешний вид программы Mcalc


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

    С целью повышения понятности программы были приняты новые проектные решения, отраженные схемой иерархии (рис. 7.7).

    Выполнение основной программы Mcalc начинается с запуска нового модуля Starting подготовительных действий программы. Модуль Starting является монитором последовательного исполнения модулей InitDisplay, Greeter, InitVars.

    Новый модуль InitDisplay теперь является монитором последовательного исполнения модулей GetSetMode, GetCursor, SetCursor, Egalnstalled, Window, InitColorTable.

    У нового модуля GetSetMode явно в качестве входного параметра указывается новый устанавливаемый видеорежим, а на выходе — старый видеорежим. Такая организация предпочтительнее прямого вызова Intr, поскольку по списку формальных параметров ясно видно назначение модуля. Реализация двух функций по выявлению и установке видеорежимов в одном модуле здесь вполне оправдана, поскольку все они реализуются вызовом одного прерывания.

    Не является оправданным использование модуля с двумя функциями GetSetCursor, который являлся монитором последовательного исполнения модулей GetCursor, SetCursor. Этот модуль исключен из проекта. Все функции вывода начальной заставки переданы новому модулю Greeter.

    Из модуля RedrawScreen исключен вызов модуля DisplayScreen. Это позволило избежать повторного вызова модуля DisplayScreen в модуле LoadSheet. Также исправлена ошибка использования операторов Write для вывода информации на экран путем использования вызовов процедуры WriteXY.

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

    If (not(HotKey(Input)) and (ConditionalKey(Input)))

    then

    GetInput(Input).

    Новая функция HotKey в случае нажатия пользователем горячей клавиши возвращает значение TRUE, в противном случае функция возвращает значение FALSE.

    Новая функция ConditionalKey в случае нажатия пользователем клавиши с кондиционным для занесения в таблицу кодом возвращает значение TRUE, в противном случае функция возвращает значение FALSE.

    Рис. 7.7. Переработанная схема иерархии модулей программы


    Новая процедура WriteXY теперь не использует вызов медленной процедуры GotoXY и медленно выполняемый оператор Write и использует прямой доступ к видеопамяти. Это позволило значительно ускорить вывод информации на дисплей. Более того, в процедуру добавлен новый параметр атрибута цвета выводимой строки, что позволило избежать цепочек первоначального вызова SetColor, а затем WriteXY.

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

    ВЫВОДЫ

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

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

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

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

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

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

    Контрольные вопросы

    1. Дайте определение понятию "структура программы".

    2. Что такое модуль программы и какими характеристиками он должен обладать?

    3. Что отражает схема иерархии?

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

    5. Дайте определение понятию "заглушка модуля".

    6. Перечислите основные средства изменения топологии схемы иерархии программы.

    7. Назовите критерии оценки качества схемы иерархии.

    8. Для чего нужен паспорт модуля?









    Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх