Текст
                    Programming System
Methodologies
Carol A. Ziegler
University of Arkansas at
Little Rock
Prentice-Hall, Inc., Englewood Cliffs, N. X
1983


К. Зиглер /ИЕТОЛЫ ПРОЕКТИРОВАНИЯ ПРОГРАММНЫХ СИСТЕМ Перевод с английского канд. техн. наук М. В. Сергиевского, канд. техн. наук Л. В. Шалашова, А. В. Чукашова и А. Е. Кондратьева под редакцией д-ра техн. наук Я. А. Хетагурова Москва «Мир» 1985
ББК 32.973 3-59 УДК 681.142.2 Зиглер К. 3-59 Методы проектирования программных систем: Пер. с англ. —М.: Мир, 1985. —328 с, ил. В книге американского специалиста по обработке данных анализируется про>- цесс проектирования и рассматриваются методы проектирования программных систем. Большое внимание уделено этапу тестирования и оптимизации программ,, написанных на языке высокого уровня. Для системных программистов, инженеров-математиков и системотехников в области САПР. 2405000000—268 ББК 32.978 3 041(01)—85 163-85> ч- * 6ф7Л Редакция литературы по информатике и электронике Кэролл Зиглер МЕТОДЫ ПРОЕКТИРОВАНИЯ ПРОГРАММНЫХ СИСТЕМ Старший научный редактор И. М. Андреева. Младший научный редактор Н. И. Сивилева. Художественный редактор Н. М. Иванов. Технический редактор Л. П. Ермакова. Корректор В. И. Постнова. ИБ № 5309 Сдано в набор 25.09.84. ГГодписано к печати 14.02 85. Формат 60X907*6. Бумага типографская № 2. Печать высокая. Гарнитура литературная Объем 10,25 бум. л. Усл. печ. л. 20,50. Усл. кр.-отт. 20;50. Уч.-изд. л. 20,56. Изд. № 20/3468. Тираж 25 000 экз. Зак. 399. Цена 1 р. 80 к. ИЗДАТЕЛЬСТВО «МИР». 129820, ГСП, Москва, И-110, 1-й Рижский пер., 2. Московская типография № 11 Союзполиграфпрома при Государственном комитете СССР по делам издательств, полиграфии и книжной торговли. Москва, 113105, Нагатинская ул., д. 1. ■-.ич u....u J. л ц. ■»■«—■ НИИ — —• © 1983 by Prentice Hall, Inc. <§) Перевод на русский язык, ?Мир», 1985
ПРЕДИСЛОВИЕ РЕДАКТОРА ПЕРЕВОДА «Еще одно руководство по проектированию программных систем»,— может подумать читатель. С одной стороны, это действительно так. Только в издательстве «Мир» в последние годы вышло несколько книг, посвященных различным вопросам разработки программных систем: структурному программированию, верификации и тестированию программ. Среди них следует выделить несколько очень удачных1). Но дело в том, что данная монография принципиально отличается от всего того, что было написано до сих пор. В ней излагаются основы методологии проектирования программ, приводится максимально широкий спектр существующих методов и инструментов проектирования, причем охватываются все этапы создания прикладных программных систем, начиная с системного обследования и кончая тестированием и оптимизацией. Впервые совместно анализируются три наиболее распространенных подхода к разработке программного обеспечения: проектирование сверху вниз, снизу вверх и расширение ядра. Хотя выбор метода зависит в основном от характеристик проектируемой системы, показано, как можно на разных этапах использовать различные методы. Большой интерес, без сомнения, представляет заключительная глава книги, где речь идет об оптимизации готовых программ. Автор подчеркивает, что оптимизация возможна на уровне алгоритмов, программных секций и отдельных операторов, и показывает, в каких случаях и на каком уровне ее целесообразно проводить. Причем конкретные методы оптимизации иллюстрируются большим количеством удачных примеров. Выделим еще один важный момент, на котором следует остановиться,— это подготовка системной документации. К сожалению, в нашей стране этой важной проблеме до сих пор уделяется мало внимания. Например, пакеты прикладных программ сдаются в таком виде, что их эксплуатация и сопровождение чрезвычайно трудоемки. Поэтому предлагаемые практически во !) Хьюз Дж., Мичтом Дж., Структурный подход к программированию.— М.: Мир, 1980. Тассел Д. ван, Стиль, разработка, эффективность, отладка и испытание программ. — М.: Мир, 1981.
6 Предисловие редактора перевода всех главах книги рекомендации относительно того, что и в какой форме включать в документацию, имеют большое значение. В то же время хотелось бы отметить, что в книге встречаются нестрогие определения. Их особенно много в гл. 5 (понятие вычислимости, определение детерминированных и недетерминированных алгоритмов). Кроме того, автор использует большое число синонимов, т. е. одному и тому же понятию очень часто соответствует несколько терминов. Переводчикам, на мой взгляд, удалось устранить указанные выше недостатки. Не вызывает сомнений, что книга будет полезна широкому кругу специалистов, занимающихся обработкой данных. Особенно важен ее материал для системных и прикладных программистов; найдут книгу интересной также те, кто занимается системным анализом. Хочется предупредить, что для чтения книги потребуется знание по крайней мере одного языка программирования высокого уровня. Перевод книги выполнен канд. техн. наук А. В. Шалашовым (предисловие, гл. 1, 2, список терминов), канд. техн. наук М. В. Сергиевским (гл. 3, 8, 9), А. В. Чукашовым (гл. 4, 5, 6) и А. Е. Кондратьевым (гл. 7). £*; Д-р техн. наук, профессор Я. А. Хетагуров
ПРЕДИСЛОВИЕ АВТОРА На протяжении нескольких последних лет в области программирования основное внимание было сосредоточено на выработке единого, стандартного подхода к проектированию и реализации программ для ЭВМ. В это же время в штатных расписаниях вычислительных центров стала появляться новая должность, получившая название «системный аналитик». Ее введение отражало объективную потребность в разделении функций между проектировщиками и программистами, подобно тому как ранее возникла необходимость отделить программистов от операторов, занимавшихся эксплуатацией программ. В курсах программирования вопросам проектирования и стандартизации редко отводится значительное место, несмотря на их очевидную важность для создания эффективных программ, снабженных исчерпывающей документацией. В процессе первоначального обучения языкам программирования преподаватели иногда знакомят студентов с принципами анализа и проектирования'применительно к назначению и присваиванию переменных. Однако чаще получается так, что студентам, изучающим языки программирования, приходится тем или иным способом самим приобретать навык составления небольших программ. До более сложных и объемистых программ дело, как правило, не доходит. Изложение методов проектирования на уровне вводного курса в основном сводится либо к разъяснению самой идеи программы как совокупности подробных инструк* ций, определяющих последовательность операций, выполняемых над данными, либо к обоснованию роли планирования в процессе проектирования. На этом уровне преподаватели вынуждены ограничиваться простейшими примерами и иллюстративными материалами. После того как основные конструкции и особенности языка освоены, появляется возможность сосредоточиться на совершенствовании техники и стиля программирования. Целью данной книги является повышение уровня знаний студентов и других лиц, достаточно свободно владеющих одним или несколькими языками программирования, в области проектирования и реализации программ. При этом предполагается, что читатели хорошо знакомы с разнообразными типами информационных и уп-
8 Предисловие автора равляющих структур: и таких, которые применяются в наиболее распространенных языках высокого уровня, и таких, как деревья и стеки, которые стали базовыми элементами многих разделов теории вычислительных машин. Эта книга в первую очередь ориентирована на студентов вычислительных специальностей, а не на студентов-экономистов или специалистов, занимающихся прикладным программированием. Тем не менее многие из приведенных в ней примеров заимствованы из приложений, связанных с коммерческими операциями, а не с разработкой программного обеспечения ЭВМ или решением научно-технических задач. Такой выбор объясняется тем, что задачи коммерческого характера просты для восприятия и не требуют значительной математической подготовки. В то же время при их программной реализации возникают не менее сложные проблемы, чем в других прикладных задачах. Кроме того, необходимо учитывать, что в США бизнес и коммерция— это те сферы, в которых студент-выпускник сможет с большей вероятностью, чем в других, найти себе работу, хотя бы временную. В других примерах рассматриваются игровые и более традиционные задачи из области вычислительной техники. Данная книга рассчитана не на системных аналитиков, а на программистов, которые хотели бы отчетливее определить свое место в рамках процесса проектирования и разработки программ. В ней сделана попытка дать общий обзор этого процесса и представить некоторые из применяемых в данной сфере методов, получивших широкое признание. Особое внимание в книге уделено таким специальным проблемам, как использование файлов, верификация данных, обработка исключительных состояний и устранение вычислительных ошибок. В первых трех главах рассматриваются наиболее общие проблемы, с которыми сталкиваются проектировщики программной системы, например: что должна делать система? какие ресурсы для нее необходимы? В следующих четырех главах обсуждаются некоторые детали построения систем: какие требования предъявляются к программному обеспечению ЭВМ? каким образом должно осуществляться управление информационными потоками и процессами переработки данных? как должны выполняться вычисления? Последние две главы посвящены вопросам качества функционирования систем: какую точность результатов обеспечивают системы и сколь эффективно их применение? Структуру книги можно представить также по-иному, рассматривая ее содержание как последовательное описание этапов создания программной системы. В гл. 1 обсуждаются способы формализации прикладной задачи. Гл. 2—4 посвящены разработке программ и данных, гл. 5—7 — их реализации, гл. 8 и 9 — испытаниям и оптимизации программ. В книге отдельно
Предисловие автора 9 не исследуется завершающая и наиболее продолжительная фаза существования системы в качестве готового коммерческого продукта, оснащенного средствами сопровождения. Однако применение этих средств с целью внесения тех или иных усовершенствований включает этапы разработки, испытаний и оптимизации, о которых идет речь в книге. По всей книге отмечается важность тщательной подготовки системной документации. Итак, предлагаемая книга призвана заполнить пробел между изучением языков программирования и использованием этих языков в качестве инструмента при создании систем для решения конкретных прикладных задач. Некоторые части книги требуют от читателя определенной математической подготовки, при чтении других он должен иметь представление о внутренних механизмах действия отдельных операторов языка программирования. Поскольку книга рассчитана на программистов, владеющих различными языками, в примерах использованы Бэй- сик, Фортран, Кобол и ПЛ/1, а в качестве целевого языка — псевдокод, подобный применяемому в языке Паскаль. Как учебное пособие книгу можно рекомендовать студентам младших курсов, знакомых с несколькими языками программирования и прослушавших вводный курс по структурам данных. Кэрол А.Зиглер
Глава 1 ВВЕДЕНИЕ Наиболее важное свойство хорошей программы состоит в том, что она работает, причем не просто делает что-то, но именно то, что было задумано. Иными словами, она дает правильные результаты в ответ на правильные исходные данные. Не менее важно и то, что хорошая программа сама способна отличать верные данные от неверных. Благодаря этому она не станет выдавать внешне вполне правдоподобные результаты, если исходные данные на самом деле неправильны. Качество программного обеспечения играет важную роль, поскольку большинство программ и программных систем, разрабатываемых с целью сбыта (например, компиляторы, текстовые редакторы, программы сбора учетной информации, пакеты программ статистической обработки), рассчитаны на длительное использование. Как правило, программное обеспечение оказывается более долговечным, чем аппаратное. Поэтому почти на каждой вычислительной установке можно найти программы, написанные еще для ЭВМ предыдущих поколений. Создание хорошей программы является весьма трудоемкой задачей, и предприятия вынуждены вкладывать большие средства в разработку прикладного программного обеспечения. С учетом того, что требования к программам со временем претерпевают изменения, модификация уже существующих программ часто обходится дешевле, чем создание новых. Фактически программы приходится постоянно корректировать, поскольку время от времени в них обнаруживаются ранее не замеченные ошибки. Кроме того, изменяется также операционная среда, в которой они функционируют. Практически не встретишь большую программу, не содержащую никаких ошибок, или окончательно сложившуюся версию программы. Необходимость модификации может быть вызвана сменой оборудования. Если корректировка программ требует времени, новые устройства целесообразно эксплуатировать в режиме эмуляции, при котором имитируется работа прежних устройств, благодаря чему могут использоваться старые варианты программ. Модификация программ может потребоваться также в тех случаях, когда вносятся изменения в программное обеспечение ЭВМ. В частности, несмотря на все усилия, направленные
Введены» И на стандартизацию языковых средств, до сих пор трудно найти два компилятора, которые работали бы с абсолютно одинаковыми версиями языка или давали бы идентичные результаты при обработке одних и тех же конструкций. По мере того как внедряются новые типы ЭВМ, как разрабатываются более совершенные подходы к решению прикладных задач и созданию языков программирования, эволюционируют и традиционные, общепризнанные языки. Так, вслед за Фортраном I появился Фортран II, затем Фортран IV и наконец Фортран 77. Обычно различные версии языка совместимы по принципу «снизу вверх», однако и в этих условиях в программы почти всегда приходится вносить небольшие коррективы. Всякий раз, когда вводится очередной стандарт на синтаксис языка Кобол, список резервируемых идентификаторов пополняется новыми именами. В частности, такие имена данных, как TIME-OF-DAY (время суток) или SUM (сумма), которые программисты ранее могли употреблять по своему усмотрению, в позднейших версиях были включены в число зарезервированных слов. Модификация программ может быть вызвана и другими причинами, в том числе изменениями в обработке учетной информации, появлением более эффективных алгоритмов, переходом на новые стандарты. По некоторым оценкам, около 80%' рабочего времени программистов-профессионалов уходит не на разработку программ, а на их модификацию. Таким образом, многие программы должны быть рассчитаны на длительную эксплуатацию, на протяжении которой они могут неоднократно модифицироваться. Учитывая это, понятие «хорошая программа» следует трактовать в более широком смысле, чтобы оно включало и такие качества, которые способствовали бы увеличению срока службы программ и их адаптации к изменяющимся условиям применения. 1.1. Качество программных систем Каждая программа, входящая в систему, должна отвечать таким требованиям, как правильность, точность, совместимость, надежность, универсальность, защищенность, полезность, эффективность, проверяемость и адаптируемость. Будем говорить, что программа является • правильной, если она функционирует в соответствии с техническим заданием. Подразумевается, что техническое задание составлено в четкой форме, позволяющей однозначно судить о том, действительно ли программа отвечает перечисленным в нем требованиям; Ф точной, если выдаваемые ею числовые данные имеют допу-
12 Глава 1 стимые отклонения от аналогичных результатов, полученных с помощью идеальных математических зависимостей; • совместимой, если она работает должным образом не только автономно, но и как составная часть всей программной системы, осуществляющей обработку информации; • надежной, если она при всех условиях обеспечивает полную повторяемость результатов.' Любой человек, имеющий опыт работы с ЭВМ, может подтвердить, что в его практике еще не встречалось ни безукоризненно работающих машин, ни абсолютно надежного системного программного обеспечения. И, несмотря на оптимистичность высказываний некоторых программистов, то же самое можно сказать о прикладных программных системах. Впрочем, уровень их надежности может быть повышен за счет использования встроенных механизмов резервирования и самоконтроля; • универсальной, если она правильно работает при любых допустимых вариантах исходных данных. В ходе разработки программ должны предусматриваться специальные средства защиты от ввода неправильных данных, обеспечивающие целостность системы; • защищенной, если она сохраняет работоспособность при возникновении сбоев. Это качество особенно важно для программ, предназначенных для решения задач в режиме реального времени. В подобных приложениях отказ оборудования может повлечь катастрофические последствия — например, аварию ракеты или ядерного реактора. Указанным свойством должны также обладать программы с большим временем выполнения, осуществляющие обработку постоянно хранимых файлов; • полезной, если задача, которую она решает, представляет практическую ценность; • эффективной, если объем требуемых для ее работы ресурсов ЭВМ не превышает допустимого предела; • проверяемой, если ее качества могут быть продемонстрированы на практике. Здесь подразумевается возможность проверки таких свойств программы, как правильность и универсальность. Можно применить формальные математические методы, позволяющие установить, действительно ли программа удовлетворяет техническим условиям и выдает достаточно точные результаты. Однако существуют и неформальные способы оценки качества программ, причем иной раз они оказываются более убедительными, чем формальные. Имеются в виду такие неформальные приемы, как прогоны с остановами в контрольных точках, обсуждения результатов с заинтересованными пользователями и др.; • адаптируемой, если она допускает быструю модификацию с целью приспособления к изменяющимся условиям функционирования. Адаптируемость в значительной степени зависит от
Введение 13 конструкции программы, от того, насколько квалифицированно она составлена и полно снабжена документацией. В какой степени удовлетворяются перечисленные требования, характеризующие качество программ, определяется знаниями и мастерством разработчиков и программистов. Хотя программисты часто пишут небольшие процедуры для своих собственных нужд, все же большинство программ составляется для тех пользователей, которые не обладают должными навыками программирования. На любой вычислительной установке (кроме самых маломощных) пользователь, прикладной программист, оператор ЭВМ, техник, отвечающий за ввод данных, системный программист — это разные лица, и качество программ сказывается на деятельности каждого из них. Часто программы создаются коллективами программистов. Некоторые из них занимаются в основном разработкой, некоторые — самим программированием, остальные — составлением документации и испытаниями программ. Если разработка алгоритмов и программирование выполняются разными группами специалистов, за подготовку документации отвечают все группы. Машинные программы — это своего рода рабочие инструменты, и пользователь нуждается в определенных инструкциях но их применению и правильному обращению с ними. Подобные инструкции должны содержаться в документации, поставляемой вместе с программой. Документация — столь же обязательный продукт процесса программирования, сколь и сама программа. Если программа взаимодействует с ЭВМ непосредственно, связь се с людьми обеспечивается в основном с помощью документации. 1.1.1. Среда пользователей Программы редко применяются как самостоятельные единицы. Чаще всего они являются элементами более крупных информационных систем, осуществляющих сбор, хранение и обработку данных. Такие системы становятся неотъемлемой частью механизма функционирования предприятий, на которых они эксплуатируются. Прямо или косвенно, они затрагивают деятельность множества людей. Чтобы это воздействие носило положительный характер, программы не просто должны быть полезными, надежными и эффективными, но должны явно обнаруживать эти качества для тех, кто с ними работает. Иными словами, как процесс проектирования программной системы, так и его конечный продукт должны быть ориентированы на нужды пользователей. Пользователи будут уверены в эффективности системы, если почувствуют, что в группе, занимающейся ее разработкой, прислушиваются к их пожеланиям, если найдут, что форма вход-
14 Глава 1 ных данных и результатов удобна и близка к привычной, и, наконец, если им будет продемонстрировано, что система должным образом перерабатывает информацию, отобранную по их собственному усмотрению. Если пользователи принимают участие в проекте на стадии разработки,.они лучше осведомлены о характеристиках системы и могут внести свою лепту в формирование ее окончательного облика. Если же пользователи привлекаются на этапе испытаний, они получают возможность оценить качества системы еще до начала эксплуатации. Располагая квалифицированно составленной, исчерпывающей документацией, пользователи смогут быть уверены, что система будет работать с полной отдачей. ВОВЛЕКАЙТЕ ПОЛЬЗОВАТЕЛЕЙ В ПРОЦЕСС ПРОЕКТИРОВАНИЯ СИСТЕМЫ Пользователями программной системы могут быть служащие административных учреждений, для которых на ЭВМ подготавливаются отчеты и ведомости, или инженеры, выполняющие на машинах научно-технические расчеты. В число пользователей входят также работники из вспомогательного персонала, отвечающие за ввод информации и заполнение бланков входных форм или контролирующие правильность и точность данных,, выдаваемых ЭВМ. Все лица, на том или ином этапе принимающие участие в процессе обработки инфорь^ации, от ее сбора до оформления результатов, еще на стадии проектирования системы должны быть ознакомлены с теми ее особенностями, которые должны приниматься в расчет в их практической деятельности. 1.1.2. Среда ЭВМ Программа неотделима от вычислительной среды, с которой взаимодействует. Она использует системные программные средства, а те в свою очередь могут пользоваться ее информацией. Программа либо сама создает файлы, либо обрабатывает файлы, сформированные другими программами. Она должна быть построена таким образом, чтобы могла применяться в различных приложениях и обходиться только имеющимися аппаратными ресурсами и средствами программирования. Процесс разработки программы в значительной степени зависит от наличия специализированных языков проектирования, каталогов данных, оптимизирующих компиляторов, генераторов тестовых задач. Существенно проще создать хорошую программу, располагая эффективными вспомогательными средствами. МАКСИМАЛЬНО ИСПОЛЬЗУЙТЕ СЕРВИСНЫЕ СРЕДСТВА АВТОМАТИЗАЦИИ ПРОЕКТИРОВАНИЯ
Введение 15 1.1.3. Среда заказчиков Обычно работа по составлению программ начинается в связи с тем, что некоторая организация предлагает создать для нее программную прикладную систему. Официальному заключению договора обычно предшествует выяснение реальной необходимости в такой системе, оценка возможности ее разработки и примерного объема затрат, а также ожидаемого эффекта от ее внедрения. Несмотря на то что ЭВМ можно спроектировать и запрограммировать так, что она будет способна решить любую задачу, которую пользователь сможет описать в виде формальных правил, определяющих последовательность действий, применение вычислительных машин далеко не всегда оправдано. ЭВМ лучше, чем человек, справляется с трудоемкими задачами, требующими многократного повторения однотипных операций, значительно быстрее и точнее выполняет арифметические вычисления. ЭВМ более эффективно осуществляет поиск и обработку больших массивов информации, состоящих из однородных элементов. В то же время человек лучше, чем машина, может разобраться в том, что и как следует делать, и способен работать с неоднородной информацией. Всякое использование ЭВМ предполагает стандартизацию данных и способов обработки. Эффективная реализация преимуществ ЭВМ возможна лишь в тех случаях, когда необходимо выполнять либо трудоемкие вычисления, либо обработку больших объемов информации. После завершения этапа предварительных исследований составляется список требований, предъявляемых к системе. В него должны быть включены результаты анализа обстановки, описание выполняемых системой функций и ограничения, которые необходимо учитывать в процессе проектирования. Под обстановкой в данном случае понимается совокупность условий, при которых предполагается эксплуатировать систему. К ним относятся аппаратные и программные ресурсы, предоставляемые системе, внешние условия ее функционирования, а также состав людей и работ, имеющих к ней отношение. Должны быть продуманы изменения в текущей деятельности организации, обусловленные внедрением системы. Возможно, понадобится иная расстановка персонала, придется внести изменения в структуру выполняемых работ. Могут также потребоваться дополнительные вычислительные мощности. Функциональные требования к системе содержат четкое описание всего того, что она должна делать. Ограничениями в процессе проектирования являются директивные сроки завершения отдельных этапов, имеющиеся в наличии ресурсы, организационные процедуры и мероприятия, обеспечивающие сохранность информации.
16 Глава 1 НЕОБХОДИМО ВСЕСТОРОННЕ АНАЛИЗИРОВАТЬ ЭФФЕКТЫ, СВЯЗАННЫЕ С ВНЕДРЕНИЕМ СИСТЕМЫ Организация-заказчик и группа разработчиков совместно составляют официальный перечень спецификаций, а также договор о порядке проведения проектных работ и приемке системы. Иногда процесс создания системы разбивается на два отдельных этапа, в которых участвуют различные группы специалистов. Первая из них занимается собственно проектированием системы, вторая — ее программной реализацией. В таких случаях договоры заключаются с обеими группами, причем между указанными этапами должен быть предусмотрен определенный промежуток, выделяемый для анализа и обсуждения характеристик системы. Подобный подход к проектированию можно проиллюстрировать на примере разработки языка Ада. В начале 70-х годов министерство обороны США объявило о создании нового языка, которым предполагалось заменить другие языки программирования во всех приложениях, связанных с решением задач военного характера. Еще до составления окончательного перечня функциональных требований и спецификаций было разработано несколько версий языка, которые анализировались и оценивались группой сторонних экспертов. Был объявлен конкурс на создание языка. Его победителем стала французская фирма Honeywell-Bull. После того как работа над языком была завершена, для его оценки вновь были приглашены сторонние эксперты. Много различных групп приняло участие в экспериментах по практической реализации некоторых наиболее нетрадиционных особенностей языка. Наконец, когда стало ясно, что Ада в целом отвечает предъявляемым требованиям, различные военные ведомства начали заключать контракты на приобретение компиляторов языка. 1.2. Постановка задачи Первый шаг в проектировании прикладной программной системы заключается в точном формулировании целей внедрения машинной обработки. Требования к системе еще не создают полной картины. В постановке задачи, разумеется, должны принять участие как представители организации-заказчика, так и те, кто будет заниматься проектированием системы. Необходимо, чтобы этот процесс был гибко организован и продолжался в течение достаточно длительного времени, поскольку на любом этапе разработки или внедрений могут вскрываться ранее не предусмотренные трудности, требующие внесения определенных изменений в проект. В то же время с целью упрощения разработки в договор следует включить пункт, который запрещал бы
Введение \Т радикальный пересмотр требований на стадии реализации системы. В этом же пункте могут быть оговорены условия внесения несущественных изменений. ВСЕ ДОГОВОРЕННОСТИ ДОЛЖНЫ ОФОРМЛЯТЬСЯ В ОФИЦИАЛЬНОМ ПОРЯДКЕ 1.2.1. Адаптация процедур ручной обработки Задача может заключаться либо в использовании ЭВМ для выполнения тех операций, которые ранее производились вручную, либо во внедрении качественно новых способов обработки. Решение последней задачи требует ответа на вопросы, во многом аналогичные тем, с которыми проектировщики сталкиваются при машинной реализации уже сложившихся процедур обработки. В одних случаях эти ответы могут базироваться на решениях, принятых организацией-заказчиком. В других случаях их приходится искать, обсуждая проблемы с лицами, занимающимися в настоящее время выполнением указанных процедур. Если на ЭВМ предполагается реализовать некую ручную операцию, проектировщик системы обязан досконально выяснить, из каких конкретных,действий состоит эта операция. Эффективное решение даже такой простой задачи, как составление на ЭВМ списка клиентов с учетом особенностей взаимодействия пользователя и машины, должно основываться на принципах, принятых в современной деловой практике. Проектировщику системы предстоит получить ответы на ряд вопросов, в том числе: какого рода информация содержится в записях о клиентах? кто предоставляет эту информацию и в какой форме? сколь долго предполагается ее хранить? какая файловая система будет применяться? имеются ли копии информации в других учреждениях? каким образом будет использоваться данная информация? кто является потребителем информации? как осуществляется корректировка информации? что предполагается делать с информацией о клиентах, переставших подавать заявки? как поступать с неполной информацией? что делать с информацией, в которой имеются ошибки? Полученные ответы будут содержать массу подробностей, которые должны учитываться в процессе работы над системой. В то же время эти ответы дают более полное представление о внутреннем механизме процедур обработки данных в организации-заказчике. НЕОБХОДИМО ФИКСИРОВАТЬ ВСЕ, ЧТО СДЕЛАНО НА ТЕКУЩИЙ МОМЕНТ Прикладная система служит своего рода машинной моделью, отражающей процессы ручной обработки информации. Однако лишь на первый взгляд ситуация проста, на деле все 2-399 ЯАУЧИАЯ БЙБЛЙ0ТШ им. Горьког® МГУ
18 Глава 1 может оказаться значительно сложнее. В принципе можно счи- I тать, что имеется всего один набор исходных данных. Допустим, I это документы, хранящиеся в канцелярском шкафу. К сожале- I нию, очень часто подобные наборы исходных данных содержат I повторения, неточности, неясности, несоответствия и не облада- I ют требуемой полнотой. Проектирование системы на базе ЭВМ 1 включает такой обязательный элемент, как определение формы, 1 в которой должна быть представлена информация, хранящаяся I в шкафу. Кроме того, необходимо установить, какие варианты 1 данных будут использоваться на различных этапах, начиная от 1 сбора исходной информации и кончая выдачей результатов. ] Очевидно, формы, применяемые на этапах сбора данных, их I корректировки или выборки с целью проведения той или иной I обработки, могут отличаться друг от друга. Варианты данных 1 должны охватывать все входящие в автоматизированную систе- I му и исходящие из нее информационные потоки. I Аналогично тому как процессы перемещения данных в ЭВМ 1 имитируют операции с бумагами в учреждении, отдельные прог- 1 раммные модули, работающие с этими данными, служат моде- I лями деятельности служащих, обрабатывающих бумаги. Конеч- I но, подобное сопоставление справедливо лишь в самых общих чертах. Внутренние механизмы обработки, используемые в ЭВМ, ъовсе не обязательно должны воспроизводить методы, приме- j няемые конторскими службами. Постановка задачи должна охватывать весь процесс сбора, хранения, использования и окончательного представления данных. В ней должны также учитываться ограничения на время обработки, финансовые затраты и вычислительные ресурсы. j Кроме того, в постановке задачи следует предусмотреть воз- I можность последующей модернизации системы, например расширения памяти для хранения большего числа записей, для включения в каждую запись дополнительной информации или для внесения изменений в отдельные элементы информации. Оценка будущих потребностей должна осуществляться совместно как проектировщиками системы, так и ее потенциальными пользователями, поскольку необходимо принимать компромиссные решения, касающиеся соотношений между универсальностью и эффективностью, гибкостью организации данных и стремлением обеспечить текущие потребности. ПЛАНИРУЙТЕ НАПРАВЛЕНИЯ МОДЕРНИЗАЦИИ 1.2.2. Определение основных элементов системы На первом этапе проектирования должны быть определены информационные потоки и взаимодействующие с ними процессы. Информационным потоком будем называть информацию, пе~
Введение 19> ремещающуюся от одного узла обработки к другому. Такими узлами обработки могут быть машинные программы или рабочие места служащих, отвечающих за выполнение определенных операций. Большие массивы информации могут размещаться з> архивах, таких, как канцелярские шкафы или накопители на магнитной ленте. Часть информации должна быть представлена в форме, воспринимаемой ЭВМ; для остальных данных эта не обязательно. Когда возникает потребность в информации, она извлекается из архива, включается в информационный поток, перемещаемый либо с помощью электронных средств, либо вручную к узлу обработки, и в конечном итоге возвращается в архив, но уже в другом информационном потоке. В подобной укрупненной линейной модели обработки машинные программы выступают в качестве процессов, воздействующих на поступающие к ним данные, преобразуя их или меняя направление их перемещения. Эти процессы запускаются при появлении данных или возникновении условий, внешних по отношению к системе (например, приближение конца месяца). Для пользователя программы представляют собой черные ящики, присутствие которых проявляется лищь в виде результатов их воздействия на данные. 1.3. Проектирование системы К основным стадиям развития программной системы относятся следующие: постановка задачи, разработка, реализация,, испытания и эксплуатация системы. Эти этапы могут частично перекрываться. Так, формулирование задачи не обязательно должно заканчиваться в тот момент, когда начинается разработка программ и подготовка данных. Почти всегда продолжают возникать те или иные детали, требующие согласования с будущими пользователями. Переделка уже законченной системы, связанная с изменением внешних условий, обычно рассматривается как элемент сопровождения системы в ходе ее эксплуатации, хотя при этом приходится анализировать новые требования и вносить соответствующие изменения, осуществлять их реализацию и проводить испытания. Все это может происходить еще до того, как система будет принята к промышленной эксплуатации. Система должна проходить испытания как на этапах разработки и реализации, так и в уже готовом виде. Вначале испытания сводятся к консультациям с будущими пользователями: подобные консультации позволяют четко сформулировать требования к системе. На этапе разработки путем тщательного анализа результатов решения тестовых задач производится детальная проверка проектной документации. 2*
20 Глава 1 1.3.1. Структурный анализ Формирование окончательного проекта системы — чрезвычайно многообразный процесс, требующий от проектировщиков не только профессионального мастерства, но также выдержки и терпения. Умение работать с людьми для разработчика не менее важно, чем знание вычислительной техники. Если в неавтоматизированных информационных системах допускается возникновение всякого рода особых ситуаций, автоматизированная система действует во всех случаях стандартным образом. Поэтому при проектировании большое внимание приходится уделять выявлению и устранению частных деталей, не имеющих прямого отношения к делу, которые могут встретиться как в данных, так и в процедурах обработки, и, что немаловажно, убеждению защитников подобных деталей в том, что они не являются существенными. По мере того как проблемная ситуация проясняется и определяется, процедуры сбора, хранения и переработки информации, рассматриваемые вначале как нечто единое, постепенно расчленяются на отдельные элементы данных и действия, совершаемые над этими данными. Метод исследования, которое начинается с общего обзора системы и затем детализируется, приобретая иерархическую структуру со все большим числом уровней, принято называть структурным анализом. Требования к системе и ее. предполагаемые характеристики не могут служить отправной точкой, поскольку помимо общего описания они содержат много ненужных деталей. Их можно рассматривать •скорее как цели и стандарты, к которым следует стремиться на всех стадиях проектирования. Верхний уровень структурного анализа представляет собой функциональное описание системы. Составление функционального описания системы — это обобщение всей информации, касающейся целей проекта. СЛЕДУЕТ СТРЕМИТЬСЯ К СОЗДАНИЮ ПОЛНОЙ КАРТИНЫ Расчленение системы на функциональные элементы .подчиняется вполне определенным правилам. Самое общее правило состоит в следующем: необходимо отделять то, что требуется сделать, от того, каким образом это можно сделать. Для иллюстрации в качестве примера рассмотрим, как в административном совете округа ведется учет списков избирателей и наблюдение за порядком голосования. Одна из задач здесь — регистрация избирателей. Предположим, что для решения этой задачи, а также для ведения списков избирателей разрабатывается автоматизированная система на базе ЭВМ. Отдельные функциональные элементы такой системы должны осуществлять регистрацию новых избирателей, корректировать регистрационную информацию в тех случаях, когда избиратели меняют место жи-
Введение 21 Избиратель Окружной отдел I здравоохранения Окружное административное управление (Избирательный участок Рис. 1.1. Потоки документов при составлении списков избирателей.
22 Глава 1 тельства или когда в округе вводится новое деление на избирательные участки, удалять из списков лиц, лишенных права голоса, выдавать демографические отчеты, подготавливать списки для избирательных участков. Следующий уровень анализа может состоять в изучении потока документов, циркулирующих при существующей методике работы со списками избирателей. Для того чтобы отобразить перемещение бумаг между различными служащими и учреждениями, целесообразно использовать функциональные схемы, подобные изображенной на рис. 1.1. На ней треугольниками отмечена информация о регистрации избирателей, которая хранится в административном управлении округа в двух различных формах. Первая форма обеспечивает быстрый доступ к данным, когда необходимо внести изменения в списки избирателей. Вторая содержит списки по всем избирательным участкам, составленные в алфавитном порядке. Служащие административного управления занимаются, в частности, тем, что проверяют, не противоречат ли записи друг другу, учтены ли в обоих наборах данных новые границы избирательных участков, внесены ли в оба набора все изменения регистрационной информации. На рис. 1.1 показана лишь часть связей между элементами упомянутых форм и процедурами их обработки. Полный их перечень должен быть получен на следующем, более детальном уровне описания задачи. При исследовании процесса регистрации избирателей и разработке соответствующей автоматизированной системы решение всех вопросов, относящихся к ее машинной реализации, можно отложить до последнего, наиболее детального этапа анализа. Схемы, отображающие потоки документов, помогают лучше понять структуру процесса обработки*, определить состав входной и выходной информации. Для анализа логических связей между элементами данных и процедурами обработки отдельных фрагментов данных могут применяться схемы других типов. Так или иначе, процесс анализа проблемы исходит из функционального описания системы в целом, затем составляются функциональные описания ее отдельных частей,, после чего исследуются информационные потоки и, наконец, определяется структура данных. 1.3.2. Структурное проектирование Логические связи, существующие между различными элементами данных, составляют основу всего процесса проектирования. Были предложены различные методы построения этого процесса. В одних методах проектирование базируется на системном анализе, согласно которому на первом этапе составляется общий обзор функционирования системы и перерабатываемых в ней данных, после чего система постепенно расчленяется
Введение ' 23 на все более мелкие составные части. Другие методы исходят из базовых элементов какого-то одного класса, будь то данные или процедуры обработки, и целиком строятся на их основе. В любом случае центральную роль играет исследование структуры и взаимосвязей, а не частные вопросы машинной реализации. Начинается ли процесс проектирования с общего обзора системы или с анализа взаимодействия ее компонентов, он ведется на полуабстрактном уровне. Система проще адаптируется к внешним условиям, если ее структура в минимальной степени зависит от конкретного выбора технических средств или программного обеспечения. Более того, очень часто выбор тех или иных способов программной реализации может быть осуществлен jia достаточно поздних стадиях проектирования. ПРОЦЕСС ПРОЕКТИРОВАНИЯ ДОЛЖЕН БЫТЬ СТРУКТУРИРОВАН Проектирование становится более целенаправленным, если в его основе лежат зависимости между данными, присущие решаемой проблеме, а не условия, диктуемые вычислительной средой. Функциональные связи между программами могут быть определены еще до того, как начнется разработка соответствующих алгоритмов. На этапе проектирования вопросы реализации решаются на абстрактном уровне с использованием диаграмм, таблиц, структурных схем и псевдокодов. Эта информация обеспечивает возможность первоначальной проверки системы. Если проект системы или программы разработан на достаточно детальном уровне, допускающем моделирование основных процессов обработки данных, количество ошибок, возникающих на стадии реализации, резко снижается. Ошибки на этой стадии обходятся весьма дорого, поскольку к этому времени в проект вложено слишком много усилий. Гораздо проще вносить коррективы-в проект на этапе разработки, чем вновь возвращаться к нему уже после его завершения. Любая прикладная система, подобно рассмотренной выше автоматизированной системе регистрации избирателей, включает не только программы, но также все данные и архивы данных. Этапы исследования и разработки системы могут считаться завершенными лишь после того, как определены все перечисленные компоненты, вплоть до структуры отдельных информационных единиц и наиболее элементарных процедур обработки. Нельзя говорить о завершенности проекта до тех пор, пока не составлено подробное описание взаимодействия различных частей системы, не подготовлены тестовые задачи и не проведена предварительная проверка, на основании которой делается вывод о том, насколько система отвечает требованиям технического задания.
24 Глава 1 1.3.3. Реализация и испытания Разработка программы и ее написание — это процессы, протекающие при ограничениях, принципиально отличающихся друг от друга. Если в ходе разработки преследуется цель выполнить требования, предъявляемые пользователем, то при написании, программ в качестве ограничений выступают требования, диктуемые особенностями аппаратного и программного обеспечения,, а также практикой, сложившейся в данном вычислительном центре. Соотношение между разработкой и реализацией программы примерно такое же, как между проведением исследования и составлением технического отчета. В идеале исследование должно быть полностью закончено и набросана схема отчета,, прежде чем можно будет приступить к его написанию. На практике, разумеется, все это далеко не всегда осуществимо. Как правило, приходится неоднократно вносить изменения и возвращаться к началу процесса. Конечно, эффективное планирование облегчает последующую реализацию, но тем не менее всегда следует проявлять достаточную гибкость, чтобы проблемы, возникающие на этапе реализации, не приводили к коренному пересмотру проекта. ЗАРАНЕЕ ПЛАНИРУЙТЕ ПРОЦЕСС ПРОГРАММНОЙ РЕАЛИЗАЦИИ На этапе реализации кодирование модулей не вызывает особых затруднений, если проект продуман достаточно тщательно. По мере написания программ модули испытываются сначала по отдельности, а затем во взаимодействии. РЕАЛИЗАЦИЯ ДОЛЖНА ПРОВОДИТЬСЯ ПО МОДУЛЬНОМУ ПРИНЦИПУ Подобно проектированию, процесс реализации необходимо структурировать. Для этого разработанную систему следует разделить на отдельные части, объединенные либо горизонтальными, либо вертикальными связями. Наиболее важные с точки зрения их функций модули следует программировать в первую очередь. В результате будет образована многоуровневая иерархия модулей и составлен сетевой график реализации, включающий промежуточные этапы проверки взаимодействия модулей. В конечном итоге вся система в целом будет испытана и готова к внедрению в промышленную эксплуатацию. На э^ой стадии организация, отвечающая за внедрение, должна продемонстрировать будущим пользователям, что система функционирует согласно требованиям технического задания. Однако до сих пор не встречалось еще системы, не содержащей никаких oiuf- бок, а если таковая система и была, то никто не смог бы поручиться за их отсутствие. Нетрудно доказать, что ошибки имеют-
Illlllllll"" L Введение 25 <ся, но доказать, что их нет, практически невозможно. Для того чтобы выявить и исправить максимально большее число ошибок, испытания программной системы должны планироваться так же тщательно, как и процесс ее реализации. ЗАРАНЕЕ ПЛАНИРУЙТЕ ИСПЫТАНИЯ СИСТЕМЫ 1.4. Вспомогательные средства проектирования I С полным основанием можно утверждать, что ключом к по- I жышению эффективности процесса программирования является I планирование. Были разработаны различные вспомогательные средства, позволяющие упростить планирование всех этапов проектирования. В некоторых вычислительных центрах применяются специализированные языки для подготовки технических заданий, которые помогают при разработке программ так же, как языки программирования — при их реализации. Основное назначение подобных языков состоит в том, чтобы информировать проектировщика обо всех характеристиках, которые могут включаться в спецификации. Пока существуют лишь экспери- I ментальные варианты таких языков, и широкого распростране- I «ия они еще не получили. I ИСПОЛЬЗУЙТЕ ИМЕЮЩИЕСЯ ВСПОМОГАТЕЛЬНЫЕ СРЕДСТВА I Опубликовано немало книг, в которых даются рекомендации I fio составлению технических заданий. Обычно эта информация | приводится либо в виде графической схемы задания, либо в виде развернутого плана проекта, в котором более подробно раскрывается содержание технического задания. 1.4.1. Графическая схема задания Графическая схема задания представляет собой схему, построенную по иерархическому приципу и охватывающую все во- | просы, связанные с разработкой проекта. На рис. 1.2 приведен I один из возможных вариантов графической схемы задания на проектирование программной системы. Содержимое схемы по- I дробно поясняется в разд. 1.4.2. Графическая схема детализиру- I стся в тексте развернутого плана задания, а каждый из входя- I Щих в нее блоков подробно прорабатывается на более поздних стадиях проектирования. Графические схемы задания и развернутые планы проекта должны включаться в состав системной Документации. Каждая графическая схема задания должна сво- I <>одно умещаться на одной странице. Поэтому рекомендуется 1 строить ее таким образом, чтобы она содержала не более трех I основных блоков по горизонтали и пяти-шести связанных с ни- \ ми блоков по вертикали. L
26 Глава 1 Проект системы Введение Вычислительная среда 1 1 Функции системы 1.2 Сфера применения 13 Сбор и корректировка данных ^\ Связь с внешней средой 21 Технические средства 22 Программные средства 2.3 Режимы работы 1 4 Выпускаемые отчеты Качество системы 31 Вход системы Соблюдение |—| стандартов и обозначений 3.2 Выход системы 3.3 Управляющие параметры 34 Рабочие инструкции [Документация 41 4.2 Универсальность системы 43 Надежность функционирования 5.1 Пособия и руководства 5.2 Спецификации программ 5.а Организация данных 44 Защита информации Рис. 1.2. Графическая схема задания для проекта системы. ШИРЕ ИСПОЛЬЗУЙТЕ ИЛЛЮСТРАТИВНЫЕ МАТЕРИАЛЫ Схему, приведенную на рис. 1.2, можно рассматривать как общий план проектирования системы. Именно таким представляется процесс проектирования автоматизированной системы извне. От системы к системе графическая схема задания меняется незначительно. Основные отличия проявляются в деталях, содержащихся в развернутом плане проекта. 1.4.2. Развернутый план проекта системы 1. Введение. Дается общая характеристика системы, в достаточной степени подробная, чтобы будущий пользователь мог принять решение о том, отвечает ли система его требованиям. 1.1. Функции системы. Поясняется назначение прикладной системы, приводится перечень основных процедур и обрабатываемых данных. 1.2. Сфера применения. Характеризуется круг пользователей, на которых ориентирована разрабатываемая система.
Введение 27 1.3. Сбор и корректировка данных. Описываются источники исходных данных, поступающих в систему, а также источники данных, используемых для корректировки. В этот пункт следует включить планы и графики корректирования данных. В дальнейшем информация используется как руководство при детальной проработке программ корректировки данных. 1.4. Отчеты. Описываются формы, определяются периодичность и общее содержание отчетов, выдаваемых системой. Эта информация служит основой для последующей детальной проработки программ генерации отчетов. 2. Вычислительная среда. Определяется минимальный состав оборудования, необходимого для нормального функционирования системы. 2.1. Технические средства. Описывается конфигурация технических средств, указывается требуемый объем оперативной памяти, определяются ограничения на сегментацию памяти, требования к внешним устройствам и т. д. 2.2. Программные средства. Указываются типы операционных систем, используемые библиотеки стандартных программ, системы управления базами данных и т. д. 2.3. Режимы работы. Определяется возможность функционирования системы в условиях пакетного режима, интерактивного режима, режима реального времени или их комбинаций. 3. Связь с внешней средой. Описывается взаимодействие пользователей с системой. 3.1. Вход системы. Определяются форматы данных всех типов, вводимых пользователями, а также внутренняя структура данных. Эта информация служит руководством при разработке бланков входных форм и подготовке данных. 3.2. Выход системы. Описываются форматы отчетов, сообщений и других выходных форм. Эта информация используется при составлении планов и подготовке данных. 3.3. Управляющие параметры. Перечисляются параметры, задаваемые при настройке системы на конкретную конфигурацию технических и программных средств. 3.4. Рабочие инструкции. Дается общий обзор содержания инструкций, касающихся обращения с лентами, хранения бумаги и т. д. Данная информация используется при составлении инструкций для обслуживающего персонала. 4. Качество системы. 4.1. Соблюдение стандартов и общепринятых обозначений. Указывается, в какой мере система соответствует стандартному варианту языка программирования и отвечает стандартам версии, эксплуатируемой в данном вычислительном центре. Кроме того, определяется степень использования общеупотребительных сокращений и математических обозначений. Это позволяет оценить трудоемкость сопровождения системы.
28 Глава 1 4.2. Универсальность системы. Обсуждается уровень незави- 1 симости системы от конкретных внешних условий, с учетом ко- 1 торых она разрабатывается. Это характеризует сложность пе- 1 ревода системы на другие вычислительные установки. 1 4.3. Надежность функционирования. Рассматриваются такие | вопросы, как ожидаемое время наработки на отказ, способы 1 корректировки ошибок, проверка достоверности информации, I точность результатов, статистические характеристики всех мо- 3 дулей, осуществляющих вероятностные расчеты, например гене- ] раторов псевдослучайных чисел. 4.4. Защита информации. Описываются средства, обеспечи- «■ вающие сохранность данных и авторизацию доступа, используемые способы кодирования. i 5. Документация по системе. J 5.1. Пособия и руководства. Приводится перечень документации, прилагаемой к системе,— пособий, форм отчетности, рабочих описаний, системной и программной документации. | 5.2. Спецификации программ. Дается общее функциональное описание отдельных программ, входящих в состав системы. Эта информация служит руководством при разработке программ. 5.3. Организация данных. Приводится общее описание взаимодействия отдельных информационных потоков в системе. Эти сведения используются при разработке принципов организации j данных. j Документы, содержащие общее описание проекта, служат, с одной стороны, своеобразным эталоном, на основании которого осуществляется проверка законченной системы, с другой — используются как руководства на этапах разработки, реализаций и испытаний. 1.4.3. Организация процесса проектирования Графическая схема задания и развернутый план проекта определяют те качества, которыми должна обладать система. Они также указывают в общей форме основные направления проектирования. Однако эти документы не могут служить планом проектных работ. В процессе разработки и реализации системы решается широкий круг задач, в том числе такие задачи, как составление рабочих спецификаций, составление перечня характеристик, внешнее описание данных, внешнее описание программ, разработка архивов данных, разработка программных модулей, разработка тестовых задач, кодирование программных модулей, проверка программных модулей,
Введение 29» объединение программных модулей, испытание системы в целом, комплектация сопровождающей документации. Поскольку многие из перечисленных задач связаны друг с другом, возникает необходимость в планировании последовательности их решения. Для анализа распределения работ часто» применяются так называемые ПЕРТ-диаграммы. Эти диаграммы .оказываются полезными и при планировании распределе- Составление внешних описаний данных Начало работ Подготовка ^тестовых данных Отладка Испытания программ- программной Составление внешних описаний Процедур обработки 0—0 Окончание работ Разработка архивов данных Разработка программных модулей Рис. 1.3. ПЕРТ-диаграмма процесса проектирования системы. ния ресурсов в прикладных системах. Данный вопрос подробно обсуждается в гл. 2. ПЕРТ является прямой транскрипцией английского сокращения PERT, составленного из первых букв названия «program evaluation review technique» (методика анализа и корректирования планов). ПЕРТ-диаграмма представляет собой граф„ содержащий описания работ и событий и характеризующий процесс взаимодействия работ во времени. Ребра графа обозначают работы, его вершины — события. Обычно под событием понимается завершение одной работы и начало другой, причем вторая не может быть начата, прежде чем завершится первая. На рис. 1.3 приведен сокращенный вариант ПЕРТ-диаграммы^ описывающей процесс проектирования прикладной системы. Поставив в соответствие каждой работе ожидаемое время ее выполнения, можно определить максимальный по продолжительности путь от начальной до конечной вершины графа, который называют критическим путем проектирования системы. На этапах разработки и реализации могут применяться разнообразные методы организации-проектных работ. Они включа-
«30 Глава 1 ют создание ведущих групп, сквозной коллективный анализ 1 проекта, свободное обсуждение программ. 1 В ведущие группы входят специалисты по программирова- I нию и лица из вспомогательного персонала, работающие сов- 1 местно над проектом с самого начала и до окончательного его I завершения. Один из них — главный программист — несет от- I ветственность за разработку программы и координацию дея- | тельности членов группы. Ему также предоставляется право вы- I ступать от имени всей группы. Имея коллектив людей, тесно I сотрудничающих между собой в течение определенного време- 1 ни, можно более целенаправленно руководить ходом работ и J ожидать более качественных результатов. \ Сквозной анализ проекта предпринимается с целью выявле- \ ния таких ошибок, как отсутствие спецификаций или неправиль- \ ное толкование имеющихся спецификаций, и проводится он на \ той стадии, когда исправление ошибок еще не вызывает особых J затруднений. Специалисты, работавшие над индивидуальными I заданиями, осуществляют совместный сквозной просмотр про- 1 екта, используя специально подобранные тестовые наборы дан- i ных. С помощью подобных просмотров спецификации отдельных i блоков системы или описания их взаимодействия проверяются до того, как фактически начнется кодирование соответствующих | программных модулей. \ Свободные обсуждения — это анализ текстов исходных мо- j дулей, проводимый всей группой. Поскольку проект представ- * ляет собой не механическую сумму результатов, полученных от- | дельными программистами, а продукт их коллективной деятель- | ности, то такие обсуждения позволяют выявлять логические ] ошибки, описки и несоответствия в спецификациях. \ 1.5. Системная документация Первым из документов, которые должны выпускаться в процессе проектирования, являются исходные требования к системе, согласованные между будущими пользователями и системным аналитиком. Многие части документации не имеют непосредственного отношения к программистам. В нее входят инструкции для персонала, занимающегося подготовкой входных данных, описания процедур контроля для управляющего персонала, описания тестовых процедур, а также рабочие инструкции для операторов. В ходе проектирования выпускается документация двух типов— рабочая (промежуточная) и отчетная (окончательная). При разработке сложных проектов часто приходится выделять специального человека — секретаря проекта, который занимается оформлением документов и сбором рабочей документации. Любой программист вспомнит немало случаев, когда он выбра-
Введение 31 сывал распечатки старых вариантов программы, а впоследствии оказывалось, что без них не обойтись. Секретарь проекта как раз и занимается тем, что подшивает все промежуточные распечатки, старые блок-схемы, спецификации, тестовые данные и тому подобные материалы, и хранит до тех пор, пока не завершатся окончательные испытания системы. Некоторые из ра- 1 Требования к системе Системная документация I 2 Проектная документация 2.1 Проект системы 2.2 Подготовка данных 2.3 Разработка программ 3 Пособия и руководства 3 1 Руководство пользователя ъ\ Руководство по обслуживанию системы 3.3 Руководство оператора 4 Реализация системы > — - 4 1 Символьный код 42 Информация, выдаваемая ЭВМ 4.3 Тестовые прогоны программ Рис. 1.4. Графическая схема задания на разработку системной документации^ бочих документов в дальнейшем должны войти в состав отчетной документации. Примерный перечень отчетной документации по системе приведен на рис. 1.4. Проектная документация служит основным; источником информации, на основании которой осуществляются разработка системы, ее эксплуатация и обслуживание.-Ниже- дается развернутый перечень системных докумецдчэв. 1. Требования к системе. Составление требований к системе- не входит в задачу программистов. Они являются специалистами по программированию, а не по гражданскому строительству, банковскому делу, численным методам или в любых других прикладных областях. В то же время составление требований к системе не может быть поручено и одному лишь системному аналитику. Они должны вырабатываться в соответствии с официально оформленным договором между организацией-заказчи-
22 Глава 1 ком и организацией-исполнителем и выражать содержание этого документа. 2. Проектная документация. Эта часть документации готовится системным аналитиком, который руководствуется сведениями, полученными от программистов и будущих пользователей. 2.1. Проект системы. К этим документам относятся графические схемы задания и развернутые планы проекта, охватывающие структуру и основные детали системы. 2.2. Подготовка данных. Этот раздел охватывает все уровни организации данных, которые будут использоваться в программах, в том числе справочники данных, описания файлов, таблицы ссылок и т. д. 2.3. Разработка программ. В этот раздел входят описания иерархической структуры программ в системе, потоков информации между программами и организации взаимодействия программ. Сюда же должна быть включена информация о внутреннем содержании программ, например описания алгоритмов, их блок-схемы или программы на псевдокоде. 3. Справочные пособия и руководства. Эти издания подготавливаются специалистами по технической документации, которые используют информацию, полученную от системного аналитика и программистов. 3.1. Руководство пользователя. В руководстве содержатся общее описание системы и подробные сведения о ее применении, а также разъясняются сообщения об ошибках. 3.2. Руководство по обслуживанию системы. В это руководство включаются отдельные разделы проектной документации и документы по реализации системы, необходимые для глубокого ознакомления с организацией данных и функциями программ. 3.3. Руководство оператора. В руководстве содержатся описания вычислительной установки и конфигурации устройств, а также инструкции по работе с программной системой. 4. Реализация системы. Данная часть документации отражает, результаты деятельности группы программирования. 4.1. Символьный код. Это набор текстов самодокументированных программ, составленных в удобной для восприятия форме. 4.2. Информация, выдаваемая ЭВМ. Сюда входят таблицы перекрестных ссылок, карты загрузки, таблицы атрибутов, данные о времени выполнения программ и любая другая машинная информация. 4.3. Тестовые прогоны программ. В эту часть документации включаются контрольные варианты входных данных и соответствующие результаты, получаемые в различных условиях. Указанная информация служит в качестве тестовой при проверке функционирования системы.
Введение 33 ХРАНИТЕ ВСЮ ПРОМЕЖУТОЧНУЮ ДОКУМЕНТАЦИЮ ВПЛОТЬ ДО ЗАВЕРШЕНИЯ РАБОТЫ НАД СИСТЕМОЙ После того как проектирование системы закончено, единственное назначение документации — обеспечить максимальную эффективность использования системы. Поскольку к системе имеют отношение различные группы людей, для каждой из них должны быть подготовлены определенные документы. В то время как для персонала, занимающегося сбором информации и вводом данных в ЭВМ, нужны лишь описания некоторых форм и инструкции по их заполнению, лицам, ответственным за обслуживание системы, необходимо иметь доступ к возможно большей части документов. Для этого персонала в документации должны быть предусмотрены разделы, содержащие конспективную информацию, позволяющую быстро разобраться в том, как действует система в целом, или наметить план работ по модернизации системы. Комплект документации строится по модульному принципу, но отдельные ее части в значительной степени перекрывают друг друга. Желательно, чтобы сборник документов предварялся общим обзором их содержания. Целесообразно также включать в этот сборник предметный указатель и перечень ссылок. 1.6. Упражнения 1. Предположим, вам предстоит взять книгу в абонементе библиотеки. Рассмотрите эту процедуру, руководствуясь следующим перечнем вопросов: а) Сколько различных бланков приходится заполнять? б) Какого рода информация заносится в бланки? в) Кто работает с этими бланками? г) Какая дополнительная информация может понадобиться любому из участников этой процедуры? 2. Предположим, имеются теннисные корты, доступ на которые разрешен только по предварительным заявкам. а) Сколько различных категорий людей принимает участие в оформлении заявки? б) Какого типа информация запрашивается? в) Будут ли чем-нидубь отличаться процедуры оформления заказа, если корты принадлежат закрытому клубу? городскому управлению спортивными сооружениями? 3. Если библиотека (упр. 1) решит автоматизировать выдачу книг на дом, то на какие вопросы о проведении этой процедуры требуется получить ответы разработчику программ? Рассмотрите возможные варианты ответов и способы их учета при программной реализации. Составьте также подробные описания процедур получения книги в библиотеке. 4. Пусть в помещении библиотеки (упр. 1) имеется один основной стол выдачи книг на дом и два выхода, где проверяют оформление взятых книг. Предложите возможный вариант реализации автоматизированной системы выдачи, включающий программные и технические средства. Как будет функционировать система и не нужно ли будет внести какие-то изменения в существующую процедуру? Желательно, чтобы соображения по этим вопросам были изложены в форме развернутого плана проекта. 3—399
34 Глава 1 5. Если в административном управлении избирательного округа решат создать базу данных, в которую информация о регистрации избирателей могла бы вводиться по мере ее поступления (в режиме реального времени), то кам это отразилось бы на схеме потока документов, проведенной на рис. 1.1? Рассмотрите изменения, затрагивающие избирателей, окружное административное управление, окружное управление здравоохранения, служащих избирательного участка. 6. Разработайте развернутый внешний план проекта системы оформления предварительных заявок на пользование теннисными кортами (упр. 2), при условии что корты принадлежат закрытому клубу и могут посещаться только его членами. Предусмотрите, чтобы плата за пользование включалась в ежемесячный счет, оплачиваемый членом клуба.
Глава 2 ПРОЕКТИРОВАНИЕ СИСТЕМ Программисты предпочитают мыслить категориями программ, а не категориями систем. Между тем только в самых простых случаях поставленную задачу наиболее успешно удается решить путем разработки одной программы. Например, с помощью отдельной программы можно получить численное решение некоторой технической проблемы. Но уже для сопровождения финансовой документации или управления вычислительными ресурсами более эффективными оказываются системы, состоящие из нескольких программ. Число программ в системе зависит как от сложности, так и порядка поступления исходных данных. Для проведения вычислений с данными из входного набора простой структуры может быть достаточно и одной программы. Если же данные относятся к нескольким типам или элементы данных поступают из разных источников в различные моменты времени, может потребоваться ряд связанных друг с другом программ. 2.1. Определение основных компонентов системы Простейшая система включает один входной поток данных, один выходной поток данных и одну программу (содержащую, возможно, подпрограммы). Если элементы данных поступают из нескольких источников и обрабатываются не вручную, а с помощью ЭВМ, для различных источников должны быть предусмотрены разные входные потоки. Если, кроме того, конечные результаты предполагается использовать несколькими способами, необходимо иметь соответствующее число отдельных выходных потоков. Данные, необходимые в течение достаточно продолжительного времени, обычно отправляются на хранение в машинной форме, воспринимаемой только ЭВМ. Данные, характеризующие текущую ситуацию, могут подвергаться корректировке. В частности, должны регулярно обновляться записи, содержащие информацию о деятельности предприятия. Изменения вносятся также в исходные программы, отлаживаемые в интерактивном режиме. Данные, получаемые в ходе длительного эксперимента, регулярно пополняются новыми записями. Во всех перечисленных случаях система должна объединять по 3*
36 Глава 2 меньшей мере три программы, выполняющие следующие основ- I ные функции: 1 а) запоминание данных; I б) корректировку данных; 1 в) использование хранящихся данных. ] Если система состоит из нескольких программ, в ней долж- 1 но циркулировать несколько различных потоков данных. Для ] каждой программы могут быть определены два потока дан- 1 ных — входной и выходной. Если программа предназначена для | корректировки данных, она имеет два входных потока. Первый I из них содержит данные, подлежащие корректировке, второй— j новую информацию. Если выходной поток направляется не на j печатающее устройство или экран дисплея, должен быть пред- j усмотрен дополнительный выходной поток, в котором отражался | бы процесс выполнения программы, отмечалось бы ее успешное | или аварийное завершение, а также фиксировались бы любые | отклонения от нормы. Если основной выход содержит только \ данные, представленные в машинной форме, оператору или | пользователю, работающему с системой, должна быть предо- | ставлена оперативная информация о ходе программы. Кроме 1 того, программа может выдавать резервные дубликаты данных, 1 результаты контрольных проверок и другую вторичную инфор- I мацию в машинном коде. I На рис. 2.1 приведена упрощенная структура системы сопро- j вождения данных. Процессы, осуществляющие преобразование ] данных, представлены на ней кружками, а потоки данных, цир- j кулирующие между процессами,— стрелками. Каждый процесс 1 реализуется в виде одной или нескольких программ. Длительное ] хранение данных показано штриховой линией, которая связывает потоки, содержащие два главных списка. Часть других потоков может передаваться на временное хранение. Если из- | менения данных фиксируются ЭВМ и подвергаются предвари- | тельной обработке с целью их упорядочения и устранения оши- \ бок, система должна содержать дополнительные программы и i потоки данных, кроме перечисленных выше. Структуру, близкую , к описанной, имеют многие автоматизированные системы управ- ; ления и ведения документации. ! Представленная на рис. 2.1 схема прежде всего отображает порядок прохождения данных через систему. Хотя о прикладных системах обычно принято судить как о наборах программ, сами , данные имеют более важное значение, чем компоненты, осуще- j ствляющие их переработку. Если программа повреждена, ее можно перезаписать. С восстановлением данных дело обстоит j значительно сложнее. По этой причине приходится периодически производить копирование данных, а иногда и хранить записи обо всех проведенных корректировках. Эти операции требуют введения в систему дополнительных выходных потоков и соот- \ \ \
Исходные данные Оперативный отчет Оперативный отчет Отчет Рис. 2.1. Упрощенная структура системы сопровождения данных. Исход ные дан- ные/Сорти ровка данны Рис. 2.2. Структура усложненной системы сопровождения данных. Исходный код/я^ы^° \трансля тор Ошибки на шаге \L трансляции Ошибки на шаге выполнения Рис. 2.3. Структура системы, предназначенной для выполнения двухшаговых заданий.
1 38 Глава 2 ветственно программ, которые их формируют. Структура систе- I мы сопровождения данных с учетом указанных дополнений при- 1 ведена на рис. 2.2. 1 Вне среды, приспособленной для его использования, набор I данных не представляет никакой ценности, однако в рамках та- 1 кой среды именно набор данных приобретает основное значение. I Это утверждение справедливо и по отношению к приложениям, 1 связанным с научными расчетами или с разработкой программ- 1 ного обеспечения ЭВМ. Например, программа может осу- 1 ществлять статистический анализ данных, извлекаемых из раз- I личных наборов, выполняя сложные действия, которые невоз- I можно воспроизвести вручную, однако без данных даже самая | изощренная программа теряет всякий смысл. Очевидно, целесо- | образно рассматривать прикладную систему как некоторую со- i вокупность данных и операций, определенных на этих данных, | а не просто как набор программ, взаимодействующих между | собой посредством обмена данными. Программы — это всего I лишь рабочие инструменты, в то время как данные — это своего 1 рода исходное сырье, из которого вырабатывается конечный II продукт. Студенты, которым предстоит выполнить на первый I взгляд элементарную операцию — прогнать на ЭВМ свои прог- 1 раммы, на самом деле сталкиваются с ситуацией столь же J сложной, что и описанная выше. Действительно, студенческая 1 программа, написанная на языке высокого уровня —например, 1 на Коболе, Фортране или ПЛ/1,— сдается на машину вместе с 1 данными для этой программы. Таким образом, возникают два I входных потока, содержащих исходный код и данные. Процесс I обработки состоит по меньшей мере из двух этапов. Сначала 1 обрабатывается исходный код, затем обрабатываются данные. I При этом можно выделить несколько выходных потоков: 1 листинг исходного кода и либо сообщения об ошибках, либо ре- I зультаты вычислений (часто то и другое). Хотя все необходи- I мые действия могут быть выполнены одной программой, напри- \ мер интерпретатором, обилие входных и выходных потоков ча- I ще всего вынуждает использовать несколько программ I (рис. 2.3). I Стандартные ситуации типа «компилируем много раз — вы- I полняем несколько раз» со студенческими программами или I «компилируем один раз — выполняем много раз» с программа- j ми, составленными профессионалами, становятся возможными || потому, что языковые трансляторы вырабатывают объектные к коды. Если же эти коды стандартизованы, исходные коды раз- 1 личных типов могут транслироваться в единый объектный код и | за счет этого одни и те же управляющие программы шага вы- | полнения могут применяться при работе не с одним, а с не- I сколькими языками высокого уровня. В частности, благодаря | тому что трансляция ведется на один машинный язык, можно 1
Проектирование систем 39 на одной и той же машине выполнять программы как на Коболе, так и на Фортране. Встречаются также ситуации противоположного характера, когда для работы с одним объектным кодом, но на машинах различных типов используются разные наборы управляющих программ шага выполнения, как это делается, например, с Р-кодом в языке Паскаль. Исходный код на Коболе / Тр-анс -3*4 пятор с , Кобола Листинг программы на Коболе Исходный код Транс- \ на Фортране лятор с фортрана/ Листинг программы на Фортране Рис. 2.4. Структура системы для работы с языками высокого уровня. В большинстве ЭВМ машинный язык является одновременно языком объектных кодов, вырабатываемых любыми языковыми трансляторами. Иногда вместо перевода непосредственно на машинный язык трансляторы выдают промежуточный код на языке Ассемблера. Одно из преимуществ такого подхода состоит в том, что программы на языке Ассемблера сравнительно легко поддаются отладке. Применяя различные варианты Ассемблера, программу можно выполнять на несколько отличающихся друг от друга машинах, либо использовать одни ЭВМ в качестве эмуляторов ЭВМ других типов. Структура подобной системы приведена на рис. 2.4, где показано также, как объектный код Дополняется стандартными программами, уже имеющимися в системной библиотеке. Возможность такого объединения обеспе-
40 Глава 2 чивается за счет разделения этапов компоновки и выполнения программ. В тех случаях, когда прикладная система формируется из отдельных программ, иногда необходимо проверить работу всех этих программ во взаимодействии. В некоторых языках высокого уровня предусмотрены средства объединения отдельных модулей под управлением специальных программ-препроцессоров. Однако и не имея таких средств, можно выполнять программы в рамках единого задания, используя программу, написанную на языке управления заданиями. Процесс проектирования системы включает определение различных потоков данных (входных, промежуточных, выходных), формализацию процедур обработки, выполняемых над этими потоками, выбор методов обработки потоков, последовательных или параллельных, и, наконец, разработку отдельных программ, способов хранения данных и потоков данных. При использовании мультипрограммных операционных систем необходимо, кроме того, установить, должны ли сами процедуры обработки осуществляться последовательно, или некоторые из них допускают параллельное выполнение. 2.1.1. Определение потоков данных Потоки данных в системе определяются согласно следующим правилам: а) Каждому источнику данных соответствует один входной поток. б) Если имеется совокупность наборов данных, получаемых из нескольких источников, эти наборы распределяются по группам обрабатываемых совместно потоков данных. в) Если не все потоки данных подвергаются обработке одновременно, процесс обработки делится на этапы, в каждом из которых участвует группа совместно обрабатываемых потоков. Кроме того, должны существовать внутренние потоки данных, связывающие последовательные этапы. г) Для каждого этапа обработки в системе выделяется основной выходной поток, содержащий результаты обработки, и дополнительный поток для выдачи оперативных отчетов, сообщений об ошибках и другой вспомогательной информации. ДЛЯ РЕАЛИЗАЦИИ ОТДЕЛЬНЫХ ФУНКЦИИ ЦЕЛЕСООБРАЗНО ИСПОЛЬЗОВАТЬ ОТДЕЛЬНЫЕ ПРОГРАММЫ 2.1.2. Определение процессов После того как определены потоки данных, необходимо определить процессы, оперирующие этими потоками: а) Если потоки данных обрабатываются порознь, для каждого из них требуется отдельный процесс.
Проектирование систем 41 б) Если некоторые системные функции должны выполняться в разное время или чаще, чем другие, они реализуются в виде отдельных процессов. в) Если некоторые из промежуточных потоков данных необходимо сохранять с целью их последующего использования, должны быть предусмотрены: процесс, с помощью которого осуществляется их запоминание, процесс для сопровождения таких потоков (если потребуется корректировка) и процесс для поиска и обработки данных. Определив всю совокупность процессов, следует выяснить, нет ли ранее написанных программ, которые могли бы выполнять часть необходимых функций. Так, для работы с проектируемым компилятором можно использовать уже имеющийся загрузчик, а для работы с прикладной программой подготовки отчетной документации, обрабатывающей отсортированные файлы,— стандартную программу сортировки. Не исключено также, что имеются и некоторые из требуемых входных величин— либо представленные в машинной форме, либо подготовленные для ввода с терминала. ИСПОЛЬЗУЙТЕ ВЕСЬ ИМЕЮЩИЙСЯ В РАСПОРЯЖЕНИИ МАТЕРИАЛ 2.1.3. Данные и их носители После того как процессы и потоки данных определены, необходимо связать с процессами конкретные программы, а с потоками данных — конкретные их носители. Желательно, чтобы те и другие были описаны в структурированной форме. Данные в системе могут циркулировать в виде дискретных единиц, таких, как записи, либо в виде непрерывного потока, если обработка производится в реальном времени. В качестве источников или приемников данных служат перфокарты, магнитные ленты и диски, терминалы или периферийные устройства других типов. Доступ к данным, хранящимся в ЭВМ, осуществляется либо непосредственно, либо с применением процедур поиска. Данные могут быть структурированы и не структурированы, упорядочены и не упорядочены. В одних случаях программы доступа необходимо писать заново, в других случаях можно воспользоваться существующими. Данные, хранящиеся на перфокартах или магнитных лентах, обрабатываются только последовательно, что обусловлено физической природой носителей. Данные на перфокартах нельзя использовать повторно в автоматическом режиме, без участия человека. Данные, записанные на ленте, допускают многократное использование одной и той же программой, хотя такой способ обработки неэффективен. Данные, передаваемые на такие
42 Глава 2 устройства, как перфоратор, принтер или накопитель на магнитной ленте, также должны обрабатываться последовательно. После того как пересылка данных завершена, их замена или выборка весьма затруднительна. Поэтому в тех случаях, когда программа нуждается в многократном доступе к данным, последние должны быть отправлены либо на временное, либо на постоянное хранение в устройство массовой памяти. В целом структура потока данных определяется их логической организацией и физическими особенностями носителя, на котором они хранятся. Очень важной является проблема планирования доступа к данным. В мультипрограммных системах одни и те же данные одновременно могут принадлежать к нескольким потокам. Если операционная система, а также состав технических средств ЭВМ обеспечивают одновременный доступ к данным со стороны нескольких пользователей, технические требования к прикладной системе могут содержать определенные ограничения на доступ. Если ЭВМ позволяет в каждый момент времени обращаться к данным только одному процессу, может возникнуть необходимость в разработке средств планирования процессов и коллективного доступа к данным. Пользователи, работающие с общими данными, могут применять как одни и те же, так и разные программы. Они могут реализовать различные процессы, например выборочное считывание данных или их корректировку. В тех случаях, когда одновременно нескольким пользователям разрешается выполнять одинаковые операции, они либо должны иметь индивидуальные копии программы и применять их по очереди, либо работать с одной программой, оформленной в виде реентерабельного модуля, в котором предусмотрены отдельные области данных для каждого пользователя. Вопрос о том, предоставляются ли разным пользователям отдельные копии или одна общая программа, решается прежде всего исходя из возможностей операционной системы ЭВМ, однако он тесно связан также со структурой прикладной системы. 2.2. Методы разработки данных Для демонстрации связей, существующих между отдельными компонентами системы, используются различные графические схемы. Некоторые из них, такие, как граф-диаграммы, отображают главным образом прохождение потоков данных между процессами. Другие, в частности функциональные, схемы выделяют моменты, связанные с хранением данных и используемыми для этого носителями. Существуют также схемы, в которых основное внимание уделяется взаимодействию процессов.
Проектирование систем 43 2.2.1. Граф-диаграммы Рис. 2.1—2.4, иллюстрирующие структуры прикладных систем, представляют собой граф-диаграммы, иногда называемые также графами потоков данных. Каждый кружок на такой диаграмме отображает некоторое преобразование данных. Потоки данных отмечаются стрелками. Этот тип схем можно использовать как на системном уровне для описания внешних входов и выходов программ, так и при проектировании самих программ для описания перемещений данных между отдельными модулями. Физические носители данных на схемах не указываются. На рис. 2.5 приведена граф-диаграмма системы, предназначенной для ведения документации в небольших торговых предприятиях. В системе применяется терминал на базе микро- ЭВМ, устанавливаемый непосредственно на рабочем месте продавца. Информация о совершенных продажах сначала заносится во внешнюю память, а затем используется при корректировке записей товарных запасов, записей счетов, предъявляемых к оплате, и файла общей бухгалтерской ведомости, а также при выдаче на печать бланка продаж. Система состоит из набора программ, обрабатывающих входные данные различных типов, и программ, осуществляющих ведение файлов и выдачу отчетов. Основные программы системы обслуживают файлы товарных запасов, общей бухгалтерской ведомости, платежеспособных счетов и счетов, подлежащих оплате. Эти же программы выводят на печать информацию из файлов. Сбор входной информации и выдача данных в виде специальных форм, таких, как бланки продаж и чеки на оплату, производятся отдельными, небольшими по размеру программами. Для пересылки данных между программами используются временные файлы. Система состоит из отдельных, но связанных между собой элементов. Некоторые из них требуют машинной обработки, другие обрабатываются вручную, в зависимости от организации делопроизводства. 2.2.2. Диаграммы Варнье—Орра На диаграмме Варнье — Орра в иерархической структуре системы выделяются ее элементарные составные части, которые снабжаются контурными изображениями носителей информации. На рис. 2.6 подобная диаграмма построена для системы сопровождения данных. Сначала система разделяется на ряд отдельных процессов. На следующем уровне иерархии указываются потоки данных для каждого процесса. Затем перечисляются наборы данных и наконец — соответствующие носители информации. Последние обозначаются с помощью стандартных условных изображений, применяемых на функциональных схе-
Сообщение о ~ ^продаже Главный файл товарных запасов f_ / \ Главный ^ файл гОбработка\ \ товарных записей \ \ запасов товарных запасов Счет- фактура г Обработка записей продаж ^Сообщение "об изменении товарных запасов Сообщение об изменений -у обработка ^ товарных запасов [ записей закупок f Сообщение Отчет о повторных заказах 'Бланк \> продаже Отчет о динамике продаж ^продаж \ __ Текущий бухгалтерский отчет YСообщение \о закупке Главный файл платеже-/ способ- / ных счетов f_ ! Обработка записей платежеспособных счетов *^ч Главный \файл платеже- \ способных счетов Сообщение о Платежеспособному счете Главный / файл / счетов / к оплате ^ j Обработка счетов к оплате \ Сообщениеj о счете к оплате Главный файл счетов к оплате Оплаченный счет г Обработка) (оплаченных] счетов Подробный отчет Отчет о поступлении счетов-фактур Оформление счетов клиентов Сводка продаж Сводка поступлений наличных средств Запись в общую ^бухгалтерскую Запись в общую ^ведомость бухгалтерскуюу ведомость /Сводка закупок /Подробный отчет (Отчет о поступлении счетов- f фактур Сводка выплат наличными Печать чеков Чеки Главный файл бухгалтерской ведомости^/ Обработка^ бухгалтерской ведомости '\ Главный файл \ бухгалтерской \ ведомости .Общая сводка Балансовый отчет Подробный отчет Итоговый отчет Сводка прибылей и убытков Рис. 2.5. Структура системы для ведения документации в небольшом торговом предприятии.
Проектирование систем 45 мах. Направления потоков данных отмечаются стрелками, проведенными между наборами данных и физическими носителями информации. Наборы данных, используемые одновременно в нескольких процессах, связаны между собой и имеют одинаковые имена. Диаграмма приведенного типа является обобщением ме- Система сопровождения < данных Создание 1 файла 1 Вход Выход 4 Корректировка^ файла i Вход < Выход Использование, файла *■ Г Вход Выход Исходные данные Оперативный- отчет Главный „ список Главный список Изменения жданных Оперативный | отчет Главный Ссписок Главный список Отчет С^ hQ С^ Л с^ Рис. 2.6. Диаграмма Варнье — Орра для системы сопровождения данных. тода фрагментации программ Варнье — Орра, который применяется главным образом при разработке программ. Более подробно этот метод рассматривается в последующих главах. 2.2.3. Функциональные схемы Функциональная схема системы состоит из одного или нескольких прямоугольных блоков, содержащих названия программ. Эти блоки соединяются входящими в них стрелками с источниками и исходящими из них стрелками — с приемниками данных. Источники и приемники изображаются в виде блоков, очертания которых напоминают определенные физические носители информации. В каждом блоке записано имя программы или набора данных; иногда оно дополняется информацией, раскрывающей назначение блока. Основное внимание в схемах этого типа уделяется описанию потоков данных в системе и используемых наборов данных.
46 Глава 2 Некоторые из стандартных символических обозначений, применяемых в функциональных схемах, приведены на рис. 2.7. Среди них можно выделить три различные разновидности символов. Блоками в виде прямоугольника, изображающего процесс, и трапеции, изображающей ручную операцию, отмечаются Процесс Перфокарта a Q Перфолента Магнитная лента 0= Пакет перфокарт Дисплей СП Память прямого доступа Оперативная память Носитель произвольного типа Линия связи Ручная операция Линия передачи данных Рис. 2.7. Стандартные условные обозначения на функциональных схемах. определенные действия, совершаемые над данными. Линии связи передачи данных указывают потоки данных. Все остальные символы обозначают различные физические носители информации. При построении функциональных схем внешние носители, такие, как перфокарты или бумага для печати, помещаются по бокам листа. Вдоль линий передачи должны чередоваться процессы и наборы данных. Процессы могут сообщаться друг с другом только с помощью наборов, и всякая пересылка данных из одного набора в другой должна осуществляться соответствующим процессом.
Проектирование систем 47 На рис. 2.8 приведены функциональные схемы фрагмента системы сопровождения файлов. Рис. 2,8, а иллюстрирует систему с файлами на магнитной ленте, в которой корректирующие данные вводятся с перфокарт. Выбор магнитной ленты в качестве носителя информации приводит к необходимости введения Рис. 2.8. Структуры систем корректировки главного файла. нового главного файла. После завершения записи всей необходимой информации новый главный файл начинает эксплуатироваться вместо прежнего! главного файла. Штриховая линия на схеме не обозначает поток данных, но просто указывает, что оба файла логически представляют собой одно и то же. На рис. 2.8,6 главный файл хранится в устройстве массовой памяти, а ввод корректирующей информации производится с клавиатуры терминала. Поскольку экран дисплея является лишь средством кратковременной визуализации, для получения документальной отчетности о проведенных корректировках система Дополнена регистрационным (контрольным) файлом.
I 48 Глава 2 2.3. Методы разработки средств управления 1 Все рассмотренные выше типы схем — граф-диаграммы, диа- I граммы Варнье — Орра, функциональные схемы — рассчитаны 1 на описание потоков данных в программно-управляемых систе- I мах, в которых только программы могут инициализировать или I прекращать генерацию потоков данных. Однако в приложениях, I для которых характерна работа в режиме реального времени, | некоторые из системных функций управляются не столько прог- i раммами, сколько самими данными, т. е. в таких системах не { процессы являются причиной, вызывающей некоторые переме- ^ щения данных, но, напротив, данные приводят в действие или I заставляют прекращаться определенные процессы. Так, регу- 1 лярная выдача месячного отчета инициируется программными ~ средствами, в то время как корректировка файла в интерактив- | ном режиме происходит потому, что пользователь терминала i имеет данные, которые он желает ввести с клавиатуры. Если | часть системы управляется данными, поступление заявок на ис- | пользование программ и информационных массивов может быть { предсказано лишь в вероятностном смысле. В одно и то же | время в активном состоянии могут находиться несколько про- | цессов. Эти процессы в свою очередь могут одновременно Я предпринимать попытки обращения к одним и тем же наборам | данных. На схемах, иллюстрирующих структуру системы, долж- )1 ны отражаться любые ситуации, когда возникает необходимость | в синхронизации процессов. Для этого можно либо расширить j набор обозначений, применяемых при составлении функцио- i нальных схем, либо использовать специальные схемы, напри- i мер ПЕРТ-диаграммы. ! А 2.3.1. Функциональные схемы, учитывающие синхронизацию I процессов I На функциональных схемах можно отобразить координацию я программ во времени, включив в число символов специальный л синхронизирующий интерфейс, помещаемый между программа- л ми и наборами данных. На рис. 2.9 показан вариант системы [I сопровождения файлов, дополненной средствами интерактивного Л доступа к данным. К наборам данных нельзя обращаться в ]1 процессе их объединения или корректировки. Запрещается так- А же выполнять корректировку наборов в то время, когда с ними || ведется работа другого типа. Это предполагает, что программа, | обратившаяся к файлу, после получения требуемой записи Я должна определенным образом известить, что файл свободен. || Операции доступа к наборам данных необходимо координиро- 1 вать. Эту функцию выполняет синхронизирующий интерфейс 1 между программами и данными, изображенный на схеме двои- I
Проектирование систем 49 ными горизонтальными линиями. Программа выборки начинает выполняться только в том случае, когда имеется запрос на выборку и главный файл свободен. В главном файле должен быть предусмотрен механизм блокировки, который позволяет активировать либо тот, либо другой выходной поток, но не оба одно- Исходные данные CREATE Программа создания файла LISTLOG Программа печати реги-j страцион- [ ного файла Рис. 2.9. Система сопровождения файлов с синхронизацией операций доступа. временно. Кроме того, ни один из выходных потоков не должен инициироваться до тех пор, пока не завершится процедура ввода данных в файл. Этот тип координации выполнения операций с файлом на схеме не показан. Имена файлов и программ даны прописными буквами в верхней части соответствующих блоков. Для процессов или наборов данных, представленных подобными блоками в системной документации, как правило, имеются подробные описания. 4-399
50 Глава 2 Приведенная система сопровождения файлов и интерактив- 1 ного доступа к данным является примером одного из возмож- I ных решений ставшей уже классической проблемы чтения-запи- 1 си. Эта проблема заключается в такой организации процедур 1 считывания и записи, которая обеспечивала бы доступ с целью 1 выборки данных одновременно нескольким процессам, но огра- 1 ничивала бы запись в файл таким образом, чтобы в каждый 1 момент времени в активном состоянии мог находиться только 1 один процесс записи. Все механизмы, отвечающие за выполне- | ние процедур параллельной обработки запросов на выборку ч | последовательной обработки запросов на корректировку дан- | ных, должны быть реализованы в соответствующих программах 1 выборки и корректировки. | В качестве меры защиты все операции считывания данных 1 фиксируются в регистрационном файле. Это делается в тех | случаях, когда не исключены попытки получения неавторизо- 1 ванного доступа к данным. Иногда удается идентифицировать 1 подобные операции и проследить их путь вплоть до источника | запроса. Фиксируются также все проведенные корректировки j данных: с одной стороны, для того, чтобы иметь отчетность об | их выполнении, с другой — для упрощения операций по восста- I новлению системы после ее отказа. На приведенной схеме не j предусмотрено копирование файлов. Однако, если значительная 1 часть обмена информацией проводится в интерактивном режи- 1 ме, весьма желательно, чтобы имелись дубликаты файлов. I Системы описанного типа характерны не только для прило- j жений, связанных с управлением предприятиями и делопроиз- ] водством. Они могут применяться для работ с базами данных, ! а также для управления совместным использованием различных системных ресурсов. Задачи разделения ресурсов возникают в ' разнообразных приложениях — например, в системах программирования или при обработке слов в реальном времени. В мультипрограммных операционных системах и системах управления файлами также необходимо регулировать доступ и координировать использование данных, процессов и устройств. 2.3.2. ПЕРТ-диаграммы Из функциональной схемы на рис. 2.9 следует, что программы QUERY и UPDATE начинают выполняться только в том случае, когда свободен главный файл и имеется запрос, введенный с терминала. Однако на схеме нельзя показать, что доступ к главному файлу не может быть получен, пока не завершено его формирование. Для этого удобно использовать ПЕРТ-диа- грамму. Диаграммы этого типа введены в гл. 1 в качестве средства описания процесса проектирования прикладных систем. Их можно применять также для иллюстрации порядка взаимодей-
Проектирование систем 51 ствия программ в интерактивных системах. На рис. 2.10 приведена ПЕРТ-диаграмма для системы сопровождения файлов. На этой диаграмме отсутствует завершающее событие, имеющееся, например, на рис. 1.3, поскольку работа системы не прекращается в том смысле, в каком заканчивается выполнение программы или проекта. Система прекращает свое функционирование, когда хранящиеся в ней данные больше не нужны и программы перестают эксплуатироваться. На ПЕРТ-диаграмме не указываются наборы или потоки данных. Она отображает связи по управлению, существующие & Прием запроса на корректировку- Создание файла ^Регистрация запроса на корректировку- Начало Прием запроса на выборку Регистрация "запроса на выборку Рис. 2.10. ПЕРТ-диаграмма интерактивной системы сопровождения файлов. в системе, а также координацию выполняемых действий. Каждая стрелка соответствует определенной операции, а каждый кружок — событию, под которым понимается завершение одной или нескольких операций и переход к другим. По содержанию эти символы прямо противоположны аналогичным обозначениям на граф-диаграммах. Событие 1 показывает, что файл свободен для использования. Подобное состояние возникает после завершения операций по созданию файла, корректировке файла или после выдачи данных в ответ на запрос. Ввод данных с терминала на диаграмме не изображен. Запрос на выборку или проведение корректировки удовлетворяется, если система находится в состоянии 1. Он реализуется путем выполнения действий, приводящих систему в состояния 2 или 3. Последние обозначают прием требования и переход к операциям обмена данными между файлом и терм^талом. Как и на функциональных схемах, учитывающих синхронизацию, на ПЕРТ-диаграмме нельзя показать, что корректировка файла и выборка данных несовместимы во времени или что параллельно•могут выполняться несколько операций выборки, а операции корректировки — не могут. Однако в большинстве случаев действия, инициируемые после возникновения общего для них события, могут выполнять
52 Глава 2 ся параллельно. Так, например, запись в регистрационный файл происходит одновременно с обработкой запроса на выборку данных. 2.3.3. Сети Петри Диаграммы, называемые сетями Петри, используются в качестве моделей, которые описывают движение потоков данных в сетях, допускающих частичное или полное переключение потоков из одних магистралей в другие. Такая ситуация характерна для интерактивной системы корректировки — выборки данных, в которой данные могут проходить либо через программу выборки, либо через программу корректировки, одновременная работа которых не допускается. В то же время от программ выборки или корректировки параллельно могут исходить несколько выходных потоков. Сети Петри позволяют исследовать как потоки данных, так и динамику передач управления в системе. Для этого строится несколько схем, отражающих последова- Рис. 2.11. Сеть Петри для интерактивной системы сопровождения файлов. тельные состояния сети, из которых видно, как происходит перемещение точек управления вдоль потоков данных. Следующие друг за другом изображения сети Петри отличаются лишь расположением указанных точек. На рис. 2.11 приведена сеть Петри для системы сопровождения файлов, соответствующая моменту, когда получен запрос на корректирование данных, а главный файл свободен. Кружки на диаграммах этого типа называются позициями (они изображают наборы данных), а вертикальные линии — переходами (отмечают процессы). Жирная точка в кружке (рис. 2.12) ука-
Проектирование систем 53 зывает, что соответствующий набор в данный момент активен. Переходы действуют подобно вентилям. Когда все наборы, из которых в процесс поступают данные, становятся активны, вентиль открывается, в результате чего возбуждаются выходящие из него потоки данных. Это изображается путем перемещения яшрных точек из входных во все выходные позиции. Если произошли описанные действия, говорят, что переход сработал. Запрет на параллельное выполнение операций корректировки и выборки реализуется в виде соглашения, согласно которому переходы не могут срабатывать одновременно. Невозможность сосуществования операций корректировки и выборки приводит к тому, что главный файл остается недоступным все время, пока жирная точка остается в позиции главного файла. Когда выборка или корректировка завершаются, жирные точки перемещаются в выходные позиции и файл вновь становится доступным для использования. Сеть Петри отображает последовательное чередование событий, возникновение которых определяется доступностью данных. В течение очередного интервала времени, следующего за тем, которому соответствует диаграмма на рис. 2.11, в активном состоянии будут находиться два файла — главный и регистрационный. Стрелка, идущая от процесса, подавшего запрос, назад а. Создание файла б. Ожидание в. Одновременное поступление запросов на выборку и корректировку д. Регистрация проведенной корректировки г. Выполнение процедуры корректировки е. Выполнение процедуры выборки Рис. 2.12. Последовательные состояния сети Петри.
54 Глава 2 Входы 1.Корректиру- | ) ющие данные у а) Ключ записи б) Код операции в) Новое значение | 2 Файл | ) CUSTOMER У а) Ключ записи б)Запись Процедуры обработки UPDATE 1.Проверка правильности сообщения 2 Проверка правильности записи из главного файла 3 Корректировка записи из главного файла 4. Сообщение в регистрационный файл Выходы У 1.Файл V CUSTOMER а) Ключ записи б) Запись у 2 Регистрацион- V ный файл а) Старая запись б)Корректирующие данные в) Новая запись Рис. 2.13. Схема HIPO для программы корректировки файла CUSTOMER. | к главному файлу, показывает, что файл освобождается и к не- \ му вновь разрешен доступ. Такие связи необходимы для возврата жирных точек в позицию, представляющую главный файл. \ На рис. 2.12 показаны последовательные состояния сети Пет- } ри, соответствующие этапам первоначального формирования файла, затем его корректировки и, наконец, этапам выборки из 1 него данных. Считается, что операция корректировки имеет при- \ оритет перед операцией выборки. На сети Петри можно также \ отметить невозможность одновременного удовлетворения нескольких запросов на проведение корректировок. Для этого до- \ статочно поставить в соответствие каждому запросу отдельный переход. 2.3.4. Схемы HIPO После составления общего наброска проекта системы следующим этапом является более детальная проработка способов взаимодействия ее отдельных элементов. Для этой цели целесообразно использовать схемы HIPO. Название схем образовано из первых букв английского словосочетания hierarchy-input- processing-output (иерархическое описание входов-обработки- выходов). Употребление здесь слова «иерархическое» связано с I тем, что данные схемы применяются в сочетании с графически- I ми схемами заданий для отдельных программ, построенными 71
Проектирование систем 55 по иерархическому принципу. На рис. 2.13 представлена схема HIPO для программы корректировки главного файла. На этой схеме указаны потоки данных и перечислены содержащиеся в них элементарные данные. Определены также основные части описываемого процесса и связанные с ними элементы входных и выходных данных. Использование схем HIPO характерно для той стадии проектирования, когда системные аналитики уже могут приступать к разработке программ и данных. Эти схемы, определяя основные функции каждой программы и перечень основных элементов данных, не конкретизируют способы организации данных, иерархическую структуру подпрограмм и выбор алгоритмов обработки. На этапе разработки программ схемы HIPO могут применяться в качестве средства описания функций, реализуемых программой, и циркулирующих внутри нее потоков данных. Их можно рассматривать как промежуточный шаг перед составлением спецификаций программ и выбором способов организации данных (блоки 5.2 и 5.3 на графической схеме задания, приведенной на рис. 1.2). При разработке внешнего плана проекта программы и информационные структуры не детализируются, указываются лишь связанные с ними потоки и наборы данных. Именно эти сведения и содержатся в схемах HIPO. 2.4. Проектная документация В графической схеме задания на разработку документации по системе, изображенной на рис. 1.4, проектная документация представлена блоком 2. На этапе проектирования, которому соответствует данная схема, проектная документация должна описывать компоненты системы прежде всего с точки зрения их организации. Различные типы схем и диаграмм, обсуждавшиеся в данной главе, следует рассматривать не только как элементы методики проектирования и средства формализации этого процесса, но й как составную часть проектной документации, дающую наглядное представление о структуре системы. Организационные аспекты проекта воспринимаются значительно легче в графическом исполнении, чем в виде чисто словесного описания, хотя это отнюдь не означает, что графические материалы вообще не нуждаются в словесных пояснениях. Проектная документация должна содержать следующее: • перечень важнейших функций, реализуемых системой; • определения потоков данных; • графические описания процессов передачи данных и управления; • перечень носителей информации, используемых для хранения всех наборов данных; • описания средств взаимодействия с вычислительной ере-
56 Глава 2 дой, включая списки уже имеющихся наборов данных н прогни рамм; I • перечень имен, подлежащих разработке наборов данных и! программ; при их выборе должны учитываться функциональное! назначение программ и отражаться особенности данных, содер-1 жащихся в наборах; 1 • требования к синхронизации выполняемых операций cl учетом доступности данных; 1 • предложения по разработке системных средств защиты; | • перечень контрольных примеров для проверки всех эле-| ментов системы с приведением ожидаемых результатов. | Значительная часть перечисленных сведений может быть! оформлена в виде разнообразных диаграмм и схем типа пред-1 ставленных в этой главе. В качестве дополнительного описа-1 тельного материала можно включить список информационных! структур и программ — с указанием разделов документации и || снабженный перекрестными ссылками. Такой список позволит Я читателю получить подробную информацию, которую нельзя по-1 черпнуть из отдельной диаграммы. ]| Следует отметить, что рассмотренная часть полного пакета | документации по системе служит отправным материалом для | обсуждения внутренних деталей проекта, а также разработки | программ и данных. (I 2.5. Упражнения ( 1. Начертите граф-диаграмму для библиотечной системы регистрации вы- А даваемой литературы (упр. 1 в гл. 1). I 2. Предположим, что в системе оформления предварительных заявок на У пользование теннисными кортами (упр. 2 в гл. 1) используется база данных, * работающая в интерактивном режиме. Начертите функциональную схему этой ! системы, а также схемы HIPO для всех входящих в нее программ. | 3. Просмотрите набор операторов языка управления заданиями, необхо- } димый для трансляции и выполнения программы на языке высокого уровня, * стандартном для вычислительной установки, которой вы пользуетесь. Начер- J тите граф-диаграмму потоков данных и процессов, участвующих в задании, f 4. Начертите функциональную схему, на которой были бы показаны про- { цессы, временные наборы данных и постоянные наборы данных, описываемые > в операторах управления заданиями, которые используются при трансляции л и выполнении программы на языке высокого уровня, стандартном для вашей i вычислительной установки. ;| 5. Начертите функциональную схему системы ведения документации в небольшом торговом предприятии, структура которой приведена на рис. 2.5. 6. Начертите схемы HIPO для всех программ системы, рассмотренной в предыдущем упражнении. 7. Начертите ПЕРТ-диаграмму или схему Петри для программ спулинга периферийной ЭВМ, которые должны принимать данные, поступающие с двух терминалов» и временно заносить их иа диск, ожидая того момента, когда освободится процессор. Когда процессор освобождается, указанные программы передают порцию данных в процессор, затем вновь временно записывают полученный результат на диск и хранят его там до освобождения терминала, пославшего данные. 8. Разработайте проект произвольной системы, которая включала бы не : менее четырех программ. Составьте всю необходимую документацию. I
Глава 3 МЕТОДЫ ОРГАНИЗАЦИИ ДАННЫХ Часто при обработке информации на ЭВМ используется такая же организация данных, как и при ручной обработке. В научных расчетах наиболее важным элементом данных является отдельное измерение. Данные представляют собой неструктурированные группы связанных друг с другом измерений. В экономике самым важным элементом данных является запись. В Коболе обработка информации осуществляется способом, принятым для экономических приложений: открывается файл, читается запись, используется из нее информация и, наконец, закрывается файл. Фортран — язык для научных расчетов, поэтому он ориентирован на обработку индивидуальных элементов данных (обычно чисел). Языки ПЛ/1 и Паскаль имеют соответствующие средства для работы в обеих названных выше областях. Разработка баз данных предусматривает совершенно другой подход к организации данных. Базы данных похожи на библиотеки каталогизированной и упорядоченной информации. В них широко используются перекрестные ссылки, дающие возможность организовать доступ к данным на основе их атрибутов и взаимных связей. На уровне аппаратных средств и в операционных системах данные представляются в форме, отличной от той, которая легко воспринимается пользователем. Кроме того, они могут быть по-другому организованы. Скалярные величины обычно перфорируются на одной и той же карте, даже если они и не связаны друг с другом. Это позволяет уменьшить число перфокарт. Независимые записи могут быть упорядочены для того, чтобы ускорить к ним доступ, причем этот порядок не определяется какими-то характеристиками данных, а вводится только для Удобства. К записям, имеющим ключи для организации прямого доступа, можно обратиться с помощью системного программного обеспечения, использующего методы просмотра таблиц и последовательного поиска. 3.1. Типы данных Элементы данных обычно разделяют на две основные группы: скаляры и структуры. К скалярам относятся флаги, коды, числа и слова; к структурам — массивы, таблицы, списки, стеки,
58 Глава 3 множества и записи1). Структуры формируются из скалярных данных, сгруппированных в соответствии с определенными правилами. Отметим, что блоки, файлы и базы данных должны обязательно состоять из структурированных элементов. Внешние для программной системы данные также формируются из структурированных элементов. В то время как некоторые языки программирования снабжены средствами для формирования различных данных, другие позволяют только явно задать определенную структуру. В Фортране, чтобы одинаково оЪисать три массива, необходимо одно и то же описание повторить для каждого: DIMENSION A (10, 20), В (10, 20), С (10, 20) (Пример 3.1> Аналогично в Коболе определяются два идентичных структурированных списка имен и адресов: 01 А. * (Пример 3.2) 03 A-PERSON OCCURS 100 TIMES. 05 A-NAME PIC X(20). 05 A-ADDR PIC X(20). 01 B. 03 B-PERSON OCCURS 100 TIMES. f 05 B-NAME PIC X(20). \ 05 B-ADDR PIC X(20). \ В Паскале имеются как средства для задания правил форми- \ рования структур, так и средства для непосредственного опи- I сания данных: \ (Пример 3.3) type TEN_BY_TWENTY = array(10,20) of real; var A,B,C : TEN_BY_TWENTY Запись на Паскале может быть определена следующим обра- , зом: type CUSTOMER = record (Пример 3.4) NAME: character_strfng; ADDRESS: character^string; TELEPHONE: numeric__string; ACCOUNT_BALANCE: real; PAYMENT-DATE: integer end !> В отечественной литературе их часто называют структурами. — Прим* перев.
Методы организации данных 59 С помощью такого рода утверждений устанавливается тип данных. Объявление переменной позволяет задать конкретный пример, относящийся к этому типу. Любой язык с динамическим распределением памяти имеет средства для задания правил объявления сложных структур. Часто невозможно заранее установить размер структуры данных. В большинстве языков программирования нельзя задавать динамически изменяемые структуры данных. Программисту необходимо указывать наибольшее число экземпляров и максимальный размер каждой структуры. Существует несколько формальных методов описания, которые следует использовать для определения динамически изменяемых структур. В дальнейшем эти описания могут быть преобразованы в программные сегменты. То, что неформально, можно записать в виде семья =отец, мать, ребенок..., формально записывается следующим образом: семья -ютец, мать, (ребенок)+ Знак «плюс» показывает, что в семье должен быть по крайней мере один ребенок. Если, определяя понятие семьи, учитывать вариант, когда детей в семье нет, имеем семья -и>тец, мать, (ребенок)* Семья может быть также определена рекурсивно следующим образом: семья -мэтец, мать, дети дети -гребенок | ребенок, дети (Пример 3.5) Вертикальная черта трактуется как «или». Нотации такого вида часто используются для определения различных типов данных в языках программирования. Имеются в виду числовые константы и арифметические выражения: действ-знач-* (знак | Л) ((цифра) *. (цифра) + | (цифра) +. (цифра)*) выражение->-выражение (+1 —) терм | терм терм-*терм(*|/)операнд|операнд (Пример 3.6) операнд-*-действ-перем | действ_знач Действительное значение определяется как знаковая или беззнаковая последовательность цифр, содержащая десятичную точку. Выражение и терм могут быть определены с помощью повторяющихся конструкций, но предпочтение отдается рекурсии, поскольку ее легче использовать для анализа арифметических выражений.
60 Глава 3 В языках программирования данные, для описания которых 1 нужны конструкции повторения или рекурсии, можно реализо-1 вать, используя массивы или указатели: I (Пример 3.7) I type FAMILY = record | FATHER: PERSON; | MOTHER: PERSON; 1 CHILDREN: array [1..3] of PERSON | end I или 1 (Пример 3.8) I type CHILDREN FAMILY Если, как в примере 3.7, использовать для реализации массивы, ; нужно задать максимально допустимое число детей в семье. ] Поскольку большинство семей имеет только двух детей, нецеле- ] сообразно отводить место для хранения информации о десяти или двадцати детях. Применяя указатели, можно добиться, чтобы размер отводимого пространства точно зависел от числа детей в семье. Правда, определенные ресурсы необходимо потратить на организацию процесса размещения памяти. Удобно использовать указатели для реализации структур данных типа деревьев. Они также позволяют наиболее естественным образом : описывать отношения между элементами в базах данных. Способы описания или изображения сложных структур данных, принятые в человеческой практике, часто отличаются от способов их описания для использования в ЭВМ. Программисты настолько привыкли к работе с массивами, что иногда забывают, что структура массива обычно не отражает структуру данных, хранящихся в нем. При использовании массива подразумевается, что, во-первых, известно число его элементов, во-вторых, все элементы принадлежат одному и тому же типу и имеют один и тот же размер и, наконец, в-третьих, доступ к элементам массива организуется в соответствии с их положениями в массиве. Реальные множества данных редко бывают однотип- = record CHILD: PERSON; OTHER_CHILDREN: t CHILDREN end = record FATHER: PERSON; MOTHER: PERSONS- OFFSPRING: CHILDREN end
Методы организации данных 61 ными. Порядок использования данных может оказаться несущественным или может определяться значениями данных. Таким образом, можно сделать вывод о том, что языки программирования накладывают искусственные ограничения на данные. 3.2. Уровни организации данных Программист, проектировщик и пользователь имеют свои собственные, отличающиеся друг от друга взгляды на организацию данных. В соответствии с этим могут быть выделены три уровня организации данных: • Логическая организация данных: проектный уровень. • Представление данных: уровень языка реализации. • Физическая организация данных: машинный уровень. Логическая организация данных отражает взгляд пользователя на данные. В ее основе лежат требования пользователя и внутренне присущие данным связи. Это наиболее важный уровень абстракции, используемый при представлении данных, поскольку именно требования пользователей определяют облик проектируемой системы. Если на этапе проектирования системы удачно выбрана логическая организация данных, изменения системных требований, не приводящие к модификации логической структуры данных, не повлекут за собой реорганизации на более низких уровнях представления данных. Только на логическом уровне могут применяться формальные методы описания динамически изменяющихся структур. Никакая дополнительная информация о членах семьи не изменит общую логическую структуру семьи: семья = отец, мать, ребенок... отец = имя, возраст, профессия мать = имя, возраст, девичья фамилия, профессия ребенок =имя, возраст, пол В этом случае новые данные о членах семьи не нарушают ее глобальной организации, но могут привести к изменениям в представлении информации. Описание данных на языке программирования относится к Уровню представления данных. Отношение между данными задаются в виде, характерном для конкретного языка. На этом Уровне оперируют массивами и указателями. На языке Кобол Данные о семье и отношения между ними могут быть представлены только в том случае, если точно задать число детей в
62 Глава 3 семье и размеры каждого элемента: 1 (Пример 3.9) 1 01 FAMILY. 1 03 FATHER. I 05 FATHERS-NAME PIC X(20). | 05 FATHERS-AGE PIC 99. | 05 FATHERS-OCCUPATION PIC X(15). I 03 MOTHER. | 05 MOTHERS-NAME PIC X(20). f 05 MOTHERS-AGE PIC 99. f 05 MAIDEN-NAME PJC X(15). I 05 MOTHERS-OCCUPATION PIC X(15). | 03 CHILDREN. ? 05 CHILD OCCURS 30 TIMES. I 07 CHILDS-NAME PIC X(20). ! 07 CHILDS-AGE PIC 99. J 07 CHILDS-SEX PIC X. j В дальнейшем информация о представлении данных может i быть распределена по отдельным программным модулям, при- ; чем можно использовать как внешнюю, так и внутреннюю фор- Я мы представления данных. Под внешним представлением пони- 1 мается взгляд на данные со стороны других программ, т. е. ц представление на уровне потоков данных. При внешнем пред- ; ставлении главным является определение возможных путей до--; ступа. Внутреннее представление — это представление в виде : внутренних областей хранения данных, т. е. структура данных может быть во внешнем представлении стеком, а во внутреннем— массивом или связанным списком. Предположим, что данные о покупателе включают номер счета, имя, адрес, даты предыдущих расходов, платежи и счета. Внешней формой их представления (возможно, в базе данных) являются отдельные агрегаты данных о личности покупателя и о его платежах. Вну- J треннее представление может состоять из таблиц, полей признаков и указателей. Уровень физической организации связан с системным программным обеспечением. На этом уровне приходится оперировать с границами слов, размерами полей, двоичными кодами и физическими записями. Большинство средств системного программного обеспечения дают возможность программисту осуществить выбор из весьма узкого круга способов представления данных, которые имеют более или менее ясную физическую организацию. Агрегаты данных, хранящихся в быстродействую- | щей памяти, могут быть представлены массивами или стеками. | К записям файла можно обращаться в последовательном или I произвольном порядке, используя ключи различных типов. !
Методы организации данных 63 В языках программирования существуют возможности блокирования и буферизации, а также управления доступом к еще меньшим единицам информации. Так, в ПЛ/1 различные типы операторов GET и PUT позволяют программисту явно или по умолчанию проводить форматирование упорядоченных или неупорядоченных данных. Проблема проектирования структуры данных заключается,, во-первых, в определении способа их логической организации и> во-вторых, в поиске пути выражения этой организации на уровне внешних представлений, допускаемых системой и языком программирования. Отметим, что системное программное обеспечение управляет потоком данных между программами и внешними устройствами, а языки программирования управляют обменом данными между программами. К сожалению, ввод и вывод являются нестандартными операциями, и поэтому системный аналитик и программист должны хорошо знать не только возможности языка программирования, но и разбираться в работе системного программного обеспечения. Проектирование и реализация должны быть проведены так, чтобы изменение требований пользователя, а также модификация аппаратного и программного обеспечения как можно в меньшей степени влияли на проектируемую программную систему. Это влияние можно свести к минимуму путем изоляции операций системного интерфейса и ввода-вывода. Системное программное обеспечение позволяет изолировать реальные процедуры ввода-вывода от программиста. Таким образом, программы в принципе не меняются при изменении внешних устройств. Программист в свою очередь должен оградить пользователя от изменений в системном программном обеспечении. Пользователь не должен также заботиться о структурах и размерах файлов. Аналогично в прикладных системах не должны использоваться внутренние представления данных вне отдельных модулей. Программы доступа, которые служат для размещения, поиска и изменения данных, должны обеспечивать контакт с данными только на уровне внешнего представления. Это означает, что процесс перехода от уровня логической организации к уровню представлений должен включать проектирование не только структур данных, но и программных модулей. Причем при проектировании структур данных используется принцип изоляции информации. Доступ к хранящейся в базе данных информации о семьях должен начинаться с идентификации конкретной семьи, и только после этого можно обратиться к данным о каком-то ее члене. Доступ осуществляется с помощью заранее определенных связей и атрибутов, которые могут отражать или не отражать реальную физическую организацию базы данных. Предположим, что нужно узнать имя старшей дочери. Пользователь, какой-
64 Глава 3 нибудь человек или программа, не должны иметь право про- J сматривать весь список имен, отыскивая дочерей и сравнивая их 1 возраст. Программа пользователя не должна быть связана с 1 методом хранения данных. Когда для хранения информации 1 используется база данных, СУБД выбирает пути доступа в со- | ответствии с начальным определением базы данных. Осущест- I вляя запрос, необходимо придерживаться правил, установлен- | ных при реализации базы данных. | Если информация содержится не в базе данных, а в файло- Ц вой системе, то, для того чтобы могли быть использованы новые } формы запросов, необходимо либо изменить существующие 1 программы, либо создать новые. Организация новых путей до- \ ступа, отличных от предусмотренных при первоначальной орга- , низации, может привести к дополнительной обработке данных. СЛЕДУЕТ ПРОДУМЫВАТЬ ПУТИ ДОСТУПА А Тщательное определение путей доступа и явное описание I структур данных только в некоторых модулях следует произво- I дить по крайней мере по двум причинам. Во-первых, пользова- I тель не должен иметь дела с не относящимися к его задаче во- I просами хранения данных. Во-вторых, как можно меньше моду- I лей должно зависеть от организации хранения данных. Если . I организация данных изолирована, изменение их представления I повлечет за собой незначительные изменения текста программы. I Поэтому необходимо стараться размещать структуру данных I только в одном программном модуле. Кроме того, это является I одной из причин, из-за которой следует избегать использовать I общие области в Фортране и глобальные переменные в ПЛ/1. I ОПРЕДЕЛЯЙТЕ СТРУКТУРЫ ДАННЫХ I КАК МОЖНО В МЕНЬШЕМ ЧИСЛЕ МОДУЛЕЙ I Наличие постоянных файлов приводит к дополнительным I проблемам. В системах баз данных постоянные файлы относятся I к внутренним структурам, и доступы к ним осуществляются I только с помощью администратора базы данных, т. е. структу- I ра постоянных файлов в базах данных изолирована. В других I системах нельзя ограничиться разрешением доступа к ним толь- I ко одной программе. Наилучшим выходом является размещение I описания файла в небольшом числе программных модулей. Это I позволяет минимизировать возможные изменения программ. I Если в каждой программе, которая работает с файлом, доступ I к нему обеспечивает один модуль, можно рекомендовать хра- I нить описание файла в библиотеке и во всех программах ис- I пользовать для доступа один и тот же модуль или по крайней I мере одно и то же описание файла. J
Методы организации данных 65 3.3. Уровень логической организации данных Логическая организация данных — это, по существу, представление данных пользователем. Индивидуальные данные должны быть сгруппированы в соответствии с их связями, а также со связями, отражающими способы их использования. В обычных библиотеках информация разделяется на классы и подклассы. В библиотеке конгресса США индекс QA используется для книг по математике и вычислительной технике. Книги, имеющие индексы от QA76,5 до QA76,9, относятся к подклассу языков программирования и операционных систем класса вычислительной техники. Последние буквы и цифры индекса идентифицируют автора. Такая иерархическая система удобна для хранения книг на полках, но не приспособлена для определения их местонахождения. Руководством для пользователя служат предметный и авторский каталоги с массой перекрестных ссылок. Как и в библиотеке, данные в ЭВМ могут быть упорядочены с помощью ключей, которые являются элементами данных. Для организации данных можно использовать уровни иерархии или многоаспектный доступ и перекрестные ссылки. Для описания информации в базах данных чаще всего используются иерархические или реляционные модели, причем реляционная модель ближе к логической структуре данных. Обычно службы, проводящие подписку на журналы, хранят информацию о каждом подписчике и о том, на что он подписался, т. е. информация сразу разделяется на два класса. Если данные используются для оповещения подписчиков об истечении сроков подписки и для направления почтой номеров журналов, они должны быть организованы так, чтобы можно было осуществить доступ не только по дате окончания подписки, но даже и по адресу так же просто, как и по фамилии подписчика или номеру подписки. Считается, что названия журналов и информация, касающаяся почтовой метки журнала, входят в состав данных. Предполагается, что человек может подписаться более чем на один журнал. На уровне логической организации описание данных может меняться, поскольку службы, проводящие подписку, естественно, не захотят вводить ограничение на число журналов, на которые может подписаться человек. Информация, необходимая для почтовой метки, состоит из фамилии и адреса подписчика, номера счета, кода регионального центра распределения и срока окончания подписки. Номер счета может быть связан либо с фамилией подписчика, либо с номером подписки. Также нужно уметь связывать каждую почтовую метку с соответствующим журналом. Предположим, что номера счетов относятся к подписчикам, а не к номерам подписки. Если имеется достаточно памяти и почтовые метки наносятся в раз- 5-399
66 Глава S ное время на разные журналы, информация о каждом журнале может обрабатываться независимо и задача упрощается. Прав-j да, появится избыточность, заключающаяся в хранении в нескольких местах одних и тех же фамилий и адресов. Но если данные хранить на лентах и редко использовать, то проблем не возникает. Отметим, что изменение адреса подписчика приведет к необходимости вносить изменения во все относящиеся к нему подписки. Тогда структуру данных можно описать следующим образом: идентификатор журнала (Пример 3.10)j информация о подписке номер счета (ключ) фамилия подписчика адрес подписчика код округа срок окончания подписки Информация такого рода должна повторяться для каждой подписки. Если информация содержится в одном файле или в базе! данных, предназначенной для непосредственного доступа, избы-] точность весьма нежелательна. Устранение избыточности и| группирование данных в соответствии с их действительными] связями приводят к следующей структуре: информация о подписчике (Пример 3.11) номер счета (ключ) || фамилия подписчика {] адрес подписчика j код округа { информация о подписке j название журнала ~ срок окончания подписки 1 информация о подписке £ название журнала ! срок окончания подписки ; ■ • • > С помощью трех процедур нормализации можно привести!! .любые данные к единой форме. Первая нормальная форма\ (1НФ) позволяет описать данные без повторяющихся группЛ Это осуществляется с помощью выделения части структуры, ко-; торая повторяется в другой структуре. Если информация о под-! писке представлена так, как описано выше, ни одно из этих, представлений не является первой нормальной формой, по-, скольку либо информация о подписчике может появиться в за* писях, связанных с несколькими журналами, либо однотипная! информация о нескольких подписках может встретиться в запи« си, связанной с одним подписчиком. В первом случае налицо^ избыточность, которая затрудняет, например, изменение адресу подписчика. Во втором — не ясно, что делать с информацией подписчике, если сроки действия всех подписок истекли. Пр<
Методы организации данных 67 цесс преобразования к первой нормальной форме сводится к задаче построения двух типов структур, связанных перекрестными ссылками: информация о подписчике (Пример 3.12) номер счета (ключ) фамилия подписчика адрес подписчика код округа информация о подписке код подписки (ключ) номер счета название журнала срок окончания подписки В качестве ключа для информации о подписчике используется номер его счета. Так как несколько подписчиков могут иметь одну и ту же фамилию, использовать ее как ключ нельзя. Совместно фамилия и адрес, вероятно, уникальны, но адреса довольно часто меняются, и их изменение не должно приводить к закрытию одного счета и открытию другого. Код подписки следует дополнить, чтобы снабдить информацию о подписке уникальными идентификаторами. Записи о подписке и подписчике должны быть связаны друг с другом либо с помощью указателей, либо с помощью составного ключа, содержащего номер счета и код подписки. Ключ — это уникальный идентификатор каждого экземпляра записи. Изменение ключа должно приводить к удалению одного экземпляра записи и созданию другого. Тип отношения подписчик — подписка есть многие-к-многим. Структура данных относится ко второй нормальной форме (2НФ), если она, во-первых, относится к первой нормальной форме и, во-вторых, неключевые элементы функционально полно зависят от ключа. Это означает, что ключ требуется для определения других элементов, и, если ключ является составным, ни один из других элементов не может быть определен на основании знания только части ключа. Приведение структуры к 2НФ осуществляется путем удаления элементов данных, которые зависят только от части ключа, и формирования из них и из частичных ключей отдельной структуры. Структура подписчик — подписка относится ко второй нормальной форме, поскольку, с одной стороны, никакая информация о подписчике не может быть получена без указания номера счета и, с другой — никакая информация о подписке не может быть получена без указания кода подписки. Код подписки является единственным составным ключом, и срок окончания подписки не может быть определен только из номера счета или только из названия журнала. Если фамилию подписчика и его адрес использовать как ключ к информации о подписчике, структура не будет принадлежать к 2НФ, так как код округа можно определить только на основании знания адреса. 5*
68 Глава 3 Структура данных относится к третьей нормальной форме I (ЗНФ), если не существует неключевых элементов, которые 1 функционально зависят от других неключевых элементов. Если 1 же такой элемент существует, он непосредственно связан с клю- I чом. Перевод из 2НФ в ЗНФ осуществляется путем удаления I функционально зависимых элементов и формирования из них и 1 из элементов, которые их определяют, отдельной структуры. 1 Данные о подписчике не относятся к ЗНФ, поскольку код окру- I га зависит от адреса и может быть определен только на основа- 1 нии адреса. Для того чтобы привести данные о подписке и под- 1 писчиках к ЗНФ, необходимо сформировать новую структуру | для адреса и кода округа: | (Пример 3.13) | информация о подписчике I номер счета (ключ) I фамилия подписчика | адрес подписчика ц информация о подписке | код подписки (ключ) *| номер счета название журнала срок окончания подписки местонахождение подписчика £ адрес (ключ) код округа * Большинство систем управления файлами и СУБД не предназначено для проведения нормализации данных. Логическая структура нормализованных данных должна быть удобна для t перехода к более аппаратно-ориентированной форме, определя- \ емой требованиями программного обеспечения ЭВМ. То, что логическая структура данных может сильно отличаться от их фи- | зической структуры, было показано на примере реляционных | баз данных, в которых данные связаны друг с другом чисто ло- \ гически. Однако большинство баз данных имеют иерархическую, I а не реляционную организацию. При реализации структуры дан- * ных являются элементами иерархической организации, а не ; связанными друг с другом схемами нормализованных данных. : Служба, проводящая подписку, хранит свои многочисленные данные в файлах. Их необходимо организовать так, чтобы можно было легко осуществить многоаспектный доступ. Для того * чтобы внести изменения в данные о подписке, необходимо знать \ код подписки; чтобы изменить фамилию или адрес подписчика, ; необходим только номер его счета; чтобы послать сведения, касающиеся окончания срока подписки, надо получить информа- . цию как о подписке, так и о подписчике. Поэтому дата оконча- , ния подписки должна играть роль вторичного, хотя и не уникального, ключа. Снабжение журналов почтовыми метками, не- ' обходимыми для их рассылки, потребует информации сразу из i
Методы организации данных §9 всех трех структур, причем доступ будет осуществляться по названию журнала. Несколько методов позволяют организовать доступ по вторичным ключам: метод, использующий инвертированные файлы, и метод формирования цепочек записей. Для описания логической структуры данных применяется несколько формальных нотаций. В функциональной нотации используются ключи и явно выделены связи между структурами данных: подписчик (номер счета) = фамилия, адрес подписка (номер счета, название журнала) = срок окончания подписки местонахождение (адрес) = код округа Номер счета \ Фамилия Номер счета + название журнала Срок окончания подписки Адрес хч )) У/ т Адрес + код округа Рис. 3.1. Диаграмма отношений, существующих между данными о подписке. Это связывает логические структуры с традиционными методами хранения наборов данных. На рис. 3.1 показана диаграмма отношений, существующих между данными о подписке. Она в большей степени, чем функциональная нотация, пригодна для организации хранения информации в базе данных. Каждая запись на диаграмме, связанная ребром с другой записью, содержит ключ или часть ключа, позволяющего организовать доступ к этой второй записи. Ребра с двумя стрелками используются для обозначения отношений типа «один-к-многим», ребра с одной стрелкой — для обозначения отношений типа «один-к-одному». Петли отражают группирование информации о подписке, во-первых, с помощью названия журнала и, во-вторых, с помощью срока окончания подписки. Диаграмма не содержит информации о конкретной реализации. Показаны только структуры данных и пути доступа к ним. Выбранное представление данных обеспечивает возможность Доступа с помощью ключей и связей, существующих между Данными. В заключение приведем правила, которые следует применять для нормализации данных: а) необходимо образовывать новые структуры из повторяющихся групп элементов;
70 Глава 3 б) если неключевой элемент зависит только от части ключа, J его необходимо перевести в отдельную структуру, ключом кото- | рой служит часть первоначального ключа; 1 в) если неключевой элемент полностью определяется другим $ неключевым элементом, его надо перевести в отдельную струк- | туру, ключом которой служит второй неключевой элемент. | РАЗРАБАТЫВАЙТЕ ЛОГИЧЕСКУЮ СТРУКТУРУ ДАННЫХ | 3.4. Представление данных I Рассмотрим представление данных, относящихся к приклад- ; ной области на системном уровне. Оно определяется взглядом i на данные из точки, находящейся вне программной среды, т. е. либо со стороны пользователя, либо со стороны системы. Внеш- п нее представление данных — это способ организации входных и \ выходных данных. Одни данные содержатся в системе, другие — поступают на ее вход, третьи — предназначены для вывода и* передачи пользователям. Внутреннее представление данных — это представление данных внутри программ, доступное только программисту. 3.4.1. Внешнее представление Данные, доступ к которым осуществляется только с помощью ЭВМ, хранятся в закодированном виде. Они не нуждаются в редактировании и преобразовании в символьную форму. На анализ потоков данных в системе и проектирование внешних , входов и выходов влияют различные физические характеристики устройств. Организация данных на внешних носителях опре- I деляется размерами перфокарты и экрана, длиной строки:! АЦПУ, объемом и характеристиками дисковой памяти. Все это I относится к физической организации. Внешнее представление I элементов, составляющих отчет о подписке на журналы, вклю- I чает описания их характеристик, заголовков, итоговых характе- I ристик и зависимостей между такими объектами, как названия I округов и данные о подписчиках. Основное внимание уделяется I организации логических элементов, а- не требованиям, опреде- I ленным аппаратными средствами. Такие факторы, как блокирование записей, размер дорожки и способ организации хранения информации на дисках, связаны с работой операционной системы, и, до тех пор пока эффективность не станет основным стимулом, эти факторы не будут оказывать большого влияния на внешнее представление данных. Особенно тщательно описываются атрибуты данных, поскольку это важно для любого языка программирования. Например, выделяются объекты, являющиеся строками символов,:
Методы организации данных 71 десятичными и двоичными числами. Также необходимо уметь определять, какие программы обрабатывают те или иные данные. Обычно для этих целей используются различные типы диаграмм и таблиц. Для того чтобы показать, как происходит обмен данными между программами и областями хранения данных, используются схемы и таблицы потоков данных. Надо стремиться к тому, чтобы потоки содержали минимально необходимое количество данных. Внутреннее представление данных и их физическая организация не должны влиять на внешнее представление. Например, если программа сопровождения файла, содержащего информацию о подписчиках журналов, идентифицирует подпис- • Номер подписки подписки Название нового журнала Новый код округа Продление подписки Новая подписка Код корректировки Рис. 3.2. Потоки данных, возникающие при корректировке главного файла. чиков с помощью номера счета и не предназначена для изменения их фамилий, фамилия подписчика не должна входить в поток данных, идущий к программе корректировки или от нее. Процесс корректировки, схема потоков данных которого приведена на рис. 3.2, предусматривает изменение адресов и сроков окончания подписок, а также добавление информации о новых подписках. Схемы такого вида объединяют индивидуальные элементы данных в потоки, идущие к областям хранения данных и программам. На уровне внешнего представления эти элементы либо считаются независимыми (скалярными), либо входят в состав записей или каких-то других языковых структур. Не все элементы данных обязательно должны быть представлены в каждом корректирующем сообщении. Так, в нем обычно присутствует либо новый адрес подписчика, либо новая дата окончания подписки. Изменения информации о подписке могут затрагивать либо только одну из трех логических структур данных, либо все три сразу. Использование одной и той же вершины на схеме для данных всех трех типов не является достаточным основанием для помещения данных в один и тот же файл. Но с другой стороны, тот факт, что используются три логические структуры, не является основанием для создания трех различных файлов. Решение о том, как хранить данные, зависит и от логической организации, и от распределения элементов данных по потокам. Физиче-
72 Глава 3 ски данные могут быть организованы в виде нескольких файлов, и может быть предусмотрена возможность передачи сразу нескольких записей от каждого файла. Обычно из-за ограниченных возможностей системного программного обеспечения программа корректировки может работать только с одной записью каждого файла. Она может работать также с дополнительной информацией. Если программа не использует дополнительную информацию, знания о том, как эта информация организована, ей не нужны. Нужно знать только то, что необходимо для модификации файла. ОПИСЫВАЙТЕ ТОЛЬКО НЕОБХОДИМЫЕ ДАННЫЕ Потоки данных, изображенные на схеме (рис. 3.2), могут также быть описаны с помощью таблиц потоков данных (табл. 3.1). Каждая таблица соответствует одному потоку данных. Таблицы потоков данных могут быть связаны не только непосредственно с самими потоками данных, но и с программами или областями хранения данных. Для составления таких таблиц Таблица 3.1. Таблицы потоков данных а) Поток данных от главного файла к программе корректировки Элемент данных Пояснение Комментарии CUST-ACCT-NO MAG-ACCT-NO MAG-EXP-DATE Номер подписчика Номер подписки Срок окончания подписки Требуется Требуется только для продления подписки б) Поток данных от корректирующего файла к программе корректировки Элемент данных Пояснение Комментарии ACCT-NO NEW-4DDR SUBSCRIP-NO SUBSCRIP-EXT NEW-SUBSCRIP TRANS-CODE Номер подписчика Новый адрес Номер подписки Дата продления Новый журнал Код корректировки Требуется Необязателен Требуется только для изменений подписки Необязателен » Требуется в) Поток данных от программы корректировки к главному файлу Элемент данных Пояснение Комментарии NEW-ADDR NEW-MAG-ACCT-NO NEW-REG-CODE NEW-EXP-DATE Новый адрес Новый номер подписки Новый региональный код Новый срок окончания Необязателен Требуется, если есть NEW-ADDR Требуется, если есть NEW-MAG-ACCT-NO
Методы организации данных 73 Таблица 3,2. Таблицы потоков данных для процессов обработки и областей хранения а) Таблица потоков данных для главных файлов v Элемент данных Пояснение Источник1) Назначение CUST-ACCT-NO CUST-NAME CUST-ADDR NEW-ADDR ADDR-REG-CODE NEW-REG-CODE MAG-EXP-DATE NEW-EXP-DATE MAG-ACCT-NO NEW-MAG-ACCT-.NO Номер подписчика Фамилия подписчика Адрес подписчика Код округа Срок окончания подписки Номер подписки СОЗДАНИЕ КОРРЕКТИРОВКА СОЗДАНИЕ КОРРЕКТИРОВКА СОЗДАНИЕ КОРРЕКТИРОВКА СОЗДАНИЕ КОРРЕКТИРОВКА КОРРЕКТИ- РОВКА, АД- РЕСАЦИЯ, ПРОВЕРКА СЧЕТОВ АДРЕСАЦИЯ, ПРОВЕРКА СЧЕТОВ АДРЕСАЦИЯ, ПРОВЕРКА СЧЕТОВ АДРЕСАЦИЯ КОРРЕКТИРОВКА, ПРОВЕРКА СЧЕТОВ КОРРЕКТИРОВКА, ПРОВЕРКА СЧЕТОВ б) Таблица потоков данных для процесса корректировки Входное имя Источник2) Выходное имя Назначение CUST-ACCT-NO Файл подписчиков ACCT-NO Корректирующий файл SUBSCRIP-NO То же MAG-ACCT-NO Файл подписчиков NEW-SUBSCRIP Корректирую- NEW-MAG-ACCT-NO щий файл NEW-ADDR То же NEW-ADDR SUBSCRIP-EXP » MAG-EXP-DATE Файл подписчи- NEW-EXP-DATE ков TRANS-CODE Корректирую- NEW-REG-CODE щий файл Файл подписчи* ков То же ') Под источником в данном случае автор, по-видимому, понимает процесс, в результате которого образуется элемент данных. — Прим. пере в. «) Здесь под источником подразумевается место хранения элемента данных. — Прим. перев. нужна информация об источнике каждого элемента данных и о том, как он используется. В табл. 3.2 указаны источник и назначение каждого элемента данных. Дополнительная информация может быть добавлена, как только это окажется нужным.
Глава 3 Таблица потоков данных, в которой представлены все дан- | ?ные, циркулирующие между отдельными частями системы, на- * зывается словарем данных. В словаре данных обычно содержит- I ся информация о каждом элементе данных. Табл. 3.3 представ- | ляет собой словарь данных, используемый программами корректировки, рассылки счетов и адресации в системе подписки . гаа журналы. В словаре присутствуют области хранения данных, * а не потоки данных, и не приведены детали физической организации хранения агрегатов данных. Например, не указано, имеют ли записи фиксированную или переменную длину, сблоки- I рованы ли, снабжены ли ключами, распределены ли по отдель- | ным файлам. Н В словаре данных должны быть предусмотрены следующие ; записи: имя элемента данных с пояснениями; диапазон возмож- ;| ных значений с пояснениями, например F, M, U (женщина, || мужчина, не определено); тип элемента данных; отношения || между данными; источник данных; использование данных; не- || обходимая степень защиты; данные о том, является ли элемент || обязательным или необязательным и при каких условиях. ц Обычно возникает конфликт между способами использова- I ния данных и их логической структурой. Это касается не только I программы адресации, формирующей адресные метки, и программы корректировки, имеющей доступ по всем структурам, но J относится также к программе проверки счетов, которая форми- : рует извещения об окончании сроков подписки, и к программе Л адресации, осуществляющей доступ к структурам с помощью ;\ элементов, а не главных ключей. Для задания представления ) индивидуальных элементов данных в словарь вводится инфор- ц мация об их типах, длинах и диапазонах возможных значений. Представление групп данных зависит от конкретной СУБД или от системы управления файлами. Необходимо искать компромиссы между временем доступа, занимаемой памятью и частотой использования данных при согласовании логической органи- | зации и представления данных. Так как издания журналов будут пересылаться подписчикам значительно чаще, чем извещения об окончании срока подписки, и, кроме того, модификация информации о подписке будет осуществляться сравнительно редко, то данные на физическом уровне должны быть организованы так, чтобы наиболее эффективно работала программа адресации. 3.4.2. Эргономические факторы Данные, являющиеся внешними по отношению к системе (например, входные и выходные данные), должны быть представлены в форме, удобной прежде всего для восприятия человеком. Как входные, так и выходные данные должны легко чи-
Таблица 3.3. Словарь данных Для подписки на журналы Имя, дополнительные имена, пояснение ACC-NO CUST-ACCT-NO Номер подписчика CUST-NAME Фамилия подписчика CUST-ADDR NEW-ADDR Адрес подписчика ADDR-REG-CODE NEW-REG CODE Код округа MAG-ACCT-NO NEW-MAG-ACCT-NO Номер подписки MAG-EXP-DATE NEW_MAG_EXP_DATE Срок окончания подписки SUBSCRIP-NO NEW-SCRIP Новый журнал SUBSCRIP-EXP Дата продления TRANS-CODE Код корректировки Тип Числовой Символьный Символьно-числовой Символьный Символьный Символьно-числовой Символьно-числовой Числовой Символьный Диапазон 10 000-59 999 — — Е, S, M, W COMPLIFE АСТ-СОМР JAN 70-DEC 90 Номер счета+ -1-название журнала 1, 2, 3 А, Т, М Цель использования КОРРЕКТИРОВКА АДРЕСАЦИЯ, ПРОВЕРКА СЧЕТОВ АДРЕСАЦИЯ, ПРОВЕРКА СЧЕТОВ АДРЕСАЦИЯ АДРЕСАЦИЯ, ПРОВЕРКА СЧЕТОВ КОРРЕКТИРОВКА, ПРОВЕРКА СЧЕТОВ -"-— КОРРЕКТИРОВКА КОРРЕКТИРОВКА Где используется Ключ подписчика "—"~ Ключ издания Ключ подписки Ключ счета Ключ подписки Ключ сообщения Источник СОЗДАНИЕ СОЗДАНИЕ СОЗДАНИЕ, КОРРЕКТИРОВКА СОЗДАНИЕ, КОРРЕКТИРОВКА СОЗДАНИЕ, КОРРЕКТИРОВКА СОЗДАНИЕ, КОРРЕКТИРОВКА СОЗДАНИЕ, КОРРЕКТИРОВКА КОРРЕКТИРОВКА КОРРЕКТИРОВКА От чего зависит — Номер подписчика Номер подписчика (и код сообщения) Адрес подписчика Номер подписчика (и код сообщения) Идентификатор подписки _ Идентификатор подписки (и код сообщения) —
76 Глава 3 таться, не требуя для этого специальных знаний о форматах, I кодах и представлении данных. В процессе проектирования этой Л части внешнего представления данных важны как общая орга- *| низация данных, так и конкретные детали представления. Op- I ганизация зависит от системных спецификаций и требований I пользователей. Детали зависят от языка реализации, но прежде I всего определяются стилем, вкусом и возможностями адаптации I к условиям, возникающим в процессе использования системы. I Формат ввода числовых данных должен включать десятич- I ную точку и стоящие слева пробелы. Выводимые числовые дан- I ные должны быть отредактированы и снабжены специальными || метками так, чтобы можно было легко узнать, являются ли они || денежными единицами или результатами измерений физических II величин. Предположим, что необходимо использовать коды, i Тогда мнемонические сокращения предпочтительнее числовых I кодов (М и F лучше, чем 1 и 2). Если нужно применить аббре- I виатуру, следует отдавать предпочтение стандартным сокраще- I ниям (М, Т, W, TH, F, SA, SU лучше, чем М, Т, W, H, F, S, U). I Если применяется нестандартная аббревиатура или код, поль- I зователю часто будет нужна подсказка, поясняющая смысл со- I общения. Соответствующие формы должны быть предусмотрены I для представления как входных, так и выходных данных. А Входные данные должны легко восприниматься человеком. I Их следует вводить в полном соответствии с организацией. При I вводе необходимо использовать разделители. Таблицы должны I вводиться по строкам. Границы строк должны приходиться на I интервалы между данными. Ошибки ввода, заключающиеся в I пропуске символа или появлении лишнего символа, можно лег- I ко обнаружить еще до ввода данных в ЭВМ, если данные пред- I ставлены в форме, удобной для восприятия. Системы ввода дан- I ных, работающие в режиме on-line, выдают пользователю вво- I димую информацию, давая ему таким образом возможность ис- I править допущенные ошибки до передачи данных в ЭВМ. Для I ввода с перфокарт и с клавиатуры на диск в системах ввода I должны быть предусмотрены специальные форматы. I ВХОДНЫЕ ДАННЫЕ ДОЛЖНЫ БЫТЬ УДОБНЫ ДЛЯ ВОСПРИЯТИЯ I Выходные данные обычно выводятся в форме, которая в эко- I номике получила название «отчеты», или в форме, которая в I диалоговых системах называется ответами на запросы. Какое I бы количество данных ни выводилось, их надо разместить в со- I ответствии с заданными форматами, озаглавить, датировать и I снабдить специальными метками. Числовые данные должны I быть отредактированы и снабжены специальными метками. Не- I числовые сообщения следует выводить вместе с пояснениями, I касающимися используемых в них кодов и сокращений. Каж- I дая страница должна быть пронумерована, озаглавлена и да- I J
Методы организации данных 77 тирована. К результатам, возможно содержащим ошибку, необходимо привлекать внимание пользователя. Для этого можно использовать флажки, например три звездочки (***). ВЫХОДНЫЕ ДАННЫЕ ДОЛЖНЫ БЫТЬ УДОБНЫ ДЛЯ ВОСПРИЯТИЯ Отчеты обычно разбиваются на страницы и часто содержат управляющие поля — элементы данных, используемые для группирования данных. Изменение значения этого элемента приводит к временному прерыванию процесса обработки, называемому управляющим прерыванием. В отчетах службы подписки на журналы информация о подписке группируется по округам. Для каждого округа можно определить различные статистические характеристики, касающиеся подписки. Такая же информация может быть в дальнейшем получена в каждом округе для каждого журнала. Коды округов и названия журналов будут служить управляющими полями. Внутри управляющих групп различные характеристики, вероятно, следует расположить в соответствии с алфавитным, числовым или некоторым другим порядком. Разделение на страницы относится к физической организации данных, а группирование в соответствии с управляющими полями — к представлению данных. Внешнее представление данных может быть описано или изображено с помощью графической схемы. Используя коды округов и названия журналов в качестве управляющих полей, можно представить отчет о подписке в следующем виде: название отчета (Пример 3.14) название округа название журнала характеристика подписки итоги подписки на журнал название журнала характеристики подписки итоги подписки на журнал итоги подписки по округу название округа • • • «тоги подписки по округу общие итоги. Графическая схема для приведенного выше описания дана на рис. 3.3. Графические схемы такого типа получили название «иерархические схемы данных». Звездочки на схеме обозначают, что блок может повторяться несколько раз. Введение нового управляющего поля приводит к появлению еще одного уровня иерархии. Каждый уровень схемы связан с одной управляющей группой. Весь отчет делится на части, соответствующие различным округам. Каждая часть, соответствующая какому-то округу, в свою очередь делится на части, соответствующие различ-
78 Глава 3 ным журналам. Заголовки отчетов, округов и журналов должны включать значение управляющего поля для определенной группы. Итоговые характеристики иногда не нужны. На рис. 3.4 показана физическая организация данных, составляющих отчет. Она учитывает такие характеристики печа- 1 Название отчета 1 Название округа 1 Название журнала Отчет Отчет по * конкретно^ му округу Отчет по * конкретному журналу Данные * о подписке на журнал I Общие итоги подписки Итоги подписки по округу Итоги подписки на журнал] Рис. 3.3. Иерархическая схема отчета (логическая структура). тающего устройства, как длина строки и размер страницы, и связана с содержанием отчета только в том случае, если распределение по страницам зависит от управляющих прерываний. Кроме того, ширина полос и расположение информации зависит от характеристик как отчета, так и печатающего устройства. Заголовки и итоги, относящиеся к страницам, должны включать г~ Заголовок страницы Отчет -й- Страница отчета Содер- * жимое страницы 1 Итоги по странице Рис. 3.4. Иерархическая схема множества страниц, составляющих отчет (физическая структура)*
Методы организации данных 79 номера страниц, дату, идентификатор отчета, легенды и, возможно, заголовки столбцов и итоговые результаты. Иерархические схемы, приведенные на рис. 3.3 и 3.4, предназначены для описания одного и того же отчета, но находятся в таком отношении друг к другу, что непосредственно сопоставить их нельзя. Диаграмма логических отношений между данными, графическая схема внешнего представления данных и графическая схема физической организации являются тремя способами представления данных, относящимися к трем различным точкам зрения на данные. 3.4.3. Внутреннее представление Представление данных в программах зависит от применяемого языка программирования. Наиболее часто используемыми типами данных являются скаляры, одномерные массивы и записи. Большинство языков программирования позволяет работать со всеми этими типами данных. Часто наиболее подходящими являются структуры данных типа множеств, связанных списков или таблиц. В принципе все подписчики на журналы составляют множество, так же как и относящиеся к одному подписчику журналы. Множество представляет собой совокупность элементов. В отличие от массива или последовательного файла элементы множества не упорядочены. Не существует причины, по которой информация о подписке должна быть упорядочена каким-то вполне определенным образом. Правда, необходимо так представить данные, чтобы можно было всегда быстро получать нужную информацию. Так как языки программирования позволяют работать с различными типами данных, задачей программиста является выбор такой структуры данных, которая наиболее удобна для представления информации. Для целей ввода-вывода скалярные элементы данных следует рассматривать либо как независимые элементы, либо как элементы записей. В зависимости от возможностей языка программирования данные могут быть либо строками символов, либо десятичными или двоичными числами; числа, в свою очередь, могут быть представлены в форме с фиксированной или плавающей точкой. Конкретная форма представления данных и их размеры зависят от конкретной ЭВМ. Десятичные числа с фиксированной точкой — основной тип числовых данных Кобола. В Фортране числа представляются в двоичной форме с фиксированной или плавающей точкой (целые, вещественные). В Бейсике используются двоичные числа с плавающей точкой. ПЛ/1 позволяет задавать произвольное количество цифр в десятичном или двоичном числе либо с плавающей, либо с фиксированной точкой. Но представление чисел в ПЛ/1 зависит еще и от
80 Глава 3 ЭВМ, поэтому реально программисту предоставляется меньше возможностей, чем предусмотрено в общем описании языка. В программе или в области размещения данных они обычна объединены в массивы, структуры или более сложные агрегаты, которые часто непосредственно не предусмотрены в языках программирования. Существующие механизмы доступа, работающие со структурами и массивами, изолированы от программи- * ста. В том случае, когда разрабатываются новые сложные типы | данных, расширяющие возможности языка, механизмы доступа f к ним также следует изолировать. Обычно для реализации сложных типов данных можно использовать несколько способов. Например, стек можно реализовать в виде массива переменной длины, находящегося внутри массива с фиксированными границами, или в виде связанного списка, управляемого указателями. Для того чтобы упростить внесение изменений в эти типы данных, их следует поместить в отдельные программные модули. Если для внутреннего представления данных удобно использовать стек или множество, программист может создать специальное расширение языка, включающее этот тип данных, причем обращаться к нему можно только путем вызова подпрограмм. Остальные модули программной системы не зависят от способа реализации этого ново- - го типа данных. Независимо от того, как реализован стек, процедуры, работающие с ним, должны уметь помещать элементы в стек, выбирать элементы из стека и проверять, не пуст ли стек. Для тога чтобы можно было работать с новым типом данных, необходимы специальные операции, например: NEW (стек) создание пустого стека PUSH (стек, элемент данных) помещение элемента в стек POP (стек) выборка элемента из вершины стека EMPTY (стек) проверка, пуст ли стек; результатом является значение «истина», если стек пуст, и «ложь» в противном случае NEW и PUSH — базовые операции, используемые при работе са стеком. Первая создает стек, а вторая помещает в него элементы. POP — процедура, характерная только для работы со стеком. Она может быть определена с помощью двух базовых промежуточных операций ТОР и REMOVE, которые позволяют выделить две основные функции, реализуемые POP, но не определяют способов организации доступа к структуре из других модулей: POP (стек) =ТОР (стек); REMOVE (стек) (Пример 3.16) TOP (PUSH (стек, элемент данных)) = элемент данных TOP (NEW (стек)) —ошибка REMOVE (PUSH (стек, элемент данных)) = стек REMOVE (NEW (стек)) —ошибка
Методы организации данных 81 Определим с помощью базовых операций функцию EMPTY: EMPTY (PUSH (стек, элемент данных)) = ложь EMPTY (NEW (стек)) = истина Согласно приведенным выше определениям, POP выбирает вершину стека и удаляет ее из стека. Вершина стека описывается следующим образом: если стек- не пуст, вершина представляет собой последний помещенный в стек элемент; в противном случае стек не имеет вершины. Операция REMOVE дает в результате стек без элемента, помещенного "в него последним. К только что созданному стеку операцию REMOVE применять нельзя. Для определения пустого стека используется понятие нового* (или только что созданного) стека: пустой стек не содержит нет одного элемента. Любая реализация стека должна удовлетворять этим определениям. Важно, что стек определяется как способ хранения данных, которые подчиняются определенным правилам, действующим при обращении к данным, а не как массив с указателями, которые передвигаются по определенным правилам. В языке Ада существует несколько механизмов, позволяющих изолировать детали реализации. Используя их, стек можно определить следующим образом: .generic (type ITEM) (Пример 3.16) package STACKS is restricted type STACK is private; procedure PUSH(I: in ITEM; S: in out STACK)? procedure POP(l: out ITEM; S: in out STACK); procedure CREATEfS: out STACK); > ^ function EMPTY(S: in STACK) return BOOLEAN; private type STACK is . . t (определение стека) end; package body procedure PUSH (I: in ITEM; S: in out STACK) is . , , (определение.процедуры) end PUSH; . . . (другие Определения процедуры) end STACKS; Пакет (package) представляет собой программный модуль, состоящий из двух частей. Первая часть содержит информацию, необходимую для использования пакета. В нее входят имена процедур PUSH, POP, CREATE, EMPTY и списки их параметров. Приватный тип STACK может использоваться только в этом пакете и только с определенными операциями. Другой па- 6-399
S2 Глава S раметр ITEM является параметром настройки. Его тип определяется в момент создания стека, и все элементы стека должны относиться к этому типу данных. Поэтому описанный пакет может использоваться для работы со стеками, предназначенными для хранения данных разных типов. Формальное определение любого сложного типа данных допускает несколько различных реализаций. Но новый тип данных можно определить, ориентируясь только на способы обращения к нему. Для определения множества можно использовать базовые операции NEW (множество) ADD (множество, ключ, элемент) и дополнительные средства обращения RETRIEVE (множество, ключ) EMPTY (множество) IN (множество, ключ) В математике определение множества как совокупности элементов подверглось за последнее время незначительным изменениям. Считается, что множество состоит не только из известных элементов — ключей, но также из элементов другого типа, связанных с ключами. В вычислительной технике идея определения информации по ключу используется значительно чаще, чем просто поиск уже известного ключа. Дополнительные процедуры для работы с множествами определяются через базовые операции следующим образом: RETRIEVE(NEW(множество), ключ) - ошибка ' (ПРимеР 317) RETRIEVE(ADD (множество, ключ1, элемент данных), ключ 2) = if ключ 1 = ключ 2 then элемент данных, else ЙЕТИ1Е\/Е(множество, ключ 2) EMPTY(NEW (множество)) = истина EMPTY(ADD (множество, ключ, элемент данных)) = ложь IN (NEW (множество), ключ) = ложь 11Ч(АОО(множество,ключ 1,элемент данных), ключ 2) = if ключ 1 = ключ 2 then истина else ^(множество, ключ2) Состав стандартных операций над множествами может быть расширен. Введем, например, операцию определения мощности множества SIZE (NEW (множество))-О SIZE (ADD (множество, ключ, элемент данных) ) = !+SIZE (множество)
Методы организации данных 83 3.5. Физическая организация данных На логическом уровне данные могут быть упорядоченными или неупорядоченными, однородными или неоднородными, содержать ключи или не содержать. Результаты физических измерений считаются упорядоченными, если они делаются в строга определенные промежутки времени и если время является одной из используемых для анализа переменных, но явно не входит в состав данных. Данные можно упорядочить и с помощыо других измеряемых величин, таких, как расстояние. Данные- считаются неупорядоченными, если та величина, в соответствии со значениями которой они группируются, не является хотя бы неявно частью данных. Упорядоченность предполагает неявное задание меры, достаточной для сравнения значений переменной; эта мера в случае необходимости может быть выражена явно, т. е. на логическом уровне данные будут неупорядоченными, если порядок вводится только из соображений удобства доступа к отдельным элементам данных и подсчета общего количества данных. Наиболее адекватной формой представления неупорядоченных данных является множество. Удобной формой. представления упорядоченных данных может быть очередь, стек,, список или массив. I Результаты физических измерений обычно неоднородны, по- I скольку либо одни величины измеряются в несколько раз чаще, I чем другие, либо единицы измерений различны, либо множество I данных содержит элементы разных типов. Записи экономиче- I ской информации также неоднородны, поскольку они состоят иа I символьных и числовых полей разной длины. Внутренние и I внешние формы представления однородных и неоднородных I данных могут быть одними и теми же, если в языке программи- I рования предусмотрено либо динамическое распределение па- I мяти, либо памятью может управлять программист. На уровне- I физической организации совокупность данных должна быть од- I нородной. I Если никогда не потребуется идентифицировать отдельные- I элементы данных только на основании их порядка, считается,, I что данные на логическом уровне не содержат ключей. В. про- I тивном случае можно считать, что данные снабжены ключами, I Характеристика, используемая для идентификации (ключ), яв- I ляется подполем данных. Иногда целесообразно иметь несколь- I ко ключей для того, чтобы обеспечить многоаспектный доступ. I Ключи могут служить основой выбора внутреннего представле- I ния данных. От них может зависеть также физическая органи- I зация данных. I Одни множества данных являются структурированными, I Другие--неструктурированными. Когда множество данных не- I в* L
€4 Глава 3 имеет характерной структуры, его элементы все же можно сгруппировать для удобства работы с ними. Это имеет место, когда результаты нескольких измерений помещаются на одну перфокарту, когда блокируются записи или когда несколько корректирующих сообщений вводятся вместе с терминального устройства. Если элементы данных связаны друг с другом, но принадлежат разным типам, их можно объединить либо в таблицы, либо в структуры (как в случае с информацией о подписчиках). Такие агрегаты данных в свою очередь объединяются в <еще большие агрегаты. Файл может состоять из строк табличной информации или из набора структурированных записей. Анализ потоков данных приводит как к декомпозиции потоков данных на повторяющиеся компоненты, состоящие из отдельных элементов данных, так и к объединению элементов данных в агрегаты с целью формирования на их основе структурированных областей хранения данных. Области хранения данных содержат одновременно несколько экземпляров различных агрегатов данных. Хотя агрегаты дан- ч ных могут быть логически неоднородными, ограничения, накладываемые языками программирования и устройствами ввода- вывода, требуют, чтобы области хранения данных были однородными на уровне агрегатов. В общем случае данные должны быть упорядочены либо с помощью ключей, либо с помощью ; искусственно введенных правил. Выбор физической организации данных позволяет определить, являются ли данные упорядоченными и требуются ли для их организации ключи. л 3.5.1. Упорядоченность/неупорядоченность Неупорядоченные данные формально можно описать с помощью множества, в котором существует правило, позволяющее определить, принадлежит элемент множеству или нет, и системы . доступа, которая организует доступ к нужному элементу. Элементы множества могут либо иметь ключи, либо сами быть ключами. Если с элементами множества необходимо выполнять какие-то операции, требуется система доступа, которая просматривает элементы множества последовательно или параллельно без учета какого-либо предварительно определенного порядка. , Так как к элементам множества необходимо постоянно обращаться, множества обычно реализуются в виде одномерных массивов или в виде файлов с последовательным доступом. Прямой доступ осуществляется с помощью выбора структуры файла, допускающего как прямой, так и последовательный доступ. Файлы и массивы могут состоять из упорядоченных и неупорядоченных данных. Фирма IBM вместо файла использует тер- <. J
Методы организации данных 85 мин «набор данных»1), для того чтобы подчеркнуть, что он часто состоит из логически неупорядоченных агрегатов данных. Если элементы множества не снабжены ключами, их можно хранить в массиве или в последовательно организованном файле. Если ключи предусмотрены, именно они служат основой организации системы хранения. Данные, которые необходимо упорядочить несколькими способами, используя различные ключи, могут храниться в базе данных, в многомерном массиве, в файле со связанными с помощью ключей элементами или в системе инвертированных файлов. Запись ID 01275 | Время 13681 I Место I 36604 | SAN FRANCISCO | а | 01275 I 13681 | 3.5.2. Однородность/неоднородность Неоднородные элементы данных отличаются друг от друга типом, организацией или длиной или состоят из частей, отличающихся друг от друга типом, организацией или длиной. На физическом уровне с неоднородными данными можно работать, если провести стандартизацию отличающихся друг от друга частей данных, т. е. необходимо унифицировать типы, размеры и все то, что связано с представлением данных в ЭВМ. Это может быть сделано с помощью введения единого для всех данных формата, содержащего пустые поля; стандартизации длины и использования самоопределяемых данных; объединения элементов различной длины в строку постоянной длины и использования нескольких описаний, то ес'гь если в записи о событии, происшедшем в определенное время в определенном месте, необходимо изменить место или время, то корректирующие данные можно описать различными способами (рис. 3.5). На рис. 3.5, а для описания любой корректирующей информации используется поле одной и той же структуры. SAN FRANCISCO организация Рис. 3.5. Физическая неоднородных данных. а — единый формат; б — самоопределяемые данные; в — различные описания. *> В отечественной литературе принято различать понятия «набор данных» и «файл». Набор данных — это совокупность данных, расположенных на внешнем носителе информации; файл — абстрактный объект, служащий для представления набора данных в конкретной программе. Автор для этих двух понятий использует один термин — файл, — Прим. перев.
86 Глава 3 Новое значение вводится в определенную часть поля, а другая его часть остается пустой. На рис. 3.5, б для значений корректирующих данных используется одно и то же поле. О том, что именно модифицируется, можно узнать либо с помощью кода сообщения, либо проанализировав входную информацию (если она символьная, изменяется место, а если числовая, изменяется время). На рис. 3.5,в корректирующая информация состоит из двух частей: первая содержит идентификатор записи и код сообщения, вторая — новые данные. Так как тип новых данных однозначно определяется с помощью кода сообщения, разные входные данные могут и не иметь одинаковых описаний. 3.5.3. Наличие ключей/отсутствие ключей Все хранимые данные должны быть доступны. Элементы данных, не имеющие ключей, должны размещаться последовательно в массиве или в последовательном файле, чтобы к ним можно было легко получить доступ. Данные, снабженные ключами, в зависимости от относительной частоты последовательных и случайных обращений к ним могут размещаться либо последовательно друг за другом, либо по некоторому методу хэширования. Если данные размещаются друг за другом, они могут быть либо отсортированы (во-первых, когда упорядочены, во-вторых, когда доступ к ним осуществляется с помощью индивидуальных ключей), либо не отсортированы. Если порядок не имеет значения, сортировать данные не обязательно- Если элементы данных размещены по определенному методу хэширования и, кроме того, возможен случай последовательного обращения к ним, необходимо дополнить специальными кодами неиспользуемые промежутки массива или файла или связать вместе информационные области памяти, вводя тем самым упо- Таблица ЗА. Формирование массива данных с помощью ключей а) Последовательно- б) Метод хэширования в) Метод хэширования и смежное размещение использование указателей Ключ Данные Ключ Данные Ключ Данные 127 215 241 369 397 482 553 0 241 482 553 0 215 0 127 397 369 241 482 553 ... ., 215 127 !.". 397 369 Указатель \ "\о 04 ► ... , ,,. 02 • • • 06 03 09
Методы организации данных 87 рядоченность. В табл. 3.4 приведено несколько разных способов хранения данных в массиве. К элементам данных, приведенным в столбце а, можно обращаться либо последовательно, либо используя метод двоичного поиска. В столбце б последняя цифра ключа используется алгоритмом хэширования, а свободные области памяти заполняются нулями. Элементы в столбце б не являются упорядоченными. Отметим, что нуль был бы неприемлемым заполнителем для столбца а. Оставшуюся часть таблицы столбца а следует заполнять данными, распознаваемыми быстрее любого числа, которое может использоваться в качестве ключа. К данным, размещенным в столбце в, можно организовать доступ либо в последовательном, либо в произвольном порядке. Последовательный доступ следует начинать с элемента восьмой строки, а для обращения к другим элементам использовать указатели. Для определения позиции элемента данных в массиве в алгоритмах хэширования часто используются либо определенные цифры, входящие в ключ, либо остаток от деления ключа на определенное число, например на размер таблицы. Когда хэширование для двух ключей дает одно и то же значение (например, для чисел 127 и 397 в табл. 3.4), конфликт можно устранить, разместив второе значение в области памяти, первоначально выделенной для первого значения. 3.5.4. Физическая организация внешних данных Физическая организация внешних данных зависит от типа используемых устройств ввода-вывода. В режиме непосредственного доступа к системе информация, предназначенная для ввода или вывода, представляет собой поток данных. Каждое сообщение, которое адресовано пользователю, сидящему за терминалом, или считывается с экрана терминала, представляет собой одиночный элемент данных. Последний может быть либо простым элементом, либо целой структурой данных, либо снимком с экрана терминала. Для хранения данных в терминале используется буфер. Он не имеет средств структуризации данных и сохраняет структуру посылаемых данных. Способ буферизации, например использование- двойной буферизации или циркулярных буферов, изолируется системой от пользователей. При решении большинства экономических задач приходится иметь'дело с файлами. Информация о служащих, клиентах, товарах, а также различные сообщения обычно хранятся в файлах. Кроме файлов для хранения исходных данных и промежуточных результатов в экономических приложениях используются вспомогательные файлы, которые по сути являются последовательными и хранятся либо на магнитной ленте, либо на диске. Место хранения определяется требованиями, существующими в конкретной организации. На ленте хранятся копии всех важных
88 Глава 3 дисковых файлов. Они обычно используются для восстановления утерянной или испорченной информации, но могут также служить для хранения важной информации в течение довольно долгого времени. Если часто используемый главный файл содержит записи с редко запрашиваемыми данными, может быть создан файл, содержащий данные о частоте использования каждой записи, и на основании информации, хранящейся в этом файле, из главного файла могут быть исключены определенные записи. Файлы, накапливающие сведения о работе с другими файлами, создаются только в тех организациях, где это является важным. Нет никаких оснований держать записи о клиентах, не ведущих больше никаких дел с фирмой. Но в медицинских учреждениях данные о пациентах должны храниться в течение нескольких лет. В учебных организациях также необходимо достаточно долго сохранять личные дела студентов, уже закончивших обучение. Файлы, предназначенные для регистрации изменений, должны содержать информацию о всех тех модификациях главного файла, которые проводились. Для каждой записи, которая была ' изменена, необходимо сохранять данные о ее начальном состоянии, изменениях и конечном состоянии. Файлы, в которых реги- : стрируются изменения, кроме того, можно использовать совместно с контрольными точками для повторного запуска программы корректировки файлов, выполнение которой было прервано» из-за системного сбоя. В областях, не связанных с экономикой, для хранения данных также используются файлы. Каждый раз, когда собрано большое количество данных, их надо где-то хранить. Файлы нужны для размещения в них экспериментальных данных, получаемых учеными и инженерами, статистических данных, собираемых социологами, и текстовых данных, которые исследуются специалистами гуманитарных наук. Файлы можно использовать просто для хранения собранной информации до тех пор, пока она необходима; их можно использовать для хранения г данных, которые вырабатываются ЭВМ, но которых слишком много для того, чтобы держать их в оперативной памяти. Кроме того, в файлах можно хранить данные, к которым необходимо периодически обращаться. Операционные системы используют файлы для временного хранения входных и выходных данных. Специальные файлы предназначены для размещения компиляторов, ассемблеров и загрузчиков. Программы пользователей могут храниться в файлах либо временно, либо постоянно. Файлы учета и регистрации содержат информацию о работе ЭВМ. Для особенно ценных пакетов дисков создаются файлы-дубликаты. ; Для решения на ЭВМ практически всех задач в той или ! иной степени необходимы файлы. С файлами, как и со всеми |
Методы организации данных 89 остальными структурами данных, могут выполняться различные операции. Файлы можно создавать, инициировать, модифицировать, переводить в пассивное состояние и уничтожать, файл модифицируется, если к нему добавляется новая информация, или часть его информации уничтожается, или некоторая информация, содержащаяся в нем, изменяется. В качестве информации могут выступать совокупности символов или строк, сообщения, записи и страницы текста. Во многих системах данные группируются в блоки фиксированной длины, называемые физическими записями. Эти блоки могут состоять из одной или нескольких логических записей. В зависимости от возможностей операционной системы записи и файлы могут иметь переменную длину (и тогда необходимая память будет выделяться динамически) либо их длина ограничена заранее определенной величиной. В языках ПЛ/1, Паскаль и Бейсик имеются операторы, которые позволяют рассматривать файл как непрерывный поток байтов или символов, игнорируя размеры реально существующих записей и границы блоков. В программах, использующих потокоориентированный ввод-вывод, преобразование из символьной формы и в символьную форму производится автоматически. Программист же управляет вводом-выводом с помощью форматов и объявлений. Для непосредственно вводимых с терминала данных можно использовать как потокоориентированную передачу, так и передачу блоками. В первом случае за один раз передается только один символ и их группирование производится средствами операционной системы только в пункте назначения. Из символов формируются изображения на экране дисплея, или они группируются так, чтобы удобнее было хранить их в массовой памяти. Во втором случае символы накапливаются в специальном буфере и передаются целыми группами. Управление передачей осуществляется с помощью ключа конец-строки, конец-сообще- яия или конец-страницы и начинается с согласования первых символов строки, сообщения или страницы. Независимо от того, состоят ли файлы из интерактивных блоков или из записей, операции ввода-вывода позволяют создавать, инициировать, выбирать, модифицировать, переводить в пассивное состояние, сохранять и уничтожать файлы. Текстовые редакторы, предназначенные для работы в режиме непосредственного доступа, позволяют манипулировать символами, строками, сообщениями или страницами. Системы управления файлами и массовой памятью ориентированы на работу с данными, разделенными на отдельные записи. Два метода доступа к данным используются при потокоори- «нтированной передаче и при передаче блоками. Если символы, строки, страницы или записи файла обрабатываются поодиноч-
90 Глава 3 ке, одна за другой, используется последовательный доступ. Если необходимы только определенные части файла, используется прямой доступ. Прямой доступ применяется также в системах редактирования текстов, предназначенных для работы в интерактивном режиме, когда редактор определяет местонахождение части текста или строки, начинающейся с заданной последовательности символов. В системах, использующих массовую память, может запрашиваться любая запись, имеющая ключ, причем ключ может быть частью записи. Процедуры, которые осуществляют доступ к информации, содержащейся в файлах, используют следующие элементарные операции: • определение начала текущего элемента данных; • определение позиции следующего элемента; • определение позиции заданного элемента; • считывание элемента из заранее определенной позиции файла; • запись элемента в заранее определенную позицию файла; • проверку доступности элемента. Определение позиции следующего элемента возможно только для файла, который содержит упорядоченную по какому-то признаку информацию. Эта операция является основой как последовательного, так и потокоориентированного ввода-вывода. Проверка доступности элемента связана с возможностью определения границ файла и с возможностью распознания элемента по его позиции, содержимому определенного поля или по ключу. Определение местонахождения заданного элемента возможно, если информация в файле снабжена ключами и можно автоматически осуществить поиск нужной последовательности символов. Ключи физических файлов не обязательно должны быть логическими ключами. Данные упорядочиваются и снабжаются ключами для удобства организации доступа к файлу даже тогда, когда они логически не упорядочены и не имеют ключей. В операционных системах наиболее часто используются последовательная, основанная на относительной нумерации, и индексная организации файлов. Существуют также различные варианты этих типов организаций. Кроме того, различные способы описания структуры файлов и различные методы доступа к ним распространены в разных языках программирования и в разных операционных системах. 3.5.5. Последовательные файлы Последовательный файл — это файл, в котором записи физически следуют одна за другой и доступны именно в таком порядке. В последовательном файле необходимо выделить первую запись, которая не имеет предшественницы, и последнюю, на-
Методы организации данных 91 зываемую обычно концом файла. В языках программирования для работы с последовательными файлами предусмотрены следующие средства: *^ ^^ Язык програм- ■"■"•^^миров ани я ^^-^^ Функции ^^\^ Инициирование файла Определение позиции первой записи Определение позиции предшествующей записи Проверка конца файла Получение текущей записи Замена текущей запи- си Размещение новой записи Размещение последней записи Закрытие файла Фортран z REWIND BACKSPACE END READ WRITE WRITE ENDFILE — Кобол OPEN OPEN — AT END READ REWRITE WRITE CLOSE CLOSE ПЛ/1 OPEN OPEN — ON ENDFILE READ REWRITE WRITE CLOSE CLOSE Такие устройства, как устройства считывания с перфокарт, АЦПУ, магнитофоны, работают только с последовательными файлами, поскольку из-за своих физических особенностей они могут обрабатывать информацию лишь последовательно. К сожалению, имеются существенные различия как между аппаратными средствами, так и между системами программного обеспечения, которые предназначены для работы с последовательными файлами. Например, одна система программного обеспечения может определять позицию записи до ее считывания или размещения, а другая система считывает или размещает запись, а затем- определяет позицию следующей по порядку записи. Использование одной магнитной ленты не позволяет провести корректировку расположенного на ней файла; для осуществления корректировки .нужна вторая лента, на которую будут переписываться измененные записи. Дисковые файлы можно корректировать. В то время как на диск или ленту можно записывать и читать информацию с помощью одной программы, для работы с устройством ввода с перфокарт и выводом на АЦПУ одной программы мало. ДЛЯ МОДИФИКАЦИИ ФАЙЛОВ НА МАГНИТНЫХ ЛЕНТАХ ИСПОЛЬЗУЙТЕ КОПИИ ЭТИХ ФАЙЛОВ В одних языках запись конец-файла размещается перед выполнением команды перехода к началу файла, но только в том
92 Глава 3 случае, если последним оператором, работавшим с файлами, был WRITE (а не READ). В других языках запись конец-файла всегда размещается перед выполнением перехода к первой записи. И наконец, в некоторых языках принято размещать запись конец-файла тогда, когда файл, открытый как выходной, закрывается. В одних случаях запись конец-файла размещается программистом, в других — автоматически, но всегда в конце области памяти, предназначенной для хранения данных, а не после последней записи файла. Для устранения трудностей, возникаю- щих при обработке последовательных файлов, весь файл должен быть просмотрен целиком, даже если не все данные, хранящиеся в нем, необходимы. Использование оператора DATA в Бейсике приводит к созданию последовательного файла, предназначенного только для чтения. В этот файл помещается поток данных ввода, состоящий из отдельных элементов данных, а не из символов или записей. В Паскале при формировании потока данных, помещаемых в последовательный файл, используются данные как в числовой, так и в символьной формах. В том случае, если элементы, составляющие файл, малы (например, символы и числа), в него включаются дополнительные символы. Эти символы, выполняющие, например, функцию индикации конца строки, распознаются так же, как и запись ко* нец-файла. Для более эффективной работы с последовательными потоковыми файлами часто требуются дополнительные средства. Например, чтобы изменить информацию в файле, надо предоставить возможность легко передвигать указатель символа илж элемента вперед или назад на заранее заданное число позиций. Паскаль позволяет это делать, так как использует программные средства, предназначенные для редактирования текстов. В ПЛ/1, Бейсике и Фортране не разрешается использовать потокоориентированную передачу для модификации файлов. В этих языках нужно создавать новый файл. Изменение данных в файле можно провести непосредственно, если использовать средства ввода-вывода, предназначенные как для работы с потоками данных, так и с записями. 3.5.6. Файлы с относительной нумерацией записей Обычно система управления файлами обеспечивает нумерацию записей, начиная с нуля или с единицы, или дает возможность пользователю самостоятельно определять номер первой записи. Задав начальную точку, можно организовать доступ* к записям, начиная с первой или с любой другой записи. Возможен также прямой доступ к записям. Для этого нужно только задать номер необходимой записи. Файлы с относительной
Методы организации данных 93» нумерацией весьма эффективны при последовательном доступе и не очень удобны для прямого доступа. Часто доступ, который пользователь принимает за прямой, в действительности является последовательным поиском, проводимым с помощью предназначенного для обслуживания дисков программного обеспечения. Задача состоит в следующем: чтобы сразу найти запись, ЭВМ должна знать адрес той области памяти, где эта запись расположена. Запись можно найти или с помощью последовательного поиска, или путем определения ее позиции, если известны длины всех записей, или с помощью справочника. Для доступа с помощью справочника, в свою очередь, требуется либо найти нужный номер в справочнике, либо вычислить адрес нужной позиции в нем. Поскольку номера записей являются упорядоченными и согласуются с последовательностью хранения данных, наиболее эффективно при создании файла использовать для ввода уже упорядоченные данные. Если система предусматривает автоматическую вставку пустых записей для неиспользуемых номеров, не все номера следует- использовать в качестве ключей. Данные можно добавлять только в конец файла; замена одной записи на другую возможна, если записи имеют одинаковые размеры и номера. Если в файл с относительной нумерацией необходимо периодически добавлять и удалять записи*, его структуру необходимо часто перестраивать. 3.5.7. Индексированные файлы Индексированные файлы создаются в основном для организации прямого доступа. Элементы в таких файлах хранятся последовательно в соответствии <с ключами доступа. Элементами файлов обычно являются записи, но в системах редактирования текстов в качестве элементов могут выступать строки или страницы. Последовательность элементов разбивается на подпоследовательности или сегменты подходящих размеров. Такое разбиение используется .специальными программами системы управления файлами. В процессе создания файла для элементов строится словарь индексов или справочник. Так как записи размещаются в памяти в соответствии со значениями их ключей, в справочнике удобно хранить только ключ последней записи каждого сегмента. Когда размеры файла достаточно велики, справочник разбивается на части, для каждой из которых создается свой справочник. Этот процесс показан на рис. 3.6. Для размещения новых записей и ключей предусмотрены свободные области памяти. Системы редактирования текстов используют в качестве ключей номера строк; однако для индексированных файлов ключи не обязательно должны быть числовыми. Они могут
94 Глава 3 быть номерами строк, номерами счетов или уникальными идентификаторами любого другого типа, например фамилией или лдресом. Справочники имеют древовидную структуру, каждый уровень которой состоит из -последовательного списка ключей. Для организации доступа требуется, чтобы поиск по дереву. дополнялся процедурами линейного поиска — просмотром глав- - «ого справочника с целью определения необходимого справочника более низкого уровня, просмотром этого нового справочника и т. д., до тех пор- пока -не будет найден нужный набор Рис. 3.6. Организация индексированного файла. записей. Эти записи просматриваются одна, за другой, пока не будет определена «искомая запись. При разработке первых вариантов структуры индексированных файлов1) предусматривалась возможность их использования для последовательного доступа. Поэтому справочники имели несколько иную структуру. Тогда в случае добавления к файлу новых записей последние помещались в конец области памяти, первоначально выделенной группе записей. Следует отметить, что при этом не было предусмотрено средств для автоматического изменения структуры файла, которое в принципе необходимо -при переполнении первоначально выделенных областей хранения. В современных системах управления файлами предусмотрено помещение новых записей в места, полностью соответствующие их ключам. Когда выделенная для групп > Автор имеет в виду индексно-последовательные файлы. — Прим. перев* к
Методы организации данных 95 записей область памяти исчерпана, структура файла претерпевает изменения: группа записей, относимая ранее к одному сегменту, разделяется на две и соответствующим образом изменяются справочники. Если файл регулярно корректируется, причем информация удаляется так же часто, как и добавляется, и близки статистические распределения ключей удаляемых и добавляемых записей, рассмотренная выше организация весьма эффективна. Если файл постоянно пополняется записями в случайном порядке, необходимо часто менять его структуру. С точки зрения программиста, индексированные файлы не зависят от аппаратного обеспечения. Программы управления файлами определенным образом подбирают величины сегментов записей и размеры областей словарей, чтобы минимизировать сумму перемещений головок записи-считывания и максимально использовать пространство дорожек. Сегментирование индексно-последовательных и других ранних версий индексированных файлов зависело от характеристик внешних устройств* и проводилось программистом. Выбор оптимальной структуры файла с виртуально-последовательной организацией осуществляется автоматически. В индексированных файлах организация последовательного* доступа усложняется, поскольку связь между записями осуществляется с помощью иерархической системы справочников.. Если записи в каждом сегменте упорядочены, последовательный доступ для индексированных файлов дает немного худшие результаты, чем для чисто последовательных файлов. 3.5.8. Обработка файлов Физическая организация файлов более или менее понятна программисту. Он также знает, что метод доступа оказывает влияние на эффективность программы. Но, выбирая подходящий тип файла и метод доступа, программист не должен заботиться о деталях организации файла, поскольку это осуществляется автоматически средствами языка программирования. Запись можно добавить в файл с помощью оператора- WRITE; ее можно модифицировать, применив сразу за оператором READ оператор REWRITE. Если запись добавляется ъ> файл с помощью последовательного метода доступа, она должна либо заменить уже использованную запись, либо быть добавлена в конец файла. При использовании прямого доступа? запись автсЗматичееки занимает свободное место, соответствующее ее ключу. Записи, подвергшиеся корректировке, автоматически занимают свое прежнее место. При проведении корректировки с помощью методов последовательного и прямого доступа записи в корректирующем файле упорядочиваются так же, как ъ главном файле. В этом случае корректировку разумнее
96 Глава 3 выполнять га пакетном режиме, а не в режиме непосредствен- I ного доступа к системе. Это позволяет избежать повторных I обращений в тех случаях, когда несколько запросов относится Н к одной записи или к записям, расположенным в одном блоке. I Если работа ведется в режиме непосредственного доступа, I как в вопросо-ответных системах, следует использовать метод I прямого доступа. I Запись, которую надо уничтожить, в действительности не I может быть удалена из файла до тех пор, пока файл не будет I модернизирован. Ключ этой записи можно приписать другой I записи, и тогда эта запись займет место первой. Запись можно I особым образом пометить и оставить на прежнем месте. Обыч- I но первый байт записи остается свободным и заполняется в I случае удаления записи. Пустые записи маркируются аналогич- | но. Записи, которые помечены таким образом, пропускаются I при обработке файла, а их место можно использовать для хра- I нения других записей. I Программист может сгруппировать записи файла так, что I количество обращений к диску станет меньше, а следовательно,: I уменьшится и время обработки запроса. Это осуществляется -1 путем объединения нескольких логических записей в одну фи- I зическую. Блокирование не оказывает влияния «а логические I связи, существующие между данными. При блокировании записей учитывается оптимальный размер сегмента, объединяю- 'I щего группу записей с близкими ключами (разд. 3.5.7), и отыскивается приемлемый компромисс между временем доступа и требуемой памятью. Наиболее эффективно использовать дисковую память можно только в том случае, когда удается за один раз считывать или записывать информацию полностью на всю дорожку. Но для этого потребуется достаточно большой буфер, в котором будет храниться информация, помещаемая на дорожке. Кроме того, это сразу сделает блокирование зависимым от физических характеристик дисковой памяти. Чтобы устранить эту зависимость, в блок помещают целое число логических записей и оставляют свободное пространство для учетной информации. Блокирование может иногда проводиться программистом. Если в конкретной системе управления файлами не предусмотрено блокирование, программист может использовать массивы и группировать записи явно. Если записи снабжены ключами, для доступа к блоку следует использовать максимальный ключ записи, принадлежащей блоку. Блокиро- \ вание программистом записей индексированного файла вруч- ную может потребовать от него разработки процедур доступа к справочникам или изменения размеров сегментов записей. В том случае, когда записи не сблокированы, большой объ- •€м внешней памяти расходуется впустую. Если файл располагается на магнитной ленте, за время, необходимое для тормо-
Методы организации данных 97 зкения и разгона между двумя обращениями, будет пропущен участок ленты, на котором могло бы поместиться 400—800 символов. Если файл располагается на диске, размеры .потерь на межблоковые промежутки будут зависеть от того, соответствуют ли размеры блока физическим характеристикам файла. Для каждого типа дисков существуют таблицы, в которых указаны оптимальные размеры блоков. В системах с виртуальной памятью размещение данных по страницам производится автоматически. 3.5.9. Выбор организации файлов Если данные и программы представлены в форме, понятной только для ЭВМ, «их можно разместить на диске «ли на ленте. Правда, магнитную ленту можно переносить, а диск — нельзя. Информация, записанная на магнитную ленту на одной ЭВМ, может быть считана на другой. Если лента достаточно мала, ее можно хранить на полке или пересылать по почте. Если под руками нет небольших съемных дисков, магнитная лента наиболее удобна для хранения редко используемой информации. В том случае, когда данные «ли программы используются часто, дисковая память предпочтительнее памяти на лентах. Для установки лент на магнитофон требуется оператор, а для диска, работающего в режиме on-line, оператор не требуется. Кроме того, время доступа к информации на диске меньше времени доступа к информации иа ленте. Файлы, размещаемые на дисках, можно организовать по-разному, используя как последовательный, так и прямой доступ. В отличие от ленты, записи на дисках -могут быть модифицированы без перестройки всего файла. Некоторые операционные системы не содержат программ для блокирования записей последовательных файлов. Часто встречаются задачи, для решения которых наиболее удобна последовательная организация файла. Если файл стабилен, или, что то же самое, не подвергается частым перестройкам (т. е. общее число обращений к нему существенно больше числа запросов, связанных с добавлением или уничтожением записей),- если записи и запросы могут быть упорядочены и, наконец, если «каждый раз, когда файл обрабатывается, используются многие его записи, без сомнения, нужно создавать последовательный файл. Эта ситуация характерна для таких приложений, как обработка контрольных счетов, ведомостей об успеваемости студентов и отчетов о товарных запасах. Но есть приложения (например, получение информации из личных дел студентов и изменение в режиме непосредственного Доступа содержимого банковских расчетных книжек), которые 7-399
98 Глава 3 связаны с организацией доступа к случайно выбранным записям файла с достаточно стабильной структурой. В данном случае последовательная организация была бы неэффективной. Наиболее целесообразно для этих целей использовать либо ин- дексно-последователь'ный файл, либо файл с относительной нумерацией. Если все-таки выбирается последовательный доступ, аргументы в его пользу должны |быть весомее аргументов в пользу прямого доступа. Если обработка ведомостей об успеваемости студентов, проводимая регулярно для всех студентов, важнее, чем внесение в ведомости локальных изменений и запросов, касающихся личных дел отдельных студентов, предпочтительнее последовательная организация файлов. Кроме того, все сообщения, касающиеся отдельных записей файла, можно временно задержать, отсортировать и в дальнейшем эффективно работать с последовательным файлом. Если регулярное появление новых курсов и отказ от старых, а также запросы, касающиеся личных дел отдельных студентов, преобладают над требованиями последовательной обработки, предпочтительнее прямая организация файлов. В системах резервирования авиационных билетов и в системах регистрации пациентов в больницах данные хранятся в файлах, имеющих весьма нестабильную структуру. Данные запрашиваются непрерывно и в случайном порядке; необходимо часто удалять и добавлять записи. Поэтому удобно использовать файлы с прямым доступом. Первоначально эти файлы должны содержать большое число пустых записей. Если можно присваивать новые ключи старым записям, пригодны также файлы с относительной нумерацией записей и файлы с индекс- но-последовательной организацией. Если повторное присваивание ключей запрещено, индексно-последовательный файл будет быстро переполняться и время доступа к нему значительно увеличится. При работе с индексно-последовательными файлами и файлами с относительной нумерацией необходимо учитывать следующие факторы: тип ключей, диапазон изменения значений ключей, возможность повторного назначения ключей записям, стабильность структуры файла. 3.6. Документирование данных В документацию, относящуюся >к организации данных, входят схемы и диаграммы, характеризующие отношения между элементами потоков данных и программами, между элемента* ми потоков данных* и областями хранения данных и различные уровни структурирования данных. Функциональные схемы используются для изображения потоков внешних по отношению к программам данных, а иерархические схемы модулей—для изображения внутренних ш>
Методы организации данных 99 токов данных. Сведения о данных, которые не локализованы в каком-то одном модуле, а участвуют в обмене информацией между модулями или программами (такие, как сведения о данных, хранящихся в разделяемых областях памяти, о списках параметров и о файлах), представляются в форме таблиц потоков данных, словарей данных и схем организации данных. Схемы организации данных .могут иметь форму графических схем, или графов отношений между данными. Описание всех уровней объединения данных в агрегаты также должно найти отражение в документации. Следует упомянуть в документации о методах хранения данных. Если в системе есть файлы, их описание должно включать «информацию о файловых метках, а в остальном они описываются так же, как разделяемые области памяти и параметры. Слова,рь данных, типа представленного в табл. 3.3, не заменит полное описание данных в системе, поскольку он предназначен для описания только представления данных и не отражает их логическую и физическую организацию. Информация о логической и физической организации обязательно должна быть включена в документацию. Кроме словаря данных в документацию должны входить: • схема и форма организации внешних данных; • указание тех компонентов аппаратного и программного обеспечений системы, которые являются источниками данных; • определение тех частей системы, которые обращаются к данным; • определение тех частей системы, которые изменяют данные; • описание всех уровней организации сложных структур данных; • описание памяти, необходимой для размещения данных, включая размер файла, блокирование, метки и т. п. В общесистемную документацию обязательно должна быть включена документация, в которой содержится информация, во-первых, о потоках данных, циркулирующих между функциональными блоками «и областями хранения данных, во-вторых* о данных, которыми обмениваются программы и области хранения (эту информацию также удобно представлять в виде словаря данных), и, в-третьих, об используемых ресурсах. Программная документация также будет содержать* информацию, относящуюся к документации данных, а именно описание потоков данных между программными модулями. Документация модулей будет, в свою очередь, включать аналогичную информацию, связанную с элементами данных, определенными внутри модулей. 7*
100 Глава 3 3.7. Упражнения 1. Используя язык, в котором есть указатели и средства для определения . различных типов данных (например ПЛ/1, Паскаль или Ада), опишите струк-"* туру, приведенную на рис. 3.1. 2. Определите логическую организацию данных для системы каталогиза- ции книг в библиотеке из упр. 1 гл. 1. 3. Определите логическую организацию данных для системы регистрация заявок на пользование теннисными кортами из упр. 2 гл. 1. 4. Разработайте словарь данных для системы каталогизации книг (см* упр. 1 гл. 1 и упр. 2). 5. Разработайте словарь данных для системы регистрации заявок на поль- ' зование теннисными кортами (см. упр. 2 гл. 1 и упр. 3). 6. Чем отличается последовательный файл от очереди? Какие операции можно выполнять с файлом и с очередью? Дайте формальные определения последовательного файла и очереди. 7. Чем отличается файл с прямым доступом от множества? Какие опера- ции можно выполнять с файлом и множеством? Дайте формальные определения файла с прямым доступом и множества. 8. Предположим, что в процессе регистрации избирателя клерк смотри? на карту и вводит в ЭВМ номер квартала, где живет избиратель. Предположим также, что, когда границы избирательного округа изменяются, данные о новых границах вводятся в ЭВМ, причем каждая точка изгиба границы избирательного округа задается путем указания номера квартала. Квартал всегда целиком принадлежит одному округу. Разработайте структуру данных, которую можно использовать для определения принадлежности квартала округу, когда границы округов меняются. 9. Предположим, что всем студентам университета присвоены номера». Каталог семестровых курсов содержит список курсов, упорядоченный по кафедрам и номерам курсов. Каждый студент должен посещать от 1 до 10 кур». сов. Количество студентов, желающих прослушать один и тот же курс, может быть любым. Для того чтобы прослушать определенный курс, студент должен зарегистрироваться. Разработайте описания структур данных для этой задачи на логическом уровне и на уровне представления данных, используя любой язык программирования с блочной структурой. Кроме того, опишите предполагаемую физическую организацию данных. \ i
Глава 4 ПРОЕКТИРОВАНИЕ ПРОГРАММ Процессы проектирования всей системы и отдельных программ во многом подобны друг другу. Но в отличие от системы каждая программа имеет единственное функциональное назначение и не может быть разбита на части, используемые в различные моменты ©ремени. Сходство программы с системой заключается в наличии внешних и внутренних потоков данных, областей хранения данных, а также в возможности разделения ее на независимые модули, которые можно разрабатывать и настраивать отдельно. В последние десять лет общее признание получил модульный принцип построения программ. Под модуляризацией понимается разделение программы 'на части по некоторым установленным правилам. Этими частями могут быть программные секции («в Коболе), внутренние процедуры (в ПЛ/1 и Паскале) или внешние процедуры (в Фортране, Коболе и ПЛ/1). Прикладные программы чаще всего слишком сложны, чтобы быть написанными как единое целое .или разработанными одним программистом. Поэтому сложность программ долгов время вызывала трудности при проектировании систем и программного обеспечения. Программы стали слишком велики, чтобы их можно было представить во всех подробностях как единое целое и поместить в памяти ЭВМ целиком. Когда для размещения больших программ в памяти машины стали применяться структуры с перекрытиями (оверлейные структуры), эти программы пришлось разбивать на модули. С появлением ЭВМ со страничной (виртуальной) памятью управление оверлейными структурами осуществляется автоматически, но эффективность распределения программы по страницам зависит от выбираемого программистом способа разбиения ее на модули. Программы разбиваются на модули для того, чтобы: • упростить -их разработку и реализацию; • облегчить чтение программ; • упростить их «астройку и модификацию; • облегчить работу с данными, имеющими сложную структуру; • избежать чрезмерной детализации алгоритмов;
102 Глава 4 • обеспечить более выгодное размещение программ в памя- ти ЭВМ. Методы проектирования программ, основанные на модуль* ном принципе, делятся на три группы: методы нисходящего проектирования, методы .расширения ядра и методы восходящего проектирования. На практике обычно применяются раз* личные сочетания этих методов. При решении задач системного и прикладного программирования могут возникнуть затруднения нескольких типов. Одно из них связано с рассмотренными выше проблемами организации начала обработки групп записей и формирования заголовков и окончаний в соответствии с переходом обработки от одной группы записей к другой. Эта ситуация возможна не только при генерации экономических отчетов. Она появляется при решении задач анализа статистических данных, при обработке программ в режиме пакетной компиляции, а также в некоторых других случаях. В потоке данных, представляющих собой нанесенные на перфокарты программы, имеются карты определения задания и окончания задания, которые предназначены для управления переходом обработки от одной группы записей к другой, выполняемого монитором управления пакетом. Каждая программа при печати должна быть оформлена в виде отдельных страниц, а заголовок и конец ее распечатки должны содержать общую информацию о соответствующем задании. Другое затруднение возникает при проверке правильности вводимых и определяемых внутри программ данных. Компилятор обрабатывает предложения языка с помощью проверки лексических единиц и синтаксиса отдельных предложений, а также с помощью общей структуры транслируемой программы. Программа сопровождения оценивает информацию в каждом очередном сообщении, определяет порядок сообщений, возможные общие переменные, а также проверяет, соответствуют ли сообщения записям главного файла. Работа компилятора по проверке исходных предложений в общем случае сложнее, чем проверка файла сообщений и главного файла, но многие аспекты этих процессов практически одинаковы. В одном только случае работу компилятора можно считать довольно простой, когда исходные предложения печатаются независимо от того, правильны они или нет. Затем, если обнаружена ошибка, на пе-. чать выдается соответствующее сообщение. Обновляемое сообщение не печатается и не обрабатывается до тех лор, пока не будут проверены и помещены в набор правильные или неправильные сообщения, поступающие на корректировку. Обработку всей группы сообщений при некоторых обстоятельствах необходимо откладывать до тех пор, пока не будет установлено, что все сообщения правильны. Такой способ обработки характерен для работы компилятора, когда программа
Проектирование программ 103 Не начинает выполняться до окончания проверки правильности исходного текста. В некоторых компиляторах печать сообщений 0б ошибках осуществляется после печати всего исходного текста. Указанный способ печати ошибок возможен, если неправильные сообщения сначала только накапливаются, а затем отдельно печатаются. В любом методе проектирования программ должны предусматриваться возможности для управления программой при переходе обработки от одной группы записей к другой, для проверки правильности данных, а также для одновременной обработки данных, .поступающих в разное время. Кроме того, каждый метод должен объединять средства управления физической организацией некоторых данных (например, распределением информации по страницам) с логической организацией обработки. 4.1. Метод нисходящего проектирования Метод нисходящего проектирования подобен методу получения детального изображения из более общего вида с помощью телескопического увеличения. На начальном шаге формируется предложение, описывающее функцию воей программы. Затем определяются ее подфункции. Эта процедура является рекурсивной, т. е., следуя ей, каждая ,из подфункций может расчленяться до тех пор, пока ее составные части ме будут окончательно уточнены. Метод нисходящего проектирования, иногда называемый функциональной декомпозицией, основан на двух стратегиях: пошаговом уточнении, разработанном Е. Дейк- строй, и анализе сообщений, базирующемся на работах Иодана, Константайна и Мейерса. Эти .стратегии отличаются способами определения начальных спецификаций, методами, используемыми при разбиении задачи на части, и правилами записи. 4.1.1. Пошаговое уточнение При пошаговом уточнении на каждом следующем этапе декомпозиции определяются программы очередного, более низкого уровня. Для этого используются процедурные языки программирования. Во-первых, задается заголовок программы, соответствующий ее главной функции, например procedure обработка-пакетов Затем определяются основные шаги обработки пакетов информации: procedure обработка-пакетов; сортировать-записи-по-управляющим-полям, ятделить-правильные-записи-от-неправильных-и-обработать endprocedure.
104 Глава 4 С помощью расширения шагов процедуры выполняется даль- нейшее уточнение программы. На некоторых шагах уточнения появляется необходимость в использовании управляющих структур: procedure обработка-пакетов; '' сортировать-записи-по-управляющим-полям; взять-первую-запись *> while не-конец (входнбй-файл) do взять-правильную-управляющую -группу обработать-группу-записей endwhile; обработать-неправильные-управляющие-группы endprocedure. -- Операция сортировки не разбивается иа шаги, так как для ее выполнения часто применяется имеющаяся в системе программа сортировки. Если же такой подпрограммы нет, ее следует написать и оформить в виде независимого модуля. Создание подпрограммы сортировки включает выбор алгоритма. Но . выбранный метод сортировки должен быть изолирован от остальной части программы. Однако не все расширения можно откладывать на более поздний срок. Допустим, что отсортированная информация помещается в файл с последовательным доступом (вполне естественное предположение для рассматриваемых условий). Если метод доступа к данным на этой стадии проектирования неизвестен, можно использовать какое-либо нейтральное выражение, такое, как while еще-есть-данные do. На следующей стадии уточнения можно приостановить определение каких-либо других предложений, выделяя их в вызываемые функции -и процедуры в том случае, если они функционально независимы от основной процедуры обработки или если необходимо отложить их детализацию: procedure обработка-пакетов; сортировать-записи-по-управляющим-полям; взять-первую-запись; while не-конец (входной-файл) do repeat взять-управляющую-группу until найдена-правильная-группа или конец-файла (входной-файл) обработать-заголовок-группы; обработать-записи-группы; обработать-окончание-группы ^ endwhile; обработать-неправильные-управляющие-группы endprocedure. Единую группу записей можно получить, сохраняя каждую из записей вплоть до перехода к обработке другой группы, ко- ^
Проектирование программ 105 торнй определяется с помощью единицы данных, указывающей на смену группы. Бели в группе найдена неправильная запись, оставшаяся часть группы не обрабатывается по установленным правилам, а только проверяется на правильность. Для некорректной группы сохраняются записи для печати ошибок или сообщения об ошибках, но в последнем случае записи уничтожаются. Если установлена корректность группы, ее записи обрабатываются установленным порядком. Конец файла указывает на момент перехода обработки для всех управляющих полей: procedure обработка-пакетов; сортировка-записей-по управляющим-полям; взять-первую-запись; while не-конец (входной-файл) do repeat инициал изировать-управляющее-поле; принять-корректность-группы; repeat проверить-запись; ■ сохранить запись; взять-следующую-запись; until неправильная-запись или переход-к-другой-группе; if неправильная-запись then группа-некорректна; передать-сохраненные-записи-в-файл-ошибок; обработать-остаток-некорректной-группы; until установлена-корректность-группы или конец-файла (входной-файл); обработать заголовок-группы; взять-первую-сохраняемую-запись-группы; while не-конец-группы do обработать-запись; взять-следующую-сохраненную-запись; end while; обработать -окончание-группы endwhile; взять-первое-сообщение-об-ошибке; while не-конец-файла (файл-ошибок) do. выдать-сообщение-об-ошибке; взять следующее-сообщение-об-ОШИбке; endwhile endprocedure. Подобная программа может быть использована для выполнения проверок в главном файле, составленном из контрольных оценок, каждая из которых записывается снова только в том случае, если все соответствующие ей сообщения корректны. Такая же программа может быть использована для пакетной компиляции отдельных модулей. Выполнение этих 'модулей
106 Глава 4 возможно только при условии, что 'В соответствующих исход- ных текстах нет синтаксических ошибок. В случае пакетного компилятора записи должны быть за- ) ранее отсортированы, даже если порядок следования входных записей указывается номерами строк, предусмотренными синтаксисом языка программирования. Для проектирования программ методом пошагового уточнения применяется способ кодирования с помощью псевдокода и управляющих конструкций структурного .программирования. Взаимное расположение записей должно обеспечивать читабельность всей программы. Обычно используется псевдокод, подобный структурированному языку программирования. Поэтому преобразование в программный код выполняется .на любом этапе проектирования непосредственно. Разбиение на модули осуществляется эвристическим способом. На каждом этапе проектирования по возможности не уточняются операции с данными (эти вопросы откладываются на более поздние сроки). Но выбор управляющих конструкций нельзя откладывать, так как на последующих этапах труднее изменить ранее выбранные конструкции. Нельзя сказать, что этот метод полностью независим от языка. Так, например, решение об использовании первоначального чтения из файла зависит от определяемого языком программирования способа обработки условия конца файла. Кроме того, некоторые конструкции циклов в одних языках реализуются легче, чем в других. Приведенный выше пример процедуры пошагового уточнения иллюстрирует основные принципы данного метода проектирования программ. Но дальнейшее уточнение нельзя проводить без рассмотрения конкретных .прикладных особенностей программы: без распределения по страницам, если целью обработки является выдача отчета; без определения главного файла, если объектом проектирования служит программа сопровождения главного файла или программа, формирующая запросы к этому файлу; без детализации обработки ошибок. На этапе, когда принимается решение о прекращении дальнейшего уточнения, оставшиеся неопределенными подфункции становятся вызываемыми функциями или процедурами, а проектируемый модуль — управляющим модулем. Преимущество метода пошагового уточнения заключается в том, что основное внимание при его использовании обращается на проектирование корректной программы, а не только на детальное понимание задачи. Поскольку первый этап проектирования корректен, а каждый последующий этап является уточнением предыдущего лишь с небольшими изменениями, то легко может быть выполнена проверка корректности процесса разработки на всех этапах. Недостаток этого метода состоит в том, что на поздних стадиях
Проектирование программ 107 проектирования может обнаружиться необходимость в структурных изменениях, требующих пересмотра более ранних конструкций. НЕ ДЕТАЛИЗИРУЙТЕ РАНЬШЕ ВРЕМЕНИ ОПЕРАЦИИ С ДАННЫМИ 4.1.2. Анализ сообщений Анализ сообщений основывается на анализе потока данных, обрабатываемых программой. Этот вид анализа в большей степени относится к процессам передачи и преобразования отдельных записей запросов или других входных элементов, чем к операциям управления потоком данных. На диаграмме, приведенной на рис. 4.1, представлены потоки информации и обрабатывающие их процессы, являющиеся внутренними для программы обработки пакетов данных. Шаги, выполняемые при идентификации потоков данных и процессов, аналогичны соответствующим шагам в проектировании систем. Первоначальный поток данных разбивается на три потока: первый содержит * Сообщения J * об ошибках * Рис. 4.1. Потоки данных для программы пакетной обработки. непреобразованные входные данные, а последний — только выходную информацию. Границы, разделяющие эти потоки, показаны на рис. 4.1 в виде штриховых линий, которые делят схему на три части. Данные, подлежащие обработке^ помощью процесса, обозначенного кружком, могут не включать всю исходную информацию, но эту информацию еще можно считать частью входных данных. Кружок, представляющий обработку, обозначает процессы кодирования, декодирования, расчета, а также другие преобразования данных. Результаты, выходящие
108 Глава 4 из кружков, представляющих обработку, являются еще не отредактированными ,и не отформатированными данными и могут содержать некоторые результаты, которые впоследствии уничтожаются как неправильные, но при этом могут считаться выходными. Три части программы принято называть соответственно истоком, преобразователем и стоком. Преобразователь представляет собой основную часть программы, тогда как исток и сток выполняют функции управления входным и выходным потоками данных. На рис. 4.2 приведена иерархическая структура модулей, соответствующая рассмотренному разбиению диаграм- Исток 4ZHOKZb Преобразователь Сток Г" А Управление В '• 1 С Рис. 4.2. Анализ сообщений. в — анализ исток-преобразователь-сток; б — схема иерархии модулей. мы. Каждый из прямоугольников обозначает действительный программный модуль и может быть реализован в зависимости от его размера и сложности как подпрограмма, внутренняя процедура или только как секция программы. Линии указывают связи по управлению, а также изображают отношения типа вызывающий-вызываемый. Метод разбиения на «сток, преобразователь и сток, рекурсивно используемый на отдельных ветвях древовидной структуры модулей, представляет собой процесс декомпозиции программы, в результате которого получаются модули нижнего уровня. Не все модули подвергаются разбиению на три части более низкого уровня. Результат декомпозиции модуля-стока должен содержать сток, модуля-преобразователя — преобразователь, а модуля-истока — исток. Однако в модуле-истоке, например, не обязательно должны содержаться части, выполняющие функции преобразователя или стока. Вызывающий модуль действует как главный сток для данных модуля-истока и как главный исток для модуля-стока. Оба они будут истоком и
Проектирование программ 109 стоком для модуля-преобразователя. Если какой-либо преобразователь использует информацию от нескольких истоков, один из них является вызывающей программой, а другие — подмодулями истока. На рис. 4.3 приведена .иерархическая структура модулей для программы обработки пакетов. Поскольку объектом обработки « Обработка данных 1 Чтение правиль- 1 ного пакета Обработка пакета Запоминание правильных результатов 1 <а 1 4 Чтение правильного пакета 1 Чтение I пакета 5 Проверка пакета 6 Запоминание правильного пакета I Обработка I \ данных | 2 ]Обработка пакета I г 8 Чтение записи Обработка записи с I I 3 J Запоминание I I правильных | | результатов \ ) 10 Проверка! результатов | Запоминание резуль- I та то в б Рис. 4.3. Анализ сообщений при проектировании программы пакетной обра-* ботки. в — декомпозиция первого уровня; б — расширенная декомпозиция. является пакет, структура отображает процесс обработки одного пакета. Если все правильные записи пакета (.независимо от того, содержатся ли среди них неправильные записи) подлежат обработке, объектом обработки была бы запись. Этап сортировки информации и этап выдачи протокола выходят за рамки этой схемы, так как их выполнение не влияет на процесс обработки групп записей и результаты этих этапов несущественны для обработки пакетов корректных данных. Программы, реализующие эти этапы, связаны с рассматриваемой здесь программой только с помощью файлов, поэтому их правильнее считать отдельными частями системы, чем подпрограммами. Есля
ПО Глава 4 иерархическую структуру дополнить еще одни»! уровнем, сортировка должна стать истоком, структура, приведенная на рис 4.3,6, — преобразователем, а выдача протокола ошибок — стоком. Не все модули структуры используются для обработки полного объема считанной информации. Каждый модуль при информационном обмене использует определенную часть данных. Описание иерархической структуры должно содержать таблицу взаимодействия модулей, показывающую передачу данных между различными модулями. В этой таблице должны быть определены все способы информационного обмена, задаваемые как с помощью явного вызова процедур, так и через общие и внешние накопители. Если элементы данных, заданные на некотором уровне иерархии, явным образом используются на том же уровне, в таблице отмечается их передача по всем управляющим линиям этого уровня. В табл. 4.1 указаны информационные связи для структуры, приведенной на рис. 4.3. С ее помощью могут быть определены порядок и условия обработки данных. Таблица 4.1. Таблица межмодульных связей Модуль 1 2 3 4 5 6 7 8 9 10 Вход Правильный-пакет Результаты-пакета Данные-пакета Неправильный-пакет Правильная запись Правильная запись Результаты-записи Правильные-результаты Выход Правильный-пакет, Конец- файла Результаты-пакета Данные-пакета, Конец-файла Правильный-пакет, Неправильный-пакет Результаты-записи Правильные-результаты Табл. 4.1 отражает только те данные, которые передаются каждому модулю в момент его вызова. Поскольку главный управляющий модуль не может быть вызван другими, то в таблице не показана ни его 'входная, ни его выходная информация. Элементы данных, используемые главным модулем, изображены как выходные для модулей, которые он вызывает. Выходные данные главного модуля отображаются как входные для вызываемых им модулей. Итак, модуль-исток имеет выходные данные, сток — входные данные, а преобразователь — те и другие.
Проектирование программ 111 Стратегия анализа сообщений включает организацию как взаимодействия между модулями, так ,и функционирования отдельных модулей. Корректность структурного разбиения основывается .на внутренней коммуникативной способности каждого отдельного модуля и различных их (сочетаний. 4.1.3. Связность модуля Связность модуля определяется как мера независимости его частей. Чем выше связность модуля, тем лучше результат проектирования. Для обозначения связности используется также понятие силы связности модуля. Типы связности модулей приведены в таблице: Связность Сила связности 10 (сильная связность) 9 7 5 3 1 0 (слабая связность) Названия и оценки связности у разных авторов различаются, но незначительно. Модуль с функциональной связностью не может быть разбит на два других модуля, имеющих связность того же типа. Модуль управления обработкой пакетов имеет функциональную связность. Если бы он включал начальную сортировку информации и конечную выдачу протокола об ошибках, то имел бы последовательную связность. Модуль, который .может быть разбит только на исток, преобразователь и сток, также имеет функциональную связность. Он выполняет единственную функцию. Такой модуль реализуется последовательностью операций в виде единого цикла. Модуль, имеющий последовательную связность, -может быть разбит -на последовательные части, выполняющие независимые функции, но совместно реализующие единственную функцию. Если один и тот же модуль используется для оценки, а затем для обработки данных, то он имеет последовательную связность. Модуль с последовательной связностью реализуется как последовательность операций или последовательность циклов. Если модуль составлен из независимых модулей, разделяющих структуру данных, он имеет коммуникативную связность. Общая структура данных является основой его организации Функциональная Последовательная Коммуникативная Процедурная Временная Логическая По совпадению
112 Глава 4 как единого модуля. Если модуль спроектирован так, чтобы Ч упростить работу со сложной -структурой данных, изолировать эту структуру, он имеет коммуникативную связность. Такой мо- | дуль предназначен для выполнения нескольких различных и независимо используемых функций,, таких, как запоминание в ' поиск данных. Если же модуль разработан так, чтобы изолировать выбор алгоритма, он имеет функциональную связность. , Такой модуль может обрабатывать данные с изолированной структурой, но при вызове считается, что он выполняет единственную функцию. Модули высшего уровня иерархической структуры программы должны иметь функциональную !или последовательную связность. Для модулей обслуживания предпочтительнее коммуникативная связность. Если -модули имеют процедурную, временную, логическую или случайную связность, это свидетельствует о недостаточно продуманном их планировании. Модифика- ! ция уже существующей программы, когда модули разбиваются на части, часто приводит к этим типам связности. Процедурная связность обнаруживается в модуле, управляющие конструкции которого организованы так, как изобра- ; жены на структурной схеме -программы. Такая структура модуля может возникнуть при расчленении длинной программы на ' части в соответствии с передачами управления, но без определения какого-либо функционального базиса при выборе разделительных точек. Процедурная связность может появиться при «. группировании альтернативных частей программы. Если компилятор обрабатывает различные типы предложений, например объявления, процедуры, присваивания и управляющие конструкции в различных секциях одного и того же модуля, этот модуль имеет единственное функциональное назначение. Если для ~ уменьшения размеров он делится на два независимых модуля (один предназначен для обработки объявлений и процедур, а другой—для выполнения присваивания и управляющих конструкций), каждый из них имеет процедурную связность. Лучшим решением следует считать такое, при котором исходный модуль вызывает четыре других, при этом каждый из них выполняет различные типы предложений. Модуль, содержащий части функционально не связанные,, но необходимые в один и тот же момент обработки, имеет ере-, менную связность или связность по классу. Связность такого- типа имеет место в тех случаях, когда все множество требуемых в момент входа в программу функций выполняется независимым модулем активации. Вместо использования одного независимого модуля для активации в начале и другого для перевода в пассивное состояние в конце программы функции следует распределить между другими модулями. Активацию переменной или файла необходимо выполнять непосредственно
Проектирование программ 11& перед первой ссылкой к этой величине или файлу. Перевод же в пассивное состояние следует производить сразу после ссылки. В результате кроме увеличения 1меры общей связности программы устраняется возможность появления некоторых ошибок, файлы, (например, должны быть открыты столько времени^ сколько требуется для их обработки. Если файлы открыты в течение предельно короткого времени, они менее подвержены системным сбоям. Если в начале программы необходимо выполнять большое количество операций активации, выделение этих, операций в отдельный модуль менее желательно, чем включение в управляющий -модуль. Если в модуле объединены операторы только по признаку их функционального подобия (например, все они предназначены для проверки правильности данных или для управления операциями обмена с внешними носителями), а для его настройки применяется алгоритм переключения, такой модуль имеет логическую связность, поскольку его части ничем не связаны, а имеют лишь небольшое сходство между собой. Модуль, состоящий из разнообразных подпрограмм обработки ошибок, имеет логическую связность. Однако модуль, предназначенный для записи разнообразных сообщений об ошибках,, имеет коммуникативную связность, если из файла сообщений об ошибках с его помощью может быть получена вся выходная информация. Если операторы модуля объединяются произвольным образом, например когда необходимо указать их непосредственное размещение в области памяти, такой модуль имеет связность по совпадению. Три наиболее слабых типа связности (временная, логическая и по совпадению) возникают в результате неправильного планирования, обусловленного переделкой программных модулей после их реализации. ДОБИВАЙТЕСЬ ФУНКЦИОНАЛЬНОЙ СВЯЗНОСТИ ПРОЕКТИРУЕМЫХ МОДУЛЕЙ 4.1.4. Сцепление модулей Сцепление модулей представляет собой меру относительной независимости модулей, которая определяет их читабельность и сохранность. Независимые модули могут быть модифицированы без переделки каких-либо других модулей. Слабое сцепление более желательно, так как это означает высокий уровень их независимости. Модули являются полностью независимыми». если каждый из них не содержит о другом никакой информации. Чем больше информации о других модулях используется в них, тем менее они независимы и тем теснее сцеплены. Эта информация появляется в результате перекрестного использо- 8-399
114 Глава 4 вания .имен модулей, назначения вызываемых последовательностей, неявного применения входных и выходных кодов, а также ^ из данных, определяемых структурами общих областей памяти. Чем очевиднее взаимодействие двух связанных друг с другом модулей, тем проще определить .необходимую корректировку одного модуля, зависящую от изменений, производимых в другом. Большая изоляция ih непосредственное взаимодействие модулей приводит к трудностям .в определении границ изменений одного модуля, которые устраняли бы неизбежные ошибки в другом. Ниже в таблице приведены меры сцепления модулей: Сцепление Степень сцепления модулей Независимое По данным По образцу По общей области По По По управлению внешним ссылкам кодам 0 1 3 4 5 7 9 (слабое сцепление) (сильное сцепление) Так же как и для связности модулей, у различных авторов встречаются расхождения в оценке указанных в таблице коэффициентов. Модули сцеплены по данным, если они имеют общие единицы, которые передаются от одного к другому как параметры, представляющие собой простые элементы данных, то есть вызы- - вающий модуль «знает» только имя вызываемого модуля, а также типы и значения некоторых его переменных. Еще меньше знает вызываемый модуль о вызывающей программе. Изменения в структуре данных в одном из модулей не влияют на другой. Кроме того, модули с этим типом сцепления не имеют общих областей данных или неявных параметров. Меньшая степень сцепления возможна только в том случае, если модули ле вызывают друг друга или не обрабатывают одну и ту же информацию. Модули сцеплены по образцу, если параметры содержат структуры данных. Недостатком такого сцепления является то, что оба модуля должны знать о внутренней структуре данных. Если программист, сопровождающий программу, модифицирует структуру данных в одном из модулей, он вынужден изменить структуру данных также и в другом. Поэтому вероятность появления ошибок, возникающих при кодировании и сопровождении программ, здесь больше. Модули сцеплены по общей области, если они разделяют одну и ту же глобальную структуру данных. В этом случае воз-
Проектирование программ 11S мощностей для появления ошибок при модификации структуры данных в одном модуле намного больше. По изменениям, производимым в объявленных параметрах, сразу можно определить модули, на которые эти изменения повлияют. Если модифицируются неявно заданные параметры, определить модули, нуждающиеся в корректировке, труднее, если только .не существует документации, отражающей использование модулями общих областей. Модули имеют сцепление по управлению, если какой-либо ,из них управляет решениями внутри другого с помощью передачи флагов, переключателей или кодов, предназначенных для выполнения функций управления, то есть один из модулей знает о внутренних функциях другого. Возвращение флага состояния как явной переменной не означает сцепление по управлению. Если модуль передает информацию о самом себе или об обработанных данных, это не всегда служит проявлением сцепления по управлению. Передача флага конца файла позволяет решить вопрос о возможности обработки этого файла. Установка флага, указывающего, какой именно способ доступа используется в операции обмена (последовательный или прямой), означает, что осуществляется сцепление по управлению, когда это влияет на модули обмена. Если модуль имеет логическую связность и лри его вызове используется переключатель, указывающий на требующуюся функцию, вызываемый и вызывающий модули сцеплены по управлению. Говорят, что модуль предсказуем, если его работа обусловлена только одними параметрами. При этом среда вычислительной 'машины -на функционирование данного модуля не влияет. Для того чтобы модуль был предсказуем, с другими модулями он должен быть сцеплен или по данным, или по образцу, или, наконец, по управлению. Он не может иметь доступа к каким-либо внешним данным или общим областям памяти. Модуль сцеплен по внешним ссылкам, если у него есть доступ к данным в другом модуле через внешнюю точку входа. Таким путем осуществляется неявное управление функционированием другого модуля. Сцепление такого типа возникает, например, при использовании ПЛ/1 или Паскаля, когда внутренние процедуры оперируют с глобальными переменными. Модули имеют сцепление по кодам, если коды их команд перемежаются друг с другом. Это сцепление возникает, когда Аля одного из модулей доступны внутренние области другого без обращения к его точкам входа, когда два модуля используют общий участок памяти с командами. Оно возникает преимущественно в тех случаях, когда модули проектируются как отдельные подпрограммы, путь через которые начинается в различных точках входа, но приводит к общему сегменту кодов. 8*
£16 Глава 4 Например, одна -и та же программа-функция может быть написана для вычисления синуса я косинуса. Между этими тригонометрическими функциями имеется следующее соотношение: -sin (я/2—x)=cos х для О^.х^п/2. Путь через точки входа SIN и COS ведет к общему участку команд. * Модули, -неявно вызывающие друг друга, -сцеплены между собой. Если один модуль косвенно обращается к другому и связь между «ими осуществляется с помощью передачи параметров через промежуточные модули или посредством использования общей структуры данных, между ними существует сцепление. Модули, не вызывающие друг друга и не использующие общих данных, не сцеплены и являются полностью независимыми друг от друга. Правильное применение анализа сообщений должно обеспечить сильную связность 'модулей. Сцепление модулей зависит от спроектированной структуры данных и способов взаимодействия между модулями. ИСПОЛЬЗУЙТЕ ПРОСТЫЕ ПАРАМЕТРЫ НЕ ПРИМЕНЯЙТЕ ГЛОБАЛЬНЫХ ДАННЫХ Идентификация истока, преобразователя и стока представляет собой в основном анализ способов обработки отдельных -фрагментов информации (например, отдельных запросов, пакетов или записей), которые на данном этапе не могут быть больше расчленены. Поэтому в первую очередь этот метод предназначен для структуризации программ обработки информации. Если структуры данных имеют большие размеры (например, файлы и протоколы), управление ими осуществляется на уровне детализации данных для модулей истока и стока. Обработка заголовков и управляющих точек является вспомогательным процессом для нормального процесса ввода-вывода й может считаться видом редактирования. Структурная схема анализа сообщений с учетом переходов к обработке следующей ! группы записи показана на рис. 4.4. Единицей обработки в этом случае является не отдельный пакет, а отдельная запись Информация о переходе к обработке другой группы используется- во -входном 'модуле для проверки пакета или счетчика записей. В выходных модулях аналогичная информация служит для управления обработкой заголовков и окончаний, а также для перевода в пассивное состояние данных пакета. Структуризация по типу исток — преобразователь — сто* (И—П—С) обеспечивает реализацию таких функций, как проверка значений данных, анализ данных о смене группы, а также позволяет осуществлять одновременную обработку всей группы после приема «всех ее данных, включая указанные функ-1 ции в модули истока и стока. Только правильные единицы дая<-1 яых передаются вызывающим модулям. Правильными едини* I
Проектирование программ 117 цами данных могут быть записи или пакеты. Модуль нижнего уровня иерархии изолирует от модулей более высоких уровней организацию памяти для хранения правильного пакета и структуру файла ошибок. Модули, указанные в иерархической структуре, могут быть как независимыми программами, так и подпрограммами. В структурном языке блочного типа они могут быть блоками Обработка данных Обработка правильной записи Чтение правильных результатов Преобразование записи и выполнение вычислений |Проверка| резуль татов (Проверка) элементов результатов! Печать строк отчета Печать сообщений об ошибках (Проверка на пере-| ход обработки Печать заголовков и окончаний Печать строки Рис. 4.4. Проектирование методом анализа сообщений с учетом переходов обработки во время смены групп записей. или внутренними процедурами. В языке Кобол возможно построение этих модулей в веде секций и параграфов. Форма, используемая для реализации модулей, зависит от возможностей языка, а также от сложности всей программы. Однако при включении модуля в виде одного блока или параграфа в другой, внешний по отношению к нему модуль следует учитывать возможные изменения меры связности внешнего модуля. 4.2. Метод расширения ядра Метод расширения ядра отличается от способа нисходящего проектирования: в нем больше внимания вначале уделяется выявлению множества вспомогательных функций, а ;не определению функции всей программы в целом. Эти функции можно получить, применяя методы проектирования структур данных,
118 Глава 4 которые используются при иерархическом модульном проектировании, разработанном Джексоном, или определяя области хранения данных с последующим анализом связанных с ними » функциональных единиц (как в методе определения специфи- * каций модуля, разработанном Парнасом). 4.2.1. Спецификация модуля Стратегия выбора спецификации модуля обеспечивает неявное определение информационных структур и условных переходов на процедурном уровне. Она позволяет формировать отдельные блоки для построения семейств программ системы. На начальной стадии выделяется круг проблем, определяющих проектные решения. При решении каждой из этих проблем формируется отдельный модуль, что впоследствии облегчает модификацию уже созданной программы. Область определения указанных цроблем зависит от организации данных и спецификации алгоритма. Для программы обработки пакетов можно выделить следующий круг задач: получение, сортировку, проверку и обработку входных данных, а также проверку и сохранность выходных данных: Получение и сортировка входных данных, а также запоминание выходной информации связаны с проектированием структуры данных. Перемена типа носителя для входных файлов с магнитной ленты на диск или замена последовательного доступа на прямой оказывают влияние на решение указанных проблем. Обработка и сортировка входной информации подразумевают решение проблемы выбора алгоритма. Проектирование спецификаций влияет на проверку входных и выходных данных на некоторых этапах. Одни модули зависят от спецификаций программы и должны перестраиваться при изменении соответствующих спецификаций, другие проектируются так, чтобы могли использоваться с измененными программами системы. Иногда возможно применение 'модулей общего назначения, используемых в различных системах. Список проблем, определяющих проектные решения, не является описанием упорядоченных функциональных элементов или иерархии вызываемых модулей. Решение этих проблем производится независимо от определения управляющих структур. Целью этого типа декомпозиции программ является не выбор структуры программы, а обеспечение изоляции критических ее частей, которая необходима для улучшения планирования. Проектирование программы затем проходит по двум направлениям: 1) дальнейшее определение составных частей решающих проблем, позволяющих нацти проблемы более низкого уровня, и 2) изменение межмодульных управляющих связей.
Проектирование программ 119 После того как выбраны алгоритмы, структуры данных <и методы доступа, связи между модулями перестраиваются с целью • объединить структуры данных >и методы доступа к ним с обслуживающими процедурами; • связать функциональные элементы с процессами подготовки их активации; • изолировать форматирование (внешних данных, выбор кодирования данных, методы доступа к данным, зависящие от структур данных и обнаруживающие их, и выбор алгоритмов. Проблемы, решение 'которых влияет на длительность «жизни» программы, должны решаться независимыми модулями. Такими проблемами являются, например, определение размещения записи с помощью непосредственного поиска или поиска по ключу или определение форматов выходных данных для печати на бумаге или для вывода на микрофиши. Некоторые модули, такие, как программа сортировки, могут уже находиться в библиотеке вычислительной машины. Может существовать возможность дополнения системной библиотеки новыми модулями, если они предназначены для выполнения общедоступных и многократно используемых функций. Эта стратегия проектирования не предназначена для построения схемы управления модулями, так как ориентирована в основном на построение вспомогательных модулей, с помощью которых затем может быть создана прикладная программа. 4.2.2. Метод иерархического проектирования модулей Метод иерархического проектирования модулей, разработанный Джексоном, применяется для построения структуры программы на основе структур входных и выходных данных. Он наиболее эффективен в случае высокой степени структуризации данных (например, в задачах печати экономических отчетов). При проектировании программ этим методом используются иерархические диаграммы, называемые схемами Джексона. Допустим, что надо отпечатать отчет, в котором и входные, и выходные данные сгруппированы в соответствии с одним Управляющим полем и, кроме того, входные данные расположены в порядке, необходимом для выдачи. На рис. 4.5 приведена структура входного потока данных для генерации отчетов и показана соответствующая ей структура отчета. Конструкции повторения отмечены звездочками. Цикл отражает процедуру выбора. Любая из записей может иметь то же самое или другое значение в управляющем поле, что и предыдущая запись. Идентификация момента перехода состояния осуществляется тогда, когда отмечается различие в значениях управляющих Яолей. Данные о моменте перехода представляют -собой вход-
120 Глава 4 ную информацию, отличающуюся от данных, значения которых содержатся ibo входных записях. Входные и «выходные структуры отличаются друг от друга потому, что на входе разные группы записей не разделяются. Все записи имеют один -и тот же формат. Если генератор отче- Файл Запись Конец файла Управляющее поле Поля данных Отчет Заголовок отчета Строка Заголовок группы Окончание отчета Данные Окончание группы Конец группы записей Начало группы записей Рис. 4.5. Структуры данных для программы генерации отчетов. а — входные данные; б — выходной отчет. Рис. 4.6. Схема Джексона с учетом переходов обработки во время смены групп записей. тов представляет собой компилятор с пакетным типом обработки, распознающий карты управления заданиями и карты конца файла, то используются те же входные и выходные структуры. Если не разделять группы записей, входной файл будет представлять собой просто последовательность записей, хотя записи сгруппированы, но группы не являются подструктурами файла. С другой стороны, отчет содержит строки с заголовком, строки с итоговой информацией, а также другие раз-
Проектирование программ 121 личные разделители между группами записей. Для последующего разбиения входной информации на правильные и неправильные записи необходимо, чтобы записи можно было различать. В основе метода Джексона лежит предположение, что программа, преобразующая данные одного вида в другой, должна быть организована соответственно структуре данных. Совокупность схем на рис. 4.5 позволяет получить программу, иерархическая структура которой показана на схеме, .приведенной на рис. 4.6. Модули обработки одних и тех же данных совмещены. Таким образом, определяя соответствие между файлом и отчетом, можно установить соответствие между двумя -модулями обработки записей. Если какая-либо из схем содержит модуль, отсутствующий в другой схеме, он включается в соответствую- Правильные* пакеты данных Информационный файл Неправиль-* чные пакеты данных Записи Поля Правильные записи |Неправильные| записи :>::^*Ш "***- <Л: Файл отчета Заголовки Данные Информационные " файлы Окончания Файл ошибок Данные * Сообщения об ошибках Рис. 4.7. Схема Джексона для программы пакетной обработки. « — структура входных данных; б — структура выходных данных; в — программа.
122 Глава 4 щее место на этой схеме. Затем к схеме /могут быть добавлены дополнительные элементы, обозначающие начальную стадию процесса обработки группы записей, а также обработку ситуации 'конца файла как модифицированного перехода обработки. Рис. 4.7в Более общий пример программы обработки пакетов, проверяющей полные пакеты входных записей и обрабатывающей только правильные пакеты, рассмотрен в этой главе при иллюстрации метода нисходящего проектирования. На рис. 4.7 показана соответствующая этому примеру схема Джексона. Входные данные логически разделяются на правильные и неправильные пакеты, причем и те «и другие подлежат обработке. Неправильные пакеты затем делятся на правильные и неправильные записи. Сравнивая рис. 4.7 и рис. 4.3, можно видеть, что основное отличие рассмотренных методов заключается в способе определения неправильных входных данных. Структуризация по типу исток — преобразователь — сток ориентиро-
Проектирование программ Ш вала на обработку правильных данных. При этом неправильные записи передаются ibo вторичный выходной поток, который позднее обрабатывается другой программой. При использовании способа Джексона правильные и неправильные данные анализируются одинаково, как если бы существовали некоторые дополнительные изменения в атрибутах записей. Схемы Джексона основаны на анализе данных, поэтому при печати отчетов возникают трудности -с распределением информации по страницам. Входные и выходные схемы не являются чисто логическими описаниями, а представляют собой комбинацию элементов логической и физической структур. Описание структуры отчета, основанное на понятии групп записей, не совпадает с описанием распределения информации по .страницам. Рис. 4.8. Декомпозиция программы генерации отчетов с формированием страниц. Если распределение по страницам подчинено в структурном отношении группам записей, то каждая группа записей будет начинаться с новой страницы и занимать столько страниц, сколько потребуется. Это позволяет размещать заголовки и окончания страниц (их номера, например) в заголовках и окончаниях групп. Если же структура групп записей подчинена распределению по страницам, то на одной странице может появиться несколько групп записей. Распределения по группам записей и по страницам, хотя внешне не зависят друг от друга, являются взаимосвязанными. Эти виды группирования информации одинаково важны, но, поскольку принадлежат к различным уровням описания данных, нельзя построить схему Джексона, используя одновременно оба эти вида группирования. Чтобы лучше понять это различие, представим оба типа распределения информации в виде независимых процессов (рис. 4.8). Первый из них превращает входную структуру в отчет, игнорируя при этом распределение по страницам. Выходная информация этого процесса является входной для второго, который распределяет отчет по страницам. Логическая структура отчета преобладает над физической, Входные и выходные структуры, приведенные на рис. 4.5, представляют собой структуры данных для процесса генерации отчета. Структуры данных для процесса распределения по страницам приведены на рис. 4.9. Можно использовать две независимые программы — одну Для генерации отчетов и другую для распределения по страницам, — но этот способ неэффективен, так как информация от-
124 Глава 4 чета выводится в промежуточный файл, а затем печатается программой распределения по страницам. При параллельной обработке эта информация .может быть передана непосредственно программе распределения по страницам. Тогда эти две программы логически связаны между собой как сопрограммы и оба процесса выполняются одновременно и независимо друг от друга, но с помощью согласованных между собой программ. Заголовок отчета Построчный отчет Строки отчета Постраничный отчет Окончание отчета Начало страницы Программа формирования страниц TW Обработка страниц Ф Печать строк страницы Конец страницы Заголовок страницы Страница Строки * страницы Окончание страницы £ Начало страницы Программа формирова- ния страниц СГ5 О Печать строк страницы Ъ-, Конец : страницы Рис. 4.9. Информация для программирования формирования страниц. а — вход в блокировщик страниц; б — выход из блокировщика страниц.. Рис. 4.10. Схема Джексона для программы формирования страниц. а — программа формирования страниц; б-* инвертированная программа. Если возможно .изменение носителя выходной информации, например смена формата бумаги или замена бумаги на микрофиши, то более удобно использование независимых программ. Если же система не может обеспечить параллельную работу программ, то выполняется инвертирование одной из них, т. е. создается программа, содержащая указанные сопрограммы в виде отдельных секций, используемых как подпрограммы другой программой. Если программа формирования страниц подчинена программе генерации отчета и разбита на подпрограммы, соответствующая ей схема, приведенная на рис. 4.10, а, преобразуется в схему, показанную на рис. 4.10,6 (где маленькими квадратами отмечены входы в подпрограммы, а стрелками — конструкции повторения). Программа формирования
1 Проектирование программ 125 страниц становится вспомогательной для генератора отчетов, который в случае 'необходимости при выводе информации на печать обращается к различным ее точкам ©хода. Необходимый в данный момент режим работы подпрограммы задается переменной внутреннего состояния, например счетчиком числа строк. В реальной практике программа формирования страниц может быть разделена на части, размещенные -в вызывающей программе, и к каждой из них возможно обращение из любой точки. Тогда переменная состояния становится частью вызывающей программы. Пр.и использовании метода анализа сообщений структура файла игнорируется, а основное внимание уделяется преобразованию единственного входа. В противоположность этому при иерархическом проектировании по методу Джексона основное внимание уделяется логической структуре данных. Оба метода основаны на построении древовидной иерархической структуры модулей, цричем число разветвлений в каждом узле в основном равно трем, т. е. каждый модуль имеет три подчиненных ему модуля. Однако это сходство является чисто внешним. Модули, соответствующие внешним сторонам схемы Джексона, выполняют вспомогательные функции, тогда как модули, расположенные внутри диаграммы и .на более низких уровнях, предназначены для выполнения основных функций обработки. В схемах И—П—С основным функциям отводятся более высокие уровни иерархии, а вспомогательным — подчиненные, более низкие уровни. 4.3. Метод восходящего проектирования При использовании метода восходящего проектирования в первую очередь определяются вспомогательные функции, которые могут потребоваться для проектируемой программы.. В этом смысле рассматриваемый метод аналогичен методу модульной декомпозиции Парнаса. Модульная декомпозиция, или анализ первичных определяющих областей, заключается в нахождении ключевых модулей промежуточных уровней, которые затем разрабатываются восходящим и нисходящим способами одновременно. Эти модули не являются вспомогательными в том смысле, что потребность в них возникает в нескольких точках программы. Необходимость в использовании этих модулей может возникать в других программах или системах. Функции, определяемые как вспомогательные при восходящем проектировании, реализуются с помощью модулей самых нижних уровней, предназначенных для выполнения таких операций, как чтение, сортировка, формирование страниц и печать. Кроме того, они обеспечивают элементарные операции над областями хранения данных, а также позволяют !расширипгь воз-
Я26 Глава 4 можности выбора -встроенных и библиотечных функций. На рис. 4.11 показаны вспомогательные функции нижнего уровня, предназначенные для управления внешними областями хранения информации в системе сопровождения файлов. Эти функции изолируют структуру файла и используют абстрактный тип данных, определяемый файлом. После того как модули низкого уровня, предназначенные для управления внешней и оперативной памятью, разработаны, они используются для определения функций более высокого уровня, таких, например, как выборка соответствующих записей, обновление записей главного фай- [Разместить Читать Писать Конец файла Главный файл • Читать Конец файла Сортировать Файл сообщений Рис. 4.11. Описание файлов на общем уровне. лз. и уничтожение остаточных записей. Эти функции используются при проектировании программы на более высоком уровне л т. д., пока не будет завершена разработка всей программы. 4.4. Анализ внутреннего потока данных Методы проектирования внешних и внутренних для программы данных аналогичны друг другу. Когда программа разбита на модули, могут быть сформированы таблицы потоков данных, определены информационные потоки между модулями и все общие области хранения информации, такие, как файлы и массивы глобальных данных, и могут быть определены также словари для идентификации данных. Внешние потоки данных переходят от одних программ к другим через области хранения информации. Внутренние потоки данных передаются между модулями программ в виде списков параметров. Они должны иметь по возможности простую структуру, чтобы обеспечить слабое сцепление модулей. Иерархические схемы модулей часто соответствуют таблицам межмодульных связей, таким, как, например, табл. 4.1. Другой метод определения потоков данных состоит в их изображении на иерархических схемах (рис. 4.12). В зависимости от назначения схемы на ней может быть определен поток только явно передаваемых данных или поток неявно передаваемых данных. Данные из областей хранения информации отличаются от пересылаемых данных тем, что они обычно не размещаются внутри модуля. Объявление глобальных данных <в блочных
Проектирование программ 12Г структурированных языках, таких, как ПЛ/1 или Паскаль, представляют собой исключение. Общие данные могут содержать информационные единицы, которые недоступны для некоторых из модулей. Полный обзор используемых данных, а также определение степени сцепления модулей могут быть обеспечены путем включения всех информационных единиц в схемах потоков данных. Здесь используются те же критерии структурности для внутренних или общих областей хранения данных, что и при анализе областей их хранения в системах. Даже если .внутренние Правильные данные Конец файла Ответы Данные Конец файла Непра- Непра- ильные вильные данные ответы Печать ответов Рис. 4.12. Иерархическая схема с потоком данных. файлы и другие области хранения данных определены в нескольких модулях программы, они должны быть логически связаны только с одним программным модулем. Глобальные данные для языков ПЛ/1 и Паскаль определяются также только в одном модуле. Передача пересылаемых данных осуществляется из специального модуля-источника в -момент его очередной активации. Причины, по которым для пересылаемых данных выделяется специальный модуль, те же, что и в случае применения особого модуля для определения файлов и областей хранения данных при проектировании оверлейной программы. Информация должна быть доступна только для тех модулей, которым она нужна. Множество модулей, вызываемых непосредственно или косвенно некоторым модулем, называется областью управления этого модуля. Область влияния модуля определяется как множество таких модулей, на которые оказывают воздействие решения ©нутри этого модуля. Программа является более простой- Для понимания и сопровождения, если область влияния каждого модуля содержится в его области управления. Вложенность
Ш Глава 4 этих областей следует определять па различным видам межмодульного взаимодействия. Так, например, передача кода возврата, отмечающего конец файла, не нарушает вложенности этих областей, поскольку возвращается лишь значение некоторого данного. Не следует избегать «использования кода возврата, обозначающего неправильные данные, поскольку решение 'модуля проверки данных влияет на модули, находящиеся вне пределов его области управления. На рис. 4.13, а показаны области управления и влияния для модуля, возвращающего флаг правильности данных. Флаг 1 Чтение правильных данных Влияние^ \ \ Управление ± Проверка данных Чтение данных 1± Проверка данных 11, \ Печать ошибок Влияние, управление \ \ I 1 Чтение данных Печать ошибок I \ V. ^* а Рис. 4.13. Области влияния и управления модуля проверки данных. . влияет «а работу вызываемого модуля следующим образом: если входные данные (неправильны, флаг примет другую информацию; если же они правильны, то возвращает правильные данные вызывающему «модулю. Модуль приема правильных данных находится вне пределов области управления модуля проверки данньих, но внутри области его влияния. Структура, показанная «а рис. 4.13,6, лучше .структуры, показанной на рис. 4.13, а. Модуль проверки данных в ней управляет модулем, принимающим очередные данные. Если область влияния модуля не содержится в его области управления, принимающий решения модуль может быть объединен с находящимся над ним модулем (рис. 4.13,6). В обеих структурах флаг физически перемещается в 'модуль более высокого уровня. Если область влияния подчинена области управления, модули организуются так, что флаг устанавливается и проверяется в том модуле, в который юга передается. Если флаг используется для управления, а не для передачи информации, установка флага на возможно более высоком уровне исключает сцепление по управлению.
Проектирование программ 129 Если решения осуществляются в том же ;модуле, в котором обнаруживается ситуация, связанная с -ними, это свидетельствует о том, что ненормальные и исключительные ситуации обрабатываются на том же уровне, на котором они возникают. Основным исключением из этого правила является организация управления .при условии обнаружения конца файла, а также при других условиях окончания, таких, как появление приводящих к аварийному завершению ошибок обработки. В этих случаях возможно 'несколько способов организации управления. Ситуации окончания обработки могут обнаруживаться в нескольких модулях. Тогда может быть определен особый модуль окончания обработки, вызываемый из различных точек. Другой способ заключается в обратной передаче флага управления для анализа условий окончания на верхнем уровне, т. е. на том уровне, где была начата обработка. 4.5. Вспомогательные средства проектирования программ Задачи сопровождения файлов, обработки пакетов и генерации отчетов выбраны для обсуждения различных сторон процесса проектирования программ потому, что они представляют сложные ситуации управления. Метод выбора спецификации модулей 'позволяет избежать решения проблем, связанных с управлением. При использовании других стратегий проектирования программы чаще всего применяются иерархические схемы модулей. Проблема управления решается с помощью размещения конкретных модулей на схемах и путем определения спецификаций модульных связей. Наличие решений о том, формировать «ли пропускать обращение к определенному 'модулю или повторять использование некоторого модуля, может быть отражено в схемах, но не исследуется во всех подробностях. Только метод пошагового уточнения непосредственно связан с решением проблемы управления. Часто управляющие решения для сложных ситуаций могут быть легко получены с использованием методов, разработанных специально для этой цели. Один из этих методов основан на применении таблиц решений. 4.5.1. Таблицы решений Метод проектирования с помощью таблиц решений заключается в перечислении вариантов управляющих решений, принимаемых на основе анализа данных. Поскольку в этих таблицах перечисляются все возможные сочетания данных, существует гарантия того, что учитываются все необходимые решения. Таблицы решений обычно состоят из двух частей. Верхняя 5-399
130 Глава 4 Таблица 4.2. Таблица решений для программы сопровождения файлов Условия Комбинации увловий Конец файла сообщений Конец главного файла Правильная запись из файла сообщений Найдена соответствующая запись Отсутствует соответствующая запись Да Да Нет Нет Нет Да Нет Нет Нет Нет Нет Да Да Нет Да Нет Да Нет Нет Нет Нет Нет Да Да Нет Нет Нет Да Нет Да Нет Нет Да Нет Нет Нет Нет Her Нет Нет Действия Комбинации действий Обновить запись главного файла Выдать сообщение об ошибке Читать запись из главного файла Читать запись из файла сообщений Повторить Закончить главный файл X Выйти из программы XX часть используется для определения условий, а нижняя — для* действий. Левая часть таблицы содержит описание условий № действий, а правая часть—соответствующую ситуацию. Табл. 4.2 демонстрирует возможность использования таблйць* решений для определения управления модулями в программе сопровождения файла. Вопросы, на которые следует ответить ib структуре управления, перечислены ,в столбце условий. Действия, выполняемые в зависимости от ответов, указаны в столбце действий. Затем рассматриваются все ©озможные комбинации ответов «да» и «нет». Бели какая-либо комбинация невозможна, она может быть опущена. Крестами отмечены действия, необходимые для каждого набора условий. РАССМАТРИВАЙТЕ ВСЕ ВОЗМОЖНЫЕ КОМБИНАЦИИ ДАННЫХ X XX X X X X X X X X X X X X X X
Проектирование программ 131 Порядок расположения условий не должен влиять на порядок их проверки. Однако действия могут быть записаны в порядке их выполнения. Таблицы решений будут более подробно рассмотрены в гл. 6. Они могут быть использованы для проектирования структуры управления модулями в иерархической схеме. Их также можно преобразовать в двоичные деревья решений и принять как основу для проектирования любого модуля, использующего решения. 4.5.2. Схемы Варнье — Орра Другой широко известный тип схем — схемы Варнье — Орра— вкратце описан как алгоритм проектирования систем. Его применение для проектирования программ подобно использованию схем Джексона, и поэтому его нельзя рассматривать как отдельный -метод проектирования. Описания данных и программ на этих схемах такие же, как на схемах Джексона, но в отличие от них строятся в первую очередь не по вертикали, а по горизонтали. На рис. 4.14 показана схема Варнье — Орра, по содержанию аналогичная схеме Джексона, приведенной на Входной файл^ Заголовок файла f Записи ЗаписиJ ПеРеХ0А к обработке новой группы файла ] Данные Конец (.файла а Входная структура {Заголовок отчета Г заголовок группы Отчет на основе одной J Строки отчета* группы записей Л 0к£нчание группы Окончание отчета V. 'б Выходная структура л Рис. 4.14. Схема Варнье— Орра для информации, используемой при генерации отчета. ]рис. 4.5. Атрибуты схем Варнье —Орра в отличие от схем Джексона объединяются вместе, как, например, на рис. 4.15, а. На рис. 4.15,6 показано, как получаются из рассмотренной «структуры соответствующие ей модули. Структура программы аналогична объединенной структуре данных. Следует отметить, Что благодаря такому объединению структур возможна детализация источников выходной информации и входных условий, инициализирующих эти источники. При построении схемы для программы информационные единицы могут быть подвергнуты расширению, что позволяет учитывать обработку данных. Применяемый в этом методе способ записи отражает этапы функ- 9*
132 Глава 4 Входной файл - для отчета Заголовок файла Заголовок отчета Группы записей входного файла* Группы запиевй отчета* Конец файла 1»Окончан1*е отчета (Переход к обработ- Г Заголовок группы ке новой группы J | Окончание группы Данные Данные записи Генерация отчета а Совмещение структуры данных [Открытие файла Начало отчета Генерация отчета по группе записей -< Печать заголовка отчета У Чтение первой записи Г Проверка смены I группы записей 1 Выдача данных fПечать заго- I ловка группы ] Печать окончания группы /Вычисления "^Печать данных Г Печать окончания группы 1 Печать окончания отчета Конец отчета б Структура программы Рис. 4.15. Схема Варнье — Орра с учетом переходов обработки во время сме» ны групп записей. циональнюй декомпозиции программы. Каждый столбец -следует понимать как описание программы, являющееся уточнением предыдущего столбца. 4.5.3. Схемы информационных связей Таблицы решений целесообразно использовать при исследовании подробных схем управления и определении данных, которые могут остаться нерассмотренными, если основное 'внимание уделяется обработке обычных данных. Схемы Варнье — Орра и другие виды иерархических схем позволяют строить основную структуру управления. Поэтому желательно объединение методов, использующих схемы потоков данных, с другими методами проектирования программ. Один из таких способов заключается в преобразований иерархических схем в схемы информационных связей, осуществляемом путем удаления всех линий управления 'модулями и включения линий потоков данных. Эти линии отражают в большей степени источники и назначение информации, чем пути ее перемещения. На рис. 4.16 показана схема информационных связей, полученная на основе схемы Джексона (рис. 4.6). Она может быть использована для установления порядка работы модулей программы и определения физического размещения данных в .ней. Данные должны быть размещены в модуле самого высокого уровня, в области управления которого на.- ходятся все модули, использующие эти данные. Таким образом,
Генерация отчета Управляю^! поле Управляющее поле Начало группы Рис. 4.16. Схема информационных связей. S Начало группы записей 1 Начало 1 первой группы записей Обработка данных Обработка смены группы • Генерация отчета Окончание отчета Обработка записей ! Рис. 4.17. Схема информационной зависимости.
134 Глава 4 флаг конца файла должен быть расположен в главном модуле генерации отчетов. Идентификация группы записей должна производиться в главном модуле. Если последовательность работы модулей неочевидна, она определяется с помощью следующих действий: 1. Удалить все модули, не имеющие входящих и1 выходящих информационных линий. Они являются вспомогательными .и должны срабатывать в соответствии с линиями управления. 2. Удалить все модули, имеющие только выходящие информационные линии. Они срабатывают во вторую очередь. 3. Повторять шаг 2 до тех пор, пока не будут удалены все модули только с выходящими информационными линиями. 4. Удалить модули, не имеющие информационных линий. Они должны срабатывать в любое время после предшествующих им модулей. В результате этих действий останутся только модули, замкнутые в циклы, что приводит к появлению обратных связей. Последовательность срабатывания этих модулей должна строиться на некоторой другой основе. Функции, выполняемые ими, могут оказаться такими, что одни из модулей должны логически срабатывать раньше других. Например, возможна ситуация типа поставщик — потребитель, когда поставщик должен что- нибудь создать, прежде чем потребитель начнет использовать созданный продукт. Применяя эту процедуру из четырех шагов к схеме, показанной на рис. 4.16, получаем схему информационной зависимости, приведенную на рис. 4.17. Модули, расположенные в верхней части схемы, должны закончить работу прежде, чем сработают нижние модули. Когда модуль А расположен выше модуля В в схеме управления данными, но ниже него в схеме информационной зависимости, это означает, что модуль А прямо или косвенно вызывает модуль В. В этом случае модуль А начинает работать до модуля В, но заканчивает после него. Если модуль А расположен выше модуля В в схеме управления и оба модуля не имеют соединений в схеме информационной зависимости, модуль А может быть выполнен перед модулем В и ему не нужно ждать данных от В. Бели модуль А находится ниже модуля В в схеме информационной зависимости и оба модуля не связаны в схеме управления, модуль А выполняется после модуля В, согласно линиям информационной зависимости. 4.5.4. Схемы HIPO Этап разработки модулей на стадии проектирования программы зависит от определения функций модулей. При обсуждении проектирования систем рассматривалось применение
Проектирование программ 135 схем HIPO для определения функций «модулей (гл. 2). На рис. 4.18 показана -схема HIPO для модуля обновления записей главного файла, входящего в состав программы сопровождения файла. Для любого независимо создаваемого модуля должна быть разработана своя схема HIPO. Любая из функций, для которой не может быть выделен отдельный модуль, Вход Файл сообщений Номер счета Код сообщения Новый адрес Новая подписка Срок подписки Главный файл \ Дата окончания подписки Обработка N 2.Обработка сообщения =я 1. Определение типа сообщения 3 Исправление записи главного файла 4.Включение новой подписки N 5.Регистрация сообщения Выход > Список ошибок Сообщение об ошибке ^Главный файл ^ Адрес Подписка Дата окончания подписки ^ЧРегистрацион- ""^ный файл Номер счета Код сообщения Старая запись главного файла Новая запись главного файла Рис. 4.18. Схемы HIPO для программы обновления файла. должна быть дополнена и описана во время (разработки модулей, а не на стадии проектирования программы. Проектирование модулей закончено, когда программа с помощью какого-либо метода расчленена на модули и определены связи между модулями, а также области хранения данных. Модули затем анализируются путем оценки их силы связности, независимости \и 'степени сцепления исходя из структурных особенностей данных. 4.6. Программная документация Программная документация должна быть организована так, чтобы была легко доступна -информация по отдельным модулям. Список модулей, описание программы и иерархические
136 Глава 4 схемы используются как справочники. Кроме того, модули должны быть пронумерованы. Модули обычно нумеруются в соответствии с их позициями в иерархических диаграммах. На рис. 4.3 показан один из способов нумерации. Другой способ иллюстрируется рис. 4.19, где номера показывают уровень модуля на схеме и отношение между вызывающим и вызываемым модулями. Перечисление модулей слева направо по уровням позволяет отобразить взаимозависимость модулей с помощью десятичных цифр. Этот способ нумерации показывает, что, например, модули 2.1 и 2.2 гаы- зываются непосредствен- 1Упни1ле"| но только модулем 2 икос- * I венно только управляющим , I 2 з I модулем. Модуль 2.2 вызы- I I I I I I вает только модуль 2.2.1. I 1 |——| I 1 Если эти модули являются I 1 1 вспомогательными модуля- jLl_l 22 | ми общего назначения, как, I I I 1 например, подпрограммы 2211 сортировки или ввода-вы- | | вода, т. е. такими, которые вызываются более чем од- Рис. 4.19. Нумерация модулей. ним модулем, удобного способа для включения их в схему перечисления нет. Если же они вызывают свои собственные подмодули, то для перечисления можно пользоваться их собственным набором номеров. НУМЕРУЙТЕ МОДУЛИ В СООТВЕТСТВИИ С ИХ ИЕРАРХИЧЕСКОЙ УПОРЯДОЧЕННОСТЬЮ Описания модулей должны быть организованы в соответствии с иерархическими схемами и порядком (нумерации. Документация всей программы должна содержать следующие данные: . а) имя программы; б) общее функциональное описание; в) описание внешних областей хранения данных; г) описание человеко-машинного интерфейса; особенно это касается входных и выходных форматов, а также инструкций по эксплуатации программы; д) схемы программы, такие, как иерархические схемы, схемы Варнье — Орра, псевдокод и таблицы интерфейса; е) краткое описание составляющих программу модулей, например схемы HIPO; ж) описание общих областей хранения данных, таких, как схемы потоков данных или их словари, позволяющие, в частно-
Проектирование программ 137 ста, идентифицировать модули, .имеющие доступ к области хранения данных, а также споообные ее модифицировать; з) тестовые данные и результаты; и) список исправлений; к) справочный указатель для документации. ТЩАТЕЛЬНО СОСТАВЛЯЙТЕ ДОКУМЕНТАЦИЮ Если программа является частью системы, руководство по использованию программы может не понадобиться. Документация в основном нужна для программиста, эксплуатирующего дрограмму. Если же программа представляет собой отдельную и независимую часть системы, используемую вне вычислительного центра, 'или вовсе не является частью системы, то нужна вся документация стадии проектирования системы, руководства для пользователя и оператора, а также инструкция по эксплуатации. 4.7. Упражнения 1. Возьмите старую программу, состоящую по крайней мере из пяти модулей, и определите тип связности каждого модуля и тип сцепления для каждой пары модулей. Для определения типа сцепления используйте таблицу. 2. Возьмите старую программу, состоящую по крайней мере из пяти модулей, и постройте для нее иерархическую схему модулей, схему управления данными и схему информационной зависимости. Для каждого из модулей определите области влияния и управления. 3. Матрица размером NXN содержит расстояние по прямой между парами из N точек. Если между точками i и / нет прямого пути, то элементы матрицы A(i,j) и Л(/,0 равны —1. Главная диагональ (элементы A(i,i), где l<i<N) заполнена нулями. Используя метод пошагового уточнения, постройте программу, рассчитывающую число треугольников, представляемых с помощью матрицы. Напоминаем, что сторона треугольника меньше суммы двух других его сторон. 4. Доска для китайских шашек на одного или двух игроков представляет собой разлинованное по вертикали и горизонтали поле размером 9X9. Первоначально 10 фишек образуют треугольник в одном углу доски. Цель игры заключается в перемещении фишек в противоположный угол за минимальное число ходов. В одном из вариантов игры ход состоит или из перемещения фишки на соседнее поле, или из последовательности «прыжков» фишки через любое число других фишек. Перемещения и прыжки должны выполняться по горизонталям или вертикалям поля. Используя метод пошагового уточнения, постройте программу для определения наилучшей стратегии игры для одного игрока. 5. Постройте программу для упражнения 3, используя метод анализа исток — преобразователь — сток. 6. Метод Парнаса, заключающийся в определении спецификаций модулей, применяется для идентификации базисных решающих модулей. Какие модули этого типа потребуются для реализации программы в упражнении 3? Какие модули потребуются для подсчета числа четырех- и пятиугольников? 7. Используя произвольный язык программирования, спроектируйте набор модулей, изолирующих обработку стека. Один из возможных методов заключается в передаче модулю управления стеками имени требуемой функции—-
138 Глава 4 ВЫТОЛКНУТЬ, ВСТАВИТЬ, СОЗДАТЬ, ПУСТО. Этот модуль будет затем вызывать другой модуль, предназначенный для выполнения нужной функции. 8. Игральная доска из упражнения 4 может быть представлена несколькими различными способами. Спроектируйте набор модулей доступа, изолирующих представление доски. 9. Постройте схему Джексона для входа и выхода модулей программы из упражнения 3 для того случая, когда матрица выводится построчно на перфокарты, а таблица треугольников печатается. Модифицируйте эти схемы так, чтобы можно было обработать четырех- и пятиугольники и напечатать соответствующую информацию, сгруппированную по числу сторон анализируемых фигур. 10. Преобразуйте схему исток — преобразователь — сток (рис. 4.4) так, чтобы ее можно было использовать для построения: а) программы обновления записей главного файла; б) программы генерации отчетов; в) пакетного компилятора. 11. Преобразуйте схему Джексона, показанную на рис. 4.7, для построения: а) программы обновления записей главного файла; б) программы генерации отчетов; в) программы печати частот появления слов в тексте. 12. Спроектируйте программу, реализующую процесс перераспределения, ^описанный в упражнении 8 гл. 3. Составьте документацию по вашему проекту, жак описано в разд. 4.6. Не включайте в документацию никаких руководств.
Глава 5 АЛГОРИТМЫ Под алгоритмом понимается совокупность действий, необходимых для решения задачи. Алгоритм отличается от системы и программы тем, что в нем содержится только описание действий, производимых над данными, но полностью отсутствуют какие-либо описания данных. Алгоритмы могут быть представлены с помощью таблиц решений и псевдокодов (гл. 4). Иерархические схемы для этого непригодны. Одни типы схем позволяют описывать данные, другие определяют вид обработки данных, но не предусматривают порядок обращений к модулям. Алгоритмы содержат определение пошагового процесса обработки данных с описанием преобразований данных и функций управления. Они могут быть записаны на естественном языке,, на языке программирования, а также с помощью математической или другой символической нотации. Название алгоритма может указывать на его назначение (например, алгоритм сортировки, обращения матриц, игры в «крестики и нолики» и т. д.) или определять используемый в нем метод решения. г 5.1. Типы алгоритмов Каждая научная дисциплина имеет свои методы получения результатов. В этом отношении программирование для ЭВМ не является исключением. Основное различие между задачами заключается в том, что для одних существуют прямые методы решения, а другие не могут быть решены без дополнительной информации, получаемой из ответов на некоторые вопросы, причем варианты ответов заранее предусмотрены. Если задача может быть решена прямым способом, говорят, что она имеет детерминированный метод решения. Детерминированные алгоритмы всегда обеспечивают регулярные решения. В них отсутствуют элементы, вносящие неопределенность, кроме того, для них невозможна произвольность -в выборе решений, определяющих последовательность действий. Для построения детерминированных алгоритмов недопустимо применение методов проб и ошибок. К задачам, имеющим детерминированные решения, относятся математические уравнения, а также такие задачи, как проверка данных и печать отчетов.
140 Глава 5 Если решение задачи не может быть получено прямым методом, а выбирается из заранее определенного множества вариантов, такая задача имеет недетерминированное решение. Алгоритм 'недетерминиро'ван, если для его реализации используются методы проб й ошибок, повторов или случайного выбора. К числу подобных задач относятся такие, как нахождение делителей числа, поиск страницы, а также ставшие классическими задачи о коммивояжере, о восьми ферзях и задачи нахождения кратчайшего пути чедез лабиринт. Задача о восьми ферзях заключается в нахождении такого способа расстановки на шахматной доске восьми ферзей, при котором ни один из них не находится под угрозой других. Найденное размещение ферзей, удовлетворяющее этому условию, легко проверить на правильность, но поиск решения представляет определенные трудности. В задаче о коммивояжере нужно найти кратчайший путь через определенное множество городов, при условии что этот путь через каждый город проходит только один раз. Для решения этих задач ,на ЭВМ используется моделирование € помощью метода проб и ошибок. Этот же метод может быть применен для нахождения ответа ручным способом. Третий, основной тип алгоритмов, предназначен не для поиска ответа на поставленную задачу, а для моделирования физических систем с -использованием ЭВМ. 5.1.1. Детерминированные и недетерминированные алгоритмы Многие задачи могут быть решены с помощью как детерминированных, так и недетерминированных алгоритмов. Алгоритм решения задачи нахождения наибольшего общего делителя двух целых чисел, построенный на основе перебора всех возможных делителей, начиная с наибольшего числа и кончая единицей, является недетерминированным. Другой способ решения этой задачи основывается на использовании алгоритма Евклида. Этот алгоритм заключается в нахождении последовательности пар чисел, каждая из которых образуется из минимального числа предыдущей пары и разности между составляющими ее числами. Ответ представляется парой, содержащей два равных, но отличных от нуля числа. В первом из этих двух методов каждое число проверяется «а соответствие условию. Во втором такая проверка не нужна. Но заранее известно* что правильное использование алгоритма (приведет к верному ответу. Известные алгоритмы разложения числа на простые множители являются недетерминированными, так как в них используется метод проб и ошибок. Каждое предполагаемое число может быть проверено на правильность путем деления на него
Алгоритмы 141 исходного числа, «о единственный известный способ разложения заключается в .испытании различных вариантов множителей. Было предпринято много попыток математически определить детерминированный алгоритм разложения числа на множители. Но до сих пор не известно, существует ли такой алгоритм. Некоторые из задач являются по природе недетерминированными, и можно показать, что для них не существует детерминированного алгоритма. Задача о восьми ферзях является недетерминированной, так как при ее решении используется метод проб и ошибок. До тех пар пока не будет найдено травильное размещение ферзей, решение производится перебором их местоположений. Перед получением решения может быть просмотрено множество различных вариантов расположения ферзей. Недетерминированный характер свойствен играм и головоломкам, и это превращает их в приятное развлечение. Смысл игр и головоломок заключается в нахождении стратегии, позволяющей победить противника или решить головоломку. По сути дела, недетерминированный алгоритм описывает систематическую процедуру поиска нужного (решения среди всех возможных. Его существование в первую очередь зависит от построения множества потенциальных решений, в котором содержится искомое. Задача о восьми ферзях может быть решена с помощью определенным образом построенной последовательности всех возможных попыток размещения ферзей на шахматной доске. Простой множитель числа может быть найден путем проверки всех чисел, ее больших квадратного корня числа. Но идеальный алгоритм для реальной игры в шахматы не может быть построен, поскольку последовательность вариантов игры слишком велика для реального использования метода перебора. Это же можно сказать о решении в общем виде задачи о коммивояжере. Различие между задачей поиска простых множителей и другими задачами Заключается в том, что при ее решении для генерации и проверки всех вариантов простых множителей возвраты и повторные попытки #е осуществляются. Таким образом, этот процесс является эффективным1). Для задачи о восьми ферзях не известен другой способ нахождения решения, кроме метода, использующего возврат и попытку перестановки некоторых, ферзей. Этот процесс не очень эффективен. Необходимым условием правильного решения задачи о восьми ферзях является размещение на каждой горизонтали и каждой вертикали по одному ферзю. Один из способов выполнения ]) Не следует смешивать понятие эффективной вычислимости, принятое в Теории алгоритмов, с понятием эффективности процесса поиска решения, используемым здесь автором. — Прим. перев.
142 Глава 5 этого условия заключается в поиске решения с помощью генерации всех перестановок ряда от 1 До 8. Значение каждой циф. ры соответствует номеру горизонтали для одного из ферзей, а ее позиция определяет номер вертикали для того же ферзя. Для каждого варианта перестановок следует проверять, не находятся ли какие-либо два ферзя на одной диагонали. В общей сложности проверке подлежат 8! (факториал) перестановок. Более сложный, ,но более эффективный способ поиска решения заключается в моделировании размещения ферзей на доске. Для исключения неправильных вариантов еще до то-~ го, как будут размещены все восемь ферзей, могут быть использованы различные виды эвристик. Например, когда первый ферзь расположен так, как показано на рис. 5.1, то следующие ферзи не должны расставляться на атакованных полях. Если для некоторого положения первого ферзя нельзя найти решение, не еле* Рис. 5.1. Позиции, атакованные фер- дует располагать ферзя в по- зем- ложениях, симметричных к рассмотренному. Например, если нет решения для ферзя, расположенного в верхнем левом углу, то его нет и для ферзя, помещенного в один из других углов. Нахождение простых множителей для числа п требует пере* бора менее чем ijn возможных вариантов множителей. Но даже с учетом этого при очень больших значениях п на процесс поиска решения может быть потрачено много лишнего времени. Определение условия расположения восьми ферзей, удовлетворяющего условию задачи, заключается в проверке большого числа вариантов. Для решения задачи размещения п ферзей на доске размером пХп потребуются намного большие за- i траты времени, чем при разложении числа п на множители. Основным фактором при выборе метода для задач, решаемых с помощью перебора большого числа возможных вариантов, является суммарное время нахождения решения. Методы, используемые для сокращения числа вариантов при переборе илк позволяющие выбирать наиболее правдоподобные варианты, называются эвристическими. Используя эвристические методы (или эвристики) для определения и сравнения позиций на доске по таким параметрам, как их сила и перспективность, можно создать программы для игры в шахматы. Такие программы могут разыгрывать хорошие шахматные партии, но не обеспе-4 ___ / / ч ч ч ч / / / ч ч ч / / / ~Г" '■#] / / / ч ч / / / / ч ч ч ч ч ч ч ч \ 1
Алгоритмы 143 чивают беспроигрышную игру. Иногда требуется найти хорошее, но не обязательно лучшее .решение. Один из эвристических методов для решения задачи о восьми ферзях заключается в размещении ферзей попарно в каждом из четырех квадрантов шахматной доски. Если для решения задачи используется метод проб и ошибок, генерация и проверка множества возможных вариантов решения в соответствующем недетерминированном алгоритме должны осуществляться по некоторому правилу, т. е. систематически. Если для решения могут быть использованы различные недетерминированные алгоритмы, перед выбором какого-нибудь из них следует оценить его эффективность по сравнению с другими. Эффективность применения алгоритма зависит от метода генерации наиболее вероятных вариантов и от эвристики, направляющей и регулирующей поиск. Выбор детерминированного алгоритма осуществляется с учетом экономии времени работы или используемой машинной памяти. В другом случае может быть выбран наиболее простой для понимания алгоритм. При выборе алгоритма решения задачи в целях повышения эффективности процесса -вычислений следует отдавать предпочтение детерминированным алгоритмам. Простым примером задачи, решаемой детерминированным и недетерминированным способами, служит задача поиска нужной строки в таблице. Если индекс строки известен, алгоритм ее поиска детерминирован. На языках высокого уровня возможна непосредственная ссылка >к этой строке. В случае использования машинного кода позиция строки может быть вычислена. Если ее нельзя непосредственно вычислить, можно применить способ случайного поиска, наиболее просто реализуемый с помощью метода цроб и ошибок. Введя в случайный процесс генерации вариантов некоторые правила, можно избежать повторного рассмотрения вариантов. Кроме применения ограничения сверху на. генерируемые числа наименее эффективным яв- лятся 'метод линейного поиска. По этому методу поиск начинается с одного конца таблицы и последовательно производится по всем строкам таблицы. Последовательность пробных вариантов получается с помощью увеличения или уменьшения на единицу номера текущей строки. Если поиск осуществляется «е случайным, а регулярным способом, для таблицы из п строк в худшем случае требуется п проб. В среднем число проб при регулярном способе перебора равно (я+1)/2. Если строки в таблице упорядочены, в худшем случае понадобится число яроб, равное log2«+l. 5.1.2. Сложность алгоритмов Зависимость времени работы программы от объема обрабатываемых даянык определяется оцендой сложности алгоритма.
144 Глава 5 Если алгоритм предназначен для обработки равных по объему данных, продолжительность его работы каждый раз остается неизменной и сложность алгоритма является постоянной. Время работы алгоритма обработки каких-либо массивов данных зависит от размеров этих массивов. Время работы алгоритма, выполняющего только операции чтения и занесения данных в оперативную память, определяется формулой ап+Ь> • где а — время, необходимое для чтения и занесения в память одной информационной единицы, и Ь — время, затрачиваемое на выполнение вспомогательных функций. Поскольку эта формула выражает линейную зависимость от п, сложность соответствующего алгоритма называют линейной. Обменная сортировка (сортировка по методу пузырька) п элементов списка представляет собой следующий процесс: определяется наименьший элемент всего списка и осуществляется его обмен с первым элементом, затем определяется наименьший элемент оставшегося списка и производится его обмен со вторым элементом \и т. д. При выполнении такого алгоритма производится п—1 сравнений при определении первого наименьшего, п—2 сравнений при определении второго и т. д.. Общий объем сравнений для такого алгоритма вычисляете» по формуле (rt_l)+(„_2)+(n-3)+...+4 + 3+2 + l= *=i Так как число сравнений здесь выражается полиномом второй степени, сложность этого алгоритма квадратична. Если сложность алгоритма оценивается по уже написанной программе, вместо числа сравнений вычисляется число внутренних циклов, являющихся основой рассматриваемой программы. Обменная сортировка может быть выполнена с помощью следующей программы: procedure сортировка; (Пример 5.1) for k := 1 to л - 1 do min := A(k); loc := к; for i := к + 1 to n do if min> A(i) then min := A(i); loc := i; A(loc) :=A(k); A(k) ;= min end.
Алгоритмы 145 Внешний цикл выполняется п—1 раз. Внутренний цикл срабатывает IB среднем я/2 раз для каждого вхождения во внешний цикл. Общее число обращений к внутреннему циклу равно (п2—п)/2. При больших значениях п за оценку этого выражения принимается /г2. Сложность можно оценить как по отношению ;к задаче, так и по отношению к алгоритму. Оценками времени работы алгоритма являются максимальное, минимальное и среднее время его выполнения. В зависимости от используемых эвристик эти оценки могут совпадать или не совпадать. В приведенной процедуре сортировки они совпадают. Но если прекращать выполнение процедуры, как только будет установлено, что список упорядочен, временные оценки работы алгоритма не будут одинаковыми. Сложность задачи должна оцениваться по реализациям правильных алгоритмов. Она представляет собой верхний предел для времени работы алгоритма. Но часто можно найти также и нижний предел. Очевидно, что для выполнения какого-либо вида обработки п элементов требуется время, по крайней -мере пропорциональное п. Для приведенного ниже простого алгоритма перемножения двух матриц размером пХп число срабатываний внутреннего цикла равно я3: procedure умножение-матриц; л Щпимео 5 2) for i := 1 to n do * K P P • / for j := 1 to n do C(i,j) :=0; for k := 1 to n do C(i.j) :=C(i,j)+A(i,k)*B(k,j) end. Из определения произведения матриц следует, что минимальное время работы также пропорционально п3. Изменения в алгоритме, в результате которых уменьшается число срабатываний внешних циклов, приведут к изменению коэффициента пропорциональности, но не вида зависимости от п. Оба рассмотренных выше алгоритма имеют полиномиальную оценку времени работы: п2 и /г3. Однако алгоритм с оценкой п2 работает быстрее алгоритма с оценкой п3. Формально сложность алгоритма определяется как порядок функции, выражающей время его работы. Функции f(n) и g(n)—одного порядка, если для больших п существует константа k, такая, что f(n)/g(n)^.k. Это записывается следующим образом: f(n) =0(g(n)). Алгоритм чтения и занесения в память набора данных имеет оценку О(п). Алгоритм двоичного поиска в таблице с упорядоченными элементами оценивается как О (log2tt). Простая обменная сортировка имеет оценку О (я2), а умножение матриц — О (/г3). 10-399
i46 Глава 5 Таблица 5.1. Сложность алгоритмов п 1 5 10 20 30 100 О (log2«) 0 2,3219 3,3219 4,3219 4,9069 6,6439 0(п) 1 5 10 20 30 100 0(nlog2n) 0 11,6096 33,2193 86,4386 147,2067 664,3856 0(/l2) 1 25 100 400 900 10000 0(2") 2 32 1,024 106 109 10зо 0(п\) 1 120 3,6.10* 2,4-10" 2,6.10s2 1051 оИ 1 3,125 1010 1026 2-104* 10200 Когда говорят, что алгоритм поиска имеет сложность О (log2n) или что сложность сортировки равна О (п2), под этим подразумевается, что элемент данных будет найден за время, в худшем случае пропорциональное log2/z, или что сортировка информации будет выполнена © самом лучшем случае за время, пропорциональное п2. В описанной процедуре сортировки оценка 0(п2) является как минимальной, так и максимальной. Сложность решения задачи можно уменьшить, если найти более выгодный метод (как, например, при замене линейного поиска двоичным) или использовать оптимизирующие эвристики. Среднее время для оптимизированной сортировки по методу пузырька равно 0(n\og2n). В таблице 5.1 показана зависимость различных видов сложности алгоритмов от возрастания /г. Логарифмическая зависимость более приемлема, чем линейная, а линейная зависимость предпочтительнее полиномиальной ,и экспоненциальной. Рассмотрим игру между двумя игроками, которые по очереди получают ход, заключающийся в выборе одного из двух перемещений. Если игра заканчивается через п ходов, это означает, что может быть разыграно всего 2п вариантов игры. Игрок, желающий найти на будущее наилучшую стратегию тсгры, может изучить все 2п возможных партий. Для этого потребуется времени 0(2")^ т. е. зависимость является экспоненциальной. Из табл. 5.1 видно, что для всех задач, кроме самых коротких, экспоненциальная зависимость нежелательна. Даже для информационных массивов, имеющих средние размеры, требуются чрезвычайно большие затраты времени. Для больших же объемов данных нежелательна даже полиномиальная сложность. ИЗБЕГАЙТЕ СЛИШКОМ СЛОЖНЫХ АЛГОРИТМОВ 5.2. Способы реализации алгоритмов Целью любой программы является преобразование входных данных в выходные. Если отдельному выходному элементу соответствует свой входной элемент, основу программы составля-
Алгоритмы 14Т ет конструкция повторений. Бели организация выходного потока отличается от организации входного (например, если каждая карта на входе содержит 10 величин, а каждая выходная строка состоит из 8 величин или если входные записи идут друг за другом непрерывно, а на выходе блокируются по 20 записей на страницу), логичнее использовать две независимые сопрограммы: одну для получения выходных данных, другую — для их организации. Как показано выше, эти сопрограммы могут быть реализованы путем инвертирования одной из них, в. результате чего образуется модуль с несколькими программными секциями, используемыми другой сопрограммой. Если порядок следования данных несуществен (например, при обновлении записей главного файла прямого доступа), можно использовать параллельную обработку. Перезапись данных, производимая в режиме on-line с помощью одновременно работающих терминалов, с точки зрения пользователей, находящихся за терминалами, выполняется параллельно. Координирование и упорядочивание реально выполняемых операций перезаписи могут производиться на любом уровне аппаратного и программного обеспечения систем управления. Перезапись данных, получаемых с помощью независимых вычислений, может рассматриваться как параллельный процесс. Если же вычисления ориентированы на обработку файла 'сообщений, они выполняются скорее последовательно, чем параллельно. Все виды обработки могут быть разделены на следующие классы: последовательную, использующую повторения, структурное распараллеливание с помощью сопрограмм и произвольную обработку с применением параллельных вычислений. 5.2.1. Итерация и рекурсия Повторение является основной управляющей конструкцией обработки данных, за исключением случаев, когда входные данные состоят из одного элемента, который преобразуется в один .выходной элемент (например, в случае системы обработки запросов или при обновлении записей в режиме on line). Существуют две основные формы повторений: итерация и «рекурсия. Итерация в основном используется для тех видов обработки, которые лучше всего определяются выражением типа «выполнить для всех л:», а рекурсия — для получения результирующих данных, которые легче всего описать рекурсивно, т. е. задать выражением вида «выполнить то же, что и в последний раз». Текущее действие определяется с помощью предыдущего ответа или предыдущих стадий вычислений. В действительности итерация и рекурсия взаимозаменяемы. Рекурсивные процедуры могут быть переписаны в итерационном виде в языках, в которых рекурсия невозможна (например, в Фортране) .- 10*
148 Глава 5 Итерационные процедуры могут быть переписаны как рекурсивные в языках, в которых невозможна итерация (например, в Лиспе). Классическим примером рекурсии может служить определение факториала в следующем виде: я,Г1 дляп = 0 (51) ( п(п— 1)! для /г> 0. Это определение рекурсивно, так как задает функцию с ее же помощью. Все рекурсивные определения в основном представляют собой множество выражений, из которых по крайней мере одно не является рекурсивным и обеспечивает окончание рекурсивных вызовов. Аналогично этому для каждой итерации необходимо наличие граничных условий. Другое, менее формальное определение факториала описывается следующим выражением: п! = Ь2.3...(я— 1)-п. (5.2) Рекурсивная процедура, построенная на математической зависимости (5.1), показана ниже: procedure факториал (п); (Пример 5.3) if n = О then return(1) else return (n* факториал (п - 1)) end. Следующая процедура реализует соотношение (5.2) и основана на итерационном алгоритме: procedure факториал (п); (Пример 5.4) f := 1; k:=n; while k > 0 do f := f * k; k :=k- 1; return (f) end. В итерационной процедуре используется переменная k для подсчета числа возвратов от п до 0. В рекурсивных процедурах для той же цели предназначен аргумент функции. Выполнение операции умножения начинается со старших чисел в итерационной процедуре и с младших—в рекурсивной. Условия окончания выполнения обеих процедур аналогичны друг другу. Итерационная и рекурсивная процедуры могут применяться для простой задачи распечатки списка вводимых чисел, которая обычно решается итерационным способом. Итерационная про-
Алгоритмы 14У цедура содержит явно заданный цикл: procedure список; (Пример 5.5) WWJe not конец файла do читать (число); печатать (число) end. Повторения рекурсивной процедуры получаются с помощью обращения ее к самой себе: procedure список; (Пример 5.6) if not конец файла читать (число) печатать (число); список end. Условие окончания обеих процедур одно и то же. Обе процедуры расчета факториала и обе процедуры печати списка имеют линейную сложность. В том-и другом случае итерационная форма более предпочтительна, так как для рекурсивной процедуры требуются дополнительные расходы памяти и времени. При каждом рекурсивном вызове содержимое всех регистров машины сохраняется, а цри возврате — восстанавливается так же, как и при любом вызове подпрограммы. Кроме этого, должны сохраняться все локальные переменные до момента возврата, так как они могут быть изменены вызываемой подпрограммой. Поскольку происходит многократное обращение из подпрограммы к самой себе, должно быть создано и сохраняться много копий регистров, переменных и точек возврата. Для хранения этой информации используется стековая память. При возврате после соответствующего вызова регистры восстанавливаются и вызов завершается. Управление возвращается к точке вызова столько раз, сколько была вызвана подпрограмма. При обычном вызове подпрограмм глубина гнездования редко превышает 6. Глубина гнездования рекурсивных вызовов обычно существенно больше. Рекурсию можно использовать только в тех языках программирования, в которых для реализации вызовов подпрограмм используется стек. Вычисление биномиальных коэффициентов представляет собой простой пример расчетов, для которых существует много алгоритмов. Обычно биномиальные коэффициенты определяют по формуле
150 Глава 5 Алгоритм имеет линейную сложность, но, если вычисляются все три факториала, время его работы .пропорционально 2п. Это время можно сократить в 2 раза, если учесть, что вычисления трех факториалов избыточны и k\ и (п—k)\ определяются во время расчета /г!. Все, что надо сделать, — это рассчитать п\ и в процессе вычислений определить и сохранить две другие величины до его окончания. Реализация С(п, k) не рекурсивна, но вычисление факториала производится с помощью рекурсии или итерации. Трудности при вычислении п\ и k\(n—k)\ заключаются в том, что значения обоих факториалов слишком велики и переполнение памяти при расчете С(п, k) возникает раньше, чем закончатся вычисления. Улучшенное математическое определение, позволяющее устранять данный недостаток, приводится ниже: II для & = 0 Этот алгоритм также может быть реализован и итерационным, и рекурсивным способом. Сложность его также является линейкой, но скорее вида О (к), чем О (п). Другой способ определения биномиальных коэффициентов дает следующее выражение: [С(п— 1,й—l)+C(/i-l,A) для 1<й<л. Треугольник Паскаля, используемый для вычисления биномиальных коэффициентов, заполняется с помощью этого рекурсивного определения. Часть треугольника Паскаля показана в табл. 5.2. Рекурсивная реализация имеет сложность 0(2k)r так как каждый вызов порождает два других вызова. Такая организация вычислений также избыточна, так как для получения С (п—1, k—1) и С (п—1, k) необходимо вычислить С (п—2, £—1), рассчитываемое в каждом случае отдельно. При использовании итерационной процедуры можно избежать избыточности в расчетах; Однако тогда требуется память для Таблица 5.2. Треугольник Паскаля 1 1 1 2 1 3 1 4 1 5 1 6 1 3 6 - 10 15 1 4 10 20 1 5 15 1 6 1
Алгоритмы 151 хранения k величин. За один цикл заполняется один ряд треугольника Паскаля, и этот ряд записывается на место предыдущего. Первый аргумент функции указывает число повторных записей массива памяти, а второй обозначает -номер позиции в этом массиве. Если запоминать весь треугольник, требуется массив размером nXk. Этот алгоритм имеет сложность О (л2). Так как алгоритм имеет полиномиальную сложность, памяти требуется больше, чем для алгоритма, использующего одномерный массив, но он -имеет преимущество перед последним, поскольку k биномиальных коэффициентов для одномерного массива и k-n коэффициентов для двумерного массива получаются при одинаковых затратах времени. Получение треугольника Паскаля иллюстрирует неоднозначность символа С (я, &), который может обозначать вызов функции или элемент двумерного или одномерного обновляемого массива. В языках программирования установлено различие между записью обращений к функциям и ссылками к массивам, поскольку существует различие в соответствующих программных средствах обработки этих элементов. Однако в математике указанные элементы часто обозначаются одинаково. При проектировании программ эти два вида записи могут использоваться как взаимозаменяемые. Параметр функции или индекс массива может обозначать индекс итерации или рекурсии. В методе Ньютона — Рафсона для нахождения корней уравнений вида xk—п = 0 используется итерация. Обычно при определении этого метода используются индексы, котррые можно применять в итерационной процедуре. Если требуется найти квадратный корень s из числа л, процедура задается следующим образом: -g- для k = О, п Ф О Ь-*+2*/5*-' для k>0. Вычисление заканчивается при том значении индекса k, когда значения s^-i и s* приблизительно равны между собой. При итерационной реализации индексная переменная указывает на необходимое число проходов цикла вычислений. В случае рекурсивной реализации индекс является аргументом вызываемой процедуры и определяет требуемое число рекурсивных вызовов. Если требуется сохранить все результаты вычислений
152 Глава 5 в массиве, индекс служит для адресации элементов массива и одновременно является индексной переменной итерационной процедуры. 5.2.2. Параллельная обработка На примере задачи обновления записей в файлах с прямым доступом можно наиболее просто проиллюстрировать приме- нение параллельной обработки. Теоретически можно допустить^ что каждого пользователя обслуживает своя подпрограмма, выполняющая одну операцию перезаписи. Общим ресурсом в этом случае является только файл. При этом необходимо координировать операции перезаписи. Трудности, связанные с параллельной обработкой, возникают при решении задачи координации доступа к файлу. Одним из наиболее распространенных видов процессоров для параллельной обработки служат матричные процессоры. Имеется много видов матричной обработки для различных математических расчетов, логика которых позволяет параллельное выполнение. Если массив заполняется при инициализации одной и той же величиной или если в каждый инициализируемый элемент пересылается величина, зависящая только от положения элемента в массиве, порядок, в котором заполняются в этой ситуации элементы, не имеет значения. Инициализация может выполняться с помощью перечисления элементов массива по возрастанию их номеров, как показано в примере 5.7, или по их убыванию, как показано в примере 5.8. procedure инициализация (А); (Пример 5.7) for i := n downto 1 do A(i):=1 end. procedure инициализация (А); e (Пример 5 8) / for i := 1 to n do r r • / A(i) := 1 end. f Возможен любой другой порядок работы, поэтому операцию инициализации можно выполнять на п процессорах, запускаемых в произвольном порядке, каждый из которых предназначен для инициализации одного элемента массива. Параллельная обработка может применяться для поиска в
Алгоритмы 153 неупорядоченном массиве .с помощью одновременного доступа ко всем элементам, для обработки неупорядоченного набора данных, выполнения операций на различных ветвях программы и .проверки множества различных вариантов решений недетерминированной задачи. Все эти операции выполняются одновременно с доступом ко всем элементам. 5.2.3. Сопрограммы Сопрограммы, так же как и параллельные процессы, выполняются одновременно. Их различие между собой заключается в том, что параллельные процессы стартуют в одно и /го же время и являются равнозначными по важности. Сопрограммы же состоят из головной программы и подчиненной ей подпрограммы. Головная программа передает управление ее сопрограмме. После этого управление многократно может переходить от сопрограммы к головной программе; пока в конечном счете оно не вернется к головной программе. При передаче управления сопрограмме последняя, как правило, продолжает выполняться с того места, на котором она была прервана, в отличие от подпрограммы, которая чаще начинает выполняться сначала. В многопроцессорных системах сопрограммы могут в действительности выполняться параллельно. Это возможно в ограниченных -цределах в большинстве ЭВМ. Центральный процессор 'инициализирует функционирование процессоров, предназначенных для обмена, после чего продолжает работать одновременно с ними. Сигналы, передаваемые между центральным процессором и другими процессорами, служат для координации работы системы. Это характерно для задачи типа «поставщик — потребитель», которая является классическим примером взаимодействия сопрограмм. Один процесс (Р) поставляет некоторый продукт, другой (С) этот продукт использует. Р может выпустить столько единиц продукта, сколько может разместиться в общей области хранения. Если же область хранения полна, то Р должен ждать С. С может использовать единицы продукта только в том случае, если они есть в области хранения. Если же она пуста, .то С должен ждать Р. Процесс начинается с того, что Р производит первую единицу продукта, а заканчивается, когда или С получил достаточное для него количество продукта, или у Р кончился материал, нужный для производства продукта, или, наконец, когда Р извещает С о прекращении (работы. Управление сопрограммами можно рассмотреть на следующем примере. Пусть читаются числа, отперфорированные по 10 на каждой карте. Эти числа надо распечатать, расположив на строке по 8 чисел. Подпрограммы могут быть организованы так, что одна из них разблокировывает числа и передает их поодиночке другой подпрограмме, выполняющей блокировку
154 ^ Глава 5 чисел для печати. Другой способ реализации этой операции основан иа применении в качестве области хранения циклического буфера емкостью 40 чисел. Первая подпрограмма может разблокировывать числа и записывать их в буфер, а вторая при этом осуществляет блокировку чисел и их печать. Печать отчетов, имеющих заголовки страниц, представляет собой аналогичную ситуацию. Подпролрамма-поставщик генерирует выходные строки отчета из данных. Подпрограмма-потребитель печатает отчет постранично, снабжая каждую страницу заголовком и окончанием. Если в языке программирования не предусмотрено (взаимодействие сопрограмм, оно достигается путем инверсии программы-потребителя, что обеспечивает выполнение ее функции с помощью обращения к отдельным секциям инвертированной программы из программы-поставщика. 5.3. Методы построения алгоритмов Из рассмотренных выше примеров сортировки, поиска в списке и вычисления биномиальных коэффициентов следует, что для решения задачи часто можно использовать разные алгоритмы. Было также показано, что каждый алгоритм может быть реализован несколькими способами. Многие известные алгоритмы приведены в томах 1—3 книги Кнута1), а также в «Сборнике алгоритмов АСМ». При разработке алгоритмов следует пользоваться стандартными подходами. Если метод решения сначала не очевиден, то проблему следует проанализировать, чтобы точно определить исходные условия и цели. Может оказаться, что проблема станет разрешимой или частично разрешимой, если будет использована некоторая дополнительная информация. Иногда проблему можно разбить на части, которые проще поддаются решению. При определенных обстоятельствах может быть использован такой подход: сначала отыскивается приближенное решение, которое затем уточняется. Способ пошагового уточнения и другие методы декомпозиции программ определяют основу для построения программных •модулей. Они обеспечивают методологию проектирования и удобные способы ведения записи в процессе разработки программ путем ее разбиения на отдельные модули. Но эти методы оказываются менее пригодными при проектировании тех модулей, которые предназначены для преобразования данных. Указанные методы не описывают всю информацию и структуры управления, необходимые при разработке модулей. !> Кнут Д. Е. Искусство программирования для ЭВМ. Т. 1—3. — М.: Мир, 1976—1978.
Алгоритмы 155 5.3.1. Метод «разделяй и властвуй» Некоторые проблемы по природе носят аддитивный характер. Такие проблемы можно разделить на части, и решение всей проблемы можно получить с помощью решения ее частей. При таком подходе можно использовать параллельную обработку. Задание начальных значений элементов массива осуществляется для всего массива и выполняется с 'помощью тех же начальных значений для частей массива. Матричные операции сложения и вычитания выполняются таким же способом, так как эти операции определены для отдельных элементов. На том же принципе отроится и численное интегрирование. Аналогичным примером из экономики служит двойной бухгалтерский учет, когда итоги по столбцам 'вычисляются для различных категорий, включающих дебиты и кредиты, после чего результаты в нужных сочетаниях сравниваются. Рис. 5.2. Площадь неправильного пятиугольника (A=B+C+D+E+F). Простым примером использования аддитивности служит задача вычисления площади неправильного многоугольника с помощью разбиения его на треугольники и вычисления ihx площадей по отдельности (рис. 5.2). Площадь всего многоугольника равна сумме площадей его частей. В сортировке слиянием, выполняющейся разделением п элементов списка на две группы и с помощью сортировки каждой из них с последующим их слиянием, используется подход «разделяй и властвуй». Для сортировки каждой половины списка с помощью процедуры из примера 5.1 требуется (п2—2я)/4 сравнений, тогда как для полного списка нужно (п2—п)/2 сравнений. При слиянии упорядоченных частей требуется п—1 сравнений. Общее число сравнений равно (п2+2п—4)/4, т. е. при таком -способе сортировки тратится в два раза меньше времени, чем при сортировке по методу пузырька.
156 Глава 5 Сортировка Шелла также основана на использовании принципа «разделяй и властвуй». Сначала список разбивается на я/2 подсписков, содержащих по два элемента каждый. Под. списки сортируются. На втором внешнем цикле сортировка про- изводится над /г/4 четырехэлементными, затем над я/8 восьми- элементными списками. На первый взгляд не очевидно, что на каждом цикле сортируется различное число подсписков^ так как подсписки перемешиваются друг с другом: procedure сортировка Шелла (список); ХПример 5.9) число групп = гг/2; while (число групп ^ 1) do j := 1 + число груол; white ) < n do if список (i) > ormooK (j) * обменять<слжхж(1),слисок(])); i :=i + 1; число групп:=ч44Сло групп/2 end. Во многих ЭВМ деление чисел «а части выполняется на аппаратном или иа 'микропрограммном уровне. Вычисления могут производиться каждый раз над одним байтом, при этом используется прямой и обратный переносы между соседними парами байтов. Это тоже иллюстрирует принцип «разделяй и властвуй». Алгоритм двоичного поиска, однако, не является аддитивным, хотя в нем и используется разделение списка на части. Каждый раз берется одна из двух частей. Но при решении задачи поиска вершины в неупорядоченном дереве может быть использован алгоритм типа «разделяй и властвуй». Дерево разделяется на поддеревья, и поиск осуществляется по всем поддеревьям. Каждое из поддеревьев может быть -в евою очередь также разделено на части. Получаем, таким образом, рекурсивную процедуру. Если дерево — двоичное, поиск по нему
Алгоритмы 157 производится с помощью следующей процедуры: procedure поиск (дерево); (Пример 5.10) if дерево^ nil return (неудача) е1$е1^ерево_вершина(=йершина--требуемая return (удача) else искать (левое-поддерево); if удача return (удача) else искать (правое_поддерево); if удача return (удача) else " ^ return (неудача) end. Поиск в левом поддереве и поиск в правом имеют одинаковый приоритет. При параллельной обработке они могут быть исследованы одновременно. РАЗБИВАЙТЕ ЗАДАЧУ НА ЧАСТИ Аддитивный подход при решении задачи поиска пути в лабиринте, показанном на рис. 5.3, а, иллюстрируется на рис. 5.3,6. Лабиринт разделяется на четыре части и через каж- а — вход и выход; б — четыре части лабиринта.
€58 Глава 5 дую из них отыскивается путь. Затем конечные точки сравниваются между собой. Объединяя в каждом из квадрантов? сообщающиеся между собой входные и выходные точки (рис. 5.3,6), можно получить следующие группы: а) А, В, D б) С в) Е, iF, G, H, I, J, К г) L, M, N, О Д) Р е) Q, R, S, Z. Вход в лабиринт отмечен квадратом А, а выход — квадратом Z. Смежные квадраты соединяются между собой по следующим конечным точкам: B—-F, С—L, Н—Р, К—Q, М—Р, О—S. Они соединяют группу а), содержащую вход, с группой е), содержащей выход, через группу в), т. е. (В—F—К— Q), или через группы в), г) и д) (В—F—Н—Р—М—О—S), определяя, таким образом, два различных пути. 5.3.2. Метод последовательных приближений Когда известно приближенное решение и есть способы для его уточнения, можно использовать подход, описанный ниже. Начиная с исходного приближения /0 производится последовательное уточнение решения f\, затем уточнение решений ^2, /з и т. д. Этот ряд представляет собой последовательность приближенных решений, которые или сходятся к действительному решению, или приближаются -к нему настолько, насколько это нужно для практического использования. Каждый член последовательности может зависеть или только от предшествующего ему члена, или от всех более ранних приближений. Метод Ньютона — Рафсона для вычисления квадратного корня (соотношение (5.6)) представляет собой пример такого подхода. Последовательные приближения используются также для математических функций, обычно представляемых такими рядами, как 5!п(*)=*-4+!—£+■•■• <5-7> Частичные суммы представляют собой сходящийся ряд приближений функции синуса. Они могут быть вычислены рекурсивно; при этом члены ряда задаются также с помощью рекурсивного _i
Алгоритмы 15Ф* соотношения: . / ч [ tk для k=0f sinfe (х) = I sink-i(*)+ffc для £>0, (5.8>< 1ЛС ДЛЯ /5=0, -2Щтггдля*>°- Каждый из членов зависит только от предшествующего, каждое новое приближение к функции синуса соответственно зависит от ранее полученного приближения и нового члена ряда. Итерационная реализация более эффективна, чем рекурсивная, так как при использовании (рекурсии .приходится выполнять избыточные вычисления членов ряда. К сожалению, эти ряды прв. больших значениях х сходятся слишком медленно, .и поэтому их нельзя использовать в пакетах подпрограмм для вычисление функции синуса. Рис. 5.4. Приближенное вычис- Рис. 5.5. Путь через лабиринт., ление определенного интеграла. Другим примером сходящихся приближений служит численное интегрирование. Определенный интеграл представляет собой площадь между кривой, изображающей функцию, ,и осью абсцисс. Для вычисления интеграла отрезок, на котором он определен, делится на равные части, и на каждой из этих частей вычисляется приближение с помощью значения f(Xk)&x: (рис. 5.4), где Xk — некоторое значение х в пределах 6-й части, а Ах— размер каждой из частей отрезка. Затем производится дальнейшее разбиение частей 1на более мелкие и вычисляется следующее приближение. Когда два .приближения достаточно близки друг к другу, то решение найдено. В этом при?-
160 Глава 5 мере использовались два различных способа построения алгоритма: аддитивный метод применялся для нахождения последовательных приближений к значению интеграла. ХОРОШЕЕ РЕШЕНИЕ ЧАСТО МОЖЕТ БЫТЬ УЛУЧШЕНО Метод последовательных приближений можно использовать, если известно, что существует такая формула, которая обеспечивает сходимость к желаемой величине. Затем, когда два приближенных ответа имеют достаточно близкие значения, они принимаются примерно одинаково близкими к действительному решению. Этот метод при численных расчетах следует использовать очень осторожно, так как влияние погрешностей вычислений на ЭВМ возрастает со временем. На рис. 5.5 приведен снова тот же лабиринт, что и на рис. 5.3. Если требуется найти кратчайший путь через лабиринт и первым найденным приближенным решением является маршрут, показанный на рисунке, с помощью последовательного исключения замкнутых частей пути, а также циклов и возвратов его можно все более укорачивать, при этом будет получаться каждый раз более точное приближение к кратчайшему пути. Наилучшее решение может быть найдено или не найдено в зависимости от того, насколько удачно выбран способ удаления лишних частей маршрута. Не существует другого метода, который позволил бы уменьшить длину пути, кроме эвристического. Изображеный путь проходит через 53 квадрата. Исключение возвратов через D и L—С—L уменьшает длину пути до 49, а затем до 45. Удаление возврата через Q—К—Е—F—Е— К—Q позволяет сократить путь до 27 квадратов. Но более правильным решением здесь было бы исключение большой замкнутой части маршрута, начинающейся и заканчивающейся в квадрате F. При этом остается путь А—В—F—Е—К—Q—Z длиной 17 квадратов. Но даже это решение может быть улучшено, если найти более короткий путь из F в К. Наилучшим решением является путь А—В—F—G—Н—I—J—К—Q—Z, имеющий длину 15 -квадратов. Каждый путь через лабиринт является приближением кратчайшего пути, т. -е. в этом примере также используется метод последовательных приближений. 5.3.3. Метод наискорейшего спуска Название этого метода отражает заложенный в него принцип: для того чтобы достигнуть дна, требуется лишь идти вниз. Следует отметить, что движение вниз не гарантирует достижения самой низкой точки. Вместо этого отыскивается дно первого обнаруженного углубления. В начале поиска необходимо очень аккуратно выбирать исходную точку. Рассматриваемый f метод позволяет получить хороший результат при решении за-'
Алгоритмы 161 дачи, когда нельзя найти оптимальное решение, потому что получение наилучшего (решения или невозможно, или обходится слишком дорого. Метод наискорейшего спуска отличается от метода последовательных приближений тем, что во втором случае за исходное берется любое приближение, которое затем улучшается. В рассматриваемом же методе направление каждого шага планируется так, чтобы он был направлен в сторону нужного решения. Использование метода последовательных приближений три создании программ заключается в том, что, принимая какую-либо работающую программу за исходную, производится ее последовательное улучшение с помощью отладки и настройки. Способ Рис. 5.6. Граф задачи о коммивояжере. наискорейшего спуска применяется в методах нисходящего проектирования и частично в методе пошагового уточнения. Задача коммивояжера представляет собой классический пример задачи, которую легко поставить, но очень трудно решить. Коммивояжер должен посетить некоторое множество городов, и задача заключается в том, чтобы найти кратчайший маршрут, следуя которому он может попасть во все города не более одного раза и вернуться в исходную точку. Во все города можно попасть из любого другого или по прямому пути между ними, или через другие города. На рис. 5.6 показана постановка одной такой задачи. Города обозначены буквами. Город А является исходным .пунктом маршрута. Цифрами указаны относительные расстояния между городами. Решение данной задачи выполняется с помощью недетерминированного алгоритма. Один из возможных способов получения решения заключается в генерации всех перестановок городов, когда А — исходный путь маршрута. При этом возможны такие комбинации, в которых рядом будут расположены города, не имеющие прямых путей между ними (т. е. в некоторые города коммивояжер попадет несколько раз). Устранив эти комбинации, с помощью вычисления длины маршрутов среди оставшихся можно определить кратчайший из «их. Сложность алгоритма равна 0(п\). Почти во всех реальных случаях эта задача становится Для слишком большого числа городов, чтобы ее можно было решить таким способом на ЭВМ за приемлемое время. П—399
162 Глава 5 Хорошее, но не обязательно оптимальное решение может быть найдено с помощью 'метода наискорейшего спуска. Начиная с города А, следующим для посещения выбирается ближайший другой город. От этого города берется следующий участок маршрута, представляемый кратчайшим расстоянием между этим городом и еще не посещенными городами. Этот процесс продолжается, пока не будут пройдены все города. Данный алгоритм имеет полиномиальную сложность. С помощью описанного метода после нахождения частичного пути А—В—Е (рис. 5.6) следует сделать выбор между Е—С и Е—D. Если выбран участок Е—С, получится маршрут А—В— Е—С—F—D—А, длина которого составляет 16. Этот путь представляет оптимальное решение для такого выбора. В том случае, когда выбран участок Е—D, маршрут найти нельзя, если только не устранить ограничение, по которому запрещается посещать один .и тот же город дважды. На самом деле, если снять это ограничение, можно использовать более короткий путь, проходящий от F к D через С и Е. При этом общая длина маршрута будет равна 14. Нахождение кратчайшего пути в лабиринте можно рассматривать как разновидность задачи о коммивояжере. Вместо условия, что каждый из городов должен войти в -маршрут только один раз, ставится условие нахождения такого подмножества городов, по которым можно провести маршрут, соединяющий начальный и конечный города (соответствующие ©ходу и выходу лабиринта) кратчайшим путем и включающий каждый город только один раз. Диагональ, показанная на рис. 5.7, а жирной штриховой линией, задает главное направление от входа в лабиринт А к выходу из него Z. Применяя стратегию наискорейшего спуска для поиска маршрута, можно использовать это направление как компас. Штриховой линией показан полученный в конечном результате маршрут. Его общая длина равна 27, а после исключения из маршрута возвратов уменьшается до 23. Другой алгоритм поиска кратчайшего пути в лабиринте, использующий метод наискорейшего спуска, основан на нумерации всех позиций, находящихся на расстоянии одного квадрата от входа, затем всех позиций, находящихся на расстоянии двух квадратов и т. д., пока не будет достигнут выход, Прону- мерованнный лабиринт показан на рис. 5.7,6. Кратчайший путь может быть получен при движении в обратную сторону от выхода. На рис. 5.7, в вход, выход и все узловые точки помечены буквами. Кратчайший путь через лабиринт соответствует кратчайшему пути между А и Z по графу, показанному на рис. 5.10. Этот метод, используемый при анализе графа, показанного на рис. 5.6, позволяет получить маршрут А—В—С—D—Е—F—G—
Алгоритмы" 163 Рис. 5.7. Определение пути через лабиринт. . а — с помощью главного направления; б — с помощью анализа расстояний; в — с помощью анализа узловых пунктов. J—М—Z, представляющий один из двух одинаково оптимальных путей. Применение рассматриваемого метода в математике можно проиллюстрировать на примере использования 'последовательных приближений, которые сами по себе не являются аппроксимациями требуемой величины. Таким примером служит ряд чисел Фибоначчи 1, 1, 2, 3, 5, 8, 13, 21, 34,..., каждое из которых является суммой двух предыдущих. Эту последовательность можно записать в виде F(fe) = (1 «ш ft-1,2. 5 ' \F(k— 2)+F(ft—1) для ft>2. 11*
|64 Глава 5 k-e число последовательности отыскивается с помощью вычисления всех предшествующих чисел, ни одно из которых не является его приближением. Ряд, образуемый этими числами, не сходится, а расходится. То же можно сказать и о последовательностях для получения биноминальных коэффициентов, задаваемых соотношениями (5.4) и (5.5). При использовании какого-либо из этих рекурсивных определений значение каждого биноминального коэффициента получается с помощью вычисления последовательности значений, заканчивающегося одновременно с получением требуемого коэффициента. ХОРОШЕЕ РЕШЕНИЕ ЛУЧШЕ, ЧЕМ НИКАКОЕ 5.3.4. Метод обратного прохода Этим методом можно решить некоторые хорошо известные головоломки. Пусть имеются два (резервуара; объем одного из них измеряется пятью, а объем другого — девятью чашками. Требуется с помощью этих резервуаров отмерить 6 чашек воды. Для .решения задачи можно применить метод обратного прохода. Если можно было бы отмерить одну чашку воды, то, вылив ее в больший резервуар и добавив в него воду из другого резервуара объемом 5 чашек, можно было бы получить требуемый объем воды. Одна чашка воды может быть отмерена» если из полного меньшего резервуара отлить 4 чашки воды. Это можно сделать, если в большем резервуаре уже имеется 5 чашек воды. Таким образом, если наполнить меньший резервуар дважды и вылить эту воду в больший, в меньшем останется 1 чашка воды. Вода из большего резервуара выплескивается, и в него переливается оставшаяся 1 чашка из малого резервуара, из которого затем переливается еще 5 чашек воды. Таким образом найдена последовательность действий, соответствующая решению задачи, путем обратного прохода. В другой задаче надо определить, сколько прыжков потребуется лягушке, чтобы выбраться из ямы глубиной 1 м, если за один прыжок лягушка поднимается на 30 см, а между прыжками сползает вниз на 20 см. Прослеживая путь лягушки в обратную сторону, находим, что она сможет выпрыгнуть из ямы, поднявшись на 70 см. Поскольку после каждого прыжка она поднимается на 10 см, ей потребуется 7 прыжков, чтобы подняться на 70 см. На восьмом прыжке лягушка выбирается наружу. Метод обратного прохода применяется тогда, когда задан порядок (направление) решения некоторой задачи; замена этого направления на обратное может помочь упростить задачу без ,ее изменения. Этот метод можно использовать не всегда. Выдача последовательности чисел от 1 до 10 направленна, но
Алгоритмы 165 необратима, так как перемена направления этой процедуры изменит результат. Задача нахождения всех простых чисел между 1 и 100 направленна и обратима, но, если начинать со старших чисел, решение ее существенно усложнится. Задача поиска пути в лабиринте направленна и обратима, если не осуществляется истинный поиск в «реальном» лабиринте. Однако, если за исходную точку взять выход и находить путь ко входу, такая задача аналогична первоначальной. 5.3.5. Метод динамического программирования. Метод динамического программирования заключается в одновременном использовании -прямого и обратного проходов для решения задачи. Перемещение в одном направлении позволяет получить одно из возможных решений. Движение в другом направлении обеспечивает альтернативное (решение. После сравнения этих решений выбирается лучшее из них. Классическим примером использования этого метода является {решение варианта задачи о коммивояжере, оз котором требуется найти кратчайшее расстояние между двумя пунктами. Эти пункты могут обозначать один и тот же город, при этом найденный путь представляет кратчайший циклический маршрут, следуя которому коммивояжер не повторяет ни один из участков пути. На графе, приведенном на рис. 5.8, точками 1 и 9 отмечены соответственно исходный и конечный пункты маршрута. Допустим, что для каждого отрезка задана его длина. Применение обратного прохода для решения этой задачи заключается в том, что перед получением кратчайшего пути «между точками 1 и 9 определяются кратчайшие пути от точки 2 к точке 9 и от точки 3 к точке 9. Для нахождения этих маршрутов необходимо определить кратчайшие пути из точек 4, 5 и 6 к точке 9. Шаг 1. Анализ путей, ©едущих к конечному пункту из соседних: 7—9, 8—9. Шаг 2. Анализ путей, ведущих к конечному пункту и имеющих по одному промежуточному: 4—7—9, 5—7—9, 5—8—9, 6—8—9. Шаг 3. Анализ путей с двумя промежуточными пунктами: 2—4—7—9, 2—5—?—9, 3—5—?—9, 3—6—8—9. Шаг 4. Анализ путей с тремя промежуточными пунктами: 1—2—?—?—9, 1—3—?—?—9. Длина путей вычисляется одновременно с определением маршрутов. Таким образом, путь 2—5—?—9, анализируемый на третьем шаге, не длиннее путей 2—5—7—9 и 2—5—8—9, поскольку при его определении используется кратчайший из путей 5—7—9 и 5—8—9, полученный на втором шаге. Перед выполнением шага 4 уже известны кратчайшие маршруты меж-
166 Глава 5 Рис. 5.8. Граф задачи о коммивояжере, решаемой методом динамического программирования. Рис. 5.9. Дерево возможных решений задачи о коммивояжере. ду 2 и 9, а также между 3 и 9, в результате чего может быть определен кратчайший путь между 1 и 9. С помощью этого метода может быть иайдено хорошее решение для задачи о коммивояжере, иллюстрируемой рис. 5.6. Из заданного множества городов наиболее удалены друг от друга города А и F. При поиске кратчайшего пути между А и F обратным методом динамического программирования необходимо рассматривать варианты (маршрута, ведущие из А через В или через D. Если известны длины кратчайших путей от В
Алгоритмы 167 до F и от D до F, можно вычислить длину путей .из А через В и через D, что позволит определить выбор между этими двумя городами. Длина пути от В к F будет известна, если найдены расстояния по кратчайшим путям от городов С и Е до F. На рис. 5.9 показано дерево рассматриваемых путей, демонстрирующее работу метода динамического программирования. Жирными линиями отмечены наилучшие выбираемые варианты участков пути для каждого пункта, начиная с нижнего уровня дерева. Найдены два пути с минимальной длиной: А—В—Е— С—F и А—D—Е—С—F. По случайному совпадению эти маршруты включают все города. Циклический маршрут через вое G 2 Рис. 5.10. Граф для решения задачи нахождения кратчайшего пути через лабиринт. города может быть получен с помощью объединения этих двух минимальных путей. Для исключения городов, вошедших в маршрут несколько раз, а также для включения в него пропущенных городов, если такие появляются, можно использовать эвристические методы. Применение этого способа решения позволяет получить хороший, но не обязательно лучший маршрут. Продемонстрируем использование описанного метода на примере нахождения кратчайшего шути через лабиринт, показанный на рис. 5.7. Граф, соответствующий данному, процессу решения и (немного упрощенный с помощью удаления квадратов В и Н, приведен на рис. 5.10. Этот пример иллюстрирует прямой метод динамического программирования. Путь Длина Шаг 1: A —G 13 A —D 7 Шаг 2: • • • — G —J 15 ... —G —F 15 (отбросить) — D — F 9 — D — Е 8 Если отбросить более длинные пути к F, останутся те из них, которые состоят из участков, проходящих через G и D (после первого шага) и ведущих к J, F и Е (после второго шага).
168 Глава 5 Шаг 3: — J —М — J —L — F —G -F — E — E^F — E-I 16 17 11 10 (отбросить) 9(отбросить) 13 Пути к F, полученные на втором и третьем шагах, имеют равную длину. Оба из них можно сохранить или отбросить один из них, что и было сделано. Новый путь к Е был отброшен, так как более ранний путь к тому же квадрату короче. Шаг 4: — M-Z — М —L — L —М -L-K — G — J — Е — I --I--K 17 17 (отбросить) 18 (отбросить) 24 (отбросить) 13 13 (отбросить) 14 Путь к Z найден, но не были просмотрены другие пути, поэтому процесс продолжается. Шаг 5: Шаг 6: — J — М — J —L -K-L — М — Z — L —М -L-K 14 15 21 (отбросить) 15 16 (отбросить) 22 (отбросить) В качестве решения принимается тот путь, который был получен раньше, если он короче других, или самый короткий из всех полученных в конечном итоге путей. Действительный путь получается с помощью обратного прохода по цепочке Z—М—J— G—F—D—[С]—А. 5.3.6. Метод поиска с возвратом Для многих задач нет других способов решения, кроме метода «проб и ошибок. Динамическое программирование представляет собой один из способов решения подобных задач. Дерево является наиболее наглядным способам изображения множества возможных решений, даже если метод выбора не имеет явно выраженной древовидной структуры. При решении задачи о коммивояжере методом динамического программирования (граф этого (решения показан на рис. 5.8) выбор нужного маршрута осуществлялся с использованием метода поиска © глубину по дереву возможных вариантов. В ранее рассмотренном решении задачи нахождения кратчайшего пути через лабиринт прямым методом динамического программирования использовался способ поиска (в ширину с отбрасыванием лишних вариантов т. е. когда становится очевидно, что некоторые из маршрутов слишком длинны; они отбрасываются, поэтому до
Алгоритмы 169 конца просматриваются ;не все возможные пути. Метод поиска в глубину состоит в том, что, когда, согласно дереву вариантов, получено решение, осуществляется возврат к той точке, где был выбран один из альтернативных вариантов. После этого генерируется другой путь и из полученных двух (маршрутов выбирается кратчайший, а затем выполняется следующий возврат и т. д. Если п — число уровней сбалансированного дерева, сложность поиска поэтому дереву будет зависеть от п по экспоненциальному закону. Для дерева, содержащего п узлов, сложность поиска зависит по квадратичному закону. Для уменьшения числа уровней иерархии дерева или для исключения бесполезных его ветвей можно использовать эвристические методы. Если дерево изображает множество всех путей через лабиринт, многие из путей должны оканчиваться в вершинах, представляющих тупики лабиринта, другие же могут привести к пройденным уже квадратам. Такие пути можно исключить, если использовать только смежные квадраты лабиринта. Данный способ позволяет также уменьшить число уровней дерева решений. Комбинируя процессы генерации возможных ветвей дерева с процессом поиска пути, можно прекратить генерацию таких ветвей, которые (не будут затем рассматриваться. Любой метод, использующий поиск с возвратом и попытки повторных решений, можно представить в виде дерева поиска. При рекурсивном поиске автоматически используется метод поиска с возвратом, что можно показать на примере следующей процедуры поиска пути прохода сквозь лабиринт. procedure искать-в-лабиринте (путь) (Пример 5.11) repeat следовать», путь until тупик or развилкаог были—здесь—раньше or достигнута—цель; if развилка if правый__путь искать-в-лабиринте (правый-,путь); if не_достигнута—цель if левый_путь искать-в_ лабиринте (левый-путь); if не— достигнута_цель if прямой—путь искать_в_лабиринте (прямой-путь) end.
170 Глава 5 Если во время каждого вызова процедуры 'печатать обозначение рассматриваемого квадрата лабиринта, будет отпечатав весь путь, проходимый при поиске. Если аналогичную печать выполнять цри каждом выходе из процедуры, когда достигнут выход из лабиринта, в результате будет напечатан путь через лабиринт, направленный в обратную сторону (от выхода к входу), без тупиков и циклов, которые проходятся при поиске. Но этот путь не обязательно будет наикратчайшим. Данный пример иллюстрирует метод поиска с возвратом к точке альтернативного выбора, обеспечивающий проверку различных возможных вариантов решения. С помощью рекурсии в ЭВМ запоминаются предыдущие состояния, что затем обеспечивает воз* врат к ним. Если программист не использует рекурсивные обращения, он должен запоминать состояние в каждой точке альтернативного выбора в стеке, а затем для возврата к этой точке извлекать описание соответствующего состояния из этого стека. Метод поиска с возвратом можно использовать для решения задачи о восьми ферзях, для построения кроссвордов из списка слов, для анализа шахматных ходов, для решения задачи о раскраске карты четырьмя цветами и т. д. Этот метод используется также для решения такой простой задачи, как вычисление арифметического выражения. Пусть задано следующее арифметическое выражение: 2 + Зх(4+5) —6. Если оно сканируется слева направо, после сложения 4 и 5 ЭВМ должна вернуться назад и выполнить умножение результата на 3, затем вернуться назад еще (раз и 'произвести сложение с 2. Указанное выражение может быть представлено в виде синтаксического дерева, показанного яа рис. 5.11. Вычисление выполняется с помощью поиска по синтаксическому дереву в глубину. Чтобы избежать повторного просмотра первой части рассматриваемого выражения, требуется запомнить символы в стеке в том порядке, в каком они были считаны, или необходимо использовать набор рекурсивных процедур, например таких, какие описаны ниже: (Пример 5.12) procedure выражение; {вычислить выражение} значение: = 0; оп: = " + "; Рис 5.11. Синтаксическое дерево.
Алгоритмы 171 значение, следующая_оп:-суммма_произведений (значение, оп); if следующая—оп=конец_выражения гешгп(значение, следующая_оп); if следующая—оп = ")" следующая—оп:=взять—символ; return (значение, следующая—оп) end. procedure сумма-произведений {вернуть сумму произведений} - repeat (значение, оп) символ:=взять—символ; {принять операнд} if символ = "(" число: = выражение {вычислить новое выражение} else число:= символ; следующая_оп:=взять символ;{принять оператор} if следующая_оп =." * "or следующая-оп = "/" число:=произведение—членов (число, следующая—оп); if оп=" + " значение: = значение + число ifon= — значение:=значение—число оп:= следующая_оп until on = ")" or оп=конец—выражения; return (значение, оп) end. procedure произведение—членов {вернуть сумму произведений} repeat (величина, оп) символ:= взять—символ {принять операнд} if символ = "(" числом выражение {вычислить новое выражение} else число: = символ if оп= " * " значение: = значение * число; if on = "/" значение: = значение/число; оп :=следующая— оп until on = ")" or on = "+" or on = "-" or on = конец—выражения; return (значение, on) end.
172 Глава 5 Более простой случай применения метода поиска с возвратом можно продемонстрировать на 'примере пакетной обработки, когда весь пакет 'информации забраковывается, если в «ем обнаружены какие-либо неверные данные. Единицы пакета не обрабатываются (выполняется только проверка на наличие ошибок), но должны быть сохранены до тех пор, пока не будет закончена проверка всего пакета. Затем необходимо вернуться к началу пакета и обработать все данные (правильные и неправильные), Часто, если обрабатывается большой объем данных, пакеты запоминаются в определенном порядке и все возвраты производятся к началу сохраненной информации. Использование в этом случае стековой памяти было бы бесполезным усложнением. Для запоминания пакета последовательных данных достаточно использовать простой информационный массив или файл. Использование управляющих полей для группирования данных также требует применения возвратов. Смена группы данных не может быть определена до тех пор, пока не будет прочитана первая запись следующей группы и не будет обнаружено, что она содержит другое значение в управляющем поле. По логике процесса эта запись не должна быть прочитана, пока не закончится обработка предыдущей группы. На практике же эта •прочитанная уже запись сохраняется до момента, когда она логически должна была бы появиться. Аналогичная ситуация возникает, если во время работы пакетного компилятора или операционной системы оказывается, что пропущена карта конца файла и вместо нее прочитана управляющая карта. В этом случае возврат осуществляется назад на одну карту. Использование точек контроля за работой программы с большим временем счета приводит к аналогичной, но более сложной ситуации. В случае сбоя ЭВМ каждый файл должен быть восстановлен до того состояния, в каком он находился в момент прохождения последней контрольной точки, и после этого обработка записей файлов, предшествующих сбою, повторяется. 5.3.7. Метод выделения подцелей Многие из приведенных в этой главе примеров могут иллюстрировать метод выделения подцелей, который заключается в разбиении задачи на отдельные подзадачи. С помощью этих подзадач исходную задачу можно решить по частям или ее упростить и решить. Идея использования подцелей пришла из математики, где доказательство некоторой теоремы А осуществляется следующим образом: Доказательство А следует из истинности В. Доказательство В следует из истинности С.
Алгоритмы 173 Доказательство С следует из истинности D. Теорема D может быть доказана. Реальное доказательство проводится от D к С, затем к В и затем к А. Использование подхода «разделяй и властвуй» может служить примером применения метода выделения подцелей, если, например, процесс решения имеет следующий вид: Подцели: разделить задачу на части; решить каждую часть. Цель: объединить ©се частичные (решения вместе, чтобы получить решение всей задачи. В методе последовательных приближений также используются две подцели. Подцели: найти .приближенное решение; уточнить его. Вторая из этих подцелей повторяется до тех пор, пока не будет получено достаточно точное приближение. В методе наискорейшего спуска используется единственная подцель — продвинуться на один шаг дальше, и эта подцель повторяется, пока не будет достигнута такая позиция, из которой дальнейшее продвижение невозможно. Применение метода поиска с возвратом для различных эвристических способов упрощения дерева поиска решения представляет собой пример использования метода определения подцелей не путем разбиения задачи на части, а с помощью ее модификации. Если для поиска кратчайшего пути через лабиринт применяется метод определения подцелей, во время фазы установления целей анализ задачи может выполняться следующим образом: а) кратчайший путь может быть найден, если известно, какие узлы расположены на нем и каким путем можно попасть к каждому из них; б) вычеркиваются недостижимые со стороны входа узлы; в) направления, ведущие в тупики или к рассматриваемым в настоящий момент узлам, вычеркиваются; г) тупики исключаются путем последовательного удаления позиций, имеющих только одну соседнюю; д) лишние направления исключаются, если удалить все направления между двумя соседними узлами, кроме кратчайших прямых. Такой способ предварительного анализа лабиринта позволяет получить кратчайший путь следующим образом: 1. Вычеркнуть все позиции, имеющие только одну соседнюю. 2. Найти вход, выход и все узлы. 3. Определить и измерить прямые пути между соседними узлами. 4. Вычеркнуть все пути между соседними узлами, кроме кратчайших.
174 Глава 5 5. Вычеркнуть узлы, из которых возможно движение только в двух направлениях. На рис. 5.12, а показан лабиринт, упрощенный описанным выше способом. Оставшиеся узлы отмечены буквами. Граф, указывающий расстояния между узлами, показан на ряс. 5.12,6. Число путей, соответствующих этому графу, может -быть еще боль- s Рис. 5.12. Упрощенный лабиринт (а) и граф (б). ше уменьшено с помощью следующего шага (6) и повторного применения всех шагов, начиная с шага 1. 6. Удалить прямые пути между каждыми двумя узлами, если между ними есть более короткие -непрямые пути. 7. Выбрать кратчайший оставшийся путь. Примером применения метода выделения подцелей служит хорошо известная универсальная программа решения задач GPS, созданная Ньюэллом, Шоу, Саймоном и Эрнстом. Для этой програм;мы описание среды задается с помощью определения объектов и операций над ними. Задается также описание желаемой среды. Программа должна рассчитать, как выполнить необходимые преобразования. Например, может быть задано множество аксиом, а также правила, позволяющие производить преобразования этих аксиом, и при этом требуется
Алгоритмы 175 доказать простые математические теоремы. Могут быть заданы какие-либо логические головоломки, например задача о миссионерах и каннибалах. В этой задаче требуется определить, как переправить трех миссионеров и трех каннибалов, подошедших к реке, с одного берега 1на другой на лодке, способной поднять только двух человек. По условию задачи во время перевоза число каннибалов -на каждом берегу не должно превышать числа миссионеров. Программа GPS решает эту задачу автоматически с помощью метода выделения подцелей, решая сначала соответствующие им задачи. 5.3.8. Метод моделирования Рассматриваемый здесь метод^ отличается от моделирования физических систем. Применение последнего заключается не столько в получении решения некоторой задачи, сколько в сборе и анализе статистических данных о некоторых коэффициентах. Полученные данные могут быть использованы для выполнения дедуктивных выводов о системе или для улучшения процесса моделирования в заданном случае. Некоторые примеры применения метода моделирования уже рассматривались выше. К таким задачам относятся задачи о поставщике и потребителе, о читателях и писателях1), а также игровые задачи. В том смысле, что ЭВМ все время делает то, что обычно выполняют люди, процесс счета представляет собой моделирование. Однако этот термин чаще употребляется в том случае, когда главным является сам процесс, а не его результат. Моделирование дискретных событий и моделирование переходов состояний представляют собой близкие по типу задачи. В обоих случаях проблемная среда описывается как совокупность функциональных единиц и действий. Действия производятся не непрерывно, а лишь в дискретные моменты времени, приводя к изменениям в среде. В задаче типа моделирования переходов состояний определяются то крайней мере п состояний среды, где п сравнительно мало, и .последовательность действий устанавливается в соответствии со средой. В задачах моделирования дискретных событий рассматривается большое число состояний или используется случайная величина. 1) Задача о читателях и писателях моделирует конфликтную ситуацию, часто встречающуюся при использовании несколькими одновременно работающими программами одной области памяти. Программы, которым необходимо осуществить запись, называются писателями. Программы, пытающиеся прочитать информацию из этой области, называются читателями. Обычно в этой задаче ставится условие, согласно которому во время доступа к области памяти какого-либо писателя для остальных писателей и читателей эта область является недоступной. Чтение из указанной области памяти может производиться произвольным числом читателей. — Прим. перев.
176 Глава 5 В большинстве игровых задач решение может быть получе-. но с помощью моделирования дискретных событий. Число возможных состояний, определяемых, например, расположением фишек на игровом поле, является конечным, но слишком большим ДЛЯ ТОГО, ЧТОбы ТОЛЬКО С ИХ ПОМОЩЬЮ ИГРОК 1МОГ ПОЛ- - ноетью рассчитать каждый свой ход. Интуитивный анализ игровой ситуации вносит в процесс определения очередного хода элемент случайности, что соответствует методу дискретных событий. В такой простой игре, как крестики и нолики, если используется поле небольших размеров, число всех возможных позиций невелико и можно рассчитать стратегию беспроигрышной игры. Ходы игрока могут быть полностью детерминированы, если он знает стратегию, в противном же случае его игра носит случайный характер. Следование стратегии во время игры соответствует методу моделирования переходов состояний. Задача о читателях и писателях решается методом моделирования случайных дискретных событий, если времена появления читателей и писателей непредсказуемы или зависят только от доступности pecypcoiB. Задача о поставщике и потребителе не имеет неопределенности или случайного элемента и поэтому решается с помощью моделирования переходов состояний. Поставщик и потребитель работают, пока есть необходимость в продукте и возможность его потребления. Их деятельность полностью определена состоянием области хранения. Классическим примером моделирования дискретных событий является процесс обслуживания клиентов единственным мастером. Течение времени отмечается событиями двух видов: появлением клиента и окончанием обслуживания клиента мастером. Если клиент появляется в тот момент, когда мастер не занят, он обслуживается немедленно. В противном случае он •встает в очередь на обслуживание. Когда мастер закончил обслуживание клиента и обнаруживает, что его ждут другие клиенты, он обслуживает клиента, находящегося первым в очереди. Иначе он переходит в состояние ожидания клиентов. Этот вариант, соответствующим образом дополненный, может служить моделью процессов операционной системы или управления дорожным перекрестком. Состояния процессоров или очередей машин на перекрестке изменяются в дискретные моменты времени и остаются постоянными до следующего события. Другой тип проблем моделирования дискретных событий связан с экологическим и социологическим моделированием. Исходной информацией для этих задач служит начальная заселенность некоторого жизненного пространства популяциями заданного вида (например, лис и фазанов, койотов и зайцев при экологическом моделировании) или людей (при социологическом моделировании). Заданы формулы, определяющие увели-
Алгоритмы 177 чешке и уменьшение численности каждой 'популяции. При социологическом моделировании законы увеличения и уменьшения популяции основаны на возрасте и плотности населения. В экологическом моделировании эти законы определяются доступностью пищи. Моделирование проводится с помощью изменений численности населения на основе текущей численности и его плотности. Вычисление новых значений для видов производится одновременно, с учетом величин всех видов популяции. Если численность зайцев зависит от числа койотов и наоборот, обе популяции изменяются одновременно. Метод моделирования дискретных событий осуществляется в основном с помощью обмена значениями между различными переменными. Новые значения переменных могли бы быть установлены, например, следующим образом: ОДНОВРЕМЕННО (X :- Y; Y := X) Этот обмен можно выполнить с помощью временных переменных temp := х; х:=у; у :«= temp Аналогично рассмотренному процессу выполняется обновление файла на магнитной ленте в системе сопровождения файла. Перед обновлением файл должен быть скопирован. Для всех остальных подсистем операция обновления выполняется так, как будто все записи (в тот момент, когда лента, содержащая обновленный главный файл, заменяет старую) переписываются одновременно. Классическим примером моделирования переходов состояний является машина Тьюринга, представляющая собой упрощенную модель ЭВМ. Машина Тьюринга в качестве входной и выходной информации имеет двоичные данные «а ленте. Набор команд состоит из инструкций: ЧИТАТЬ, ПИСАТЬ-0, ПИСАТЬ-1, СДВИНУТЬ-ВПРАВО, СДВИНУТЬ-ВЛЕВО и СТОП. Кроме этого, машина Тьюринга имеет память, в которой может храниться только один символ из ограниченного алфавита. В языке программирования для этой машины содержатся обычные управляющие структуры. Данная модель представляет собой теоретическую абстракцию, используемую для изучения основных возможностей вычислительных машин. Несмотря на ее простоту, возможности машины Тьюринга такие Же, как и у любой ЭВМ, и поэтому она может служить моделью работы любых ЭВМ. Поскольку в этом случае в памяти и на ленте могут находиться символы лишь из конечного алфавита, здесь используется моделирование переходов состояний. Состояние машины Тьюринга определяется символом, расположенным под головкой записи-чтения, и. символом, находящимся 12-399.
178 Глава 5 в памяти машины. В этой модели явно выражены принципы моделирования переходов состояний, поскольку ее функционирование зависит только от ее состояния. Другим примером моделирования переходов состояний может служить инвертирование программы печати страниц для генерации отчетов. Обычно программа распределения по страницам функционирует независимо от генератора отчетов и имеет следующий вид: procedure распределение..полстраницам; (Пример 5.13) repeat печатать_заголовок_страницы; while not конец—страницы and not конец—отчета do взять_строку—отчета; печатать_строку_отчета; печатать_ конец_страницы until конец_отчета end. Если программа распределения по страницам используется в виде подпрограммы, она при каждом обращении должна быть организована так, чтобы выполнять одну из следующих трех функций: печатать заголовок страницы, печатать строку отчета или печатать окончание страниц. В этой программе должна быть предусмотрена переменная состояния, которая указывает, какую из трех функций необходимо выполнить. Эта переменная состояния инициализируется таким образом, чтобы при первом вызове подпрограмма печатала заголовок страницы. В описанном случае -используется моделирование переходов состояний, поскольку применяется переменная состояния, управляющая работой программы >и содержащая лишь конечное число заранее определенных значений. Подпрограмма имеет следующий вид: (Пример 5.14) procedure распределение- полстраницам (строка-отчета); initial состояние=начало; if состояние = конец; печатать—конец-страницы; состояние =начало; if состояние:=начало печатать—начало—страницы; состоянием продолжение; if состояние-продолжение печатать-_строку_ отчета;
Алгоритмы 179 if конец_страницы состоянием конец; if конец—отчета печатать- конец»страницы end. Эта подпрограмма должна вызываться генератором отчетов каждый раз, когда необходимо выдать на печать строку. 5.4. Документация алгоритмов Документация алгоритмов является частью документации программ и модулей. Если используемый алгоритм хорошо известен или его описание можно найти в специальном источнике, документация должна содержать имя алгоритма или ссылку на источник и авторов. Так, например, простые числа могут быть получены с помощью алгоритма «решето Эратосфена», а для решения задачи о коммивояжере можно воспользоваться алгоритмом Дейкстры. Для нахождения квадратных корней в основном используется метод Ньютона — Рафсона, а для вычисления sin (л;)—полиномы Чебышева. Упорядочение списков выполняется с помощью сортировки Шелла, а также различных видов пузырьковой сортировки. Если в алгоритме использован специальный математический аппарат (как, например, в случае биномиальных коэффициентов), в документацию должно быть включено математическое обоснование метода. Если для упрощения задачи или увеличения скорости счета ее на ЭВМ применяются какие-либо эвристические методы, они должны быть указаны в документации. В ней должно содержаться общее описание используемого метода и подхода к применяемой модели, если оно поможет лучше понять алгоритм. Так, например, должны быть указаны особенности реализации алгоритма в случае применения рекурсии или параллельной обработки. Хорошо известные алгоритмы не нуждаются в детальном описании. Если же используются менее известные алгоритмы или они были разработаны программистом, описания Должны быть составлены подробно, возможно с применением псевдокодов. 5.5. Упражнения 1. С помощью псевдокода напишите две сопрограммы: одну для чтения с перфокарт чисел, по 5 с каждой карты, и другую для печати этих чисел. Во время каждого обращения к печати должно выдаваться по три числа. Использовать в каждой программе инструкцию wait until <условие>, переводящую программу в состояние ожидания, когда появляется указанное условие. 12»
180 Глава 5 2. Объедините подпрограммы из предыдущего упражнения в одну двумя способами: а) с помощью инвертирования подпрограммы печати, обеспечив обращение к ней из программы чтения; б) с помощью инвертирования подпрограммы чтения, обеспечив вызов ее из подпрограммы печати. 3. Функция Аккермана определяется следующим образом: п + 1 ДЛЯ т= 0, А (т, п) = J А (т — 1, 1) для п = 0, А (т — 1, А(т, я-—1)) для т, п>0. С помощью псевдокода напишите рекурсивную процедуру вычисления А(т9п). Вычислите А (4, 5) вручную. 4. Напишите с помощью псевдокода рекурсивные и итерационные процедуры для вычисления: а) квадратного корня с помощью метода, заданного соотношением (5.6); б) функции синуса с помощью метода, заданного соотношением (5.8). 5. Определите сложность каждого из описанных в этой главе методов нахождения кратчайшего пути через лабиринт. Сложность должна выражать зависимость от п (для лабиринта пХп) или от k (длина пути). 6. Измените процедуру поиска пути в лабиринте из примера 5.11 так, чтобы с ее помощью можно было определить кратчайший путь через лабиринт. 7. Измените процедуру вычисления арифметического выражения из примера 5.12 так, чтобы можно было осуществлять проверку на правильность рассматриваемого выражения. 8. Для каждого из типов алгоритмов: аддитивного, последовательного приближения, наискорейшего спуска, обратного прохода и поиска с возвратом приведите пример задачи, которая не рассматривалась в этой главе, но может {5ыть решена с их помощью. Объясните использование при решении этих задач рассматриваемого подхода. 9. Найдите два различных способа вычисления среднего для набора чисел без сортировки. Реализуйте каждый из них с помощью рассмотренных в этой главе методов и определите их сложность. 10. Задача о рюкзаке заключается в определении способа размещения по возможности большего числа объектов в заданном фиксированном объеме. Найдите три различных способа решения, представляющих три разных типа алгоритмов. 11. Последовательность бинарных чисел называется ^-случайной, если в «ей не существует повторяющейся дважды последовательности длиной к. Последовательность с таким свойством, имеющая максимальную длину, называется максимальной &-последовательностью. Так, например, 10 и 01 представляют собой максимальные 1-случайные последовательности, а 10011—максимальная 2-случайная последовательность. Постройте алгоритм генерации максимальной ^-случайной последовательности. Используйте дерево поиска. 12. Изобразите дерево поиска для задачи о каннибаллах и миссионерах (разд. 5.3.7). 13. Преобразуйте процедуру постраничной печати из примера 5.13, использующую метод моделирования состояний, так чтобы каждая новая группа записей отчета печаталась с новой страницы. 14. Постройте алгоритм поиска пути для игры в китайские шашки для одного игрока, описанной в упражнениях 4 и 8 гл. 4. 15. Постройте алгоритм поиска для задачи о треугольниках и квадратах, описанной в упражнениях 3 и 9 гл. 4.
f Глава 6 ПРОЕКТИРОВАНИЕ МОДУЛЕЙ Программисты, имеющие опыт работы только на одном языке программирования, выполняют свои разработки, пользуясь определениями этого языка. Когда проект программного модуля в общих чертах закончен, определены входные и выходные данные и разработаны структуры данных, в этот момент существует большой соблазн приступить к кодированию модуля. Первый вариант модуля представляет собой часто версию модуля с комментариями или обращениями к процедурам в тех местах, где детали еще не определены. Другими словами, первый вариант модуля является скелетной схемой программы, дополненной комментариями. Вместо того чтобы использовать возможности естественного языка, программист при планировании модуля ограничивается формулировками, определяемыми правилами очень простого искусственного языка. Очень часто некоторые второстепенные задачи программирования, предназначенные, например, для того, чтобы удовлетворить требованиям заказчика или чтобы обеспечить отладку, формулируются только после того, как программа уже написана. Это может привести к тому, что программа будет настолько «залатана», что лучшим выходом окажется начать ее разработку заново. Проектирование последовательности передач управления программы и ее кодирование не одно и то же. Программист не должен решать вопросы синтаксиса, пунктуации, а также проблему хранения данных одновременно с задачей, как заставить ЭВМ делать то, что требуется. Если начинать сразу с кодирования, это впоследствии приведет к тому, что структуры данных и программ сформируются прежде, чем полностью будет определено их использование. Предварительное планирование в основном предназначено для такого способа формирования конструктивных деталей программ, чтобы впоследствии можно было избежать больших переделок из-за изменений в структурах. Проектирование модулей включает разработку модульных интерфейсов, внутренних структур данных, структурной схемы передач управления, средств управления в исключительных состояниях, а также подробное описание алгоритма. При рассмотрении управления в исключительных состояниях обычно ограничиваются обработкой информации о коррект-
182 Глава 6 ности или некорректности функционирования модулей, а также об использовании ошибочных условий при управлении работы циклов. Проблема учета всех условий ненормального функционирования модулей возникает при проектировании схемы передач управления в каждом отдельном модуле. ВСЕ, ЧТО МОЖЕТ ИСПОРТИТЬСЯ, ПОРТИТСЯ Когда возникают исключительные состояния, вызванные неправильной работой аппаратуры, всего программного обеспечения или отдельного модуля, управление может осуществляться в соответствии с одним из следующих вариантов: а) попробовать выполнить операцию еще раз; б) устранить ошибку и продолжать; в) сформировать диагностическое сообщение и выйти из модуля, выдав сигнал об ошибке; г) сбросить ошибку. Когда детали проекта уточнены, каждая из них проверяется на возможность появления исключительных ситуаций, для которых должен быть выбран один из приведенных выше вариантов управления. Известны три основных вида средств проектирования структуры управления программой: структурированные алгоритмы, схемы передач управления и управляющие таблицы. Все они предназначены для организации нормального функционирования программы, т. е. с их помощью определяются следующие свойства программы: порядок следования отдельных шагов обработки, ситуации и типы данных, вызывающие изменения процесса обработки, а также повторно используемые функции программы. Средства проектирования представляют собой стандартные способы выявления всех перечисленных свойств. Эти способы относительно независимы от специфики языков программирования и предназначены для организации процесса всестороннего планирования, выполняемого на модульном уровне и аналогичного процессу разработки систем и программ с помощью схем HIPO и структурных схем. Названные выше средства применяются не только для разработки схемы управления модуля, но также используются для создания основной части программной документации, особенно если позволяют отразить все модификации программ. Кроме того, они обеспечивают проектирование средств отладки и тестирования программ еще на стадии планирования. Средства программного проектирования просты в практическом применении. С их помощью можно создавать стандартные структуры, но в более свободной форме, чем это позволяют языки программирования, что обеспечивает как простоту проектирования, так и простоту понимания разрабатываемых структур.
Проектирование модулей 183 6.1. Структурированные алгоритмы Псевдокод представляет собой метод применения стилизованного естественного языка для описания структуры управления программы, конструкции которого близки к конструкциям блочных структурных языков программирования. Это позволяет создавать машинно-независимое описание процесса решения задачи, допускающее использование языков программирования. Псевдокод состоит из таких предложений, которые могла бы выполнить ЭВМ, но которые содержат вместо идентификаторов, зависящих от используемого языка программирования, фразы в свободной форме. Большая свобода при описании процедуры— основное достоинство псевдокода — является и его недостатком. В противоположность языкам программирования при использовании естественного языка трудно придерживаться нужных ограничений и точных определений. Поэтому, применяя псевдокод, следует придерживаться определенных условий. Основные команды псевдокода аналогичны командам для ЭВМ, т. е. командам машинного языка. Они комбинируются стандартным способом, в результате чего образуются более сложные команды. Основными типами сложных команд (кроме последовательностей инструкций) являются команды выбора, повторений и обработки исключительных состояний. Эти команды псевдокода являются обобщениями инструкций типа if, а также команд организации циклов и обработки прерываний. Выполнение данных команд заключается в реализации простых команд в зависимости от условий среды. Под средой здесь понимается состояние ЭВМ, данных и переменных программы. Псевдокод отличается от обычных детализированных устных алгоритмов стандартизацией конструкций, форматированием описания, использованием ключевых слов и удобным для понимания, строгим оформлением. Ключевые слова выбираются так, чтобы сделать алгоритм ясным, строгим и однозначным. •к 6.1.1. Основные управляющие конструкции Обычная конструкция следования имеет следующий вид: Р\ Q Здесь Р и Q — простые предложения, обозначающие операции преобразования данных или информационного обмена, например такие, как ЧИТАТЬ, ПИСАТЬ или ВЫЧИСЛИТЬ; а также определяющие обращения к другим процедурам. Повторения
184 Глава 6 имеют несколько стандартных форм, среди которых наиболее распространены while С do P и repeat P until С Здесь С — проверяемое условие, а Р — простое предложение. Не обязательно использовать обе эти формы, так как с помощью дополнительных управляющих конструкций одна из них может быть преобразована в другую. Так, конструкция while do эквивалентна следующей: ifC then repeat P until not С тогда как конструкция repeat until эквивалентна конструкции Р while not С do P Различие между конструкциями while do и repeat until состоит в том, что если в первой из них проверка условия производится в начале (таким образом, Р может вообще не выполняться), то во второй — в конце, после выполнения Р. Обе_ конструкции типа повторений считаются базовыми, и поэтому каждая из них обеспечивается многими яэыками программирования. Конструкция выбора имеет следующий вид: if С then P else Q Можно показать, что сочетания этих четырех базовых конструкций путем их гнездования и соединения позволяют реализовать все структуры программ. Кроме того, программы, использую-' щие только эти конструкции, могут быть формально проверены на корректность. Для правильно совмещенных конструкций может быть использован символ группировки begin... end. Кроме того, иногда используются конечные ограничители операторов endif и endwhile. Оформление программы, при котором предложения вложенных блоков начинаются правее предложений внешних по отношению к ним блоков, а предложения одного уровня вложенности располагаются с одинаковых позиций, не является существенным для ЭВМ. Однако для программиста оформление с использованием выравнивания по уровням вложенности может обозначать группирование отдельных конструкций. Широко применяется еще один дополнительный тип оператора, который может быть добавлен к группе базовых управляющих конструкций. Это оператор case, обозначающий множест-
Проектирование модулей 185 венное ветвление (т. е. выбор одной ветви из многих). Он имеет следующую форму: case Сх: Рх; С2:JP2;...otherwise Pn Предложение case представляет собой расширение предложения if, предназначенное для передачи управления одной из нескольких ветвей данного оператора. Для ограничения этого оператора применяется слово endcase. ИСПОЛЬЗУЙТЕ СТАНДАРТНЫЕ КОНСТРУКЦИИ 6.1.2. Конструкции следования Конструкции следования представляют собой наборы операторов, выполняемых в порядке их записи. Если запись ведется на псевдокоде, операторы должны быть расположены последовательно на отдельных строках и выровнены по левому краю. При необходимости оператор можно продолжить на второй строке, причем продолжение должно начинаться правее позиции, по которой осуществляется выравнивание: Читать карту Печатать данные с нее, как заголовки столбцов, располагая их в верхней части страницы Читать другую карту Печатать данные с нее, располагая их под заголовками соответствующих столбцов Прописные и строчные буквы, а также знаки препинания должны использоваться таким образом, чтобы их применение подчеркивало структуру программы и повышало ее читабельность. Иногда требуются дополнительные комментарии. Так же как это принято в языке Паскаль, комментарии, поясняющие структуру программ, приводимых в примерах, заключаются в фигурные скобки. 6.1.3. Конструкции выбора Конструкции выбора представляют собой операторы, выполняемые только один раз и при определенных условиях. Существуют различные способы реализации этих конструкций в разных языках программирования. Обычно синтаксически параллельные части в данных конструкциях принято выравнивать по левому краю. Это означает выравнивание всех слов then и else,
186 Глава 6 относящихся к одному уровню, и смещение вправо всех вложенных конструкций. Если требуется более подробное объяснение использования различных вариантов выбора, следует добавлять комментарии: if найдена_карта_.конца_файла (Пример 6.1) then {данных больше нет} печатать—окончание it конец_данных {обнаружен} (Пример 6.2) then печатать-окончание (Пример 6.3) if ь2 - 4ас <0 { отрицательный дискриминант} then {уравнение не имеет действительных корней} печатать- сообщение—об—ошибке else {существуют два действительных корня} вычислить (-b ± V(b2 - 4ас))/(2а) печатать_корни В операторе case параллельные части не имеют ключевых слов и отмечаются парами типа условие — действие. Они могут быть выровнены, как показано ниже: case of тип—сообщения (Пример 6.4) проверить или отложить: найти_запись— главного— файла (проверка) обработать—сообщение взнос: найти-запись_главного—файла (ссуда) обработать-ссуду добавление или изъятие: найти_запись_ главного— файла(сохранение) обработать_сообщение otherwise {тип сообщения не опознан}: печатать—сообщение—об_ ошибке endcase Конструкция if хорошо известна по многим языкам программирования. Конструкция case, имеющаяся в одних языках, в других может быть реализована в упрощенной форме как вычисляемый оператор goto (например, на языке Фортран): GO TO (10, 20, 36, 45), К
Проектирование модулей 187 или в форме каскада операторов if, как, например, на языке Кобол: IF...ELSE IF...ELSE IF...ELSE... Если структура case построена в виде последовательности операторов if, условия должны быть взаимоисключающими. При такой реализации оператора case выполняется та его ветвь, которая соответствует первому истинному условию. Если множество заданных условий не полно, должен использоваться выход по несоблюдению условий, задаваемый ключевым словом otherwise. Соответствующая этому выходу ветвь выполняется всегда, когда ни одно из условий не соблюдено. Если выход по несоблюдению условий в структуре case или выход else структуры if не используется, эта ветвь может быть пропущена или ей должен соответствовать пустой оператор. В языке Паскаль имеется конструкция case, но она ограничена использованием значений единственной переменной. Для псевдокода не требуется такое ограничение, но если условия, подлежащие проверке, независимы, следует применять структуру if, а не case, поскольку при этом повышается читабельность программы. УЧИТЫВАЙТЕ ВСЕ ВОЗМОЖНЫЕ СЛУЧАИ 6.1.4. Конструкции повторений Конструкции повторений представляют собой последовательность операторов, выполняемых несколько раз. К их числу относятся циклические структуры различных видов, в том числе и циклы, использующие счетчики и индексные переменные. Управление функционированием большей части циклических структур осуществляется с помощью проверки условия окончания или условия продолжения выполнения цикла. В псевдокоде, описывающем цикл, следует указывать, что тело цикла выполняется по крайней мере один раз, если проверка производится в конце-цикла, или что возможен случай, когда тело цикла ни разу не выполняется, если проверка осуществляется в его начале. Способы реализации циклов в языках программирования существенно отличаются друг от друга. При разработке программы на псевдокоде и при использовании какой-либо разновидности оператора управления циклами следует соблюдать осторожность при переходе к кодированию программы. Так, например, программисту, использующему для кодирования язык Кобол, следует заметить, что конструкция repeat until не эквивалентна оператору Кобола PERFORM ...UNTIL. Этот оператор Кобола реализует конструкцию while do.
188 Глава 6 При описании циклов следует использовать выравнивание строк и применять комментарии: (Пример 6.5) while not конец—файла db {обработка данных} читать данные сообщений найти соответствующую запись главного файла обновить запись главного файла (Пример 6.6); while счетчик изменяется до 50do {печатать данные} читать карту печатать карту {эхо-печать} repeat {печатать карты с помощью:} (Пример 6.7) читать карту печатать ее until конец—файла {найден} Включение в псевдокод комментариев, отмечающих цель цикла, повышает читабельность программы. Программа в окончательном виде должна быть оформлена таким же образом. В конструкциях повторений тело каждого цикла состоит из независимых операторов. Обычно эти операторы повторяются или заданное число раз, или пока выполняется условие цикла, или пока не произойдет некоторое заранее предусмотренное событие. Главным фактором, влияющим на выбор нужной конструкции повторения, является метод проверки условий окончания циклов. Проверка может быть проведена сразу, и, если соответствующие условия выполняются еще до входа в цикл, тело цикла не будет выполняться. Если проверка осуществляется в конце, операторы тела цикла будут выполнены по крайней мере один раз. В некоторых случаях для окончания цикла могут проверяться условия, сигнализирующие о том, что произошли определенные события. Например, цикл, управляемый считываемыми картами, заканчивается, когда обработана последняя карта данных, несмотря на то что была считана следующая карта данных. ПРОВЕРЯЙТЕ КОРРЕКТНОСТЬ ОКОНЧАНИЯ ЦИКЛОВ
Проектирование модулей 189 Особую проблему представляют проверки концов файлов. Выбранный язык программирования может обеспечивать изменение значения внутреннего флага при чтении последней карты с данными (Паскаль) или при попытке прочитать файл (Кобол и Фортран). Проверка флага может быть включена в операторы чтения (Кобол и некоторые версии Фортрана) или неявно использоваться в этих операторах, как, например, в случае использования одного из предложений управления в исключительных состояниях языка ПЛ/1 ON ENDFILE. В других же случаях проверка флага может производиться программистом (Паскаль, Бейсик). Эти варианты влияют на окончательную реализацию модулей, но не должны отражаться на процессе проектирования общей схемы управления, поскольку разработка должна быть подробной как для этапов, учитывающих использование конкретных данных, так и для этапов, не зависящих от наличия данных. Другая проблема возникает, когда имеется несколько условий окончания. Если при чтении файла на перфокартах проверка выполняется и для условия окончания файла, и для условия обнаружения карты конца файла, сначала проверяется конец файла, и, если он обнаружен, на этом проверка заканчивается, поскольку не осталось больше карт. Порядок проверки составных условий зависит от языка реализации. Если запись, составленная на псевдокоде, понятна, трудностей такого рода на этапе программирования не возникает. Беспорядочное использование в программе операторов goto свидетельствует о плохом стиле программирования, поскольку оно приводит к тому, что программы, написанные таким способом, трудны для понимания и сопровождения. Многие авторитетные программисты считают, что при проектировании циклов, которые должны завершаться сразу после возникновения условия окончания цикла, удобно пользоваться командой выхода, но применение для этой цели команды goto нежелательно, так как она имеет слишком общий смысл. Для обеспечения такого завершения цикла нужна команда, передающая управление первому оператору вне цикла. С помощью флага, указывающего, что условие окончания выполнилось, можно проводить проверку как внутри цикла, так и в конце его. В псевдокоде для отметки точки выхода из цикла можно пользоваться предложением exit: (Пример 6.8) while данные_не_кончились do {чтение и печать карт} читать-карту case of состояние—чтения~ карт карт-нет: *
190 Глава 6 печатать «пропущена карта конца данных» exit считана_карта„конца: пропустить строку exit otherwise {прочитана карта с данными}: печатать данные с карты endcase endwhile Иногда требуется такой же оператор выхода из подпрограммы, как и оператор exit в цикле. Часто необходимо различать нормальный выход от выхода в случае ошибки. Иногда необходимо производить установку возвращаемых значений при выходе из цикла или подпрограммы. Так, например, во время выхода можно устанавливать нужные значения индикаторов событий. В псевдокоде предложение вида exit (...) можно использовать для перехода к строке псевдокода, следующей непосредственно за циклом в момент выполнения условий выхода. Это позволяет использовать структуру повторений более общего вида, т. е. цикл без условия окончания. Такой вид управления работой цикла необходим в операционных системах и программах, работающих в режиме on-line, в которых нет условий, ограничивающих число их циклов. Применение предложений exit позволяет также программисту управлять последовательностью проверки любых ограничивающих работу цикла условий: N=:0 (Пример 6.9) взять число while {запоминание списка чисел} do case of данные—найдены конец_файла: ~—, exit(N) карта_конца „файла: exit(N) список—заполнен: exit (длина—списка) не-цифра: запросить—исправления ,. if нет—исправлений return (неправил ьные_данные)
Проектирование модулей 191 else N := N + 1 список(Ы) :-число взять_число endcase endwhile Для возврата значения вызывающей подпрограмме можно использовать предложение return (...). При выходе из подпрограммы можно возвращать флаги, сигнализирующие нормальное завершение или наличие ошибок. Если на псевдокоде явно указываются возвращаемые переменные, при этом не уточняется, передаются ли с помощью соответствующих аргументов значения данных или ссылки на них (например, адреса). Поскольку способы передачи аргументов в различных языках программирования отличаются, программист при разработке модуля на уровне псевдокода должен избегать определений в терминах конкретного языка программирования. В примере 6.9 ситуации появления неправильных данных, конца файла или карты конца файла рассматриваются как нормальные альтернативные варианты для обработки правильных данных. На самом деле, если предполагается, что после правильных данных последует карта конца файла, ситуации обработки неправильных данных и конца файла уже не являются нормальными. Программы создаются для обработки правильных данных. Проверка же данных на корректность включается потому, что при ручном кодировании информации могут быть допущены такие ошибки, которые ЭВМ не может обнаружить автоматически без специальной обработки, и это заставляет еще на стадии планирования учитывать возможность их появления. Можно считать, что в рассмотренном выше примере считывание неправильных данных и обнаружение конца файла являются ненормальными условиями. Считывание и определение карты конца файла является «почти нормальным» условием, потому что, хотя такая ситуация и учитывается, она не является частью нормальной обработки. Вместо того чтобы рассматривать, ненормальные и почти нормальные условия как обычные альтернативы, их можно гораздо эффективнее обрабатывать как исключительные состояния. 6.1.5. Исключительные состояния Исключительные состояния представляют собой особые условия, которые во время нормальной обработки не возникают часто, но с их появлением требуется применять специальное управление. Эти условия могут быть машинно-зависимыми, и
192 Глава 6 управление при их возникновении производится на уровне язы- ка ассемблера с помощью обработки прерываний. Они могут также создаваться программистом для выполнения операций восстановления или переходных процессов, возникающих во время непрерывной нормальной обработки. Эти ситуации создаются, например, при обнаружении неправильных данных, отсутствия данных и при переходе к обработке другой группы. В языках Ада и ПЛ/1 существуют средства, позволяющие программисту вызывать прерывания и исключительные состояния для таких ситуаций. В ПЛ/1 оператор ON (оператор задания обработки прерываний) позволяет программисту определять действия, которые необходимо выполнить при возникновении некоторых ненормальных или «почти нормальных» ситуаций. Обработка исключительных состояний осуществляется с помощью небольшой подпрограммы. При возникновении прерывания нормальная обработка приостанавливается, управление передается обработчику прерываний, после чего восстанавливается нормальная обработка. Каждый on-оператор в псевдокоде должен размещаться так, чтобы при чтении программы было ясно, относится ли этот оператор ко всем случаям возникновения данного исключительного состояния или только к некоторым из них. Таким образом, область действия on-оператора должна легко определяться. Выделение on-оператора в отдельную структуру позволяет программисту перемещать его в то место программы, которое наилучшим образом соответствует языку реализации: (Пример 6.10J procedure обновление {последовательного главного файла} on конец_главного_файла печатать «файл1 пропущен» return on конец_файла_сообщений копировать остаток главного файла return читать главный файл while not конец_файла_сообщений do repeat читать сообщение проверить сообщение на правильность until найдено_правильное_сообщение while главный_ключ < ключ_сообщения do on конец^главного^файла - ^ установить максимальное значение главного ключа '
Проектирование модулей 193 exit читать главный файл endwhile if найдена_соответствующая_запись обновить запись главного файла else печатать «запись пропущена» endwhile end Пример 6.10 иллюстрирует гнездование конструкции on. Первые два on-оператора относятся ко всей процедуре, но первый из них не работает внутри вложенного цикла while, в котором определен on-оператор для того же условия. С помощью выравнивания выделены тела циклов и on-операторов. Для улучшения читабельности программы использовано ограничительное предложение endwhile. Каждый простой оператор начинается с глагола, что подчеркивает функциональную сущность структур программы даже на уровне операторов. Управляющие конструкции начинаются ключевыми словами, и подструктуры каждой из них либо выровнены по левому краю, либо начинаются правее. Нумерация отдельных предложений и использование меток не обязательны, так как все переходы заданы неявным образом структурами программы, а также предложениями exit и return, При реализации программы псевдокод должен быть модифицирован в соответствии с использованием языка программирования. Псевдокод не следует понимать как один из языков программирования. При этом использование стандартных конструкций . sequence if...then...else... case...:...;...:...;. ..otherwise... while...do... repeat...until... exit(...) return (...) on... а также метода выделения структур с помощью выравнивания в сочетании с описанием операторов на естественном языке обеспечивает большую свободу при обдумывании разрабатываемой программы и более надежную проверку ее правильности, чем это позволяют языки программирования. 6.2. Схемы передач управления Для изображения передач управления в программном модуле обычно используются структурные схемы программ. Структурное программирование и его влияние на применение основ- 13-399
Подготовка Ограничение/прерывание Решение Обработка Вызов модуля б ? Соединитель Линия передачи управления I 1_. Пояснение Цикл со счетчиком индекса Блок обработки с несколькими выходами Рис. 6.1. Символы структурных схем. а — стандартные символы; б — нестандартные символы. *• Рис. 6.2. Базовые управляющие конструкции. а — следование: б — выбор; в — повторение.
Проектирование модулей 195 ных управляющих конструкций способствовали тому, что стандартные символы схем были дополнены новыми символами и были разработаны новые типы схем. В частности, схемы Насей— Шнейдермана представляют для программиста средство описания вложенных управляющих структур. 6.2.1. Структурные схемы программ На рис. 6.1 показаны стандартные и более новые нестандартные символы для изображения структурных схем. Их можно использовать для представления организации программы так же, как и для передач управления. Как и псевдокод, структурные схемы можно применять на любом уровне абстракции. Поскольку они воспринимаются в первую очередь визуально, их следует изображать таким образом, чтобы структура программы становилась сразу очевидной. Краткость, выразительность и планомерность при проектировании позволяют создавать структурные схемы высокого качества. Основная тенденция в использовании if... then ... else 1 Рис. 6.3. Вложенные конструкции структурных схем. структурных схем в настоящее время — не указание последовательности операций, а группирование символов, выражающих базовые конструкции: следование, выбор и повторение. На рис. 6.2 показаны схемы этих управляющих конструкций. Каждая конструкция имеет единственный вход и единственный выход. Структурные схемы изображаются по вертикали. Если первый прямоугольник обработки конструкции повторения опущен, эта конструкция имеет вид while do. Если же отсутствует второй прямоугольник, конструкция повторения имеет вид repeat until. Такую конструкцию называют «циклом в п с половиной повто- 13*
196 Глава 6 рений». Управляющие конструкции могут быть представлены в виде гнезд. На рис. 6.3 показана конструкция выбора, где альтернативами являются следование и другая конструкция выбора. Альтернативные ветви внизу объединяются, формируя законченный вид конструкции выбора. Конструкции могут быть заключены в штриховые прямоугольники. Это упрощает понимание их функционального назначения. case ... when ... when.. .when ... case ,.. when ... when ... when ... of erwise ,,. Рис. 6.4. Варианты конструкций выбора. ^ Конструкции выбора с двумя ветвями (рис. 6.2 и 6.3) соответствуют конструкции if с двумя вариантами выбора, общепринятыми в большинстве языков программирования. Для изображения структуры выбора из множества вариантов, соответствующей конструкции case, схемы дополняются, как показано на рис. 6.4. Это обеспечивает компактный способ изображения структуры, существующей в различных видах в большинстве языков программирования, но в состав стандартных элементов схем не входит. В структуре на рис. 6.4 явно выделен параллельный характер множества альтернатив, который при использовании гнездованных конструкций выбора из двух ветвей не был бы очевидным. Применение стандартных символов структурных схем (рис. 6.1, а) при преобразовании схем в программы не обеспечивает структурности программ и не является адекватным использованию блочно-структурных языков или даже также конструк-
Блок входа/выхода. Этот символ используется для обозначения операций ввода и вывода информации. Отдельным логическим устройствам или отдельным функциям обмена должны соответствовать отдельные блоки. В каждом блоке указывается тип устройства или файла, тип информации, участвующей в обмене, а также вид операции обмена. /Вернуть запись на/ г магнитную ленту/ fc главным фай- / лом анкетных данных /Читать карту с анкетными данными у служащего / Блок обработки. Этот символ применяется для обозначения одного или нескольких операторов, изменяющих значение, форму представления или размещения информации. Для улучшения наглядности схемы несколько отдельных блоков обработки могут быть объединены в один блок. Представление отдельных операций достаточно свободно; например, для обозначения вычислений можно использовать математические формулы, для пересылок данных — стрелки, а для остальных — пояснения на естественном языке. Содержимое блоков структурных схем никогда не имеет вид машинных кодов. В зависимости от уровня детализации схемы пояснения на естественном языке могут быть более или менее подробными. Структурные схемы, так же как и псевдокод, независимы от специфики языков программирования. Поэтому в описаниях операторов не следует использовать резервированные слова и символы языков программирования, а также применять имена данных, составленные в соответствии с синтаксическими требованиями этих языков. Блок решения. Этот символ используется для обозначения переходов управления по условию. Для каждого блока решения должны быть указаны: вопрос, решение, условие или сравнение, которые он определяет. Стрелки, выходящие из этого блока, должны быть помечены соответствующими ответами так, чтобы были учтены все возможные ответы. Следует отметить, что ответы, которые не могут появиться при анализе правильной информации, иногда появляются при рассмотрении некорректных данных. Следует всегда учитывать такие ситуации. Учесть сумму в строке баланса и в итоговой строке Перезаписать дубль Символ ограничения/прерывания. Этот символ . . предназначен для обозначения входов в структурную (^начало) схему, а также для указания всех выходов из нее. ' Каждая структурная схема должна начинаться или заканчиваться символом ограничения. В этих символах следует размещать пояснения к их использованию. Если символ указывает на прерывание, он дол- > N Жен идентифицировать Соответствующую ИСКЛЮЧИ- ( Конец J (данные) тельную ситуацию и блок структурной схемы, осуществляющий управление в данной ситуации. Между символами ограничений могут находиться цепочки таких символов структурных схем, как последовательности блоков, обработки, вызовов модулей, ввода/вы- Вода, решений И Т. Д. (Возврат) ^ис. 6.5. Примеры использования символов структурных схем.
198 Глава 6 /'Открыть файлы > Обнулить таблицу Сбросить флаг i конца файла у Блок подготовки. Этот символ используется для обозначения процессов предварительной обработки. Для многих программ должно быть определено выполнение вспомогательных функций, таких, как операции открытия и закрытия файлов, установка начальных значений флагов, а также инициализация переменных. Эти функции не реализуют какую-либо часть основного алгоритма. Для обозначения вспомогательных функций часто используются блоки обработки, но более правильно размещать их внутри блока подготовки. С Начало J ЧИТАТЬ И ПЕЧАТАТЬ карты, пока не встретится карта конца файла Сортировать файл Г Конец J Считать и Л Спечатать/ Лв оз врат J Блок вызова модуля. Этот символ используется для представлений обращений к модулям или подпрограммам. Вертикальные линии обозначают обращение к внешним модулям обработки, горизонтальные— данный блок представлен в документации отдельной структурной схемой. Блоки вызова модулей используются для обращений к библиотечным подпрограммам, а также для обозначения части программы, не зависящей от основной схемы управления. В таких языках как Алгол или Паскаль, эти блоки обозначают вызовы процедур, а для языка Кобол соответствуют использованию параграфов. Блоки вызова модулей применяются также для обозначения некоторой части программы, которая будет кодироваться вместе со всей программой, но в документации представлена отдельной структурной схемой. Если такая часть программы представляет итерационный процесс, в соответствующий ей блок вызова должны быть включены описания условий окончания цикла. Если блок вызова представляет рекурсивное обращение, его выход должен быть соединен со входом стрелкой. По мнению некоторых авторов книг по программированию, использование более одной структурной схемы для одной программы затрудняет ее понимание. Однако практика показывает, что удобнее всего применять структурные схемы, разбитые в соответствии с уровнями абстракции. Таким образом, блоки вызова модулей могут быть использованы для определения отдельных частей программ, которые удобнее представлять отдельными структурными схемами. Обозначение циклов в виде указанных символов позволяет производить детализацию этих частей программы на более поздних этапах проектирования. ций, как предложения языка Кобол GOTO...DEPENDING ON и READ... AT END. С помощью этих символов особенно трудно изобразить управление в исключительных состояниях. Поэтому были предложены дополнительные символы; некоторые из них показаны на рис. 6.1,6. Примеры одновременного использова-
Проектирование модулей 199 Линии переходов. Эти символы используются для обозначения порядка выполнения действий. Для повышения читабельности программы следует придерживаться стандартных правил изображения передач управления: сверху вниз и слева направо. Если необходимо показать передачу управления снизу вверх или справа налево, в этом случае следует отметить направление стрелкой. За исключением блоков прерываний, все подструктуры, такие, как последовательности, разветвления и повторения, должны иметь по одной входной и одной выходной линии переходов. ... ,, ФАКТОРИАЛ J п! ! __J Соединители. Эти символы используются в том случае, если структурная схема должна быть разделена на части или не умещается на одном листе, или для того, чтобы избежать излишних пересечений линий переходов. Применение соединителей не должно нарушать структурности при изображении схем. Блок пояснений. Этот символ позволяет включать в структурные схемы пояснения к функциональным блокам. Частое использование комментариев нежелательно: оно усложняет структурную схему. Но иногда необходимо пояснить применение атрибутов некоторых переменных, принятые допущения или назначение программных сегментов. /Читать / карту А, __,__ . Первая карта должна содержать I описание колоды Блок множественного выхода. Этот символ представляет собой комбинацию какого-либо функционального блока с блоком решений. Он используется в тех случаях, когда, например, исключительные состояния, возникающие при открытии файла, вызывают передачу управления из процедуры открытия в точку головной программы, отличающуюся от точки обычного возврата. Для соблюдения принципов структурности для всей программы последняя должна иметь вид конструкции выбора, а решения следует определять с помощью блока множественного входа. Каждая ветвь этого блока Должна быть помечена. Конец файла / Читать "^^Фау /сообщение/Ч^ ^?~~~~Ъ Обнови счет Данные Ошибка пропущены Неправильные данные
200 Глава 6 l*-1 l-H + 1 <м> ^ Нет A(l)«-0 Цикл со счетчиком индекса. Этот символ используется для организации циклических конструкций, таких, например, как DO — цикл в языках Фортран и ПЛ/1, PERFORM...VARYING —в языке Кобол или FOR... — в языке Паскаль. Верхняя левая часть блока предназначена для определения начального значения индексной переменной, нижняя левая часть показывает правило изменения этой переменной, а правая задает граничные условия указанного правила изменения. Блок размещается вверху циклической конструкции, для управления которой он предназначен, даже в том случае, если изменение индекса и проверка условий окончания цикла при реализации модуля осуществляется не в начале, а в конце цикла. Так, например, цикл может быть осуществлен в виде конструкций while do или repeat until. ния стандартных и нестандартных символов приведены на рис. 6.5. Один тип ситуации, встречающейся при программировании и с трудом поддающейся изображению с помощью стандартных символов структурных схем, представляет собой исключительные состояния, которые нельзя обработать с помощью замкнутой подпрограммы управления прерываниями. Например, условие возникновения состояния конца файла для последовательного файла может быть проверено с помощью явного тестирования соответствующего флага, выполняемого отдельным предложением программы. Это предложение изображается в виде блока решения. Если проверка флага встроена в предложение чтения, такой блок не нужен, т. е. подразумевается, что решение не может быть отделено от операций чтения. Необходимость использования блока с несколькими выходами очевидна, а это нестандартный символ структурных схем. В более общем виде та же проблема возникает при переходах обработки от одной группы данных к другой. Тогда используются условия, связанные с операциями обмена, ошибками в размерах и типах данных, а также с обработкой ошибочных или необычных выходов из вызываемых процедур. Если изображенный с помощью структурной схемы модуль возвращает вызывающему его модулю флаги, сигнализирующие о нормальном или ошибочном его завершении, применение явной установки флагов и использование единственного блока выхода приведут к усложнению структурной схемы. Хотя обычно каждая структура управления и структурная схема модуля должны иметь только один входной и один выходной блок, тем не менее, если возможен раздельный выход по нормальному завершению и по ошибке, лучше использовать более одного помеченного блока выхода. Следует добавить, что для выхода по
( Начало j Нет Печатать ' «запись пропущена» СОРТИРОВАТЬ Файл сообщений /Читать главный Z^^^4^ файл /s^^ Конец файла t Читать сообщение Проверить сообщение Нет ^ Сообщение" правильное9 Нет Обновить запись| главного файла j г~ ( Возврат J Конец файла Обработать остаток главного файла ъ Да I Печатать «файл пропущен» Выход по ошибке о Конец файла Установить максимальное] значение ключа главного файла Рис. 6.6. Программа сопровождения файла.
202 Глава 6 нормальному завершению должен использоваться по крайней мере один отдельный блок. Выход по ошибке, когда главный файл не обнаружен, и выход по необычному завершению операций обмена из блоков ввода/вывода показаны на рис. 6.6, где изображена структурная схема программы, записанной на псевдокоде в примере 6.10. И нормальный, и ненормальный выходы обеспечивают передачу управления в вызывающую программу. Таким,образом, в определенном смысле можно считать, что из вызываемого модуля есть только один выход. 6.2.2. Схемы Насси — Шнейдермана Способ изображения модуля с помощью схем Насси — Шнейдермана представляет собой попытку использования требований структурного программирования в структурных схемах модулей. Он позволяет изобра- Обработка Решение Следование Цикл жать схему передач управления не с помощью явного указания линий переходов по управлению, а с помощью представления вложенности структур. Некоторые из используемых в этом способе символов соответствуют символам структурных схем; другие символы аналогичны структурам псевдокода. Эти символы показаны на рис. 6.7. Каждый блок имеет форму прямоугольника и может быть вписан в любой внутренний прямоугольник любого другого блока. Блоки помечаются тем же способом, что и блоки структурных схем, т. е. с помощью предложений на естественном языке или с использованием математической нотации. Этот метод проектирования не зависит от языка программирования, используемого для реализации модуля. Примеры применения описанных блоков приведены на рис. 6.8. Модуль сопровождения файла, структурная схема которого показана на рис. 6.6, изображен на рис. 6.9, а в виде схемы Насси — Шнейдермана. Из рисунка видно, что включение в схему обработки исключительных состояний, например проверки конца главного файла, повлечет за собой усложнение схемы. Если использовать символы схем Насси — Шнейдермана одновременно с дополнительным символом структурных схем для Цикл Рис. 6.7. Символы схем Шнейдермана. Конструкция case Насси —
I Проектирование модулей * 203 Блок обработки. Каждый символ схем Насси — Шнейдермана является блоком обработки. Каждый прямоугольник внутри любого символа представляет собой также блок обработки. Блок следования. Этот символ объединяе-i ряд следующих друг за другом процессов обработки. Блок решения. Этот символ применяется для обозначения конструкций типа if...then...else.... Вопрос располагается в верхнем треугольнике, варианты ответов на* этот вопрос — по сторонам треугольника, а процессы обработки обозначаются прямоугольниками. Блок case. Этот символ представляет расширение блока решения. Те варианты выхода из этого блока, которые можно точно сформулировать, размещаются слева. Остальные выходы объединяются в один, называемый выходом по несоблюдению условий и расположенный справа. Если можно перечислить все возможные случаи, правую часть можно оставить незаполненной или совсем опустить, а выходы разместить по обе стороны блока. Блок dowhile. Этот символ обозначает цикличе- сую конструкцию while do с проверкой условия в начале цикла. Условия окончания цикла размещаются в верхней полосе, сливающейся с левой полосой, указывающей границу цикла. Блок repeatuntil. Этот символ аналогичен блоку dowhile, но условия располагаются в конце. Рис. 6.8. Примеры использования символов Насси — Шнейдермана. изображения множественных выходов и обработки прерываний, то представление рассматриваемого модуля может быть упрощено, как показано на рис. 6.9, б, где основная схема отражает только режим нормальной обработки. 6.3. Управляющие таблицы Если логика управления программы состоит преимущественно из решений или идентификации и обработки вариантов, не зависящих друг от друга и не связанных с каким-либо общим процессом вычислений, для представления такой программы проще всего использовать табличные способы описания. Работа анализатора входных символов в компиляторе состоит в идентификации^ и обработке множества различных типов символов. Поэтому при проектировании анализаторов часто используются управляющие таблицы. Вид обработки зависит от типов сим- Читать данные Печатать данные [«равны»! |4-й|ледний| Пока не конец файла I Читать карту Печатать | каргу I Читать карту Печатать I карту Пока не конец файла
Рис. 6.9. Программа сопровождения файла, представленная с помощью схемы Насси— Шнейдермана. а — схема Насси — Шнейдермана; б — модифицированная схема Насси — Шнейдермана.
Проектирование модулей 20 волов. Соответствующая таблица содержит запоминаемые в программе значения, зависящие от символьных данных. Эти значения служат для организации переходов управления между частями программы. К этой же категории программ относится интерпретатор, который анализирует символы, вводимые во время его работы. В гл. 5 рассмотрены примеры моделирования переходов состояний. 6.3.1. Таблицы данных Данные часто состоят из символов, которые могут быть заранее определенным способом классифицированы на основании ряда проверок. Такие случаи встречаются в разнообразных приложениях: при проверке данных на правильность, при расчете размеров вычетов из заработной платы, для управления светофором в соответствии с ситуацией на перекрестке, а также при анализе математических выражений. На псевдокоде такие случаи представляются с помощью предложений if или case. В структурных схемах для той же цели применяются блоки решения. Наиболее естественным решением в подобных случаях было бы составить таблицу, перечисляющую ситуации и соответствующие типы данных. Такие таблицы часто используются только для запоминания данных, но их также можно применять и для организации управления программой. Примерами таких таблиц служат таблицы перехода управления в языке ассемблера или списки переключателей в языке Алгол. Таблица 6.1. Значения римских цифр Текущая цифра М D С L К . V I м 1000 —100 D 1000 —100 Цифра справа С 1000 500 100 —10 L 1000 500 100 —10 от текущей X 1000 500 100 50 10 —1 V 100G 500 100 50 10 —1 I 1000 500 100 50 10 5 1 Пусто 1000 500 100 50 10 5 1 Табл. 6.1 позволяет получить десятичные значения римских цифр. Левая сторона таблицы представляет очередную вычисляемую цифру римской системы счисления. Следующая за ней цифра отыскивается по верхнему ряду таблицы. Таким образом, в числе MCMLXXXIX символ С имеет значение —100, а I равно —1. В числе же MDCLXI символ С имеет значение 100, а I равно 1. Пропуски в таблице соответствуют неправильным
206 Глава 6 сочетаниям римских цифр. Такая таблица может храниться в памяти, и для вычисления десятичного значения римской цифры необходимо обращаться к этой таблице. Аналогичная таблица может быть реализована в виде ряда проверочных вопросов. Если учесть упорядоченность римских цифр, представляемую в виде ряда М, Д С, L, X, V, I, таблица может быть переписана в более сжатой форме. Для вычисления значения римской цифры в первую очередь осуществляется поиск в таблице с целью определения правильности рассматриваемого сочетания соседних символов. Если сочетание правильно, найденное табличное значение прибавляется к накапливаемой сумме. Таблица 6.2. Значения римских цифр Текущая цифра Цифры справа от текущей младше? Значение MDCCLXXVII Да Да Да Нет Да Да Нет Да Да Нет 1000 5С0 100 —100 50 10 —10 5 1 —1 Сжатая форма таблицы (табл. 6.2) составлена в соответствии,с упорядоченностью римских цифр по старшинству. При использовании этой таблицы сначала осуществляется проверка, является ли следующая цифра за рассматриваемой старше ее. Если да, то числовое значение рассматриваемой цифры вычитается из общей суммы, если нет — прибавляется к ней. Ни одна из этих двух таблиц не позволяет провести полную проверку правильности рассматриваемой римской записи числа, так как для этого необходимо установить предел появления каждого из символов. Вычисление арифметического выражения может быть осуществлено с помощью таблицы, аналогичной рассмотренной, но содержащей вместо десятичных значений цифр символы, которые могут представлять собой обращения к модулям. Каждому символу, найденному в таблице, соответствует определенный модуль. Для вычисления арифметического выражения с помощью табл. 6.3 требуется стек, который в начальном состоянии пуст. Арифметическое выражение просматривается слева направо. В примере ниже приведены вызываемые модули: procedure вст; (Пример 6.11) if (текущий_символ)п =значение then верхний—оператор: = текущий_символ; вставить (текущий—символ, стек); читать (текущий-символ) end
Проектирование модулей 207 procedure выт; значением вытолкнут^ (стек); сброс: = вытолкнуть (стек); верхний—оператор: =■ верх (стек); вставить (значение, стек) читать (текущий символ) end; procedure выч; значение-2:= вытолкнуть (стек); оператор:=вытолкнуть (стек); значение—1:=вытолкнуть (стек); верхний—оператор:=верх (стек); j результата считать (значение—1, оператор, значение-2);' вставить (результат, стек) end; procedure ошиб; печатать сообщение об ошибке; неисправимая—ошибка:= истина end; procedure конец; return (верх (стек)) < end. Рассматриваемая таблица будет выявлять лишь некоторые синтаксические ошибки. Остальные же можно обнаружить, проверяя тип каждого принимаемого из стека элемента, т. е. анализируя, является ли он оператором или значением. Кроме того, когда закончены вычисления, в стеке должен остаться только один элемент. Таблица 6.3. Верхний ( оператор + — * / Пусто Управление вычислением арифметических выражений Текущий символ ( вст вст вст вст вст вст + вст выч выч выч выч вст — вст выч выч выч выч вст * вст вст вст выч выч вст / вст вст вст выч выч вст ) вст выч выч выч выч ошиб Значение вст вст вст вст вст вст Пусто ошиб выч выч выч выч конец
208 Глава 6 Таблица 6А. Таблица решений для вычисления арифметических выражений Текущий символ «(» Текущий символ — значение Текущий символ старше верхнего оператора Верхний оператор «(» Процедура Да — — — вст Нет Да -— — вст Нет Нет Да — вст Нет Нет Нет Да выч Нет Нет Нет Нет выч Так же как и в случае римских цифр, табл. 6.3 может быть сжата и представлена в виде табл. 6.4. Для построения табл. 6.4 использовалась иерархия старшинства операторов: */ + — Эта таблица представляет пример таблицы решений. 6.3.2. Таблицы решений Таблицы решений сочетают в себе характерные черты предложений ветвления if и таблиц передачи управления. Они представляют собой средство программирования, которое используется в основном при решении экономических задач и предназначено для классификации данных и для указания действий, которые необходимо выполнить для каждого класса данных. Набор таблиц решений может быть использован для организации всей программы. Так как эти таблицы ориентированы на обработку данных, одна из таблиц может определять условия обработки файлов, другая — условия обработки записей, а остальные— описывать условия обработки полей данных. В табл. 4.2' показан пример описания обработки записей для программы сопровождения файла, выполненного с помощью таблиц решений. Таблица решений состоит из четырех частей: столбца условий, входов условий, столбца действий и входов действий. Взаимное размещение этих частей показано в табл. 6.5. Столбец условий содержит вопросительные предложения, совокупность ответов на которые позволяет описывать ситуацию. Вход уело- вий представляет собой перечень всех возможных ответов на: Таблица 6.5. Таблица решений Столбец условий Столбец действий Вход условий Вход действий
Проектирование модулей 209» указанные вопросы. Столбец действий содержит описания всех действий, которые могут быть выполнены в различных ситуациях. В части таблицы, расположенной под входом условий и называемой входом действий, содержатся пометки, указывающие, какие действия необходимо выполнить при каждой комбинации результатов проверок условий. Совокупность входа условий и входа действий образует часть таблицы, называемую входом. Каждый столбец входа таблицы называется правилом. В отличие от предложений описания состояний условия из столбца условий не обязательно являются взаимоисключающими. Различают таблицы решений с ограниченным и расширенным входами. Таблица решений с ограниченным входом в столбце условий содержит вопросы, на которые можно ответить «да» или «нет», а каждая позиция входа действий содержит пометку, указывающую на необходимость применения данного действия, или остается незаполненной, если действие не применяется. Если правила заполнения части входов отличаются от описанных, тогда говорят, что таблица имеет расширенный вход. Таблица решений может быть полной и неполной. Таблица называется полной, если в ней содержатся столбцы для всех комбинаций ответов «да» и «нет». Табл. 6.4 представляет собой неполную таблицу решений с расширенным входом. Некоторые ответы, соответствующие определенным условиям, исключают друг друга. Столбцы, содержащие комбинации взаимоисключающих ответов, опускаются. Таблица 6.6. Печать наибольшего из А, В и С Л>В в>с А>С Печатать А Печатать В Печатать С Ошибка Да Да Да X Да Да Нет X Да Нет Да X Да Нет Нет X Нет Да Да X Нет Да Нет X Нет Нет Да X' Нет Нет Нет X Табл. 6.6 является полной таблицей решений с ограниченным входом. Она служит для управления процессом печати наибольшего из трех чисел: Л, В и С. Некоторые комбинации (например, Л>В, С>А иВ>С) невозможны и отмечены во входе действий как ошибочные. Эта таблица имеет ограниченный вход, так как во входе условий содержатся только «да» и «нет», а во входе действий — только пометки и пробелы. Эта 14-399
210 Глава 6 таблица является полной, поскольку на три вопроса, предполагающих один из двух ответов, можно ответить восемью (23) способами, и все комбинации ответов в ней представлены. Нужный столбец таблицы решений определяется следующим образом. Сначала сканируется (поэлементно выбирается) слева направо верхняя строка входа условий до того столбца, в котором содержится правильный ответ. Затем по этому столбцу осуществляется переход вниз на следующую строку, и сканирование продолжается вправо по этой строке до следующего столбца, содержащего правильный ответ на соответствующий вопрос, и т. д. Движение осуществляется все время вправо и вниз. Столбец с последним правильным ответом представляет собой правило, содержащее во входе действий перечень тех действий, которые необходимо выполнить при данной комбинации ответов. В табл. 6.6 при условии Л = 1, 5 = 3 и С=2 первая строка сканируется до первого столбца, содержащего «нет». Затем переходим на строку, расположенную ниже и соответствующую сравнению В к С. Так как для рассматриваемого примера В>С и в этом же столбце для этой строки находится правильный ответ «да», переходим на одну строку вниз и рассматриваем третий вопрос: А>С? Перемещаясь вправо по этой строке, находим правило, соответствующее комбинации ответов нет-да-нет и указывающее на действие «Печатать В». Таблицу 6.6 можно упростить и записать в более компактном виде, если опустить те из столбцов-правил, которые не могут соответствовать реальным условиям. Кроме того, не для всех комбинаций данных необходимо отвечать на все вопросы. Одновременная истинность первого и третьего условия определяет действие, не зависящее от истинности второго условия (В>С), которое при этих условиях оказывается несущественным. Поэтому таблицу можно упростить с помощью совмещения первого столбца с третьим, четвертого с восьмым, а также пятого с шестым. Несущественное условие в каждом случае отмечается прочерком (—) (табл. 6.7, случай а)). Правила совмещаются только в том случае, если имеют один и тот же набор действий Таблица 6.7, Упрощенные формы таблицы решений для печати наибольшего числа из А, В и С а) Таблица с ограниченным входом б) Таблица со смешанным входом А>В В>С А>С Печатать А Печатать В Печатать С Да — Нет — Нет Да Да Нет — • х х X А>В В>С А>С Печатать Да Да А _ Нет Нет С Нет Да В
Проектирование модулей ^ 211 и различаются только одним входным условием. Использование прочерка упрощает формирование таблицы вручную, но не означает использования расширенного входа вместо исходного ограниченного. Дальнейшее упрощение (табл. 6.7, случай б)} приводит к нарушению ограниченности входа. Табл. 6.7,6 имеет вход смешанного типа. На стадии подготовки спецификации прикладных программ таблицы решений помогают выявлять пожелания пользователей относительно режимов обработки данных. Таблица может заполняться во время обсуждения свойств системы пользователем и системным аналистом. Если ответы «да» и «нет» проставлены заранее, они служат для аналиста «сигналами» о режимах обработки нерегулярных данных. Основной недостаток использования таких таблиц решений заключается в том, что в обычных таблицах, заполняемых при оформлении технического задания, содержится гораздо больше различных типов данных, чем может быть размещено в единственной таблице решений при условии сохранения ее наглядности. В рабочих материалах может рассматриваться намного больше различных комбинаций условий, а также намного больше соответствующих им действий. Полную таблицу решений, представляющую более пяти или шести условий, уже довольно трудно изобразить. Эффективную же обработку таблицы с числом условий, намного превышающим 8 или 10, трудно выполнить даже с помощью ЭВМ. При числе условий, равном пу число правил составляет 2rt, т. е. число столбцов входа таблицы растет по экспоненциальному закону. При больших значениях п проверка вручную таблицы на избыточность и противоречивость представляет большие трудности. Таблица решений избыточна, если хотя бы два ее столбца перекрывают друг друга по значениям условий и определяют одни и те же действия (например, как в табл. 6.8,а). Таблица решений противоречива, если хотя бы два столбца ее перекрывают друг друга по значениям условий, но определяют различные действия (как, например, в табл. 6.8,6). Проверка условий таблицы, а также совмещение столбцов путем выявления несущественных условий, отмечаемых прочер- Таблица 6.8. Неправильно сооставленные таблицы решений а) Избыточная таблица б) Противоречивая таблица с, с2 л, Да - — Нет X X Да Нет X X U*
12 Глава 6 ками, обычно производятся в произвольном порядке, хотя какой-либо один порядок выполнения этих операций может иметь преимущество перед другими. В том случае, когда последовательность проверок условий должна подчиняться какому-либо порядку (например, если проверка конца файла производится ^обязательно перед проверкой карты, закрывающей файл, которая в свою очередь выполняется перед проверкой смены данных), легче использовать расширенную форму таблицы решений. Таблицу 4.2 можно было бы использовать совместно с таблицей, описывающей соответствующие действия для случаев, когда главный файл пуст или файл сообщений не отсортирован, а также с таблицами, описывающими проверку правильности записей и их обновление. Каждая таблица решений представляет конструкцию выбора, альтернативные ветви которой, возможно, представляют собой конструкции следования. Все четыре управляющие базовые конструкции могут быть описаны с помощью таблиц решений. .Для этой цели используются ссылки одних таблиц на другие и на самих себя. Следование определяется порядком перечисления действий в столбце действий. Логика всех таблиц решений в своей основе представляет конструкцию выбора. Цикл while do соответствует повторному использованию таблицы решений. Таблица, ссылающаяся на саму себя, может быть реализована в виде цикла или в форме рекурсивных обращений. Обработке исключительных состояний соответствуют определенные столбцы таблицы. Вызовы, возвраты, а также выходы из процедур представляются в форме явных действий. Для создания программного модуля, соответствующего таблице решений, последнюю сначала следует представить в форме дерева решений. Чтобы эффективно использовать таблицу решений, правила должны быть организованы таким образом, чтобы число проверяемых условий было минимальным. Существуют различные методы минимизации числа проверяемых на практике условий. Они отличаются друг от друга затратами на .их реализацию и применение какого-либо из них зависит от сложности преобразований таблицы решений и требований к эффективности проверки. Если условия таблицы являются независимыми и значения условий распределены в таблице неравномерно (например, по какому-либо условию ответов «да» больше, чем «нет»), в первую очередь должны проверяться условия, которые разбивают таблицу на части, по возможности одинаковые по размерам. Если для упрощения таблицы некоторые ее правила могут быть объединены, в первую очередь проверяются условия, соответствующие наименьшему числу прочерков, так жак они являются наиболее существенными для определения соответствующего правила. Таблица решения после проверки условия разделяется на две части, и процесс повторяется для
Проектирование модулей 213 Рис. 6.10, Таблица решений и соответствующие ей деревья решений. с — таблица решений; б — дерево решений для случая, когда в первую очередь проверяется условие С\\ в — дерево решений для случая, когда в первую очередь проверяется условие С2; г — дерево решений для случая, когда проверяется условие Сз. каждой половины (таким образом, заранее строится дерево проверок). Порядок проверки условий может влиять и на объем используемой памяти, и на время работы модуля, как это следует из примера, приведенного на рис. 6.10. Все три варианта дерева решений получены из единственной таблицы решений с помощью изменения порядка проверки условий. Сбалансированное дерево (рис. 6.10, г) получено из таблицы решений без выделенных
214 Глава 6 прочерками несущественных условий. Варианты дерева решений, показанные на рис. 6.10, бив, построены по упрощенной таблице решений. Для реализации последних двух вариантов требуется меньший объем памяти, чем в случае сбалансированного дерева, и выполнение проверок производится в среднем быстрее. 6.4. Документация модулей Документация модулей представляет собой документации самого нижнего уровня (если не считать уровня кодов программ) и используется в основном при сопровождении и модификации программ. На документацию модулей значительное влияние оказывает используемый язык программирования. Для модулей, которые реализуются в виде внутренних процедурно- стоящих из небольшого количества команд, требуется небольшой объем документации. Для модулей же, представляющих собой внешние процедуры, т. е. независимо компилируемые подпрограммы, требуется больший объем документации. Для программиста, занимающегося сопровождением программного обеспечения, наиболее важной является документация, в которой описываются отдельные подпрограммы, поэтому оформлению» документации этого уровня следует уделять большое внимание- Программная документация должна быть организована таким способом, чтобы документацию по отдельным модулям: можно было легко найти. Для этой цели могут служить оглавления, описания программ или иерархические схемы, а также индексные указатели и основные характеристики для отдельных модулей. Аналогичные требования предъявляются к документации для внутренних подпрограмм. Однако для этих подпрограмм в общем случае не требуется отдельная документация и может оказаться достаточной идентификация подпрограмм по» имени. Для более удобного оформления документации модули организуются определенным образом и, кроме того, для них составляются словари перекрестных ссылок с указанием назначения отдельных ссылок. Для каждого модуля достаточно подробно описывается логика его работы и указываются виды обработки исключительных состояний, чтобы программист, не принимавший участия в разработке модуля, мог модифицировать модуль и вносить корректировку. Каждая подпрограмма обозначается на иерархической схеме, а алгоритм ее работы описывается с помощью каких-либо средств проектирования высокого уровня, таких, как структурные схемы, псевдокод и т. д. Описание каждой подпрограммы должно быть функциональным и содержать следующие части: _
Проектирование модулей 215 Идентификатор модуля. Содержит номер идентификации и имя модуля и, кроме того, расширенное имя, поясняющее его значение. Основные функции модуля. Представляет краткое описание общего функционального назначения подпрограммы и цель ее создания. Этот раздел может включать функциональные описания подмодулей. Описание положения модуля в общей структуре. Содержит список подпрограмм, вызывающих модуль. Этот раздел входит как часть в словарь перекрестных ссылок. Пример вызова модуля. Поясняет порядок следования и число параметров. Для некоторых языков программирования необходимо также указать типы и способ передачи параметров. Список вызываемых подпрограмм. Представляет собой часть словаря перекрестных ссылок. Входные параметры и исходные данные. Этот раздел состоит из словаря внешних для модуля данных и словаря информации, используемой при операциях обмена. Здесь представлены типы всех данных, а также допустимые границы их значений. Кроме того, каждый из параметров классифицируется как входной или выходной и указываются входные или выходные условия, связанные с этими параметрами. Словарь внутренней информации. Содержит все флаги, результаты вычислений, таблицы и другие структуры данных и элементы, определяемые на этапе проектирования. Временные переменные, индексы, указатели и описатели, которые не могут бить определены на указанном этапе, в эти словари не включаются. Описание основных алгоритмов. Представляет собой документацию алгоритмов, рассмотренную в гл. 5. Описание внутренних процедур. Включает имена, функции и вызываемые последовательности всех внутренних процедур. Все виды доступа к глобальным данным должны быть представлены в этом разделе. Описание потоков данных и структуры модуля; структурная схема переходов управления в модуле. Все схемы, созданные в процессе разработки модуля, следует включать в состав документации. Среди них могут быть схемы HIPO для модуля и его подмодулей (если этот вид схем применялся при их разработке), схемы потоков данных, псевдокод, структурные схемы модуля, а также таблицы решений. Описание обработки ошибок. В этом разделе приводится перечень ошибок, которые могут быть обнаружены, а также указаны действия, предпринимаемые в случае их обнаружения. Данные об эффективности модуля. Содержат тесты для проверки модуля и результаты, получаемые с их помощью. Все данные по эффективности и сложности модуля приводятся в
216 Глава 6 этой части документации. Здесь же указываются все возможные ограничения на режимы использования модуля и на требуемый объем памяти. 6.5. Упражнения 1. Напишите программу печати данных из последовательного входного файла, если данные содержат одно управляющее поле, а печать должна быть организована так, что каждая новая группа записей начинается на новой странице, каждая из которых содержит не более п строк. Для этого используйте: а) структурные схемы; б) схемы Насси — Шнейдермана; в) таблицы решений. 2. С помощью структурных схем или схем Насси — Шнейдермана разработайте систему модулей, осуществляющих чтение из последовательного файла. Файл содержит группы по 51 записи, из которых 50 содержат код «S» и число, а 51-я состоит из кода «Т» и числа, представляющего сумму 50 чисел, содержащихся в предыдущих записях. Система проверяет наличие в каждой группе точно 50 записей типа «S», после которых следует одна^ запись типа «Т», допуская при этом, что в последней группе может содержаться меньше 50 записей типа «S». Если число записей группы (или сумма, находящаяся в последней записи) окажется неправильным, группа должна быть распечатана. 3. Разработайте систему модулей для решения упражнения 12 гл. 4 с помощью структурных схем или схем Насси — Шнейдермана. 4. Разработайте систему модулей для задачи расчета числа треугольников и прямоугольников из упражнения 15 гл. 5 с помощью структурных схем или схем Насси — Шнейдермана. 5. Разработайте систему модулей для решения задачи поиска пути для игры в китайские шашки на одного игрока из упражнения 14 гл. 5 с помощью структурных схем или схем Насси — Шнейдермана. 6. Сколько столбцов содержится в полной таблице решений с ограниченным входом, содержащей четыре (пять, шесть) условия? Если все действия отличаются друг от друга, сколько может существовать вариантов дерева решений для таблицы решений с четырьмя (пятью, шестью) условиями? 7. Составьте таблицу решений для определения правильности представления римских цифр и для их вычисления. 8. Разработайте таблицу решений для проверки данных. Данные содержат следующие поля: а) порядковый номер (число); б) имя (алфавитный код); в) код потребителя (цифры 1, 2, 3 или 4); г) месяц (цифры от 1 до 12); д) код поставки (буквы «D» или «W»); е) объем поставки (положительное число); ж) баланс (неотрицательное число, и, если код поставки равен «W», баланс не должен быть меньше объема поставки). Если какое-либо поле пропущено — фиксировать ошибку. Если код поставки неправилен, баланс не проверяется. Модуль устанавливает флаг правильности данных и печатает их и, если имели место ошибки, сообщения об ошибках. 9. Разработайте таблицу для сложения римских цифр, позволяющую получать сумму в виде правильной римской цифры, не пользуясь переводом чисел в другую систему счисления.
Глава 7 РЕАЛИЗАЦИЯ ПРОГРАММНОГО МОДУЛЯ Кодирование связано, с одной стороны, с грамматикой языка программирования, а с другой — со стилем написания программ. Если заданы язык программирования и проект модуля, последний должен быть адаптирован к смысловым конструкциям языка. Кроме того, надо стремиться к тому, чтобы проект был запрограммирован эффективно. Стилистика программирования в настоящее время достигла такого уровня, когда существуют критерии «хорошего» стиля написания сегментов исходного текста программы, так же как существуют критерии «хорошего» стиля написания разделов текста на естественном языке. Многие критерии, используемые для оценки текста на естественном языке, пригодны и для оценки программ. Человек, который пишет программы, должен уметь ясно выражать свои мысли, правильно писать слова, эффективно использовать интервал и быть последовательным в стиле написания. 7.1. Подходы к реализации Фазы проектирования и реализации программной системы могут перекрываться, если они выполняются одним и тем же методом. Когда разработка выполняется по нисходящему принципу, можно начать нисходящее кодирование и тестирование, как только завершается проектирование модуля. То же самое справедливо для разработки по восходящему принципу. Если же- проектирование системы полностью завершено, реализацию можно проводить любым из названных выше методов или двумя одновременно. 7.1.1. Нисходящее кодирование При реализации программы нисходящим методом первым кодируется головной модуль. Если он вызывает другие модули, кодируются фиктивные модули, называемые заглушками. С помощью заглушек можно организовать непосредственный возврат в вызывающий модуль или осуществить передачу значений тестовых данных и печать результатов работы. Если же заглуш-
218 Глава 7 ки имеют параметры, полезно организовать печать этих параметров. На рис. 7.1 представлена двухуровневая иерархическая структура, в которой первым кодируется головной модуль О, а заглушки 1 и 2 используются для его отладки. Поток данных между этими модулями представляет собой передачу корректирующей записи из модуля 1 в модуль 2 через модуль 0, передачу метки конца файла корректирующих записей из модуля 1 в модуль 0 и метки конца основного файла из модуля 2 в модуль 0. Последняя передача осуществляется при обнаружении конца основного файла до того, как будет обработана последняя корректирующая запись. Модули могут быть оттестированы с о I Модифи- I кация файла I 1 | 2 I Получение I корректирующей записи Рис. 7.1. Нисходящее кодирование. помощью заглушки 1, считывающей фиктивные записи, и заглушки 2, распечатывающей записи основного файла и обнаруживающей метку конца файла. Таким образом проверяется не только логика головного модуля, но также связи с другими модулями. После того как отлажен головной модуль, заглушки последовательно заменяются на функциональные модули, которые в свою очередь будут вызывающими для следующих заглушек. При завершении реализации программных модулей нижнего уровня иерархической структуры можно гарантировать, что из-за большого числа текстовых прогонов управляющие модули верхнего уровня практически не будут содержать ошибок. 7.1.2. Восходящее кодирование Когда программа проектируется по восходящему принципу, ее кодирование должно выполняться тем же методом. Первыми модулями, подлежащими кодированию, являются вспомогательные подпрограммы и подпрограммы ввода-вывода нижнего уровня. Поэтому в процессе создания всей программы модули, используемые чаще других, будут оттестированы наиболее тщательно. Если при нисходящем кодировании необходимо писать заглушки, вызываемые разрабатываемым модулем, при восходя- Модифика- ция записи основного файла
Реализация программного модуля 219 щем кодировании необходимо использовать драйверы. Драйвер для модуля ввода данных, имеющего параметры, может вызывать этот модуль, передавая ему множество различных значений параметров, а получив снова управление, распечатывать результаты ввода. Для модулей вывода данных драйвер может считывать тестовые данные и передавать их на входы этих модулей. Если завершена разработка нескольких модулей нижнего уровня, которые взаимодействуют с помощью программы вышележащего уровня, все эти модули могут быть оттестированы совместно общим драйвером. Только когда завершена разработка всех модулей, необходимых для вызывающего модуля, можно приступать к его кодированию. Если в работе над проектом планируется использовать нескольких программистов, при нисходящем кодировании они привлекаются к работе по мере развертывания иерархической схемы проекта сверху вниз. При восходящем кодировании после окончания разработки проекта все программисты могут начать работу одновременно, освобождаясь по мере объединения частей проекта. В процессе разработки по восходящему принципу значительное место во взаимодействии программистов занимает согласование связей между программами. Когда связи будут спроектированы, должна быть уверенность в том, что из виду не упущена ни одна деталь. 7.1.3. Смешанное кодирование Этот метод кодирования соответствует методу расширения ядра, используемому при проектировании программ. Когда проект ориентирован на использование структур данных, следует выделить две группы модулей, а именно модули нижнего уровня, в которых определены структуры данных и которые их обрабатывают, и управляющие модули верхнего уровня, которые координируют ввод, и вывод данных. Эти модули могут кодироваться одновременно в направлении к средним уровням. Вместо написания заглушек полезно иметь работающие подпрограммы ввода-вывода. Без этих подпрограмм можно было бы говорить о нисходящем кодировании. Этот подход можно также назвать подходом «с двух сторон одновременно». Правое поддерево иерархической схемы модуля реализуется легче, если уже было закодировано левое. В общем случае в левом поддереве заготавливаются данные, пригодные для использования в правом поддереве. Для кодирования слева направо (как сверху вниз, так и снизу вверх) требуется одно и то же число людей. Основная проблема состоит в том, что, начиная кодирование с двух концов, программисты могут не встретиться в середине. При таком подходе необходим строгий контроль над проектом.
220 Глава 7 7.2. Реализация данных На этапе проектирования модуля уже должны быть готовы описания большинства данных, используемых в каждом модуле. Для их реализации более предпочтительны простые представления данных. Большинство типов данных может храниться в виде скаляров, однородных одномерных массивов или записей. Информация, которая логически представляет собой множество, дерево, стек или какую-либо другую структуру, не поддерживаемую языком программирования, должна быть описана в отдельной группе модулей и скрыта для остальных. Тогда доступ к ним может быть организован так, как будто эти данные поддерживаются языком. Значения в зависимости от их применения можно классифицировать следующим образом: а) константы, б) системные константы, в) значения ссылок, г) значения времени выполнения, д) временные значения. 7.2.1. Константы Константы, системные константы и значения ссылок обрабатываются внутри модуля как постоянные величины. Они различаются только сроками использования. Истинными константами являются величины, значения которых никогда не меняются» Они могут быть литералами или поименованными значениями. Истинные константы должны иметь имена в том случае, если их распознавание легче осуществить по имени, чем по значению' (например, имя PI более понятно, чем значение 3.145926). В том случае, если истинные константы передаются в качестве аргументов в другие подпрограммы, они также должны иметь имена» Рассмотрим пример INTEGER TEN (Пример 7.1> DATA TEN/10/ CALL GET (LIST, TEN) Если подпрограмма GET изменяет передаваемый ей второй параметр, это может быть обнаружено в вызывающей подпрограмме путем проверки значения TEN. Если был передан литерал 10,. обнаружить ошибку трудно. Имена констант должны соответствовать их значениям, как, например ZERO для 0, ONE для 1У FALSE для «FALSE», T для «Т», причем в той степени, насколько позволяет используемый язык. Если константа имеет определенный смысл (например, в случаях обозначения размера массива, идентификации внешнего устройства или выбора
Реализация программного модуля 221 сообщения об ошибке), ей необходимо присвоить имя, соответствующее ее использованию в качестве системной константы или значения ссылки. Системные константы определяются характеристиками вычислительной среды. Системными константами могут быть внутренние коды символов, размер машинного слова и характеристики внешних устройств, такие, как размер дорожки и длина строки печатающего устройства. Этим константам должны быть присвоены мнемонические имена, которые помещаются в отдельный модуль и обрабатываются как описания абстрактных данных. Их значения изменяются только с заменой самих устройств. Имена должны быть присвоены даже таким системным константам, как номер логического устройства для АЦПУ и устройства чтения с перфокарт. Использование литеральных констант делает программу менее мобильной, поскольку номера логических устройств в разных системах различны. Кроме того, смысл литералов часто бывает неясен. НЕ ЗАБЫВАЙТЕ ПРИСВАИВАТЬ НАЧАЛЬНЫЕ ЗНАЧЕНИЯ ПОИМЕНОВАННЫМ КОНСТАНТАМ Данными типа значения ссылок являются налоговые таблицы, коды категорий клиентов и списки цен, которые изменяются редко. Если ожидается, что эти данные будут оставаться неизменными в течение большого числа рабочих прогонов программы, они могут использоваться в программе как имена с ранее присвоенными начальными значениями. Их следует считать абстрактным описанием данных пользовательской среды и определять только в одном программном модуле. Если же данные все же меняются от прогона к прогону, значения ссылок следует считывать в память в процессе каждого выполнения программы. Список цен на бакалейные товары, изменяемый ежедневно, может храниться на диске и считываться с него. Число дней в. феврале меняется каждые четыре года, поэтому оно определяется системой только один раз. Если значения ссылок не считыва- .ются в память, им присваиваются начальные значения с помощью оператора DATA (в Фортране), фразы VALUE (в Коболе) или атрибута INITIAL (в ПЛ/1). В таких языках, как Паскаль и Ада, они должны быть объявлены константами. Если в модуле содержится много скалярных величин, их можно объединить в группы в соответствии с правилами использования (как в Коболе): 01 STATUS-FLAGS. 05 CARD-EOF 05 DISK-EOF 05 INVALID-DATA PIC X. PIC X. PIC X. (Пример 7.2),
222 Глава 7 01 TRANSACTION-TOTALS. 05 TOTAL-DEPOSIT PIC 9(7)V99. 05 TOTAL-WITHDRAWAL PIC 9(7)V99. 05 TOTAL-SERVICE PIC 9V99. Однако это возможно и в языках (подобных Фортрану), которые не имеют средств описания структур: <€ —STATUS FLAGS (Пример 7.3) LOGICAL CRDEOF, — DSKEOF, — BADCRD С TRANSACTION TOTALS REAL TOTDPT, — TOTWDL, — TOTSC В Коболе уровень 01 вводится лишь для организации иерархии данных и целей документирования. В Фортране для тех же целей используются комментарии и пунктирные линии. В языках Паскаль и ПЛ/1 структура — обычный способ описания данных. Аналогичным образом можно сгруппировать нескалярные элементы данных. ГРУППИРУЙТЕ ДАННЫЕ ПО ФУНКЦИОНАЛЬНЫМ ПРИЗНАКАМ Связанные по смыслу списки значений, такие, как максимальные и минимальные значения или коэффициенты в таблице, можно хранить в двумерном массиве, но разумнее размещать их в одномерных массивах равной длины. Данные, различные по природе, не следует помещать в один массив. Использование структур позволяет хранить неоднородные данные в одном массиве, однако работать с такими массивами труднее, чем с массивами, содержащими однородные как по смыслу, так -и по типу и размеру элементы. Мнемонические имена для массивов выбирают так, чтобы они давали представление о типе хранящегося элемента. Не следует присваивать таких имен, как ARRAY или TABLE, которые характеризуют только форму хранения данных. Если размер таблицы коэффициентов равен 4, но он может изменяться, следует объявить РАЗМЕР-ТАБЛИЦЫ-КОЭФФИ- ЦИЕНТОВ как константу, которая должна использоваться всякий раз, когда размер упоминается в модуле, например INTEGER TBLSIZ (Пример 7.4) DATA TBLSIZ/4/ DIMENSION RATE (TBLSIZ) Такое описание следует включить в модуль, который содержит табличные значения. В этом случае изменения вносятся не бо- -лее чем в два места программы: в описание табличных значений и в описание размерности самой таблицы. Кроме того, не потребуется вносить изменения в управляющие операторы цик-
f Реализация программного модуля 22$ ла и в операторы проверки границ индекса. Если таблица передается в другие модули, следует применять описание ее текущей размерности. При таком подходе не только информация о данных размещена в возможно меньшем количестве модулей,, но и уменьшается количество описаний и инструкций, что облегчает сопровождение. используйте средства для описания текущей размерности данных 7.2.2. Переменные Переменная однозначно определяется именем. Значения переменных связаны с ними не более чем на период одной активации программы. Следует различать временные значения и значения времени выполнения. Такое различение необходимо* главным образом для целей документирования программы. Переменные, которые важны для выполнения функций данного модуля и используются большинством его операторов, следует включить в словарь данных и присвоить им соответствующие мнемонические имена. Временные переменные, к которым обращаются лишь в процессе выполнения нескольких операторов программы (такие, как управляющие переменные циклов, индексы и результаты промежуточных операций), могут быть включены в словарь данных по желанию программиста. Временные переменные и переменные времени выполнения должны всегда обрабатываться как переменные, а не как константы. Для их инициализации следует использовать выполняемые операторы. Не следует заранее инициализировать такие элементы,, как суммы и счетчики, которые впоследствии изменяются. НЕ ИНИЦИАЛИЗИРУЙТЕ ПЕРЕМЕННЫЕ ЗАРАНЕЕ Если необходимо установить начальное значение таким элементам, как генератор случайных чисел или флаг, указывающим состояние файла (открыт или закрыт), — и это не может быть сделано с помощью выполняемого оператора — соответствующее значение должно быть передано как параметр. Если: язык программирования позволяет продолжать выполнение программы после выявления ошибок ввода-вывода (таких, как попытка чтения неоткрытого файла), процесс восстановления после ошибки предпочтительнее, чем использование состояния флага. Передача флага инициализации приводит к размещению данных в одной подпрограмме, в то время как они логически относятся к другой. Следует избегать применения флагов, показывающих, является ли текущая активация подпрограммы: первой. Значения флагов должны сохраняться во время многократных выполнений программы, иначе программа не будет ре-
"224 Глава 7 'ентерабельной и повторно используемой. Использование флагов также нежелательно при применении оверлейных структур. ПИШИТЕ ПРОГРАММЫ, КОТОРЫЕ ДОПУСКАЮТ ОДНОВРЕМЕННОЕ МНОГОКРАТНОЕ ВЫПОЛНЕНИЕ Все переменные и поименованные константы должны иметь мнемонические имена, которые отражали бы их смысл. Имена должны записываться аккуратно и не должны быть слишком похожими друг на друга. Не используйте такие имена, как PRNTER и PRINTR. Поскольку они очень похожи, их легко спутать, а также трудно вспомнить, какое из них использовалось. Наличие связи между переменными можно показать с помощью их имен. Если.буфер ввода-вывода имеет имя BUF, связанным с буфером переменным могут быть присвоены имена BUFPTR и BUFSIZ. Функционально связанные переменные должны иметь похожие имена. Такими именами в случае просмотра нескольких массивов могут быть BUFPTR, CRDPTR и TBLPTR, а также BUFSIZ, CRDSIZ и TBLSIZ. Следует избегать имен, отличающихся только последним символом (как, например, PTRB, PTRC и PTRT). БУДЬТЕ ВНИМАТЕЛЬНЫ ПРИ ВЫБОРЕ ИМЕН В том случае, когда временные переменные не включены в словарь данных, необходимо использовать общепринятые обозначения, если они существуют; например, лучше использовать A(i, /), чем A(j, t), и Л*Х**2 + £*Х+С, чем Х*Л**2+Y*A + Z. Все имена данных, включая имена временных переменных, должны быть явно отнесены к определенному типу, даже если язык не требует этого. Назначение атрибутов по умолчанию приводит к появлению странных имен данных, таких, как ITALLY. В языке ПЛ/1 это может привести к неожиданным результатам. Использование в одних случаях объявления по умолчанию, а в других явного объявления атрибутов, стилистически непоследовательно и затрудняет обнаружение ошибок. Если всем переменным явно назначены атрибуты и даны соответствующие комментарии, они играют роль словаря данных в листинге исходного текста программы. ОБЪЯВЛЯЙТЕ ТИПЫ ДАННЫХ В ЯВНОЙ ФОРМЕ 7.3. Реализация ввода-вывода Модули могут вводить и выводить информацию двумя способами. В первом способе данные передаются с помощью межмодульного интерфейса и общих областей пам'яти, а во втором — от модулей к внешним устройствам и наоборот.
Реализация программного модуля 225 7.3.1. Обработка входных данных Необходимо всегда проверять достоверность данных, поступающих от внешних устройств. Не доверяйте пользователю или аппаратуре. ПРИ ПРОГРАММИРОВАНИИ ПРЕДУСМАТРИВАЙТЕ ЗАЩИТУ ОТ НЕПРАВИЛЬНЫХ ДАННЫХ Данные, поступающие из других подпрограмм и внутренних областей хранения данных, должны быть проверены модулем, который их использует. Не доверяйте другим программистам. Не доверяйте даже себе. Необходимо проверять тип данных, пропуск полей, знак и принадлежность к известному диапазону значений. Если в данных содержится избыточность (такая, как одновременное присутствие номера счета и фамилии клиента или кода выпуска и описания выпуска), следует проанализировать таблицу перекрестных ссылок, чтобы убедиться, соответствуют ли друг другу поступающие элементы. Если в данных содержатся счетчики записей, контрольные цифры, контрольные суммы и другие средства контроля, их следует использовать для проверки данных. Счетчик записей представляет собой число, равное количеству ожидаемых записей. Не используйте такой счетчик для управления обработкой записей. Это может привести к ошибке. Вместо этого сосчитайте количество обработанных записей, сравните результат со значением счетчика записей и запишите отклонение. Контрольная цифра — это цифра, присоединяемая к некоторому важному значению, такому, как номер счета. В результате формируется число, обладающее определенным математическим свойством. Контрольная цифра используется для проверки числа с целью обнаружения ошибок перезаписи. Например, можно присоединить к числу такую контрольную цифру, что получившееся число будет кратно 11. Контрольная цифра может быть подобрана так, чтобы отразить все или чередующиеся цифры числа. Лучшие методы, основанные на использовании контрольных цифр, выявляют не только неверные цифры, но и ошибки в порядке их следования. Контрольная сумма представляет собой сумму чисел, находящихся в определенном поле некоторого множества записей. Например, можно сложить номера счетов в каждой группе, состоящей из 50 корректирующих записей. Результат сложения помещается в 51-ю запись. Это не относится к решению задачи, но может быть использовано для обнаружения ошибок. Подобные суммы могут быть вычислены для всех чисел в записи и помещены в нее. Как контрольные цифры, так и контрольные суммы часто используются для двоичных данных. 15-399
226 Глава 7 Все внешние входные данные должны попасть на экран терминала, устройство печати или в регистрационный файл. Следует отмечать неправдоподобные значения, такие, как цены в 1 000 000 долл. При программировании ввода с терминального устройства необходимо предусмотреть подтверждение правильности ввода важных и неправдоподобных входных данных. Ошибки при передаче данных между модулями в основном возникают из-за различий в атрибутах данных. Эти различия обычно не могут быть обнаружены в результате просмотра исходного текста. Большинство из них обнаруживается при отладке модулей путем проверки принадлежности входных данных допустимому диапазону значений. Если при вызове модулю передается действительный аргумент, в то время как он должен быть целым, или наоборот, это, вероятно, будет обнаружено с помощью проверки диапазона значений. Каждый модуль должен быть написан так, чтобы при его отладке могла быть выполнена проверка принадлежности входных данных допустимому диапазону значений. Операторы проверки входных и выходных данных должны постоянно присутствовать в модулях, поскольку даже при наличии правильной программной связи между модулями существует возможность выхода за пределы диапазона значений вновь созданных или переданных данных. Если подпрограмма контролирует выход за пределы диапазона возможных значений, это должно найти отражение в документации. ПРОВЕРЯЙТЕ ДОСТОВЕРНОСТЬ ДАННЫХ 7.3.2. Обработка выходных данных Выходные данные перед передачей внешнему устройству должны быть проверены. Для проверки данных с преобладанием цифр используются такие методы контроля, как промежуточное и перекрестное суммирование. Для всех видов вывода должна собираться статистика прогонов. Вычисление общей суммы выполняется отдельно от вычислений любых промежуточных сумм. Результат сложения промежуточных сумм в этом случае можно сравнить с общей суммой с целью обнаружения переполнения или ошибок в логике программы. Если при обработке промежуточных сумм не возникает прерываний, эти суммы можно генерировать обычным путем для целей внутреннего контроля. ДУБЛИРУЙТЕ ПРОВЕРКУ ОТВЕТОВ Перекрестное суммирование представляет собой метод, при котором суммирование проводится по строкам и столбцам, после чего складываются суммы строк и столбцов и полученные результаты сравниваются между собой с целью выявления оши-
Реализация программного модуля 227 бок. С точки зрения самих данных эти суммы могут иметь .(или не иметь) смысл. Они могут быть (или не быть) частью выходных данных. Если значение счетчика записей известно, его можно использовать для проверки правильности числа отпечатанных заполненных строк. Если значение числа записей неизвестно и вывод осуществляется на запоминающее устройство типа магнитного диска или магнитной ленты, необходимо подсчитать число записей и поместить его в первую запись вместе с другой информацией о файле. Необходимо также проверять достоверность выходных данных и помечать маловероятные значения. Результаты проверки таких значений, как 1 000 000 долл., помечаются вручную. 7.3.3. Редактирование Важно, чтобы значения данных имели форму, удобную для восприятия, когда они обрабатываются людьми, и машинно-ориентированную форму, когда они обрабатываются машиной. Редактирование текста может заключаться в перемещении символов пунктуации, пробелов и разделителей. Не перекладывайте черновую работу на пользователей. Пусть редактирует ЭВМ. ПИШИТЕ ПРОГРАММУ ТАК, ЧТОБЫ ЧЕЛОВЕКУ УДОБНО БЫЛО ЕЮ ПОЛЬЗОВАТЬСЯ Желательно, чтобы данные вводились поименно в свободном формате, как это делается с помощью оператора GET DATA в языке ПЛ/1. Этот оператор идентифицирует данные по имени, а не по занимаемой позиции или порядку следования во входной записи. Такой ввод удобен для восприятия и позволяет избежать ошибок в размещении данных. Если это возможно, разрешайте вводить числа с десятичной точкой и стоящими слева пробелами. Используйте значения, принятые по умолчанию. Пользователь, корректирующий файл, не должен повторно вводить информацию, которая не изменяется. Элементы входных данных должны быть отредактированы и проверены на достоверность сразу же после того, как они получены. Значения выходных данных должны быть также проверены на достоверность и отредактированы непосредственно перед передачей их устройству вывода. Редактирование выходных данных должно включать присваивание меток ответам, выравнивание и разделение элементов данных и печать чисел без стоящих слева нулей, но со знаками, десятичными точками и при необходимости со знаками доллара. Для обработки и хранения входных данных в машинно-ориентированной форме их не нужно ни редактировать, ни приводить к определенным формам. В целях эффективной обработки данных, расположенных на диске и ленте, следует использовать двоичный ввод-вывод. 15*
228 Глава 7 7.4. Реализация управления Программные модули, спроектированные с использованием базовых управляющих структур, обычно необходимо в той или иной мере модифицировать, чтобы они соответствовали структурам, поддерживаемым языком программирования. При этом должны использоваться только стандартные средства языка. 7.4.1. Модификация управляющих структур Использование языка программирования с блочной структурой, такого, как Паскаль или ПЛ/1, потребует небольшой модификации проекта; использование Кобола приведет к несколько большей модификации проекта, а использование Бейсика и Фортрана, в которых отсутствуют многие управляющие структуры,— к весьма существенной модификации. Фортран не поддерживает никаких базовых структур, за исключением следования. Базовые управляющие структуры, предусмотренные в псевдокоде, можно смоделировать на любом языке, снабдив их комментариями. В Фортране структуры выбора IF(C) GO TO 10 GO TO 20 THEN 10 action GO TO 30 ELSE 20 action GO TO 30 30 CONTINUE (Пример 7.5) CASE OF С IF(C1)GOTO10 IF(C2)GOTO20 (Пример 7.6) 10 20 GO TO 90 WHENC1 action 1 GO TO 100 WHENC2 action 2 GOTO 100
Реализация программного модуля 229 С OTHERWISE 90 default action GOTO 100 С ENDCASE 100 CONTINUE и структуры повторения WHILE С DO (Пример 7.7) 10 IF(.NOT.C)GOTO20 action GOTOJ0 20 CONTINUE и С REPEAT (Пример 7.8) 10 action IF(.NOT. C) GO TO 10 С UNTIL С требуют только абзацного отступа и нескольких карт комментариев. На Коболе также можно описать управляющие структуры. Оператор PERFORM может использоваться для реализации обеих базовых структур повторения, но при этом управление циклом отделяется от тела цикла. Цикл while do принимает форму PERFORM A UNTIL NOT С. (Пример 7.9) Цикл repeat until принимает форму PERFORM A. (Пример 7.10) PERFORM A UNTIL С. Оба этих цикла неэффективны при реализации на ЭВМ с виртуальной памятью, если тело цикла и управление циклом размещены на разных страницах. Если об этом известно, можно вставить дополнительный параграф: PERFORM WHILE-C-DO-A. (Пример 7.11) WHILE-C-DO-A. PERFORM A UNTIL NOT С. совмещающий управление циклом и тело цикла. В Коболе отсутствуют операторные скобки и разделители для групп вложенных операторов, поэтому невозможно реализовать 16-399
230 Глава 7 вложенные структуры выбора (типа . приведенных на рис. 7.2) без модификации. Схема на рис. 7.2,а может быть реализована, если использовать вложенные структуры выбора вида IFC1 (Пример 7.12) THEN PERFORM TEST-C2 A3 ELSE A4 или если несколько раз по* вторить блок A3, IFC1 THEN IF C2 THEN Al, A3 ELSE A2, A3 ELSEA4. (Пример 7.13) Средства, предусмотренные в Коболе для обработки исключительных состояний с помощью условных выражений, делают невозможным использование составных операторов для непосредственной реализации структур типа приведенной на рис. 7.2,6, поскольку один из операторов (но не последний) содержит переход при появлении исключительной ситуации. Изменение порядка следования операторов иногда позволяет устранить это затруднение. В противном случае вводится флаг и проверяется его значение: Рис. 7.2. Вложенные структуры. а — вложенные операторы IF; б — вложенные операторы обработки исключительных состояний. IF NOT EOF READCARD-FILE AT END DISPLAY "DONE" MOVE "YES" TO EOF-FLAG. IF NOT EOF WRITE PRINT-LINE FROM CARD RECORD. (Пример 7Л4]
Реализация программного модуля 231 Необходимо проявлять осторожность при использовании составных условных выражений для управления циклом или для выбора альтернатив. Некоторые реализации языка при обработке выражения if CI and С2 не проверяют значение С2, если значение С1 окажется ложным. В других реализациях значение С2 всегда проверяется. Подобным образом в ряде систем при обработке условия if С1 or C2 проверка заканчивается, если первое условие истинно. В некоторых языках не определен даже точный порядок вычисления булевых выражений. Когда важен порядок вычислений или нежелательно проводить проверку всех условий, не следует применять составные условные выражения. Аналогично, если условные выражения слишком сложны, их надо записать в более понятной форме. Так, например, условный оператор if CI and C2, then P эквивалентен ifCl (Пример 7.15) then if C2 thenP а оператор if CI or C2, then P эквивалентен ifCl (Пример 7.16) thenP «else if C2 then P. Если язык допускает использование имен булевых переменных вместо условных выражений, это позволяет часто значительно лучше определить смысл условия. Такой условный оператор, как IF END-OF-FILE, лучше, чем IF EOF-FLAG=«Y», a оператор IF (DIGIT (CHAR)) лучше, чем IF <CHAR.GE.«0».AND.CHAR.LE.«9»). у СТАРАЙТЕСЬ, ЧТОБЫ СМЫСЛ УСЛОВИИ БЫЛ ПОНЯТЕН В гл. 6 речь шла о преобразовании таблиц решений в деревья. Если имеются таблицы решений со ссылками к самим себе, возможно, желательно преобразовать их сначала в стандартные циклические конструкции. Отметим, что таблицы реше- Таблица 7.1. Таблица решений а) Итеративная б) Рекурсивная Таблица 1 Сх €2 А{ А2 Обратиться к таблице 1 Выход Закрыто Да Да X X Да Нет X X Нет Да X Нет Нет X Таблица 2 Сх с2 л, А2 Обратиться к таблице 2 Выход Закрыто Да Да X X Да Нет X X Нет Да X Нет Нет X 16»
232 Глава 7 ний предполагают всегда сначала принятие решения и только потом выполнение действия. Поэтому табл. 7.1, а, которая предусматривает повторение действий до тех пор, пока не будут удовлетворены условия выхода, имеет форму конструкции while do (рис. 7.3,а). Таблица решений 7.1,6 вызывает саму себя, а не возвращается к начальной точке. Поэтому она имеет форму рекурсивной подпрограммы (рис. 7.3,6). В случае отсутствия ( Начало j <Г Cf or С/ \Да ci\ сг\ А'\ А*\ 1 Да Да Да Нет 1 Х X Нет ( Начало Л -^Н Да Да Да Нет Рис. 7.3. Циклические конструкции для табл. 7.1. a — итеративная; б — рекурсивная. передаваемых параметров и возвращаемых значений эта таблица может быть преобразована непосредственно в итеративную форму. 7.4.2. Устранение рекурсии Если язык не поддерживает рекурсию, необходимо преобразовать рекурсивные алгоритмы в итеративные. Некоторые алгоритмы гораздо проще могут быть описаны рекурсивно, даже если они должны быть реализованы итеративно. Метод преобразования зависит от того, выполняются ли вычисления во время рекурсивного спуска или возврата, используется ли однократная или многократная рекурсия и являются ли функции,, предназначенные для вычисления параметров, обратимыми. Рекурсивные определения данных можно классифицировать в зависимости от степени сложности рекурсивной части описания. Однократной рекурсией является рекурсия не более чем с одним рекурсивным вызовом в каком-либо одном выражении. Она
Реализация программного модуля 233 имеет вид '<И"™,тдл'"'м,\ <71> (c(f(b(x))) для ~р(х). Два примера однократной рекурсии приведены ниже: GCD (п, т) = ( ™ для п = О, (П ? 17) 1 GCD (m, rem (n, т)) для п > 0. factorial (n) = ( * ДЛЯ П = °' (Пример 7.18) In* factorial(n—1) для n>0. В примере 7.17 вычисления выполняются во время рекурсивного спуска, так как функция вычисления остатка вызывается перед рекурсивным вызовом функции GCD. В примере 7.18 вычисления выполняются во время рекурсивного возврата1). Рекурсивные программы могут,, конечно, содержать вычисления, выполняемые в обоих направлениях. Итеративная форма примера 7.17 имеет вид procedure GCD(n,m) (Пример 7.19) while n ~1= 0 do k := rem(m,n); m := n; n :=k end; return(m) end. и заменяет проверку окончания и рекурсию на цикл while do, в теле которого производится модификация параметров. Если вычисления выполняются во время рекурсивного возврата, рекурсию можно заменить простым итеративным циклом лишь тогда, когда изменения параметров являются обратимыми: procedure factorial(n); (Пример 7.20) k:=0 fact := 1; while k < n do k := k + 1; fact: = n * fact end; return (fact) end. *> В отечественной литературе термины «рекурсивный спуск» и «рекурсивный возврат» не используются. — Прим. перев.
234 Глава 7 Здесь k играет роль рекурсивного параметра, возрастающего от О до некоторого значения /г. Если вычисления выполняются во время рекурсивного спуска или возврата, итеративная форма содержит два цикла. Если изменения параметров не являются обратимыми, как, например, в процедуре печати связанного списка в направлении снизу .вверх procedure print_Jist(link); (Пример 7.21) if link = null return else call print_list(next_link); print(info); end. в итеративном варианте процедуры для построения стека параметров и очистки его используются два цикла: procedure print Jist(link); (Пример 7.22) while link П= null do push (info,stack); link := next—link end; while "I stack-empty do info := pop(stack); print(info) end end. Многократной рекурсией называется рекурсия с несколькими рекурсивными вызовами, например (Пример 7.23) ( "found" for node.key = item search (tree) = \ search (left_jsubtree) for node.key Ф item V search(right_jsubtree) for node.key + item Приведенный выше алгоритм осуществляет поиск по неотсортированному двоичному дереву заданного символа. Если однократную рекурсию можно считать спуском по связанному списку функциональных активаций с последующим возвратом, многократную рекурсию можно считать анализом связанного дерева активаций. Преобразование любой многократной рекурсии в итеративную форму требует реализации поиска по дереву.
Реализация программного модуля 235 Стек требуется не только для локальных переменных и параметров, но должен показывать также направление, выбранное для каждого ветвления, чтобы при возврате можно было выбрать другое направление. Для поиска в лабиринте или для другой задачи, где дерево поиска имеет степень ветвления больше двух, стек должен содержать информацию о всех предыдущих путях для каждой вершины. 7.4.3. Программирование без оператора GO ТО Структурное программирование часто рассматривается как программирование без операторов GOTO. Очевидно, что такое программирование невозможно на неструктурированных языках. Тем не менее даже в Фортране можно ограничить применение операторов GO TO при реализации базовых программных структур. На Коболе, в языке ПЛ/1 и в других более структурированных языках можно писать программы, не содержащие операторов GOTO. Уменьшение числа операторов GOTO улучшает программу, однако их полное устранение может привести к тому, что из-за введения дополнительных флагов программа становится сложнее для восприятия. Наибольшая трудность при исключении операторов GO TO возникает при обработке исключительных ситуаций и реализации нескольких выходов из цикла. Способы обработки таких ситуаций определяются в зависимости от применяемого языка программирования. В случае отсутствия оператора EXIT и необходимости устранения операторов GO TO в теле цикла должны быть установлены и проверены флаги. Фрагмент программы на языке Кобол, приведенный в примере 7.24, содержит несколько выходов, проверку правильности данных и обработку исключительной ситуации; все эти действия требуют установки флагов: (Пример 7.24) PERFORM PROCESS-CARDS THRU PROCESS-CARDS-END UNTIL EOF. PROCESS-CARDS. READ CARD-FILE AT END MOVE "YES" TO EOF-FLAG GO TO PROCESS-CARDS-END. IF CARD-VALUE IS NOT NUMERIC GO TO PROCESS-CARDS-END.
236 Глава 7 ADD CARD-VALUE TO TOTAL ON SIZE ERROR WRITE ERROR-LINE GO TO PROCESS-CARDS-END. PERFORM WRITE-DETAIL-LINE. PROCESS-CARDS-END. EXIT. Использование специального оператора чтения позволяет удалить один из операторов GOTO. Другие операторы GOTO можно устранить, используя флаги и дополнительные параграфы и изменяя условия в операторе IF. Если изменить структуру параграфа, используя метод анализа исток — преобразователь — сток (И—П—С), можно получить PERFORM PROCESS-CARDS UNTIL EOF. (Пример 7.25) PROCESS-CARDS. . MOVE "GOOD" TO ANSWER-STATUS. MOVE "GOOD" TO t>ATA-STATUS. PERFORM GET-DATA UNTIL VALID-DATA OR EOF. IF VALID-DATA ADD CARD-VALUE TO TOTAL ON SIZE ERROR MOVE "BAD" TO ANSWER-STATUS WRITE ERROR-LINE. IF VALID-ANSWERS PERFORM WRITE-DETAIL-LINE. GET-DATA. READ CARD-FILE AT END MOVE "YES" TO EOF-FLAG. IF NOT EOF IF CARD-VALUE IS NOT NUMERIC MOVE "BAD" TO DATA-STATUS. Выбор между двумя рассмотренными реализациями зависит от стиля программирования и привычки. Самый простой стиль является безусловно наилучшим. Если операторы GO TO применяются в таких языках, как Кобол, их использование следует ограничить передачей управления на небольшие расстояния в прямом направлении к концу параграфа или секции. ПРИМЕНЯЙТЕ КАК МОЖНО МЕНЬШЕ ОПЕРАТОРОВ GO TO
Реализация программного модуля 237 7.4.4. Вызовы и возвраты Небрежное использование вызовов и возвратов модулей может привести к определенным трудностям. Если подпрограммы компилируются раздельно, при выполнении вызова обычно не предусматривается автоматическая проверка атрибутов. Невы- явленные ошибки могут возникнуть при задании неверного числа параметров, несоответствии типов или использовании параметров для вывода. Параметры могут передаваться по имени, значению, ссылке или значению-результату. Если используется вызов по имени, значения аргументов не анализируются до тех пор, пока не будут использованы в вызванной подпрограмме. Такой вызов неэффективен для передачи выражений. Если используется вызов по значению, передается только значение каждого аргумента. Такой вызов не позволяет использовать параметры для присваивания им значений, защищая, таким образом, аргументы от неумышленного изменения. Этот вызов неэффективен для передачи структур данных, так как последние должны копироваться. При использовании вызова по ссылке передается адрес области памяти каждого аргумента. При вызове по значению-результату значение аргумента передается на вход подпрограммы и возвращается на ее выход (возможно, в модифицированной форме). Если рассматривать действительное значение как целое в Фортране или арифметическое значение как данное для вывода в Коболе, могут, очевидно, возникнуть проблемы, поскольку внутреннее представление данных будет отличаться от ожидаемого. Трудности возникают также из-за несоответствия других атрибутов. Структуру массива нельзя скрыть в некоторых реализациях Фортрана с помощью передачи структуры через промежуточные модули как скаляра. Скаляры и массивы могут обрабатываться по-разному, причем скаляры передаются как значение-результат, а массивы — с помощью ссылки. При использовании стандартного компилятора ПЛ/1 константа может быть защищена от непредусмотренного изменения с помощью передачи ее в качестве выражения с дополнительной парой скобок. CALLP(3) является вызовом по ссылке, в то время как CALL Р ((3)) — вызовом по значению. В действительности оба вызова являются вызовами по ссылке, однако в случае с дополнительными скобками создается фиктивная область памяти для размещения значения. Такой прием непригоден при использовании оптимизирующего компилятора ПЛ/1. БУДЬТЕ ВНИМАТЕЛЬНЫ ПРИ ПЕРЕДАЧЕ ПАРАМЕТРОВ При описании интерфейсов модулей необходимо указывать, какие параметры используются в качестве входных, выходных и одновременно входных и выходных. Это реализуется при про-
238 Глава 7 граммировании на языке Ада. Например, программист может записать SUB (X in; Y out; Z in out). В языке Паскаль вызовы по значению тех переменных, которые используются лишь в качестве входных [например, SUB (X: integer)], отличаются от вызовов по имени тех переменных, которые используются в качестве входных и выходных [например, SUB (varX: integer)]. Неправильное применение аргументов, например обработка константы или выражения как выходного параметра, обычно не обнаруживается компилятором. Наилучший способ избежать таких ошибок заключается в выборе другого имени для каждого значения, если язык не поддерживает вызов параметров по значению. Независимо от используемого способа передачи параметров возникают проблемы с неоднозначностью имен, которая появляется, когда доступ к значению некоторой переменной возможен по двум именам и когда переменная используется в качестве двух аргументов или является, аргументом и глобальной переменной одновременно. В каждом из приведенных ниже примеров имена А и X принадлежат одной и той же переменной: COMMON X, Y, Z (Пример 7.26) CALL S (X) SUBROUTINE S (A) COMMON X,Y,Z CALL SUB (X, X) (Пример 7.27) SUBROUTINE SUB (A, X) varXrreal; (Пример 7.28) procedure P (A: real); P (X)' Если какое-либо одно из двух имен, относящихся к одной и той же переменной, изменяется, воздействие этого изменения на другое имя зависит от реализации и может привести к непредвиденным результатам. НЕ ИСПОЛЬЗУЙТЕ НЕСКОЛЬКИХ ИМЕН ДЛЯ ОДНОЙ ПЕРЕМЕННОЙ Ссылки на глобальные переменные являются возможным источником ошибок даже тогда, когда неоднозначность имен устранена. Использование таких ссылок приводит к появлению связи между подпрограммами через общие области. СТАРАЙТЕСЬ НЕ ПРИМЕНЯТЬ ГЛОБАЛЬНЫХ ПЕРЕМЕННЫХ Функция, которая изменяет какие-либо глобальные переменные, параметры или выполняет операции ввода-вывода, называется нестандартной. Стандартная функция выполняет только
m Реализация программного модуля 239 одну вещь: возвращает значение в точку вызова. Поскольку вызов функции может содержаться в некотором выражении, где значение этой функции используется, может возникнуть недоразумение, когда пользователь обнаружит еще какие-то изменения, а не только возврат значения в точку вызова. Поэтому не следует применять нестандартные функции, хотя иногда их можно использовать для печати сообщения об ошибке или для возврата флага ошибки. Если модуль выполняет ввод-вывод, либо обращаясь к внешним устройствам, либо с помощью параметров, его следует вызывать как подпрограмму, а не как функцию. СТАРАЙТЕСЬ НЕ ПРИМЕНЯТЬ НЕСТАНДАРТНЫЕ ФУНКЦИИ Существуют два типа параметров, которые следует использовать во всех вызовах в любых программных системах: флаги ошибок и флаги отладки. Флаги ошибок являются выходными параметрами, используемыми для сигнализации об ошибках. Поскольку ошибки обычно обрабатываются на самом низком уровне, можно избежать возвращения кодов ошибок. Простого булева флага достаточно для сигнализации о появлении ошибки, приводящей к прерыванию работы программы. Для получения большей информации можно применять булевы флаги с такими мнемоническими именами, как EOF, INVALID и NULL. Они гораздо проще в работе, чем коды ошибок. Эти флаги также обеспечивают большую независимость модуля. Флаги отладки являются входными переменными, которые используются для получения различных дампов памяти и проведения трассировки. Они применяются в процессе отладки всей системы и устанавливаются с помощью тестовых данных или управляющих параметров. В частности, полезно завести флаг отладки, с помощью которого можно управлять печатью имен подпрограмм и значений параметров на входе и выходе моду: лей. Но это необходимо только тогда, когда не существует системной подпрограммы, выполняющей указанные выше действия. ПЛАНИРУЙТЕ ПРОЦЕСС ОТЛАДКИ Некоторые языки программирования допускают нестандартные возвраты в точки, отличные от точки вызова. В языках блочного типа это соответствует завершению выполнения процедуры с помощью оператора GO TO, передающего управление вне тела процедуры. Нестандартный возврат противоречит принципу модульного проектирования, предусматривающему наличие только одного входа и одного выхода для каждого модуля. Такие программы сложнее для понимания, отладки и не удобны для формальной верификации. х ■
240 Глава 7 7.5. Сложность программы Одно из основных правил программирования — соблюдение простоты. Более простой считается та программа, которая легче для понимания, отладки, сопровождения и модификации. Однако не существует общепринятого мнения о том, что делает программу простой. Проще та программа, которая использует привычную систему обозначений. Программа с небольшим числом уровней вложенности проще, чем программа, в которой таких уровней много. Вероятно, проще и та программа, которая тщательно спланирована. 7.5.1. Сложность исходного текста и длина модуля Обычно длина модуля измеряется числом строк исходного текста. При таком критерии логично считать модуль, состоящий из 50 строк, более простым, чем модуль из 150 строк. Любой модуль, длиннее 150 строк, вероятно, слишком сложен, и его необходимо заново спроектировать. Если модуль имеет слишком большую длину, могут возникнуть трудности при его компиляции и сегментации. Использование длины модуля в качестве меры его сложности подтверждает идею о том, что программа, написанная на языке высокого уровня, проще той же программы, написанной на языке низкого уровня. Длину модуля не всегда можно брать в качестве меры сложности: например, при использовании составных операторов, при структурировании исходного текста и при красивом оформлении листинга длина модуля увеличивается, а сложность программы снижается. ЧЕМ КОРОЧЕ ПРОГРАММА, ТЕМ ЛУЧШЕ Сложность текста часто определяется также числом управляющих связей между секциями. На рис. 7.4 показаны управляющие линии для двух версий одного и того же модуля, приведенных в примерах 7.24 и 7.25. Сложность каждого равна 4. Преобразование программы из примера 7.24 увеличивает ее длину, но уменьшает число внутренних секций, несмотря на то что используется отдельный выполняемый параграф чтения. В этом случае сложность текста, оцениваемая с помощью двух введенных выше критериев, увеличивается. Простой мерой сложности модуля, не зависящей от структурирования и формы распечатки, является цикломатическое число модуля. Предположим, что задан управляющий граф модуля. Тогда цикломатическое число модуля равно числу ребер минус число вершин плюс два, или, проще, на единицу больше числа точек ветвлений в модуле при условии, что все точки ветвлений имеют две выходные ветви. Пользуясь этой мерой, находим, что программы в примерах 7.24 и 7.25 имеют относительную сложность, равную 5 и 8 соответственно. A PERFORM ...UNTIL считается точкой ветвления. Аналогично точкам ветвления счита-
Реализация программного модуля 241 PERFORM PROCESS-CARDS. I— READ - AT END MOVE GOTO ^ IF GOTO " ADD ON SIZE ERROR WRITE GOTO ► PERFORM PROCESS-CARDS-END. - EXIT. .PERFORM -PROCESS-CARDS. MOVE PERFORM MOVE Рис. 7.4. Управляющие связи для подпрограмм на Коболе. а — с оператора GO TO; б — без операторов GO TO. ются READ ...AT END, ON SIZE ERROR и, конечно, IF. Оператор PERFORM не содержит ветвления, поскольку после него всегда выполняется следующий оператор. 7.5.2. Меры Холстеда Вместо измерения длины программы с помощью строк исходного текста Холстед предложил измерять длины числом символов в модуле. Если модуль на языке Фортран вычисляет синус
242 Глава 7 числа, текст с использованием библиотечной функции может быть записан как Y=SIN(X), или, если значение X мало, его синус может быть вычислен как Y=X—Х**3/6+Х**5/120— —Х**7/5040. Каждый такой оператор может быть записан в одну строку. Вызов библиотечной функции проще для понимания, чем приближенные вычисления, поскольку для вызова функции используется стандартное обозначение. Кроме того, в вызове функции содержится меньше различных символов, чем в экспоненте, и вызов короче. При вызове функции используются пять различных символов: Y, +, SIN, (...), X, а при использовании выражения — тринадцать: Y, =, X, —,**,/, +, 3, 6, 5> 120, 70, 5040. Общее число различных знаков равно 21. Холстед ввел следующие меры: Tii число различных операторов, т]2 число различных операндов, T] = T]i+r)2 словарь модуля, N\ число появлении операторов, N2 число появлений операндов, N=Ni+N2 длина модуля. Длина модуля по Холстеду может быть вычислена по приближенной формуле М^т\г logi (Лх)+% loga Ы- В программе любого размера г\\ примерно равно числу операторов языка. Следовательно, длина модуля зависит от количества выбранных имен и частоты их использования. Поскольку программист может повторно использовать некоторые имена и менять формы управления циклом, длина модуля, по Холстеду, однозначно не определяется числом операторов, составляющих модуль. Объем модуля определяется выражением V = Nlog2r\. Минимально возможный объем модуля определяется объемом обращения к библиотечной функции, выполняющей то же, что и модуль, если предположить, что такая функция существует. Минимальная длина модуля на три единицы больше, чем удвоенное число параметров для функции, и на единицу больше удвоенного числа параметров для подпрограммы. Для функции» вычисляющей синус, объем равен V* = 5 log2 (5) = 11,6 для вызова функции; У = 21 log2(13) = 77,7 для приближенных вычислений. Уровень записи модуля определяется следующим образом: 1-Г=» у | где V* — минимальный объем. Поскольку объем модуля не
Реализация программного модуля 243 меньше V*, уровень записи модуля не может быть больше 1. Уровень записи модуля можно приближенно определить по формуле Удивительно то, что величина X=L2V оказывается практически постоянной для всех модулей, написанных на одном и том же языке программирования. Экспериментальные значения величины А, приведены в табл. 7.2. Когда модуль спроектирован и вы- Таблица 7.2. Уровни языков (по Холстеду) Язык ПЛ/1 Алгол 58 Фортран AL(CDC) Я 1,53 1,21 1,14 0,88 Отклонение 0,96 0,86 0,90 0,65 <бран язык реализации, с помощью этих характеристик и величины минимального объема модуля можно вычислить длину исходного текста. Сложность модуля определяется по формуле Величина Е соответствует усилию, затраченному на кодирование и понимание модуля. Опираясь на результаты психологов, Холстед пытается прогнозировать время и усилия, которые понадобятся для кодирования модуля, а также число ошибок, ожидаемых в конечной версии. В табл. 7.3 приведены значения Таблица 7.3. Меры Холстеда для программных сегментов из примеров 7.24 н 7.25 Л1 *12 Л *1 N2 N V L Е К С операторами GO TO 17 10 26 28 17 44 206,8 0,0692 2988,3 0,990 Без операторов GO TO 15 17 32 41 25 66 330 0,0907 3638,4 2,715
244 Глава 7 мер Холстеда для программных сегментов из примеров 7.24 и 7.25. Выбор операторов и операндов произволен. Имена данных, имена параграфов и атрибуты считаются операндами. Все остальные символы считаются операторами, за исключением символов, всегда появляющихся вместе (таких, как GO TO и «...») и считающихся одиночными символами. Значения мер Холстеда указывают на то, что модульная версия программы может считаться записанной на языке более высокого уровня, чем немодульная версия, но в то же время она может считаться более сложной и трудоемкой. Таблица 7.4. Показатели относительной сложности программы Строки исходного текста Управляющие связи между секциями Цикломатическое число Мера Е по Холстеду С операторами GO TO 15 4 2988,3 Без операторов GO TO 18 4 8 3584,6 Значения четырех мер сложности для модулей из примеров 7.24 и 7.25 приведены в табл. 7.4, из которой видно, что ограниченное использование операторов GO TO в Коболе может привести к более простой программе, чем полный отказ от них. 7.6. Оформление программы Программу можно сделать простой, если ее тщательно спроектировать, закодировать в соответствии с требованиями структурного программирования, представить в удобной для понимания форме и снабдить документацией в необходимом объеме. Модуль должен быть по возможности самодокументирован. Внутри текста документация должна использоваться как можно меньше и записываться так, чтобы не отвлекать от самого текста. Она должна дополнять текст, а не прилагаться к нему. Кроме того, документирование должно проводиться аккуратно. ДОКУМЕНТАЦИЯ ДОЛЖНА ОТВЕЧАТЬ СОВРЕМЕННЫМ ТРЕБОВАНИЯМ 7.6.1. Пролог Кодирование модуля должно начинаться с общего описания. Напомним, что аналогичная процедура проводится на этапе проектирования модуля. Это описание должно содержать: а) номер и имя модуля;
Реализация программного модуля 245» б) фамилию программиста, дату завершения работы над модулем и номера всех версий данного модуля; в) функциональное описание модуля; г) перечень основных используемых алгоритмов со ссылками; д) словарь данных для параметров; е) имена подпрограмм, вызывающих модуль; ' ж) имена подпрограмм, вызываемых модулем; з) словарь данных для разделяемых областей хранения данных; и) словарь данных для внутренних данных; к) описание ввода-вывода; л) описание процесса обработки ошибок, выполняемого модулем. Пролог следует включать в исходный текст модуля в виде комментария для того, чтобы его можно было получить при каждой распечатке. Все подпрограммы работают с различными типами данных. Эти данные могут быть простыми переменными различных типов, структурами данных, вспомогательными константами а константами, являющимися параметрами программы. Все переменные и структуры данных должны быть отнесены к определенному типу, даже если язык не требует этого. Имена данных должны быть мнемоническими, т. е. отражать сущность описываемого процесса. Не следует использовать: слова, в которых обычно делаются орфографические ошибки; имена, различающиеся только одной буквой; слова, именрщие более одного очевидного сокращения, а также слова, являющиеся ключевыми в языках программирования. Операторы, служащие для объявления переменных, должны быть сгруппированы и структурированы, во-первых, в соответствии с типами переменных и, во-вторых, в соответствии с их функциями в программе. В Коболе все счетчики, индексные переменные, временные значения и т. п. следует отнести к определенной категории и сгруппировать. В языке, не имеющем формальных средств группирования данных, тот же эффект можна получить за -счет использования пустых строк и комментариев. Если при объявлении используются коды, их следует расшифровать в прологе или же предусмотреть комментарии, расположенные _рядом с объявлениями: type transaction-code= («D» {занести}, (Пример 7.29> «W> {удалить}, «S» {откорректировать}) Если имена условий улучшают восприятие исходного текста, их: следует объявлять явно. 17—399
246 Глава 7 7.6.2. Правила оформления листингов Исходный текст воспринимается лучше, если он удачно рас- шоложен на странице, выделены поля, строки не перегружены информацией, использованы отступы и пробелы. ИСПОЛЬЗУЙТЕ ПРОБЕЛЫ В исходном тексте часто имеются базовые структуры — следование, выбор и повторение. Для документирования этих структур в большинстве случаев достаточно правильно их располо--. жить на листинге. Пустые строки могут указывать на окончание группы последовательно выполняющихся операторов. Абзацный отступ используется для выделения подструктур. До тех пор пока в языках программирования отсутствуют автоматические средства оформления текста, для получения листинга, удобного для восприятия, можно рекомендовать следующие правила: а) оставлять между основными частями подпрограммы три пустые строки, между неосновными частями подпрограммы — две пустые строки и после каждой последовательно выполняющейся группы операторов — одну пустую строку; б) использовать базовые управляющие структуры; в) соблюдать абзацный отступ в два — пять пробелов для тел циклов, подчиненных операторов и продолжений операторов; г) абзацный отступ для продолжений операторов должен быть больше, чем для подструктур; д) выравнивать альтернативные ветви операторов; е) использовать закрывающую скобку, если структура имеет открывающую скобку; ж) нумеровать строки с шагом, равным 10. 7.6.3. Комментарии Кроме пролога и комментариев, относящихся к данным, требуется определенное количество внутренней документации модуля, которое зависит от используемого языка. При программировании на языке низкого уровня довольно часто необходимо снабжать комментариями каждую строку исходного текста, а также давать дополнительные комментарии отдельным частям модуля. В Фортране циклы должны быть всегда документированы, чтобы можно было определить их назначение и условия завершения. Такие базовые структуры, как выбор и следование, часто не требуют никаких комментариев. Исключительные ситуации обычно обрабатываются отдельно и поэтому требуют предварительных комментариев. Существует хорошее правило, заключающееся в том, что каждой пронумерованной строке должен предшествовать комментарий.
Реализация программного модуля 247 Если удачно выбраны имена данных и параграфов, то для программ, написанных на Коболе, требуется небольшая документация. Объем внутренней документации модуля зависит оттого, как организован и записан программный модуль. В Паскале и ПЛ/1 каждая внутренняя процедура, являющаяся отдельным модулем, должна быть документирована. Секции исходного текста, содержащие однотипные операторы, отделяются друг от друга и имеют имена, например ***** ОПИСАНИЯ ****** (Пример 7.30> ***** ПРОЦЕДУРЫ ***** ** ОБРАБОТКА ОШИБОК • Любые дальнейшие пояснения, касающиеся этих или других секций текста, группируются и размещаются таким образом, чтобы можно было читать комментарии без текста, а текст без комментариев. В Фортране модуль для вычисления наибольшего общего делителя двух целых чисел может быть документирован и оформлен следующим образом: (Пример 7.31) С* С* GCD * С* С* АВТОР. К.ЗИГЛЕР * С* С* ДАТА: 6/26/82 * С * ФУНКЦИЯЮПРЕДЕЛЕНИЕ НАИБОЛЬШОГО ОБЩЕГО ДЕЛИТЕЛЯ* С * ДВУХ ЧИСЕЛ С* С* ПАРАМЕТРЫ * С* I ПЕРВОЕ ЧИСЛО * С* J ВТОРОЕ ЧИСЛО * С* * " С* ЛОКАЛЬНЫЕ ПЕРЕМЕННЫЕ * С* М БОЛЬШЕЕ ИЗ ЧИСЕЛ * С* N МЕНЬШЕЕ ИЗ ЧИСЕЛ * С* С* ВЫЗЫВАЕМЫЕ ПОДПРОГРАММЫ :IREM,IAB$ * С* С FUNCTION GCD(U) INTEGER UGCD С С ЦЕЛЫЕ K,L,M,N С N = IABS(f) М = IABS(J) *************«*«***»»*»*»**» #*****«************##** С С * М=БОЛЬШЕМУ ИЗ ЧИСЕЛ * С * N ^МЕНЬШЕМУ ИЗ ЧИСЕЛ * £ **»*-**»*#*************** ****#********** 17*
248 Глава 7 IF(N.GT.M)GOT0 5 GOTO 10 С ТОГДА ИЗМЕНИТЬ N И М 5 K = N N = M М = К С. WHILE N>0 DO * ПОВТОРЯТЬ ДО ТЕХ ПОР ПОКА С . * ОСТАТОК НЕ БУДЕТ=0 * , 10 IF(N EQ. 0) GO TO 50 L = MOD(M,N) М = N N=L GOTO 10 С . * ПОСЛЕДНЕЕ НЕ РАВНОЕ НУЛЮ * С , * ЧИСЛО ЕСТЬ GCD 50 GCD = М RETURN END Объявления, комментарии, относящиеся к данным, и другие комментарии вместе с прологом составляют документацию исходного текста. 7.7. Вспомогательные средства, используемые при реализации Выше уже говорилось об организации бригады программистов и о взаимодействии программистов, работающих над модулями одной программной системы. На всех стадиях проектирования и реализации каждый член бригады может рассчитывать на помощь своих коллег. На всех уровнях должны быть организованы сквозные просмотры результатов работы, завершающиеся просмотром текста готового модуля. Наилучший способ обнаружить ошибки в своей программе — объяснить работу программы кому-нибудь другому. ОДНА ГОЛОВА ХОРОШО, А ДВЕ ЛУЧШЕ 7.7.1. Машинные средства Существует много машинных вспомогательных средств, встроенных в системное математическое обеспечение. Следует максимально использовать возможности, предоставляемые средствами трассировки и отладки, схемами загрузки и таблицами перекрестных ссылок. Программист может использовать собственные средства трассировки и дампы участков памяти. Дампы всей памяти должны использоваться только в крайнем случае. Если предусмотрен вспомогательный модуль, позволяющий получать отладочные дампы, ему передается идентификационный код для определения этапа выполнения программы в момент выдачи каждого дампа.
Реализация программного модуля 249 Первое зондирование Венеры было неудачным, так как программист сделал ошибку: вместо оператора DO 3 1 = 1,3 он написал DO 3 1 = 1.3. Эта ошибка могла быть выявлена с помощью просмотра таблицы перекрестных ссылок или словаря переменных. Поэтому следует использовать все средства контроля ошибок и максимальное количество сообщений компилятора. Любой оператор, вызвавший выдачу предупреждающего или ка- 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 GCD 1 J К L М N FUNCTION GCD(I,J] INTEGER 1. J, GCO INTEGER K, L,M, N N = IABS(I) M = IABS (J) IFIN.GT M) GO TO 5 GO TO 10 5K=N N = M M= К 10 IF(N EQ 0) GO TO 50 L = IREM(M.N) M = N N = L GO TO 10 50 GCD = M RETURN END a 1, 2, 16 1, 2, 4 1. 2. 5 3, 8, 10 3, 12, 14 3, 5, 6, 9, 10, 12, 13. IS 3,-4, 6, 8, 9, 12, 13f 14 6 ( Конец ) Рис. 7.5. Вспомогательные средства реализации. а — подпрограмма — функция, написанная на Фортране; б — таблица перекрестных ссылок; в — управляющий граф. кого-либо другого сообщения, должен проверяться особенно тщательно. Схемы загрузки и таблицы перекрестных ссылок можно использовать для проверки правильности имен модулей и переменных, а также для поиска необъявленных имен и переменных, которым не присвоено начальное значение. На рис. 7.5 приведена подпрограмма из примера 7.31 с пронумерованными компилятором операторами, таблица перекрестных ссылок и управляющий граф. На этом рисунке специально опущены комментарии и листинг оформлен таким образом, чтобы подчеркнуть структуру, описываемую с помощью управляющего графа, а не структуру, подразумеваемую программистом. Строки 6—10 можно было бы удалить из алгоритма без ущерба для выполнения функции, однако это затруднило бы понимание программы.
250 Глава 7 Каждая переменная в таблице перекрестных ссылок идентифицируется в соответствии с объявлениями во второй ила третьей строке. Необъявленные переменные отсутствуют. Если программа имеет правильную структуру, в первом операторе (не считая объявления), в котором появляется необъявленная переменная, последней должно быть присвоено значение (ис* ключение может быть сделано только для входных параметров). Так, переменным I и J значения присваиваются в строке 1, N — в строке 4, М — в строке 5, К — в строке 8 и L — в строке 12. Функции GCD значение присваивается в строке 16, где находится первая ссылка к имени GCD, отличающаяся от ссылок в строках 1 и 2, где данная переменная объявлена. Управляющий граф, приведенный на рис. 7.5, в, содержит но- мера операторов двух типов: во-первых, операторов, которые снабжены метками (и поэтому ссылка на них может осуществляться с помощью управляющих операторов), во-вторых, операторов IF. Если существуют DO-циклы, номера содержащих их операторов следует также включать в граф. Управляющий граф вместе с таблицей перекрестных ссылок можно использовать для контроля области возможных значений переменных и проверки базовых управляющих структур. Из сопоставления таблицы перекрестных ссылок с управляющим графом видно, что переменной К значение присваивается при попадании на одну из ветвей оператора IF и что ссылка к этой переменной имеется только на этой ветви. Так как областью действия переменной К является указанная ветвь и значение переменной присвоено до того, как появилась ссылка на эту ветвь, считается, что переменная К определена правильно. Активная область действия переменной начинается с первой строки, в которой эта переменная получила конкретное значение, и заканчивается выходом из последней управляющей конструкции, в которой есть обращение к этой переменной. Области действия переменных программы, изображенной на рис. 7.5, а, указаны в табл. 7.5. Присваивание значения переменной L и ссылки к ней осуществ- Таблица 7.5. Области действий переменных программы, приведенной на рис. 7.5, а Переменная I J к L М N GCD Область действия 1-19 1—19 8—10 13-16 5—19 4—16 17-19
Реализация программного модуля 251 ляются только внутри цикла, начинающегося с оператора 11. В области действия переменных М и N имеются структуры выбора и повторения. Отметим, что операторы внутри областей действия переменных должны составлять правильно вложенные управляющие структуры. Управляющий граф показывает, что функция GCD представляет собой последовательность операторов, состоящую из начальной подпоследовательности, конструкции выбора, цикла while do и завершающей подпоследовательности. a 6 в Рис. 7.6. Управляющие структуры, которые могут содержать ошибки. Если управляющий граф содержит структуры типа приведенных на рис. 7.6, а или б, его необходимо дополнительно проверить. На рис. 7.6, а приведен цикл с несколькими выходами. При его реализации возможны ошибки. Обнаружив такую управляющую структуру, программист должен проверить свою работу, чтобы быть уверенным в правильности выполнения программы. На рис. 7.6,6 приведена структура, соответствующая как оператору IF со многими выходами, так и паре неудачно вложенных друг в друга операторов IF. Хотя может быть, что именно такая структура была задумана программистом, это служит предупреждением о возможности возникновения сложной ситуации. Ветвь от оператора 12 к оператору 17 на рис. 7.6, а и ветвь от оператора 19 к оператору 28 на рис. 7.6,6 будем интерпретировать как выходы по ошибке. Тогда управляющий граф будет проще и лучше структурирован, если вместо указанных ветвей предусмотреть пути к блокам удачного и неудачного завершения (рис. 7.6,в). 7.8. Упражнения 1. На любом языке, кроме Фортрана и Кобола, привести примеры реализации базовых управляющих структур выбора и повторения, а также обработки исключительного состояния. 2. Возьмите неструктурированную программу, структурируйте ее, используя базовые управляющие структуры, оформите ее с целью получения удобной распечатки и составьте документацию.
252 Глава 7 3. Предусмотрите проверку правильности данных, представляющих множество измерений даты, времени, скорости и направления ветра. Предполагается, что измерения выполнены с интервалом 1 ч в течение нескольких месяцев. Данные наносятся на перфокарту каждый раз, когда выполняются измерения.* Все перфокарты обрабатываются одновременно. 4. Для оперирования со стеком разработайте модуль, позволяющий скрыть истинную структуру данных. Выполните это для двух различных представлений стеков. 5. Разработайте модуль для работы с информацией из базы данных о студентах курса, рассмотренной в упражнении 9 гл. 3. Используйте оперативную память и определите методы доступа. 6. Напишите подпрограмму на Фортране или Коболе, предназначенную для обнаружения числа, окруженного пробелами. Число может иметь десятичную точку и знак. Если используете Кобол, поместите это число в область вывода, если используете Фортран, значение возвратите в форме действительного числа. 7. Реализуйте таблицы решений, приведенные в упражнении 7 гл. 6, для проверки правильности задания римских цифр и оценки их значений. 8. Используя дополнительную таблицу, приведенную в упражнении 4 гл. 6, реализуйте управляемую таблицей подпрограмму для определения суммы двух римских цифр. 9. Решите задачу, возникающую при пакетной обработке записей в упражнении 2 гл. 6. Для этого используйте такие методы доступа к данным, которые позволяют сначала отладить подпрограмму, имитируя размещение файлов в оперативной памяти, а затем работать с реальными файлами. 10. Определите с помощью вызовов внешних процедур в каком-нибудь языке программирования (кроме Кобола), как передаются константы, выражения, скалярные переменные и массивы. 11. Реализуйте программу получения всех перестановок из п элементов. 12. Напишите программу решения задачи поиска путей для игры в китайские шашки, приведенную в упражнении 5 гл. 6. 13. Разработайте итеративный вариант программы для определения значения функции Аккермана (упражнение 3 гл. 5): а) без использования массива; б) с одномерным массивом; в) с двумерным массивом. 14. Разработайте итеративный вариант программы для определения биномиального коэффициента из примера 5.5: а) без использования массива; б) с одномерным массивом; в) с двумерным массивом. 15. Реализуйте алгоритм на нескольких различных языках программирования и сравните меры Холстеда полученных программ. 16. Приведите несколько вариантов управляющих графов для базовых управляющих структур. 17. Нарисуйте управляющий граф любой работающей (но не простой) программы, найдите в нем базовые управляющие структуры и отклонения от них. j
Глава 8 ПРОВЕРКА ПРАВИЛЬНОСТИ ПРОГРАММ Программу нельзя использовать до тех пор, пока не будет уверенности в ее надежности. Надежность — это свойство программы, более строгое, чем корректность, поскольку программа может быть корректной, но не быть надежной. Программа является корректной, если удовлетворяет внешним спецификациям, т. е. выдает ожидаемые ответы на определенные комбинации значений входных данных. Программа является надежной, если она корректна, приемлемо реагирует на неточные входные данные и удовлетворительно функционирует в необычных условиях. В процессе создания программы программист старается предвидеть все возможные ситуации и написать программу так, чтобы она реагировала на них вполне удовлетворительно. Этап тестирования является последней попыткой определить надежность и корректность программы. Проверка надежности включает в себя просмотр проектной документации и текста программы, анализ текста программы, тестирование и, наконец, демонстрацию заказчику того, что программа работает надежно. 8.1. Обнаружение ошибок Ошибки могут появиться на выходе программы по трем причинам. Во-первых, могут быть неверно заданы входные данные. Такая ситуация получила название «мусор на входе — мусор на выходе». Во-вторых, могут быть допущены логические ошибки в самой программе. Но даже при условии, что входные данные введены верно и программа написана правильно, на выходе все же могут появиться ошибки в результате не совсем удовлетворительного выбора алгоритма или накапливания незначительных числовых погрешностей. 8.1.1. Проверка правильности данных В гл. 7 шла речь о проверке правильности данных. Было Установлено, что проведение четкой диагностики неправильных Данных — важная функция любой надежной программы. Существуют три принципиально различных типа ошибок ввода: °шибки передачи, ошибки перезаписи и неправильные данные.
254 Глава 8 Ошибки передачи возникают в том случае, когда аппаратные ч средства ввода искажают данные. Неисправности этих средстэ могут быть как механическими, так и электрическими. Напри« мер, карта может быть отперфорирована со смещением отцент= ра позиции или устройство считывания с перфокарт может неправильно считать данные. Также могут быть повреждены участок на ленте записи или электрическая цепь. Ошибки перезаписи появляются, когда данные неверно ко* пируются еще до ввода в вычислительную систему. Здесь еле* дует выделить стандартные ошибки, вносимые человеком (подобные ошибкам, возникающим при печатании на машинке). Наиболее часто встречаются следующие ошибки: неправильное размещение данных, появление лишнего символа, пропуск символа, замена одного символа другим, перестановка двух символов. Если результаты ввода контролируются пользователем, ошибки такого рода могут быть легко обнаружены. Эти ошибки можно обнаружить и программным способом: метод цифрового контроля, заключающийся в проверке как позиций цифр, так и их значений, часто позволяет выявить замену одной цифры другой и перестановку двух цифр. Контроль типа и встроенные методы контроля избыточности дают возможность находить вставки, пропуски и ошибки в размещении данных. Иногда данные можно проверить путем сопоставления с элементами заранее сформированного словаря. Программист должен сам принимать меры для защиты от ошибок перезаписи. Аппаратное и программное обеспечение позволяет находить лишь ошибки передачи. Если исправление ошибок не производится автоматически, программист должен предусмотреть действия, предотвращающие аварийное окончание задания. Такие действия не входят в круг обязанностей программиста по контролю данных. Вычислительная система должна обеспечивать целостность информации, которая уже введена. Часто ошибки в данные вносятся неавторизованными пользователями; ограничить доступ таких пользователей лучше всего с помощью физических средств защиты. В самой вычислительной системе могут вводиться пароли для работы с файлами, а доступ к файлам для большинства пользователей может разрешаться только для чтения. Более надежно данные можно защитить, если сделать файлы доступными только для некоторых программ, терминалов и пользователей, что возможно лишь при наличии специального программного обеспечения. Для защиты от несанкционированного доступа могут применяться методы шифрования данных. Данные, расположенные в оперативной памяти, также должны быть проверены, поскольку ошибки в них могут привести к: сбою системы. Так, необходимо следить за индексами перед обращением к массивам, контролировать значение индекса перед
Проверка правильности программ 255 выполнением множественного ветвления, не имеющего альтернативной ветви, проверять знаки чисел до выполнения операции возведения в степень, избегать переполнения или потери значимости при вычислении экспонент. 8.1.2. Логические ошибки Процедуры контроля, позволяющие обнаружить логические ошибки, должны быть предусмотрены на всех этапах разработки системы. Проект проверяется путем сопоставления со спецификациями, относящимися к логике работы всей системы. Текст программы должен быть проверен членами бригады программистов, а не самим разработчиком. На этапах проектирования и реализации, просматривая программные модули, необходимо следить за значениями наиболее характерных данных. Установлено, что 70—80% ошибок может быть обнаружено после тщательного просмотра текста за рабочим столом. ПРОСМАТРИВАЙТЕ ТЕКСТЫ ПРОГРАММ Не принимая во внимание орфографические и синтаксические ошибки, можно сказать, что причинами большинства логических ошибок являются неправильная инициализация переменных, несвоевременный выход из цикла, ошибки в типах данных, пропуск одного или нескольких условий при организации ветвлений. Управляющие графы и таблицы перекрестных ссылок можно использовать для того, чтобы определить, проведена ли инициализация переменной до ее применения. Пометка ветвей управляющего графа позволяет определить, все ли альтернативы учтены. При сравнении значений двух переменных возможны три случая: первая переменная больше второй, равна ей или меньше. Так как конструкция if ...then... else учитывает только две возможности, программист должен быть внимателен и следить за тем, чтобы случай равенства обрабатывался правильно. Несвоевременный выход из цикла часто возникает при подсчете записей файла. Ниже приведена программа на псевдокоде, которая будет правильно вести счет только в том случае, «если флаг конца файла устанавливается во время считывания последней записи файла: procedure подсчет числа записей; (Пример 8.1) счетчик: = 0; while not конец файла do чтение записи счетчик: = счетчик + 1 end Если же флаг конца файла устанавливается специальной допол-
256 Глава 8 нительной записью, подсчитанное число записей будет больше реально существующего. Эта проблема становится еще более важной, если записи помещаются непосредственно в массив, поскольку возможны ошибки в определении границ массива. Программа на псевдокоде procedure подсчет числа записей (Пример 8.2) i :=1; while not конец файла and not i > n do чтение записи (A(i)); i := i + 1 end будет заканчивать свою работу со значением индекса на единицу большим, чем число записей в массиве, если флаг конца файла устанавливается последней записью файла. Значение индекса будет на два больше,' чем число записей в массиве, если флаг конца файла устанавливается специальной записью. Для правильного определения числа записей файла надо точно знать условие установки флага конца файла, завершающее цикл. ТОЧНО ОПРЕДЕЛЯЙТЕ ЧИСЛО ЗАПИСЕЙ В ФАЙЛЕ 8.1.3. Числовые расчеты Использование чисел, в частности нецелых, в качестве данных или в вычислениях является причиной возможных ошибок. В процессе счета могут возникать погрешности разных типов. Некоторые из них, такие, как переполнение или потеря значимости при вычислении экспоненты, отбрасывание значащих цифр можно обнаружить с помощью программных или аппаратных средств. Это фактически ошибки в логике программы. Ошибки других типов не очевидны до тех пор, пока не проанализированы результаты счета или не осуществлены процедуры проверки правильности результатов. Эти погрешности не являются ошибками в обычном смысле. В случае известных чисел они могут быть предсказаны с помощью математических методов. Однако ошибки машины и ошибки программиста не могут быть предсказаны. Эти ошибки классифицируются в зависимости от причин их появления: • толерантности в измерении данных, • неточности числового представления, • округления или отбрасывания дробных цифр при выполнении операций на* ЭВМ, • использования алгоритмов аппроксимации. Все эти причины приводят к одним и тем же последствиям: некоторые цифры чисел, подсчитанных ЭВМ, не являются достоверными.
Проверка правильности программ 257~ Толерантность данных. Предположим, что подсчет количества осадков производится измерительным устройством с ценой~ одного деления шкалы 0,01 см. Тогда ошибки в одном измерении не превышают 0,005 см. Допустим, что при вычислении* среднего числа осадков за 30 дней погрешности измерений взаимно уничтожаются. Тогда в числе, равном сумме 30 измерений, достоверными можно считать две дробные десятичные цифры. В действительности полученный результат может отличаться от реального и на 0.15 см. Многие статистические характеристики позволяют учесть погрешности такого типа. Если сумма осадков за 30 дней составляет 4.28, то среднее количество осадков за день составит 0.142666... . Число знаков- после десятичной точки определяется длиной разрядной сетки ЭВМ, т. е. результат может быть меньше 0.143 и больше 0.142 или может быть просто равен 0.14 в зависимости от используемого выходного формата и от представления числа в памяти. Если сумма состоит из трех значащих цифр и достоверность последней цифры вызывает сомнение, следует признать неверным требование обеспечить более двух или трех значащих цифр в среднем. Программист не должен выводить на печать больше- цифр, чем требуется. Некоторые пользователи могут принять на веру эти лишние цифры. В зависимости от использования результат вычислений должен быть округлен до нужного числа значащих цифр или лишние цифры должны быть отброшены. ВЫВОДИТЕ НА ПЕЧАТЬ ТОЛЬКО ДОСТОВЕРНЫЕ ЦИФРЫ Представление чисел. Аналогичная ситуация может возникнуть и для других типов погрешностей, но по другим причинам. Задача представления чисел в ЭВМ не связана с тем, в какой' системе счисления — десятичной или двоичной — работает ЭВМ. Такие трудности возникают в обеих системах. Если желательно выполнить цикл так, чтобы управляющая переменная менялась от 1 до 5 с шагом 2/3, управляющая переменная должна принимать точно семь значений. Программист может записать это по крайней мере тремя способами: for х := 1.0 to 5.0 step .66666 (Пример 8.3> for x : = 1.0 to 5.0 step .66667 (Пример 8.4) forx := 1,0 to 5.0 step 2.0/3.0 (Пример 8.5> Значения х в десятичной форме будут выглядеть для шага. 0.66666 так: х=1.0, 1.66666, 2.33332, 2,99998, 3.66664, 4.33330, 4.99996 и для шага 0.66667 так: jc= 1,0, 1.66667, 3.44445, 3.00001, 3.66668, 4.33335, 5.00002.
J258 Глава 8 Таким образом, в первом случае цикл выполнится, как и ожидалось, семь раз, а во втором — только шесть, так как значение 5.00002 больше верхней границы управляющей переменной. Однако второй вариант обеспечивает более точное представление значений jc, чем первый. Проблема остается и в случае более точного представления величины шага. Если ЭВМ сама вычисляет величину шага (пример 8.5), возникнет неопределенность: заранее будет неизвестно, сколько раз — шесть или семь — выполняется цикл. Для управления циклом с максимальной точностью следует в качестве значений новой управляющей переменной использовать целые числа, а нужные значения вычислять внутри цикла, например fori := 3 to 15 step2 do (Пример 8.6) x:=i/3.0 яли x := 1.0 (Пример 8.7) ~fori:=* 1 to 7 step 1 do x:- xH-.66667 СТАРАЙТЕСЬ УПРАВЛЯТЬ ЦИКЛАМИ С ПОМОЩЬЮ ЦЕЛЫХ ЧИСЕЛ Аналогичная проблема возникает, когда сравниваются два действительных числа, например при использовании действительных чисел для управления циклом: сравниваются значения управляющей переменной и ее верхняя граница. Эти значения теоретически должны быть равны, но в действительности они не равны. Можно утверждать, что если числа не были получены одним и тем же методом.и на некотором этапе одно или оба -числа имеют дробные части, они практически никогда не будут точно равны. Если используется двоичная арифметика, числа могут быть неравными даже в том случае, если они представлены с помощью одинаковых десятичных чисел. Некоторые компиляторы языка Фортран, получая программный сегмент 7С=1.3 HEAD 5, Y fF(X.EQ.Y)... и значение входного данного 1.3, производят программу, выполнение которой дает в результате значение «ложь». Это происходит потому, что разные алгоритмы используются компилятором м программой ввода для преобразования действительных чисел во внутреннюю двоичную форму. Независимо от того, какая (двоичная или десятичная) арифметика используется в ЭВМ, некоторые числа не могут быть ^представлены точно. Запишем 1/4 в десятичной и двоичной системах счисления. Поскольку дробь 1/4 может быть записана в виде а/Ьпу где а — целое число, а Ь — основание системы счисле- лия, то 1/4 представляется в обеих системах счисления точно.
Проверка правильности программ 25£ Например в десятичной системе счисления 1/4=25/102, а в двоичной 1/4= 1/22. Любое действительное число может быть представлено точно в десятичной или двоичной форме, состоящей из» бесконечного числа цифр. Так, 2/3 записывается как 0.666666... в десятичной и как 0.10101010... в двоичной системе. Для любых двух действительных чисел, состоящих из конечного количества цифр, существует бесконечно много чисел, меньших первого л больших второго. Однако в вычислительных системах,, допускающих только семь значащих десятичных цифр, существует только около 107 чисел, которые можно представить точно. Если используется форма представления с плавающей точкой и на порядок отводятся две цифры, точно можно представить 10106 действительных чисел. Покажем, что только конечное число чисел может быть представлено в ЭВМ точно на примере изображения четырехбитовых чисел с фиксированной точкой. Считая, что двоичная точка находится слева, получаем, что только ограниченное количество чисел, лежащих между 0.0 и 1.0, а именно 0.0, 0,0625^ 0.125, 0.1875, 0.25, 0.3125, 0.375, 0.4375, 0.5, 0.5625, 0.625, 0.6875, 0.75, 0.8125, 0.875, 0.9375, может быть представлено точно. Десятичное число 0.3 представляется с помощью двоичного числа .01012 = .3125ю и частное 1.0/3.0 также определяется в виде .ОЮЬ. Таким образом, бесконечное число значений должна быть представлено ограниченным числом двоичных чисел. Десятичное число 0.35 может быть представлено как .01012 либо* как .01 Юг в зависимости от того, округление или отбрасывание младших разрядов используется при преобразовании. Проверка на равенство может показать, что два числа считаются ЭВМ равными, когда пользователь и не подозревает об этом. Это происходит из-за близости значений чисел, и используемое представление не позволяет их различить. Также может возникнуть ситуация, когда пользователь считает числа равными, а они в действительности не равны, поскольку получаются различными способами. Необходимо устанавливать лишь приблизительное равенство действительных чисел. Так, оператор ifabs (х— у) < 0.00001* abs (x) проверяет совпадение первых четырех цифр чисел х и у. НЕ ПРОВЕРЯЙТЕ РАВЕНСТВО ДЕЙСТВИТЕЛЬНЫХ ЧИСЕЛ Примеры 8.3—8.5, показывающие, как производится управление циклом с шагом 2/3, не только характеризуют погрешности представления чисел, но также акцентируют внимание на Распространении этих погрешностей. Незначительная погрешность увеличивается в результате выполнения ряда операций^ сложения.
260 Глава 8 Пусть ,е — значение ошибки в представлении чисел, тогда 2/3=0.66666+18, а после шести сложений ошибка становится равной бе. Следовательно, после шести итераций ошибка влияет только на значение последнего разряда. Но уже после 15 итераций ошибка распространяется на предпоследний разряд. Если использовать более точное значение 0.66667, ошибка рас- лространится на предпоследний разряд только после 30 итераций. Вычисленное ЭВМ значение для 2/3 будет влиять на точность, если оно содержит больше цифр, чем предусматривалось ^программистом. ЭВМ США ДОЛЖНА ПРОИЗВОДИТЬ АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ При анализе ошибок, возникающих в результате вычислений, полезно обратить внимание на типы чисел, имеющихся в распоряжении программиста. Каждый язык оперирует с одним или несколькими типами данных: целыми числами, действительными числами с фиксированной точкой, действительными числами с плавающей точкой. Числа могут быть десятичными и двоичными в зависимости от используемого языка программирования и аппаратного обеспечения. Для определенности предположим, что все числа представляются в десятичной системе счисления. Кроме того, будем считать, что целые м действительные числа с фиксированной точкой содержат не более трех цифр и представляются в виде знак-величина. Тогда можно представить любые целые числа, лежащие в диапазоне *от —999 до +999. Арифметические действия с целыми числами будут выполняться точно при условии, что числа остаются внутри указанного выше диапазона возможных значений. Если промежуточные значения в процессе вычислений выходят за пределы диапазона, ошибка обычно не обнаруживается, а это ведет к непредсказуемым результатам. Программист должен пытаться использовать любые средства языка программирования, пригодные для обнаружения ошибок такого рода. Выполним оператор присваивания N.-=900+100—200. Результатом может быть число 800. Но если сложение производится первым, а четвертой цифры для хранения результата не предусмотрено, то в итоге получим —200. Поскольку правило, заключающееся в том, что порядок выполнения операторов одного и того же приоритета слева направо не всегда выполняется компилятором, изменения порядка следования операций не будет достаточно для решения этой задачи. В случае когда важен порядок операций, программист должен использовать скобки. В Коболе программист, работающий с большими целыми числами, может контролировать каждую операцию с помощью :SIZE ERROR. В Фортране наилучшим выходом для программи-
Проверка правильности программ 261 ста в таких ситуациях является чередование знаков операций и использование арифметики действительных чис^л. Действительные числа с фиксированной точкой, состоящие из трех цифр, также лежат в диапазоне от —999 до +999, но неравномерно распределены в этом диапазоне. В диапазоне от 0.0 до 1.0 помещается 999 чисел (0.001, 0.002, ..., 0.010, 0.011, 0.012, ..., 0.100, 0.101, 0.000, 0.999), в то время как в диапазоне от 998 до 999 нет ни одного такого числа. Считая, что в процессе представления чисел ошибок не вносится, выделим два источника ошибок: выход промежуточных значений за пределы диапазона и потерю значащих цифр при выполнении операций. Выполнение оператора N : = 900.+ 100. —200. * может привести к выходу за пределы диапазона, а выполнение оператора N :=100. + 10.5—80.0 к потере значащих цифр. В зависимости от порядка выполнения операций результат может быть либо 30, либо 31.- Такого рода ошибки возникают, когда программист использует языки типа Кобол и ПЛ/1, которые позволяют задавать положения точки для каждой переменной. Если объявление N приводит к потере значащих цифр, ошибка обнаруживается. Но если наименьшие значащие цифры теряются при выполнении промежуточных вычислений, обнаружить ошибки не удается. Проблема не решается, даже если N имеет один дробный разряд. В последнем случае результат зависит от правил выполнения арифметических операций, и N может принять любое из следующих трех значений: 30.0, 30.5, 31.0. Программист при работе с числами с фиксированной точкой должен использовать один и тот же формат для всех чисел, чтобы избежать непредсказуемых результатов. Аналогичные проблемы возникают при работе с числами с плавающей точкой. Предположим, что используются трехразрядные десятичные мантиссы, а порядок меняется от —4 до +4. Тогда возможные значения лежат в интервалах от —9990 до —0.0000999 и от +0.0000999 до +9990. Распределение чисел снова неравномерное, и большинство из 20 млн. возможных чисел лежит в диапазоне от —1 до +1. Отметим, что, хотя формально число 0.0 не включено в множество представимых чисел, фактически оно присутствует в виде 0.000X10°. Дополнительные ошибки появляются при работе с числами с плавающей точкой, когда порядок выходит за пределы отведенного ему Интервала. В общем случае такая ситуация может быть обнаружена, но реакция на нее не всегда одна и та же. Например,
262 Глава 8 в ПЛ/1 программист может задать другой путь вычислений. В Фортране такую ситуацию устранить нельзя. В Бейсике потеря порядка приводит к неверному обнулению, и выполнение программы продолжается. ПРИ НАПИСАНИИ ПРОГРАММ СЛЕДИТЕ ЗА ТОЧНОСТЬЮ Отбрасывание дробных разрядов. Вычисление на ЭВМ функций y=f(x) дает результат у', который отличается от истинного значения у из-за возможных погрешностей и ошибок, возникающих при вычислениях. Пусть х'— представление х в ЭВМ; ех=х—х' — ошибка представле- ния; га=-р7п относительная ошибка представления х'\ (8.1) г — относительная ошибка, вносимая в процесс выполнения операций на ЭВМ; г—гх' — абсолютная ошибка, вносимая в процесс выполнения операций на ЭВМ; sx — число достоверных цифр в х. Если у=а+Ь, то у' = а' + Ь' — г и % = У—у'= %+%+*• Последнее выражение не требует никаких комментариев. Абсолютная ошибка 0.1 очень велика, если у' имеет только одну дробную десятичную цифру, и мала, если у' имеет восемь цифр слева от десятичной точки. Больше пользы принесет использование относительной ошибки. Если относительная ошибка равна 0.00001, это означает, что ошибка может повлиять на значение шестой слева цифры результата, т. е. в этом случае результат содержит пять достоверных цифр. Относительная ошибка, равная 0.1, влияет на значение второй цифры слева, оставляя достоверной лишь одну цифру. Число достоверных цифр в представлении можно приблизительно определить по формуле в,= 1оьв7г-вЬ«1о(|уЧ)-1с«1о(1^1)- (8.2) Если то sy™log10(\a + b\)—log10(K+e6 + 8|).
Проверка правильности программ 263 Предположим, что абсолютные значения а и 6 приблизительно равны и одно из них отрицательно, например а = 0.42598, Ь= -0.42406. Тогда, считая, что в представлении чисел используются три значащие цифры и производится отбрасывание остальных дробных цифр, а не округление при преобразованиях и проведении арифметических операций, имеем га = 0.00098, гь = —0.00006, У' ГУ у = а + Ь = 0.00192, V =0.425, Ь' = - = 0.425—0.424 + 8 = 8? = 0.00092, 0.00092 од2_! ^ 0.001 и-у^~1» -0.424, = 0.001+е, sy^0. То есть сумма не будет иметь ни одной достоверной цифры. Если а' — трехразрядное десятичное число с фиксированной точкой, любая ошибка при преобразовании а к а' приводит к уменьшению числа достоверных цифр. Истинное значение а лежит в интервале между о! и l.OlX^'» и относительная ошибка равна 0.01. Поскольку относительная ошибка и число достоверных цифр связаны между собой, приблизительная оценка относительной ошибки может быть найдена для значений, получаемых путем измерений, в том случае, если число достоверных цифр известно. Пусть sx — число достоверных цифр; тогда относительная ошибка гх<Ю~\ (8.3) Предельные значения ошибок, вносимых вычислениями, можно получить для любой ЭВМ. Относительная ошибка представления для десятичных чисел задается в виде гх<Ю^9 (8.4) а для двоичных в виде гх<21-*, (8.5) где t — число цифр в мантиссе значения, представленного в форме с плавающей точкой при условии, что в процессе преобразования производится отбрасывание младших разрядов. Если же производится округление, то г^^101_72 и rx^2~f. Относительная ошибка суммы двух чисел может быть найде-
264 Глава 8 на из следующих соотношений: ву = ев+86+б, Тогда = '*KI , '»!*'! .r (86) Если действия производятся с числами, состоящими из трех десятичных цифр, максимальная относительная ошибка возникает в процессе сложения а7 =100 и &'=0.999. Так как абсолютная ошибка 8^1, то относительная ошибка г^О.01, т. е. равна относительной ошибке представления чисел. Для трехразрядных десятичных чисел имеем га< Ю"2, г6< Ю-2 и г< 0,01. Поэтому / 0,01|а' + У| Q01 В случае представления двоичных чисел в форме с плавающей точкой (в мантиссе 24 разряда) гх^2~23^ 0.00000012. Если у=а+Ь, то ^<2-23((|а/| + |Ь/|+2-23)/г//. Если знаки а и Ь совпадают, то гу^2~23+2~23 = 2~22. Это —максимальное значение ошибки, В большинстве случаев ошибка гораздо меньше, а иногда может и не возникнуть. С другой стороны, если абсолютные значения а и Ь близки, но знаки разные, тогда у'язО и относительная ошибка очень велика. Для разности двух чисел максимальное значение относительной ошибки будет таким же, как и для суммы г __га\а'\ . гь\Ь'\ . (R7. Это объясняется тем, что разность двух чисел равна сумме первого и второго числа, взятого с обратным знаком. Если y = ab, а' = а—га, b' = b—eb, то a'b' = ab—агь— Ьга + гагь, г __ 1ву1 _ \аеь-\-Ьса — еа&ь + г\ . | а'еь | + | Ь'еа | 4- | е | у~~ \у'\ W\ ^ W\W\ •
Проверка правильности программ 265 Следовательно, для относительной ошибки произведения двух чисел справедливо неравенство гаь<га+гь+г. (8,8) Если ТО д' _ a' —eg a' — sfl 1 £' - 6'-^ *' 1 —(еа/^) _ а'—га ( . гь гь* гь* \ _ а' — Ъд ( 1 , 4_\^,j£ la I а'Ч ЪаЧ ~ 6' \1^ bf )^ Ь' Ь' "Г" (У)* (б')2 f о /у' 8а/& ^ "р "р" гь + 8- Следовательно, для относительной ошибки частного двух чисел справедливо неравенство r«tb< -$\- + rb+r<ra+rb+r9 (8.9) т. е. формулы для относительных ошибок произведения и частного одинаковы. Если а и Ь имеют одни и те же знаки, относительные ошибки для операций сложения и вычитания значительно меньше относительных ошибок для операций умножения и деления. Однако, если знаки а и Ь различаются, операции умножения и деления выполняются значительно точнее, чем операции сложения и вычитания. Полученные выше формулы могут быть использованы для определения относительных ошибок, возникающих при вычислении более сложных выражений. Если то г^т^т(2|^!^+г2|^|)+л (8ло) v=i ;=2 / Если t=l *8-399
266 Глава 8 ТО rP<^rxi + r(n-l). (8.11) Эти формулы позволяют сделать вывод о том, что ошибки при суммировании и умножении нескольких чисел могут быть больше, чем при суммировании двух слагаемых и умножении двух сомножителей. Возможность распространения ошибок была показана выше, когда рассматривались примеры выполнения оператора цикла с шагом 2/3. Формулы для ошибок суммы и произведения можно использовать совместно для определения верхних границ относительных ошибок, возникающих при вычислении значений сложных выражений. Если f(x) = ax2-\-bx+c, то raX2<ra+2rx+2r, гах*+ьх< {ax2l+bx\ (\™2\(ra+2rx+2r) + \bx\(rb+rx+r))+r, поэтому rf(*)< i^ + te + d (l«**l(rfl+2rx+2r) + +\bx\(rb + rx+r)+\c\rc+ \a/+bx{ (\ax*\(ra + 2rx+2r) + + \bx\(rb+r, + r))+r<: lax* + \x + c{ (b\ax*\r+3\bx\r-\- 4-IHr4 *r*\ax*\+3r*\bx\+r*\ ■ < |af+ lr+c| (5\ax*\r+3\bx\r+\c\r)+r СТАРАЙТЕСЬ СВЕСТИ К МИНИМУМУ РАСПРОСТРАНЕНИЕ ОШИБОК Аппроксимация. Ошибки четырех различных типов появляются из-за того, что во многих числовых алгоритмах используются методы аппроксимации. Теоретически аппрокси- . мация может быть проведена с любой степенью точности. Но на практике вследствие распространения ошибок, возникающих в результате использования ЭВМ, всегда существует предел достижимой точности. Ошибки, свя&анные с аппрокси* мацией (например, при использовании только первых трех
Проверка правильности программ 267 членов ряда при разложении функции в ряд), не могут рассматриваться без учета распространения погрешностей. Аппроксимация осуществляется так, чтобы обеспечить максимальную точность. Используя дополнительные члены ряда, не всегда можно добиться большей точности, поскольку в процессе вычисления этих членов возникают ошибки, связанные с распространением погрешностей. Часто самые простые ряды не являются наилучшими для расчетов на ЭВМ. Рассмотрим функции LOG и ЕХР10, которые имеются во многих языках программирования. Теоретически если Х= = LOG(Y), то Y=EXP10(X). На практике эти функции не являются взаимно-обратными, поскольку для определения их значений используются приближенные методы. Ниже приведен один из способов определения значений функции LOG: при 1<*< 10, (8.12) где С\ = 0.86304, с3 = 0.36415. Максимальная абсолютная ошибка определения значения логарифма равна 0.000602, а относительная ошибка 2^:0.0001. Более точная приближенная формула имеет вид +с5 (^=Щ^ при 1 < х < 10, (8.13) где а = 0.8690286, сг = 0.2773839, с5 = 0.2543275. Максимальная абсолютная ошибка равна 0.0000337, а максимальная относительная ошибка составляет 0.00007. Добавление еще одного члена ряда и пересчет коэффициентов уменьшает относительную ошибку до 0.000004. Эти формулы напоминают первые члены разложения в ряд функции logio (х). Но они не являются членами ряда, поскольку ряд сходится слишком медленно и эффект распространения ошибок значителен. Эти выражения являются полиномами Чебышева. Для каждого интервала значений х специально с целью минимизации ошибки вычислены коэффициенты полиномов. Абсолютные ошибки — это максимально возможные отклонения аппроксимирующих значений от истинных внутри заданного интервала. Чтобы использовать эти выражения для значений аргумента, выходящих за пределы интервала, необходимо представить значения аргумента в виде произведения сомножителей, больших 0 и меньших 10. В некоторых пакетах подпрограмм фир- 18*
268 Глава 8 Таблица 8.1. Методы аппроксимации, используемые в IBM 360 Функция LN (X) SIN (X) EXP (X) SQRT (X) Интервал Х>0 Х<2"п Х^ 174.673 Л>0 Максимальная относительная ошибка 2.37Х10-8 3.4 X Ю-8 1.8X10"» 1.59ХЮ-8 Методы Чебышева Чебышева бесконечных дробей Ньютона — Раф- сона мы IBM используются приближенные методы, перечисленные в табл. 8.1. В случае работы с действительными числами вероятность появления максимальной ошибки можно уменьшить, если выполнять следующие рекомендации: а) складывать наименьшие члены суммы первыми; б) определять все положительные и все отрицательные слагаемые и поочередно их складывать; в) избегать показателей степени, представленных в форме действительных чисел; г) использовать в процессе вычислений двойную точность, а результаты представлять с обычной точностью; д) уменьшать число операций; е) избегать цепочек операций, в которых используются неточные значения; ж) использовать алгоритмы, для которых известны оценки и границы ошибок. Вычисление функции у=х^х производится более точно, чем вычисление функции у=х2-°9 так как в случае действительного показателя степени экспонента вычисляется с применением логарифмов и антилогарифмов по формуле у=ехр[2.01п(л;)]. Значения функций ехр и In вычисляются по приближенным формулам. Сочетание этих функций приводит к росту ошибки. По той же причине вычисление SQRT (X) может быть выполнено точнее, чем вычисление Х**0.5. 8.2. Тестирование модулей Программные модули должны разрабатываться и реализо- вываться так, чтобы они содержали как можно меньше ошибок. Машинное время необходимо использовать для тестирования модулей только в том случае, если ручная проверка не позволяет больше обнаруживать ошибки. Задачей тестирования является обнаружение ошибок, а не демонстрация правильности работы программы. Поэтому часто разумнее поручать подготовку тестовых данных человеку, не принимавшему участия в
Проверка правильности программ 269 реализации программы. Следует начинать с тестирования и отладки отдельных модулей, а потом переходить к их сопряжению. Так как модули соединены друг с другом, они тестируются по группам. На заключительном этапе пользователи проводят тестирование всей системы. ТЕСТОВЫЕ ДАННЫЕ НЕОБХОДИМЫ ДЛЯ ОБНАРУЖЕНИЯ ОШИБОК При тестировании на уровне модулей каждый оператор должен выполняться по крайней мере один раз и каждый путь в программе должен быть пройден по крайней мере один раз. Кроме того, должна проверяться каждая спецификация. Для выполнения этих проверок разработано несколько подходов. 8.2.1. Тестирование путей Все возможные пути прохождения модуля могут быть выявлены с помощью управляющего графа. На рис. 8.1, а показан управляющий граф (см. рис. 7.5, в) для программы из приме- Г Начало J Q Конец 3 С КонеЦ О Рис. 8.1. Схема путей управляющего графа. а — управляющий граф; б — схема путей. ра 7.31. Эта программа используется для вычисления наибольшего общего делителя двух чисел. Все возможные ветвления на рисунке помечены. На рис. 8.1,6 вершины управляющего графа, принадлежащие сразу нескольким путям, размножены, чтобы сформировать древовидную структуру. Цикл 11—11 развернут, что позволяет показать возможность прохождения через него нескольких путей. Циклы, управляемые счетчиком, разво-
270 Глава 8 рачиваются в единственный путь, а циклы, управляемые событиями (такие, как рассматриваемый нами), имеют неопределенное число возможных путей, так как число итераций зависит от начальных значений параметров функции J и I. Существуют четыре основных типа путей, проходящих по управляющему графу: начало-6-8-11-16-конец; начало-6-8-11) *-16-конец; начало-6-11-16-конец; начало-6-11-(11)*-16-конец Первые два объединяют пути, включающие левую ветвь, выходящую из вершины 6, а вторые два объединяют пути, включающие правую ветвь, выходящую из этой же вершины. Второй и четвертый связаны с путями, идущими через цикл. Звездочка (*) означает повторение. Все возможные пути проверить нельзя, так как их число бесконечно, но можно проверить все четыре типа путей. Ниже приведены процедуры, позволяющие определить значения данных, необходимые для выбора того или иного пути. Путь 1: начало-6-8-11-16-конец. Условие N>M для вершины 6 не противоречит условию N = 0 для вершины 11, поскольку между вершинами 8 и 11 значения N и М могут измениться. Этот путь выбирается, когда начальное значение М = 0. Путь 2: начало 6-8-11-(11)*-16-конец. Он выбирается, когда для начальных значений справедливы соотношения M<N и М=^=0. Путь 3: начало-6-11-16-конец. Этот путь выбирается, когда начальное значение N = 0. Таблица 8.2. Тестовые данные для анализа путей I 5 6 5 10 0 2 <N 6 J 0 2 2 6 5 6 5 10 GCD 5 2 1 2 5 2 1 2 Тест Левая ветвь, цикла нет Левая ветвь, цикл выполняется один раз Левая ветвь, цикл выполняется " дважды Левая ветвь, цикл выполняется трижды Правая ветвь, цикла нет Правая ветвь, цикл выполняется один раз Правая! ветвь, цикл выполняется дважды Правая ветвь, цикл выполняется трижды Путь 1 2 2 2 3 4 4 4
Проверка правильности программ 271 Путь 4: начало-6-11-(11)*-16-конец. Он выбирается, когда начальные значения N и М связаны соотношением N^M и N=^=0. Поскольку значения N и М равны модулям параметров I и J соответственно, тестовые данные, необходимые для анализа путей, должны быть следующими: Путь 1: 1=^0, J = 0. Путь 2: 0<|J|<|I|. Путь 3: 1=0, J — любое. Путь 4: 0<|I|<|J|. Четырех пар значений достаточно для проверки всех типов путей, но лучше предусмотреть больше значений для проверки механизма работы цикла. В табл. 8.2 приведены наборы тестовых данных. В качестве тестовых данных должны использоваться простые числа, чтобы ответы могли быть определены заранее. 8.2.2. Тестирование структур управления При тестировании структур управления используются результаты тестирования путей. Тестирование структур отлича* ется от тестирования путей только способом проверки работы циклов, управляемых событиями. Поскольку в случае циклов, управляемых событиями, невозможно проверить все пути, их тестирование не снимает вопрос о необходимости проверки путей, содержащих цикл. При тестировании управляющих структур считается, что одного прохода по циклу достаточно. Так как тестирование включает сравнение получаемых результатов с ожидаемыми, для данного управляющего графа одного прохода по циклу достаточно. Если данные, вызывающие однократное выполнение цикла при входе в цикл, принимают значения п и т, а на выходе значения я'=0 и m'=GCD (п, т)=* = GCD (/, /), то по индукции можно доказать, что данные, вызывающие выполнение цикла k раз, на выходе имеют значения /?<*) = 0 и m(*> = GCD (^-1>,m^-1))=...=GCD (n, m). Если в цикле предусмотрено больше одного условия завершения, тестирование управляющих структур подразумевает тестирование каж- Таблица 8.3. Тестовые данные для анализа структур I 0 0 6 J 5 0 2 GCD 5 0 2 Тест Правая ветвь, N<M, цикла нет Правая ветвь, N=M Левая ветвь, N>M, цикл выполняется один раз
'272 Глава 8 дого условия. Также все структуры выбора с составными условиями необходимо проверять для каждой комбинации условий. В табл. 8.3 даны наборы данных, достаточные для тестирования управляющих структур. Все возможные результаты, получаемые при сравнении N и М, проверяются. Также тестируются варианты обхода цикла и однократного вхождения в него. Трех наборов тестовых данных достаточно для проверки как структур выбора, так и структур повторения. В отличие от тестирования путей здесь не предпринимаются попытки найти все возможные комбинации ветвлений и ситуаций, возникающих при выполнении циклов. 8.2.3. Тестирование ветвлений Если в модуле много ветвлений и циклов, нецелесообразно проверять все возможные пути, даже если циклы не относятся к типу циклов, управляемых событиями. При тестировании ветвлений анализируются точки ветвлений управляющего графа, чтобы определить данные, необходимые для выбора каждой ветви по крайней мере один раз. Каждая ветвь рассматривается отдельно, комбинации ветвей не анализируются. Тестовые данные для управляющего графа, показанного на рис. 8.1, должны удовлетворять следующим четырем условиям: M<N, N<M, N = 0, N^O. Эти условия могут быть учтены с помощью двух наборов тестовых данных, приведенных в табл. 8.4. Эти данные не пред- Таблица 8.4. Тестовые данные для анализа ветвей I 3 0 J 2 1 GCD 1 1 Тест M<N, N^=0 назначены для тестирования только отдельных ветвей. Тест N^0 приводит к выполнению цикла, причем число итераций в данном случае не имеет значения. 8.2.4. Тестирование утверждений Если в программе встречаются блоки on, для проверки всех операторов будет недостаточно тестирования только ветвлений и путей. Работа блоков on также должна проверяться. Тестирование необходимо проводить так, чтобы каждое утверждение блока on выполнялось хотя бы один раз. Для этого надо за-
Проверка правильности программ 273 давать данные, вызывающие возникновение необычных ситуаций. Так как в программе GCD нет операторов обработки исключительных состояний, то при тестировании путей, структур или условий будут выполняться все операторы программы. Таблица 8.5. Тестовые данные для классов значений I 0 1 2 2 J 1 2 0 1 GCD 1 1 2 1 Класс 0=N^M 0<N<M 0=M<N 0<M<N В более сложных программах, где трудно проанализировать условия, необходимые для выполнения каждого оператора, следует выделять классы значений данных для анализа точек ветвлений и исключительных состояний. В табл. 8.5 представлены наборы тестовых данных для различных классов значений N и М. СТАРАЙТЕСЬ ИЗБЕГАТЬ ИСПОЛЬЗОВАНИЯ ОПЕРАТОРОВ ОБРАБОТКИ ИСКЛЮЧИТЕЛЬНЫХ СОСТОЯНИИ 8.2.5. Тестирование специальных значений Тестирование специальных значений дает больше возможностей, чем тестирование операторов не только при поиске таких значений данных, которые приводят к активации операторов обработки исключительных состояний, но также при выборе значений, которые позволят выявить логические ошибки, если они есть. Поскольку наиболее часто встречается логическая ошибка, заключающаяся в неправильном применении равенства для организации ветвления, для рассматриваемого примера должны быть предусмотрены тесты, в которых N = M, причем значения N и М следует выбрать в одном случае очень большими, а в другом —очень маленькими. Для проверки работы функции, вычисляющей абсолютное значение числа, надо использовать положительные, отрицательные и .нулевые значения I и J. Для тестирования функции, вычисляющей остаток от деления, желательно предусмотреть вариант, когда N = 0. В табл. 8.6 приведены данные, предназначенные для тестирования как обычных, так и специальных значений. Поскольку программа не содержит логических ошибок, функция, вычисляющая остаток, не будет работать, когда N = 0. Успешное использование оператора N = 0 показывает, что «подступы к этой Функции надежно охраняются».
274 Глава 8 Таблица 8.6. Данные для тестирования специальных значений I 0 6 3 4 0 5 3 6 6 10 J 0 6 4 3 5 0У 6 3 10 6 GCD 0 6 1 1 5 5 3 3 2 2 Тест l4-lJl-° Ш-Ш^о 0< о< 0= 0= о< о< о< о< I J I J I J I J < < < < < < < < J I J I J I J I , GCD=1 , GCD=1 , GCD= , GCD= Ц J , 1<GCD<|I| |,1<GCD<|J| i / Рассматриваемый подход к анализу нестандартных ситуаций заключается в использовании тестов, позволяющих выделить классы данных, которые могут привести к серьезным системным ошибкам. Например, делаются попытки найти данные, которые вызывают деление на ноль, выход индекса за границы, приводят к возведению отрицательных чисел в действительные степени, потере значимости, неопределенным значениям. На рис. 8.2 показан простейший вариант записи функции GCD. Анализ нестандартных ситуаций, которые могут возникать при выполнении этой программы, дает следующие резуль- 1 2 3 4 5 6 7 8 9 FUNCTION GCD (N, M) 10 R = M-M/N * N IF(R.EQ. 0) GO TO 20 M= N N= R GOTO 10 20 GCD = N RETURN . END Рис. 8.2. Неправильная версия программы. таты. Во второй строке используется операция деления нацело, которая может являться источником ошибок. При выполнении операции деления всегда возможен также вариант деления на ноль. Если N = 0, выполнение операции деления нацело приводит к неудаче. В третьей строке сравниваются действительные и целые числа. Отметим, что R явно не объявлено действительным; на самом деле оно должно быть объявлено целым. Такая
Проверка правильности программ 275 операция сравнения является потенциальным источником ошибок, поскольку относится к операциям, проводимым с операндами разных типов. Также возможны ошибки при назначении новых значений параметрам в строках 4 и 5. Присваивание новых значений переменным было бы верным для подпрограммы, в которой явно объявлено, что эти переменные являются одновременно как выходными, так и входными параметрами. Но для функции такие действия являются если не ошибочными, то по крайней мере спорными. Результаты анализа нестандартных ситуаций используются в основном не для разработки тестов, а для решения вопросов, связанных с проверкой правильности программ. Эти вопросы возникают после просмотра текста программы и поиска значений переменных, которые могут привести к возникновению исключительных ситуаций и ошибок. Еще один важный момент не учитывается программой, приведенной на рис. 8.2: наибольший общий делитель должен быть положительным. Для отрицательных значений N и М возможен вариант, когда возвращается отрицательное значение. Если М равно нулю, в качестве GCD возвращается значение N. Хотя такой ответ в принципе является верным, отчасти теряется смысл, связываемый с именем функции. Если оба входных значения равны нулю, то значение GCD считается теоретически неопределенным. В данном случае возвращается значение, равное нулю. Это меньше вводит в заблуждение, чем возврат любого другого значения. Кроме того, возникают трудности, если значение функции GCD используется в качестве делителя. Следует помнить, что, если имя функции не точно отражает сущность выполняемого действия, необходимо использовать спецификации, вносящие недостающую ясность. В спецификации модуля должна быть включена информация об обработке ошибок и о присваивании значений по умолчанию. В спецификациях для данного примера также должно быть отражено, во-первых, следует ли параметрам быть положительными и, во-вторых, стандартным ли образом программа выполняет свои функции. 8.2.6. Фиктивное выполнение программы Сквозной структурный контроль программы позволяет промоделировать процесс выполнения программы. Его можно осуществить либо с реальными, либо с фиктивными данными. Если просмотр проводится с реальными данными, он соответствует тестовому прогону на ЭВМ. Для представления различных категорий данных можно использовать фиктивные данные, представляющие собой математические выражения в символьной форме. Модуль считается фиктивно выполненным, если переменные или выражения преобразуются, проходя через модуль.
276 * Глава 8 в соответствии с правилами символьных преобразований. Если имеются языки для работы с алгебраическими выражениями, прогон программы с фиктивными данными может быть осуществлен на ЭВМ. Для данного примера предпочтительнее начинать с простых переменных N и М или I и J. Затем уже проще определить выражения, которые легко могут быть использованы для фиктивного выполнения программы. Например, если g— наибольший общий делитель для I и J, выбираем I = g и J = i*g, где i — целое. Если i не равно нулю, эти выражения могут быть исполь- Таблица 8.7. Наборы фиктивных данных I g 1» g g g 0 i* g i*g + g i*g + g J i'g g g 0 g i*g+g i* g i» k* g+k*g + i* g зованы для проверки однократного прохождения цикла. Для двух итераций надо положить I=i*g и J = i*g+g, а для трех итераций — I = i*g + g и J = i*k*g+k*g+i*g, где к — целое число. Меняя значения I и J и выбирая различные значения i, s том числе отрицательные и положительные, ноль и единицу, можно проводить тестирование ветвлений и путей. В табл. 8.7 приведены некоторые варианты значений I и J. 8.3. Формальные методы доказательства правильности программ Формальный подход к доказательству правильности программ заключается в использовании логики для демонстрации корректности программного модуля. Пользуясь этим подходом, можно показать, что модуль удовлетворяет заданным спецификациям, но нельзя определить, полон ли набор спецификаций и верны ли они. Считается, что модуль выполняется не на реальной машине, для которой характерно распространение ошибок и возникновение нестандартных ситуаций, а на гипотетической. Другими словами, текст программного модуля рассматривается как математическая модель его спецификаций, а не реальная программа для ЭВМ. Для формального доказательства корректности программист должен второй раз описать ло-
Проверка правильности программ 277 гику программы (первым описанием является текст самой программы). Это требует формального задания спецификаций. Таким путем осуществляется дополнительный контроль ошибок. Доказательство правильности состоит из двух частей. Сначала показывается, что выполнение модуля обязательно завершится; затем с помощью определенных утверждений, связанных с входными переменными модуля, доказывается истинность других утверждений в момент завершения модуля. Для этого обычно используются формальные методы доказательства или неформальная аргументация. При использовании формальных методов удается избежать ошибок, свойственных неформальной аргументации, но эти методы менее удобны в том случае, если нужно убедить людей в корректности программы. Кроме того, проведение формальных доказательств достаточно утомительно. 8.3.1. Утверждения Первым шагом в процессе доказательства корректности модуля является задание формальных утверждений, касающихся значений переменных в различных точках структурной схемы программы, в частности в начале, конце, до и после точек вет- ;p{f}s (Р Л Q) {F} R (Р Л -Q) {G}S (Р Л R) {F} S Рис. 8.3. Правила вывода утверждений для основных конструкций программы. ■о — следование; б — развилка; в — узел слияния, вления и слияния. На рис. 8.3 показаны основные способы вывода утверждений для последовательностей операторов F и G и утверждений Р, Q, R и S. Утверждение Q считается истинным, если логическая проверка q дает положительный результат. Запись P{F}Q означает, что, если утверждение Р истинно и выполнена последовательность операторов F> утверждение Q истинно.
278 Глава 8 Утверждения могут быть сформулированы на любой стадии разработки программы. Если принят подход, основанный на пошаговой детализации проекта, неформальные доказательства утверждений могут проводиться на каждой стадии детализации. Формальные же методы доказательства можно использовать только тогда, когда полностью готов текст программного модуля. Если в программе, вычисляющей наибольший общий делитель (пример 7.31), предусмотрены входные и выходные спецификации, они могут считаться утверждениями для всей функции. На псевдокоде это записывается так: Р: утверждается, что i, j имеют наибольший делитель, равный g (Пример 8.8) F: вычисляется GCD (I, J) для I=i, J=j Q: утверждается, что GCD (i, j)=g Здесь прописными буквами обозначены переменные программы, а строчными — значения переменных. Все утверждения касаются значений, а не самих переменных. Утверждения должны быть сформулированы таким образом, что если Р истинна и выполнена программа F, то Q также истинно. Для примера 8.8 Р не является истинным, если i и j равны нулю. Но в утверждениях не содержится ничего относящегося к этому случаю. Поэтому не считается неверным переход от Р к Q с помощью F. Таким образом, набор спецификаций неполон, и необходимо его расширить так, чтобы учесть вариант, когда определяется GCD (0, 0). Расширим текст программы на псевдокоде, введя дополнительные утверждения, следующим образом: (Пример 8.9) Р: утверждается, что i, j имеют наибольший общий делитель, равный g F: N:-mln(|I|, |J|); M:=max(|I|, |J|); R: утверждается, что g=gcd (i, j), g=gcd (n, m), 0<n<m G: вычисляется GCD (N, M) для N=n, M=m Q: утверждается, что GCD (i, j) = g Тогда задача определения наибольшего общего делителя двух чисел может быть сведена к более простой задаче определения наибольшего общего делителя двух упорядоченных по возрастанию неотрицательных чисел, т. е. P{F}Q может быть расширено до выражения P{F}R{G}Q, обе части которого имеют свои собственные спецификации. Чтобы показать корректность всей программы, необходимо показать, во-первых, что если Р истинно и F выполнено, то R истинно, и, во-вторых, что Q следует из R и G. Описания для F и окаймляющих его утверждений могут быть расширены в соответствии с текстом программы в примере 7.31;
Проверка правильности программ 279 (Пример 8.10) Р: утверждается, что i, j имеют наибольший общий делитель, равный g F: N:=abs(I); M:=abs (J); утверждается g=gcd (i, j), g=gcd (n,m), n>0, m>0 if N>M then поменять местами значения N и М R: утверждается, что g=gcd (i, j), g=gcd (n', m'), 0<n'<m' Отметим, что значения М и N в R могут отличаться от значений, приписываемых этим переменным раньше. Так как пит использовались для представления первоначальных значений N и М, то для представления этих переменных в R используются п' и т', причём либо п' = п и т' = т, либо п' = т и т' = п. Для детализации описания процедуры G введем цикл: (Пример 8.11) R: утверждается, что g=gcd (i, j), g=gcd (n', m'), 0<n'<m G: while N=#0 do утверждается, что g=gcd (i, j), g=gcd (n', m'), 0<n/<m/ L := remainder (M, N); M:= N; N:«L; утверждается, что g=gcd (i, j), g=gcd (n", m"), 0<n"<m" endwhile утверждается, что g=gcd (i, j),m"=g GCDi^m" Q: утверждается, что GCD (i, j)=g Утверждения, стоящие в начале и в конце тела цикла, очень похожи. Новые значения п" и т" в конце цикла становятся п' и т' для следующей итерации. Утверждения, связанные с циклом, должны отражать отношения, которые являются истинными, независимо от того, сколько раз цикл выполняется. Эти неизменные отношения получили название инварианты цикла. Инвариант — это выражение, аналогичное выражению, применяемому для рекурсивного определения функций, которое выражает текущие значения переменных через их предыдущие значения. Инвариант служит для описания связей значений, принимаемых переменными в течение одной произвольно взятой итерации, с предыдущими значениями тех же переменных. Отметим, что в этом случае для доказательства можно использовать индукцию. На рис. 8.4 показана структурная схема функции GCD, дополненная полученными утверждениями. Формальная запись программы принимает вид Ах {FJ А2 (((п > ш) {FJ)\/((п < т) { })) А3 ((п' Ф 0) AJ{F3}]A5)* (П' = 0) Ae {FJ А7 Корректность модуля подтверждается тем, что последовательное выполнение сегментов программы позволяет перехо-
280 Глава 8 /- ,v** -» A4 g= gcd (i, j), g = gcd (n\ m'), 0<n'<m' A, ' 1 77 "Г 1 , g=gcd (ij). . g=gcd (n", m"), 1 0<n <m" ' ( F, f2 Start } --- N «-Ц I M<MJ| N<M |N0 K*-N N<-M h" J F3 <^"n= - A, 4 g= г —1 L A2 g= -'■ J No L <-lREM(M, N) N <-L F 4 1 ( 1 1 GCD*-M E id > г H I A7 GC gcd(i, j) gcd (i, j), g=gcd(n, m), n>0, m>0 Yes ^*' ^ A3 ,rg = gcd(i,j), g=gcd (n', m'), L0 < n' < m' ' A6 1 g = gcd (i. j), m" = g D(i,j) = g Рис. 8.4. Утверждения для структурной схемы программы нахождения GCD. W' дить от одного утверждения к следующему. Для наиболее глубоко лежащих структур (рис. 8.4) порядок следования утверждений меняется на обратный. Правильность структуры выбора на рис. 8.4 может быть установлена, если удастся показать, что справедливо А2 (((п > m) {F2}) V ((п < т) { })) А3, Т. е. сегмент F2 позволяет перейти от А2 к А3 при условии, что n>m, и Аз следует непосредственно из n^m. Для цикла необходимо проверить следующие утверждениям А8 (п' = 0) Ав, А3 (п" Ф 0) А4, А4 {F8} A6, А6(п'¥=0)А4 и А5(п'«0)А<.
Проверка правильности программ 281 После этого формальная запись текста программы примет вид Ах {Fx} A2 {G^ А3 {G3} (п' = 0) Ae {F4} A7, причем известно, что d, G2 — правильные сегменты. Тогда должны быть проверены только утверждения Ai{Fi}A2 и A6{F4}A7. Неформальная аргументация проводится следующим образом: 1) A2(((n>m){F2})V((n<m){ }))A3. А2 есть g = gcd (i, j) = gcd (n,m), n > 0, m > 0, и А3 есть g = gcd(i,j) = gcd(n\m'), 0<n'<m'. Если n' = n, m' = m и n<m, то А8 сразу же следует из А2. Если n > m, то п' = т и т' = п, поэтому n' < m' и А3 справедливо» 2) А3(п' = 0)Ав. Из А3 следует, что g = gcd(n',m'); поэтому g = gcd (0,m') = = m', поскольку нуль делится нацело на т\ Считая, что 111^ = п'^. приходим к Ав. 3) А3(п'=£0)А4. А4 есть g = gcd(i, j) = gcd(n',m'), 0 < n' <[m'. 4) A4{F3}A5. A5 есть g = gcd(i,j) = gcd(n",m"), 0<пЧю". Выполнив операторы присваивания, получим m" = n' и n" = rem (m',n')9 так как п'=£0. Поскольку остаток всегда меньше делителя, имеем n" < n' = т", но так как остаток может быть нулем, то 0<!n"<m". Согласно утверждению А4, существуют такие целые числа а и Ь, что n' — a*g, a m' = b*g. Тогда n" = b*g—(b*g/(a*g))* *(a*g) = b*g—(b/a)*(a*g) = g*(b—(b/a)*a); отметим, что здесь берется целая часть частного. Тогда m" = n' и п" делятся нацело на g. Кроме того, а и b—(b/a)*a для п' и т' выполняют те же функции, что а и b для п' и ш'. Следовательно, g = gcd(n",m").^3Ta и доказывает.утверждение А5. 5) А5(п' = 0)Ав. Из А5 следует, что g = gcd(n", m"). Отождествим п" из А5 с ir из условия завершения цикла. Тогда g = gcd(0, m") и m" = g. 6) A5(n'^0)A4. Из А5 следует, что 0 < п" ^ т'. Отождествим п" и т" в нижней части цикла сп' и т' б его верхней части. Тогда 0 < n' <т' и g = gcd(n'\m") = gcd(n ,т). 7)A1{F1|Aa. Так как g=gcd(i,j), to существуют а и Ь, такие, что i = a*g и j = b*g. Поэтому n = |a*g| и m = |b*g|. Таким образом, g = gcd (n, m) и п > 0, т ^ 0. 19-399
282 Глава 8 8) Ae{F4}A7. Так как m" = g и значение М назначается функции GCD, то ■GCD(i,j) = g. Таким образом, доказано, что GCD вычисляет значение наибольшего общего делителя чисел I и J в том случае, если он существует. 8.3.2. Завершение выполнения модуля Чтобы показать, что выполнение программного модуля дает верные результаты, недостаточно продемонстрировать его корректность. Необходимо также показать, что выполнение модуля приведет в то место (точку) программы, где эти результаты получаются. Выполнение будет достигать заранее определенной точки, если, во-первых, точка принадлежит всем путям, проходящим через модуль, во-вторых, все циклы заканчиваются и, в-третьих, в этом модуле или в других, вызываемых им, не возникает исключительных ситуаций, попадание в которые приводит к нестандартному завершению выполнения модуля. В случае программы GCD существует только одна такая ситуация: имеется в виду вызов функции, вычисляющей остаток от деления двух чисел с вторым аргументом, равным нулю. Других подобных функций модуль не использует. Все пути проходят через ту точку программы, где формируется результат. Остается проверить только условие завершения цикла. То, что цикл завершается, может быть показано с помощью анализа убывающей последовательности положительных целых чисел, связанных с циклом: п', п", п'", ..., п<*>. Поскольку для некоторого k n(*> = 0, обязательно произойдет выход из цикла. Процесс формального доказательства правильности программ очень сложен, в первую очередь для записи. Хотя утверждения можно записать так, что их правильность сможет проверить ЭВМ, эта процедура потребует очень много времени. Поиск утверждений, соответствующих каждой структурной единице схемы, труден и не может быть полностью автоматизирован. Любые ошибки при формировании утверждений приводят к ошибкам в доказательстве. Разработка методов формального доказательства — еще один способ проверки соответствия между текстом программы и спецификациями. Способы, позволяющие математически выражать значения переменных, дают возможность обнаруживать логические ошибки. Они также помогают проверять спецификации на полноту и согласованность. Запись формальных утверждений для входа и выхода модуля может быть использована при подготовке документации для этого модуля.
Проверка правильности программ 283я 8.4. Оценки ошибок Тестирование предназначено для поиска ошибок, а не для демонстрации того, что для некоторых данных программа работает правильно. Тем не менее, чем больше ошибок найдено, тем меньше уверенности в надежной работе модуля, так как если найдено много ошибок, то, во-первых, их было много и, во-вторых, возможно, многие ошибки остались необнаруженными. Поэтому необходимо разработать метод, позволяющий оценивать количество ошибок в модуле. Если приблизительное число ошибок известно, то, чем больше их найдено и исправлено, тем больше уверенности в корректности модуля. 8.4.1. Намеренное внесение ошибок в текст Один из способов оценки числа ошибок заключается в преднамеренном внесении ошибок в программу; при этом считается, что во время тестирования будет обнаружен один и тот же процент намеренно внесенных ошибок и реальных ошибок, имеющихся в программе. Поэтому, когда проводится тестирование, необходимо уметь определять, сколько реальных и намеренно внесенных ошибок обнаружено. Пусть N— неизвестное число ошибок в модуле, 5 — известное число намеренно внесенных ошибок, п — количество найденных и исправленных ошибок и 5 — количество найденных и исправленных намеренно внесенных ошибок. Будем считать, что s/S=njN. Тогда первоначальное количество ошибок в модуле можно приблизительно оценить по формуле N=nS/s. После тестирования остается N—я = = n(S—s)/s ошибок, т. е., если все намеренно внесенные ошибки найдены, имеется надежда, что и все первоначально существующие ошибки будут найдены. Степень уверенности в том, что существует не больше N ошибок в модуле, в который было намеренно внесено S ошибок, а п и s ошибок соответственно того и другого типа было обнаружено при тестировании, определяется выражением Когда s = 5, то n = N, и приходим к выражению ( s ) •cmf«AQ- (NS+~s+\V (8Л5> { N + S ) которое представляет степень уверенности в том, что все ошиб- 19*
284 Глава 8 Таблица 8.8. Степень уверенности в обнаружении всех ошибок 5=5 5 10 20 5 10 20 5 10 20 n=N 0 0 0 1 1 1 2 2 2 con i (<N) 0,83 0,91 0,95 0,71 0,83 0,91 0,625 0,77 0,87 ки найдены. В табл. 8.8 приведены значения степеней уверенности, вычисленные по формуле (8.15), для разных значений намеренно внесенных и первоначально существующих "ошибок. Чем больше ошибок из числа намеренно внесенных было обнаружено, тем выше уверенность в надежности модуля. Но чем больше реально существующих ошибок обнаружено, тем вероятнее, что есть еще другие, которые пока обнаружить не удалось. Уравнение (jy+l)conf«jV) ^^ 1— сопЦ^ло (8.16) -полученное из уравнения (8.15), позволяет определить количество ошибок, которые должны быть намеренно внесены в модуль, чтобы получить необходимую степень уверенности в его надежности. Причем считается, что тестирование продолжается до тех пор, пока все намеренно внесенные ошибки не будут обнаружены. Например, можно считать, что обнаружение 5 ошибок дает 90% уверенности в том, что все ошибки найдены, если в модуль было намеренно внесено 60 ошибок и в процессе тестирования все были обнаружены. Следует отметить, что необходимо вносить в модуль ошибки всех возможных типов, а именно: орфографические, пунктуационные, синтаксические, логические, ошибки в размещении, в типах данных и т. п. Можно ввести таблицу, в которой ошибки размещены в соответствии с их типами, и пользоваться ею. Можно случайно вносить ошибки в произвольным образом выбранные операторы модуля. ЕСЛИ В МОДУЛЕ ОБНАРУЖЕНЫ ОШИБКИ, -НЕОБХОДИМО ПРОВЕРИТЬ ЕГО ЕЩЕ РАЗ
Проверка правильности программ 285 8.4.2. Ошибки объединения модулей Фирмой IBM установлено, что в среднем одна ранее не обнаруженная ошибка появляется в каждых 100 операторах программы. Большинство ошибок содержимся в сопряжениях модулей и операторах ввода-вывода. Такого рода ошибки затрагивают обычно сразу несколько модулей. После того как проведено тестирование отдельно каждого модуля, последние объединяются в систему. Экспериментально установлено, что в 90% всех новых модулей и в 15% старых необходимо вносить изменения. Приблизительно в 15% новых модулей и 6% старых следует внести более 10 изменений. Если осталось D старых модулей и сформировано М новых, число 'необходимых изменений N определяется выражением W = 2(0.9M + 0,15D) + 23(0.15M + 0.06D). (8Л7) 8.4.3. Другие оценки ошибок Эксперименты показали, что число ошибок в неоттестиро- ванных программах пропорционально £2/3, где Е — мера Хол- стеда. Коэффициент пропорциональности равен примерно 1/3200. В программах, прошедших стадии тестирования и отладки, это отношение сохраняется, но коэффициент пропорциональности уменьшается. Различные формулы оценки количества ошибок не учитывают вероятность внесения k новых ошибок при исправлении п старых. ТЩАТЕЛЬНО ОПИСЫВАЙТЕ ИЗМЕНЕНИЯ, ВНОСИМЫЕ В РАБОЧИЕ ПРОГРАММЫ Вероятность того, что новый тестовый прогон позволит обнаруживать ошибки, будет зависеть от процента оставшихся ошибок. Последние ошибки обнаружить труднее. Формула ад<Г-) = т2тг=Т (8-18) позволяет установить среднее количество проверок, необходимых для обнаружения п ошибок. Здесь р — неизвестное значение, которое может быть определено экспериментально. Исходя из формулы (8.18), получаем формулу л—1 р _ avg (Tn) _ k~o ,ft 1Qv *-0
286 Глава 8 которая показывает, какой процент Р тестовых прогонов, необходимых для обнаружения всех N ошибок, был сделан, когда в процессе этих прогонов было найдено п ошибок. Если п = 10 по формуле (8.19) получим, что половина всех ошибок может быть обнаружена в первых 22% тестовых прогонов, необходимых для обнаружения всех ошибок. Процент прогонов возрастает до 37, если требуется найти 7 из 10 ошибок, и до 66, если требуется обнаружить 9 ошибок из 10, т. е. третья часть времени затрачивается на обнаружение одной последней ошибки. Если в программе имеется 100 ошибок, то 90 обнаруживается за первые 44% тестовых прогонов. Этот закон, устанавливающий, что эффект от тестирования уменьшается со временем,, позволяет сделать вывод о необходимости прекращать тестирование в тот момент, когда оно становится экономически невыгодным. Кроме того, можно утверждать, что в отлаженном модуле почти всегда остаются ошибки. НЕТ ПРОГРАММ БЕЗ ОШИБОК 8.5. Средства защиты программных систем С целью повышения надежности реализация системы проводится так, чтобы ошибки во время ее эксплуатации легко обнаруживались. Кроме того, следует стремиться к полному устранению ошибок. Несанкционированный доступ к аппаратному и программному обеспечению и данным может привести к сбою системы. Защита того места, где находится система, — наиболее важная предохранительная мера. Для защиты программного обеспечения наиболее часто используются, во-первых, пароли, позволяющие ограничить доступ к системе при работе в интерактивном режиме и доступ к файлам, во-вторых, возможности назначения прав доступа к ресурсам системы пользователям и программам пользователей и, в-третьих, методы шифрования тех данных, которые не имеют средств защиты от несанкционированного доступа или должны передаваться по незащищенным линиям связи. 8.5.1. Пароли Пароли — это секретные слова, которые либо выбираются пользователем, либо назначаются системой. Они используются для того, чтобы показать, что имеет право делать пользователь, которому присвоен определенный учетный номер. В отличие от других данных пароли не могут появляться на устройствах вывода информации. Их нельзя хранить в системном журнале для того, чтобы различать пользователей, или в системе учета пользователей. В целях идентификации пользователю
Проверка правильности программ 287 должен быть присвоен несекретный учетный номер, который можно выводить на печать. Множество авторизованных активных паролей должно находиться в таблице защиты, хранящейся в ЭВМ. Она используется для сравнения паролей в процессе регистрации пользователей. Таблица должна быть защищена от копирования, вывода на печать и неавторизованного доступа. Пароли необходимо часто менять. Каждый пароль должен состоять по крайней мере из восьми символов. Если пароль содержит меньше восьми символов, его легко установить методом проб и ошибок. Пользователи, которые выбирают свои пароли самостоятельно, имеют склонность использовать фамилию, номер телефона и другие удобные для запоминания комбинации. Этого делать не следует, поскольку тот, кто знает пользователя и хочет узнать его пароль, имеет все основания начать поиск. 8.5.2. Права пользователей Пароли позволяют ограничить доступ к системе как к единому целому. Их можно также использовать для ограничения доступа к файлам и другим ресурсам системы. Кроме того, данные можно защитить, сделав их доступными только с помощью специального программного обеспечения, предназначенного для доступа. Программы пользователей по отношению к этому программному обеспечению должны обладать определенными правами. Наиболее ранней формой этого было использование спе-. циальных команд супервизора, которые имели право применять либо некоторые пользователи, либо пользователи, находящиеся за определенными терминалами. Современные системы управления информацией и СУБД снабжены специальными средствами доступа для упрощения доступа к данным. Они могут быть использованы- как для защиты данных, так и для ограничения доступа к ним. Пользователь может иметь право на доступ к некоторым ресурсам системы для выполнения определенных действий. Например, существуют права на чтение, модификацию, копирование, уничтожение данных или права на вызов и изменение программы, ее копирование, передачу прав на доступ к программе другому пользователю. Пользователю могут быть даны права наделять полномочиями других пользователей. Обычно существуют правила, регулирующие предоставление и отмену прав. Считается, что пользователями являются не только люди, но и программы. Информация о правах каждого пользователя хранится вместе с информацией о нем самом. Система назначения и передачи прав должна быть достаточно надежной и гибкой.
288 Глава 8 » 8.5.3. Шифрование Данные, передаваемые по обычным линиям связи, могут быть перехвачены. В этом случае защита доступа к данным невозможна, но данные сами по себе должны быть защищены. Так как все больше и больше фирм использует электронные средства связи, вероятность перехвата растет. Шифрование необходимо как для предотвращения доступа к данным, так и для их защиты от намеренного искажения. Шифрование или кодирование сообщений используется уже в течение нескольких столетий для Защиты от перехвата и расшифровки злоумышленниками. Для кодирования обычных текстов были разработаны многочисленные методы. Простейшим является метод, заключающийся в замене каждого символа сообщения одним или несколькими символами. Если адресат имеет список, состоящий из пар «символ — заменяющая его последовательность», сообщение может быть легко расшифровано. Необходимость иметь кодовые списки, содержащие ключи к кодам, ставит под сомнение защищенность кодов такого типа. Даже если кодирование и декодирование производит ЭВМ, ключи не могут быть защищены надежно. Для шифрования можно также применять методы хэширования, если их использование не приводит к коллизиям и допустимо обратное преобразование. Если для замены символов обычного текста использовать числа, а затем возводить их в квадрат, декодирование не будет состоять только из обратной замены, а будет также включать математическую операцию извлечения квадратного корня. Процессы кодирования и декодирования являются основой криптографии. За достаточно большое время с помощью ЭВМ можно подобрать ключ практически к любому коду. Предположим, что закодированное сообщение достаточно длинно. Тогда оно может быть проанализировано, и, если использовались методы .замены или хэширования, ключ почти наверняка будет определен. ЭВМ позволяет перебрать все подходящие методы декодирования. Часто для предотвращения декодирования приходится менять код. Для использования в вычислительной технике было предложено два основных метода: шифрование с общим ключом и стандарт шифрования данных (DES) Национального бюро стандартов США. Оба используют такие процедуры кодирования, что невозможно осуществить декодирование за разумное время с привлечением ограниченных ресурсов. Обычно функция для кодирования и обратная ей функция для декодирования разрабатываются одновременно. Ключ, используемый для построения этих функций, не легко найти даже в том случае,
Проверка правильности программ 289 если есть как закодированное сообщение, так и исходный текст. Основное различие между двумя методами заключается в том, что стандартные коды создаются с единственной целью — защитить сообщения от декодирования людьми, не имеющими на это права. Правда, они позволяют также защищать сообщения от намеренного искажения. При шифровании с общим ключом защита сообщений осуществляется с помощью своевременного изменения процессов кодирования и декодирования. Так же как в других традиционных методах, при использовании DES управление кодированием осуществляет отправитель сообщения, который разрабатывает ключ и сообщает его пользователям. При использовании кодов с общим ключом управление кодированием осуществляет тот, кто принимает сообщение. Он извещает других о способе кодирования сообщения. Оба метода используют две функции (процессы): Е и D. Пусть М и С — соответственно сообщение и его код. Тогда С=Е(М) и M=D(C). Сообщение кодируется с помощью функции Е и декодируется с помощью функции D. Если сообщение сначала кодируется, а затем декодируется, первоначальный текст сообщения должен восстанавливаться, т. е. M = D(E(M)). Выбрав подходящие функции, можно сначала декодировать сообщение, а затем кодировать его. Тогда M=E(D(M)), и получен другой вариант шифрования. Следовательно, возможна защита данных, передаваемых между двумя абонентами, когда одному известна функция Е, а другому Ь. При использовании стандартов шифрования данных ключ требуется для выполнения обоих процессов: Е и D; если один из процессов известен, можно определить ключ. При шифровании с общим ключом ключ нельзя определить, если известен только один из процессов: D или Е\ также один процесс не может быть получен на основании знания другого. Стандарт шифрования данных. При преобразовании используется 64-битовый ключ, включающий биты четности. Этот ключ применяется для кодирования блоков, состоящих из 64 символов. Процесс кодирования блока состоит в применении определенным образом чередующихся операций перестановки символов с использованием ключа и замены символов. Весь процесс повторяется определенное число раз. Возврат к исходному тексту производится с помощью обратных операций. Выполняемые операции не обязательно должны быть секретными. В действительности они являются основными операциями ЭВМ, но, не зная ключа, декодирование осуществить практически невозможно. Как и в обычных криптографических системах, и отправитель, и адресат должны знать ключ. Аппаратные средства можно применять для того, чтобы сократить время кодирования и декодирования. Так как устрой-
290 Глава 8 ства, предназначенные для работы с шифрами, являются частью ЭВМ, для их защиты надо использовать методы ограничения доступа и защиту местонахождения. Устройства кодирования сообщений обычно предназначены для эксплуатации в течение длительного времени. Если защита на физической уровне обеспечена, дополнительные меры защиты при смене кодов не требуются. Существуют некоторые сомнения, касающиеся использования рассматриваемых кодов. Они связаны с тем, что, возможно, 64-битовый ключ не является достаточно длинным, чтобы не быть разгаданным злоумышленниками. Шифрование с общим ключом. Метод шифрования с общим ключом основан на построении специальных функций, значения которых легко вычислить, но построить обратную функцию для которых достаточно сложно. В то же время задача формирования как самой функции, так и обратной к ней из множества простых функций должна быть разрешимой. Этот про* цесс должен быть секретным. Если функция кодирования Е такова, что функция декодирования D не может быть определена, если Е известна, то нет необходимости организовывать защиту Е. Если функции кодирования двух абонентов Л и В несекретны, то А может послать В сообщение в форме ЕВ(М), которое В сможет декодировать, используя DB. Кроме абонента В, никто не может декодировать это сообщение, поскольку никто не знает функции DB. Послать ответ абонент В может в форме Еа(М). Далее, если все функции имеют ту же область значений, то А может послать сообщение в виде C = EbDa(M), а расшифровать его с помощью M=EaDb{C). Использование абонентом А секретного процесса при кодировании сообщения, направляемого 5, служит гарантией того, что сообщение не будет искажено. Идентификация исходного текста необходима тому, кто принимает закодированное сообщение. Абонент А может также послать сообщение в виде C=*DAEB(M), которое декодируется с помощью M<=DbEa(C). Этот процесс будет подтверждать подлинность сообщения и фактически служит доказательством его подлинности уже на третьем этапе, поскольку может быть показано, что ЕА(С)=ЕВ(М). Сообщение С может быть закодировано только абонентом А и может быть предназначено только абоненту В. При использовании DES и метода шифрования с общим ключом остаются нерешенными следующие проблемы: во-первых, неизвестен метод определения, является ли данный код сложным для раскрытия; во-вторых, неизвестен метод генераций группы кодов, все элементы которой сложны для раскрытия.
Проверка правильности программ 291 8.6. Качество документации Тестирование проводится как во время разработки программы, так и в процессе ее модификации. В обоих случаях необходимо, чтобы документация отражала текущее состояние программы. При документировании должна быть учтена возможность существования более чем одной версии программы. В документацию необходимо вводить тестовые данные и результаты тестирования для каждой версии программы, причем тестовые прогоны должны быть осуществлены заново, если в программу внесены изменения. ПРИ ПОДГОТОВКЕ ДОКУМЕНТАЦИИ УЧИТЫВАЙТЕ РЕЗУЛЬТАТЫ ТЕСТИРОВАНИЯ Для удобства сопровождения может оказаться полезным учет всех изменений, которые вносятся в систему. Тогда, если во время модификации в систему вносятся новые ошибки, их локализация облегчается. Как уже говорилось в гл. 5, аргументация, касающаяся выбора алгоритма решения задачи, должна найти отражение в документации. Кроме того, следует приводить данные, оказывающие влияние на надежность алгоритма, а именно допустимые диапазоны значений входных переменных, уровни возможных ошибок и т. п. Если применяются фиктивное выполнение программы или формальные методы проверки правильности, их описание должно стать частью документации. При сопровождении программы очень важным является знание тех категорий данных, для обработки которых предназначена программа, и соотношений, связывающих эти данные. 8.7. Упражнения 1. С помощью ЭВМ вычислите корни квадратного уравнения, используя следующие соотношения: а) х = (—Ь + УЬ*—Ш)/(2а); б) х = — Ь/(2а) + /б2/(4а2) — с/а\ в) х = —2c/(b + Yb2 — 4ac). Сравните ответы и объясните, почему они разные. Xs Хъ х'3 2. Используя ЭВМ, вычислите значения sin(#)=#— -—f-—+—+... для *в1, 10, 100, 1000. Какими должны быть ответы? Почему они не такие? 3. Используя ЭВМ, вычислите сумму четырех чисел по следующим двум формулам: a) ((a + b) + c) + d; б) a + (b + (c + d)) Для различных действительных чисел, больших и малых, положительных и отрицательных. Сравните точность методов вычислений.
292 Глава 8 4. Напишите программу, определяющую, могут ли три числа являться сто* ронами треугольника. Прогнать ее со следующими входными данными: 3.0 0.3 2.0 0.2 1.0 4.0 0.4 3.0 0.3 1.33333 5.0 0.5 5.0 0.5 1.66667 5. Проанализируйте изложенные в конце разд. 8.1.3 правила, предназна- ченные для уменьшения эффекта распространения ошибок. Объясните, почему эти правила действительно помогают. 6. Предположим, что десятичные числа преобразуются в двоичные с помощью восьмибитовой ЭВМ и хранятся в виде чисел с фиксированной точкой (точка находится слева от числа). Запишите, как в ЭВМ будут представлены десятичные числа 0.1; 0.3 и 0.4, если используются округление и отбрасывание младших разрядов. Каковы десятичные значения ошибки в случае округления и отбрасывания при вычислении 0.3+0.1—0.4? Определите верхнюю границу ошибки и относительную ошибку при вычислении значения выражения х+у—z в обоих случаях. 7. Для неверной программной функции, представленной на рис. 8.2, разработайте тестовые данные для анализа: а) путей, б) структур управления, в) ветвлений, г) специальных значений. Определите, какой способ тестирования не позволит обнаружить ошибки, допущенные при программировании функции. Если существуют ошибки, не обнаруженные после применения всех вышеописанных способов тестирования, предложите метод их поиска. 8. Разработайте тестовые данные для задач: а) о треугольниках и квадратах (пример 5.15); б) о нумерации с использованием римских чисел (пример 7.7 или 7.8). Аргументируйте свой выбор тестовых данных. 9. Используйте метод фиктивного выполнения для тестирования функции, программа которой приведена на рис. 8.2. Проследите за всеми шагами этого процесса. Оцените эффективность метода для обнаружения ошибок. 10. Проанализируйте методы, используемые вашей ЭВМ для вычисления значений основных математических функций.
Глава 9 ОПТИМИЗАЦИЯ ПРОГРАММ Большинство методоз проектирования и реализации программ предназначено для повышения эффективности работал программиста. Тщательное планирование программы уменьшает суммарное время, затрачиваемое на программирование. Разбиение на модули приводит к уменьшению времени отладки. Хорошая документация и структурирование программ и данных позволяют экономить время, затрачиваемое на модификацию - программы. Качественная документация в конечном счете всегда приводит к экономии времени. Современные системы управления базами данных и редактирования предназначены для.; повышения эффективности работы пользователей. Это достигается с помощью автоматизации многих этапов процесса программирования. Эффективность работы программиста и пользователя непосредственно связана с эффективностью программы. Чаще всего- повышение эффективности работы ЭВМ достигается за счет усовершенствований, вносимых в аппаратное и базовое программное обеспечения. Однако и проектировщик, и программист- тоже имеют возможности для улучшения программы. Эффективность программы может трактоваться по-разному. Например, можно потребовать, чтобы программа работала быстро, или занимала как можно меньше памяти, или как можно реже- обращалась к данным, расположенным на дисках, или, наконец, быстро обслуживала терминальные устройства. Одновременно выполнить все эти требования нельзя. В общем случае экономия памяти приводит к увеличению времени работы программы, и, наоборот, уменьшение времени работы приводит к использованию дополнительной памяти. Главным средством,, позволяющим проводить оптимизацию, является системное программное обеспечение. Каждый компилятор проводит оптимизацию, руководствуясь определенными критериями. Так, компиляторы, предназначен» ные для использования студентами и снабженные дополнительными средствами отладки и контроля ошибок, позволяют уменьшить время, затрачиваемое на отладь за счет увеличения времени компиляции. Стандартные компиляторы работают быстрее, поскольку меньше времени тратят на контроль оши-
594 Глава 9 бок и оптимизацию кода. Оптимизирующие компиляторы требуют больше памяти и работают медленнее, чем стандартные, но создают объектную программу, занимающую меньше места и работающую быстрее и эффективнее. Многие возможности, предусмотренные в компиляторе (например, контроль данных во время выполнения программы), могут использоваться или не использоваться в зависимости от желания пользователя. Если оптимизирующий компилятор не предусмотрен, программист может вставить в исходный текст программы операторы, позволяющие провести необходимую оптимизацию. Так же как и в случае любых других изменений программы, изменения, направленные на повышение эффективности, либо должны быть частью первоначального проекта, либо должны выполняться после того, как разработка программы закончена и проведена ее отладка. Следует отметить, что, хотя первый вариант реализовать легче, чем второй, все-таки эффективность программы на этапах проектирования и реализации не следует рассматривать в качестве главной цели. Главная цель — это решение задачи, а не поиск наилучшего решения. СТРЕМИТЕСЬ СДЕЛАТЬ ПРАВИЛЬНО РАБОТАЮЩУЮ ПРОГРАММУ ДО ТОГО, КАК ВНОСИТЬ В НЕЕ ИЗМЕНЕНИЯ 9.1. Экономия памяти В программном модуле используются три типа памяти: внешняя память (магнитные диски или ленты) для хранения данных, внутренняя память для данных (массивы и буферы) и память для размещения программы. 9.1.1. Данные на внешних носителях Память, которая требуется для размещения последовательных файлов, можно сэкономить либо делая записи короче, либо объединяя их в блоки. Для хранения чисел в двоичной форме требуется меньше памяти, чем для хранения их в символьной форме. Если ЭВМ работает с числами разной длины, для экономии памяти их следует представлять в упакованном виде. Большинство часто используемых целых чисел лежит в диапазоне 0—4095, поэтому для их хранения достаточно небольшого участка памяти. Символы при размещении следует упаковывать. Нельзя форматировать и редактировать данные, а также использовать заполнители, если память, где они размещены, предназначена только для чтения. ЭКОНОМЬТЕ ПАМЯТЬ ПРИ РАЗМЕЩЕНИИ ДАННЫХ После каждой физической записи на магнитной ленте предусмотрен свободный участок. Если информация размещается
Оптимизация программ 29^ на ленте с плотностью ~300 бит на 1 см, 80 символов займут только ~0,3 см. Но длина свободного участка может составлять ~1,3 см. В этом случае можно сказать, что магнитная лента используется в основном для хранения свободных участков, а не данных. Если данные сблокировать так, чтобы одна физическая запись состояла из 10 логических, то информативные участки будут занимать ~2,5 см. При использовании памяти на магнитных дисках физические записи размещаются начиная с границ секторов. Если запись занимает только часть сектора, оставшаяся часть не используется; если же размер записи больше размера одного сектора, запись занимает следующий сектор. Для эффективного блокирования записей программист должен определить оптимальный размер блока, исходя из того, сколько информации помещается на одной дорожке данного устройства. Если планируется использовать непоследовательные файлы, надо предусмотреть дополнительное пространство для их расширения. Уменьшение размера записи ведет к уменьшению этого пространства. Единственным способом уменьшения необходимого для размещения файла объема памяти (включая память для расширения файла) является либо более точная оценка требуемой памяти, либо усовершенствование алгоритма хэширования. Если файлы созданы, попытки сэкономить память для их размещения приведут к перестройке структуры файлов. Если файлы не были сблокированы, их блокирование позволит разместить информацию на диске или ленте с более высокой плотностью. Это позволит не только сэкономить внешнюю память, но и приведет к уменьшению времени доступа, поскольку блок считывается и записывается целиком. Правда, при этом потребуется дополнительная память для размещения буферов. В случае применения дисковой памяти наименьшие потери будут тогда, когда один блок занимает целиком всю дорожку. Если разработаны модули, позволяющие изолировать структуры данных, изменение структуры файла потребует минимальных изменений программы. Если файл используется несколькими программными модулями, структуру файла не следует изменять до тех пор, пока все применения файла не будут хорошо документированы. ПРОВОДИТЕ БЛОКИРОВАНИЕ ДАННЫХ 9.1.2. Данные в оперативной памяти Некоторые из описанных выше идей можно применить к данным, находящимся в оперативной памяти. Хранение символов в упакованном виде и чисел в виде двоичных полуслов при-
296 Глава 9 ведет к экономии памяти, но эта экономия будет слишком мала, если речь идет о небольших областях памяти, занимаемых данными. При использовании больших разряженных массивов данных необходимо разработать специальные методы, предназначенные для хранения таких массивов. Если программа работает со стеками, последние должны размещаться так, чтобы их' концы не перекрывались. Выбор эффективной дисциплины заполнения стеков приводит к экономии памяти. Сбалансированные двоичные деревья можно хранить, не указывая связей между вершинами. Изменение структур хранения эквивалентно «выбору нового алгоритма. Если программа правильно разделена на модули, такого рода изменения отразятся только на нескольких модулях, т. е. будут в той или иной мере локализованы. В общем случае нецелесообразно использовать одну и ту же область памяти для размещения значений нескольких элементов данных. Модули, где это все же используется, сложнее .для сопровождения. Исключение может быть сделано лишь для буферов файла. Если логические записи достаточно велики или сблокированы в физические записи большого размера, буферы требуют значительно больше памяти, чем может быть сэкономлено с помощью методов оптимального размещения данных в оперативной памяти. В этом случае полезно, чтобы несколько -файлов использовали один и тот же буфер. Но это возможно не во всех языках программирования. Поскольку такая ситуация порождает ошибки, она должна быть подробно описана при доставлении документации. Кроме того, необходимо тщательно следить за тем, чтобы файлы, работающие с одним и тем же буфером, не использовались одновременно. 9.1.3. Текст программы Создание виртуальных операционных систем способствовало снижению требований к экономии оперативной памяти, занимаемой программой. В любой ЭВМ может оказаться недостаточно быстродействующей памяти, предназначенной для размещения программы. В виртуальных операционных системах .большие программы автоматически сегментируются. Сегменты либо находятся в оперативной памяти, либо передаются в нее из внешней памяти по мере необходимости. Если нужно освободить память для очередного запрошенного сегмента, один или несколько сегментов передаются из оперативной памяти во внешнюю. Неудачная сегментация может привести к «пробуксовке». Например, если управление циклом располагается на одной странице, а тело цикла — на другой, возможен вариант, -когда эти страницы поочередно считываются из внешней памяти <я сбрасываются в нее. Содержимое дорожки страничных пре-
Оптимизация программ 297 рываний показывает, насколько в каждом конкретном случае важна эта проблема. «Пробуксовку» можно уменьшить, если, во-первых, размеры модулей сделать равными размеру страницы и, во-вторых, размещать модули в порядке их использования. Для улучшения страничной организации памяти в виртуальной системе необходимо: • программные модули делать небольшими; • операторы управления циклом и тело цикла располагать ближе друг к другу; • располагать вместе модули, часто вызывающие друг друга; • программы, которые вызываются одной и той же группой модулей, размещать вместе; • данные размещать вместе с модулями, которые их используют; • не использовать общих областей; • большие области данных располагать в том порядке, в котором они обрабатываются. В невиртуальных ЭВМ обычно предусматриваются средства, пользуясь которыми программист может сегментировать программу и организовать оверлейную структуру. Использование оверлейной организации позволяет нескольким сегментам поочередно занимать одну и ту же область памяти: новый сегмент загружается в эту область памяти только в случае его вызова. Основой оверлейной структуры является иерархическая схема модулей, которая сначала разделяется на неперекрывающиеся поддеревья. Затем главный модуль, разделяемые области данных, используемые несколькими модулями вспомогательные программы и, возможно, подмодули высшего уровня помещаются в управляющий оверлейный сегмент, который остается в оперативной памяти в течение всего времени выполнения программы. Группы модулей, представленных различными поддеревьями, поочередно занимают одну и ту же память. Различные операционные системы поддерживают разное число уровней поддеревьев. Секции управления оверлейной структурой (рис. 9.1, а) можно описать в управляющей части программы следующим образом: MAIN (А, В, D) LEVEL ONE (С, Е, F, Н) LEVEL ONE (G, I, J) На рис. 9.1,6 приведена схема сегментации еще одной программы. Отметим, что модуль Н вызывается как модулем Е, так и модулем F, а модуль I ^вызывается модулями Е и G. Тогда либо копии модулей Н и I следует разместить в обоих сегментах, либо модули Н и I следует продвинуть вверх по дереву, так чтобы они оказались в оверлейном сегменте, который по- 20—399
Глава 9 ^^ .1 в У s У / / / J / / / / \ \ \ ч 1 Е ***—.-, А С 1 ^^ N \ \ Ч.^ ~"Л 1 \ \ N \ \ F Н \ \ ""*-■■». \ \ \ Ч^ / / / / \ \ г 1 \ D - \ \ | 1 / / У G X \ \ \ А J 1 1 ; I L \ dh-~. Л: У / 1 Н Е • ч \ 1 J ' \ \ ^ / / \ \ ч У А i D "— -"" G / / / .' "^"^ч \ 1, К Рис. 9.1. Сегментация программы. а — двухуровневое дерево сегментов: корневой сегмент и оверлейные сегменты; б — трех* уровневое дерево сегментов: корневой сегмент н два уровня оверлейных сегментов. крывает оба сегмента, использующие модули Н и I. Следовательно, Н можно разместить вместе с С, а I вместе с Л. Тогда оверлейные секции могут быть расположены следующим обра-» зом: MAIN (А, В, D, I) LEVEL ONE (С, Н)
Оптимизация программ 299 LEVEL TWO (E, J) LEVEL TWO (F) LEVEL ONE (G, K) Схема расположения приведена на рис. 9.2. Наименование оверлейных сегментов производится операционной системой и зависит от числа допускаемых уровней. Расположение сегментов всегда согласуется с их положением в оверлейном дереве. Поддеревья необходимо выбирать таким образом, чтобы число взаимных ссылок было минимальным. Если из тела цикла, содержащегося в программе X, вызывается программа Y, то по возможности X и Y следует поместить в один и тот же сегмент. Если программа X вызывает программы Y, Z и W, то программы Y, Z и W должны быть в одном и том же сегменте. с,н I j А, В, D .' E,J X G.K F А В D 1 А В D 1 С Н А В D \ С Н Е J А В D 1 С Н F - А В D 1 i 1 G KJ а 6 в Г Л Рис. 9.2. Оверлейная Рис. 9.3. Распределение памяти для оверлейной структура. программы. а — корневой сегмент; б — первый уровень, вызов модуля С; в — второй уровень, вызов модуля Е; г — второй уровень, вызов модуля F; д — первый уровень, вызов модуля G. Программы, которые замещают друг друга, будут попадать в одну и ту же область памяти. Поэтому следует стремиться к тому, чтобы размеры этих программ были приблизительно равными. На рис. 9.3 показано распределение памяти для оверлейной структуры, изображенной на рис. 9.2. Каждый сегмент помещается в память в момент его вызова и остается там до тех пор, пока управление не будет возвращено вызывающему модулю и память не потребуется другому сегменту. Только поддеревья, которые располагаются в параллельных ветвях иерархической схемы программы (рис. 9.1), могут находиться в памяти одновременно. Поэтому модуль, находящийся в памяти, может вызвать другие модули, сам оставаясь в памяти, только если эти другие модули, во-первых, принадлежат сегменту, находящемуся в дереве сегментов выше искомого модуля, и, во- вторых, находятся в следующих лежащих ниже по отношению 20*
800 c Глава 9 к искомому модулю сегментах. Если вызываемых модулей нет в памяти, они сразу же в нее помещаются. Существует лишь одно ограничение при использовании оверлейных структур: программы, не входящие в главный сегмент, должны быть повторно используемыми, поскольку каждый раз, когда они помещаются в память, они будут находиться в первоначальном состоянии. Любые изменения значений, происшедшие во время выполнения программы, не сохранятся до ее следующего вызова, если в промежутке между двумя вызовами этой программы на ее место загружалась другая программа. Виртуальные ЭВМ не поддерживают повторно используемых программ, так как программист не участвует в управлении сегментацией. Это может считаться преимуществом, но фактически это снижает скорость обработки, так как кроме загрузки модулей из внешней памяти в оперативную требуется обратный процесс. При использовании оверлейных структур надо помнить, что в различных языках программирования области данных в памяти размещаются по-разному. В Коболе все области данных размещаются в управляющем сегменте. В Фортране локальные данные помещаются вместе с каждым модулем, поэтому в момент очередной загрузки модуля значения локальных переменных будут не определены. Общие области данных в соответствии с утверждениями типа COMMON могут быть размещены в сегменте самого высокого уровня (из тех сегментов, что содержат модули, которые используют эту область данных). Поэтому важно, чтобы COMMON-область, разделяемая двумя модулями, относящимися к разным поддеревьям, была определена в модуле, который покрывает оба первых модуля, даже если эта область там и не нужна. В некоторых версиях Фортрана требуется, чтобы все COMMON-области были определены в главном модуле. Для получения эффективной оверлейной структуры необходимо следующее: О использовать большие по объему сегменты и небольшое число сегментов; • группировать модули так, чтобы минимизировать количество межсегментных связей; • помещать в один сегмент модули, которые часто вызывают друг друга; • группировать модули в сегменты в соответствии со структурой иерархических поддеревьев; • размещать вспомогательные программы в сегменте, который покрывает все модули, вызывающие эти программы; • располагать модули, которые обрабатывают ошибки, инициируют или завершают выполнение, в отдельных сегментах, расположенных на нижних уровнях иерархии;
Оптимизация программ 301 • размещать разделяемые области данных или модули, которые не являются повторно используемыми, в сегментах, покрывающих все модули, в которых используются эти области или вызываются модули, не являющиеся повторно используемыми. Страничная организация и оверлейные структуры позволяют экономить быстродействующую память, а удачное распределение модулей по сегментам или страницам — процессорное время. В Алголе используется динамическая активация процедур, во многом аналогичная страничной организации и оверлейному управлению. Процедуры размещаются в памяти только тогда, когда они нужны. Каждая процедура загружается столько раз, сколько раз она вызывается. Если процедуру необходимо записать обратно на диск, не возвращая в исходное состояние, при описании переменных этой процедуры программист должен использовать атрибут own. Этим атрибутом должны быть снабжены переменные, значения которых сохраняются между вызовами. В некоторых версиях языка ПЛ/1 используется аналогичный метод динамической активации для блоков. Если блоки имеют локальные переменные, после выполнения эти блоки вновь записываются на диск, т. е. в программах, написанных на ПЛ/1, экономия памяти достигается путем использования блоков. Если же время для данной задачи является более важной характеристикой, рекомендуется применять DO-группы вместо блоков. 9.2. Экономия времени Использование несблокированных файлов и страничной организации для экономии памяти может привести к увеличению времени выполнения программы из-за низкого быстродействия устройств ввода-вывода. Если переопределить модули для предотвращения «пробуксовки» страниц и сблокировать данные, можно уменьшить время выполнения программы. К экономии времени приводит также размещение программ и данных на различных дисках. Если язык программирования допускает одновременное выполнение программы и операций доступа к данным, это также ведет к ускорению счета. 9.2.1. Доступ к данным При работе с магнитными лентами много времени тратится впустую на остановку и повторный запуск ленты. Так как скорость устройств ввода-вывода ниже скорости выполнения вы-
302 Глава 9 числений, лента может постоянно находиться в движении. Возврат и перемотку ленты следует исключать всегда, когда это возможно. Если информация с ленты должна считываться несколько раз или сначала должна производиться запись, а потом чтение, обработка ленты сначала в прямом, а затем в обратном направлении эффективнее перемотки. Если информация на ленте должна быть изменена, копирование содержимого ленты с включением изменений более эффективно, чем модификация записи в старом наборе. При работе с дисковой памятью основные задержки связаны t необходимостью передвигать головку записи-считывания на другую дорожку. Если под файл удается выделить один или несколько цилиндров, перемещения головки записи-считывания минимальны. Увеличивая размеры блоков, можно добиться уменьшения числа обращений к разным дорожкам при условии, что нужные записи находятся в одном и том же блоке. Организуя доступ к записям в соответствии с порядком их расположения на диске, удается минимизировать общее время доступа, которое определяется в этом случае только скоростью вращения диска. Для эффективного проведения модификации файла с прямым доступом .вспомогательные записи надо упорядочить так, чтобы одного обращения к диску было достаточно, когда на каждую запись основного файла приходится несколько вспомогательных, и одного обращения к диску хватило бы для чтения сблокированных записей главного файла для группы вспомогательных записей. Если записи не сблокированы или корректирующие записи поступают в случайном порядке, система сначала использует одно обращение к диску для определения нужной дорожки, а затем по крайней мере еще одно обращение для обнаружения искомой записи на этой дорожке. В том случае, когда системные средства не позволяют осуществлять блокировку файлов с прямым доступом, время, затрачиваемое на доступ, можно уменьшить, если использовать программы блокирования и разблокирования записей. Если мультипрограммный режим работы не поддерживается ЭВМ, определение местонахождения записи перед ее_ считыванием дает возможность сэкономить время. В мультипрограммной среде это может привести к лишним затратам времени, поскольку в промежутке между тактами выполнения команд определения местонахождения и чтения другой пользователь может обратиться к тому же пакету дисков. По той же причине в мультипрограммной среде размещение файла на одном или нескольких цилиндрах и разделение программ управления дисками могут не привести к уменьшению времени доступа.
Оптимизация программ 303 9.2.2. Организация доступа к программам Время, необходимое для доступа к программе оверлейной структуры или при страничной организации памяти, зависит от размеров модулей и от того, как они упорядочены. Желательно, чтобы модуль располагался на одной странице. Данные должны размещаться вместе с программами, которые их используют. Если массивы слишком велики и не помещаются на одной странице, обращение к их элементам должно осуществляться в том порядке, в котором они размещаются в памяти (для Фортрана — по столбцам, для других языков — по строкам). Оверлейные структуры и страничная организация памяти предназначены для экономии оперативной памяти. Но чтобы не привести к дополнительным затратам времени, они должны быть тщательно разработаны. 9.3. Повышение эффективности программ Анализируя все множество средств, используемых для оптимизации программы, можно сказать, что наибольшей экономии времени можно достичь, эффективно используя внешние устройства, поскольку именно небольшое быстродействие устройств ввода-вывода часто ведет к увеличению времени 'выполнения программы. Это связано с тем, что механические устройства работают значительно медленнее электронных. Однако, если программу надо сделать более эффективной, более вероятно, что к успеху приведет либо модификация алгоритма, либо выбор другого алгоритма, а не совершенствование самой программы. Программы для научных расчетов в большей степени, чем любые другие программы, зависят от быстродействия процессора. В случаях когда все средства уже использованы, но нужный результат не получен, может оказаться необходимой оптимизация текста программы. 9.3.1. Выбор алгоритма Как показано в гл. 5, часто для решения одной и той же задачи можно использовать несколько алгоритмов, которые могут иметь разную математическую сложность и требовать различных вычислительных ресурсов. Если, например, выполняется сортировка, ее можно быстрее выполнить в оперативной памяти, а не на ленте или диске. Также можно помещать данные порциями в оперативную память и там их сортировать. Эти операции требуют достаточно большого объема памяти для хранения данных. Разработано много алгоритмов для проведения сортировки в оперативной памяти. В книге Кнута (см. ссылку на стр. 154 гл. 5) показано, что одни алгоритмы эко-
304 Глава 9 номят время за счет памяти, а другие, наоборот, экономят память, но увеличивают время. Сложность такого рода алгоритмов сортировки различна. Даже если два алгоритма имеют одну и ту же сложность, один может быть предпочтительнее другого из-за того, что в большей степени соответствует данным, имеет меньший коэффициент пропорциональности в формуле, описывающей сложность, или снабжен большим числом эвристик, позволяющих ускорять сортировку. Выполнение наиболее простого алгоритма часто занимает очень много времени. СОВЕРШЕНСТВУЙТЕ АЛГОРИТМ Если программа работает в течение долгого времени и не выдает результатов, она, вероятно, зациклилась. Возможно также, что выбранный алгоритм, не соответствует конкретным данным. Так, один алгоритм сортировки может работать быстрее другого, если данные частично упорядочены. В пакетах программ, предназначенных для научных расчетов, для вычисления функции sin(jc) используются полиномы Чебышева, а не разложение в ряд, поскольку при больших значениях углов ряды сходятся слишком медленно и не только затрачивается много времени на вычисления, но результирующая погрешность больше из-за эффекта распространения ошибок. Для малых значений углов использование членов разложения в ряд дает удовлетворительные результаты. Для вычисления квадратных корней применяется итерационный метод, который и по точности, и по времени реализации предпочтительнее метода, использующего логарифмы и экспоненты. Выбрав удачное начальное приближение, можно уменьшить общее число итераций. Можно найти хорошее решение задачи преобразования таблицы решений в дерево решений, затратив на это меньше времени, чем необходимо для поиска оптимального решения. Экономия времени возможна и тогда, когда алгоритм для большинства входных данных работает лучше, чем предполагалось, даже если в некоторых случаях его производительность ниже ожидаемой. То, как запрограммирован алгоритм, в меньшей степени влияет на эффективность программы. В современных ЭВМ вызов подпрограмм и возврат из них осуществляется неэффективно, поскольку для проведения этих операций необходимо выполнить много машинных команд. Если функция должна вызываться много раз, можно сэкономить время, вызвав ее только один раз и используя первое значение этой функции для определения всех остальных ее значений. Поэтому итерационная форма алгоритма предпочтительнее рекурсивной формы. Значительно проще организована работа с процедурами в ЭВМ, снабженных аппаратно-реализованными стеками. В таких ЭВМ вызовы и возвраты осуществляются аналогично обычным вычислениям, использующим стеки для аргументов и
Оптимизация программ ^^ 305 промежуточных результатов. Выгоднее включать тексты подпрограмм в циклы и ветви условных операторов, чем вызывать их. Но, выбирая итерационный метод, а не рекурсивный, даже тогда, когда язык программирования допускает использование рекурсий, можно сделать программу менее понятной и сложной для сопровождения. Аналогично изменение программы с целью уменьшения числа вызовов может сделать ее слишком длинной и сложной для понимания. Процедура выбора алгоритма — это, по существу, определение того, что следует и чего не следует включать в алгоритм. В результате определяются общие контуры алгоритма. Предположим, что программа в процессе выполнения часто оперирует с календарем. В частности, она тратит время на то, чтобы определить, какие годы, делящиеся на 100 и не делящиеся на 400, являются високосными. На практике эффективнее было бы использовать системный параметр, позволяющий через каждые четыре года добавлять один день к февралю, чем каждый раз проверять, делится ли номер года на 4. В процессе выбора алгоритма следует принимать во внимание его сложность и степень общности. 9.3.2. Оптимизация текста программы Поскольку быстродействие современных ЭВМ очень велико, обычно не имеет смысла экономить время путем оптимизации текста программы. Перед тем как изменить рабочую программу, необходимо тщательно проверить, не приведет ли уменьшение времени выполнения программы на несколько миллисекунд к значительному увеличению средств, необходимых для отладки новой программы. Используя информацию операторов программы, обращающихся к таймеру, или данные о времени выполнения различных частей программы, можно выделить наиболее часто выполняемые части программы. После этого легко оценить, какая часть времени может быть сэкономлена. В первую очередь следует оптимизировать наиболее часто выполняемые части программы. ОПТИМИЗИРУЙТЕ НАИБОЛЕЕ ЧАСТО ВЫПОЛНЯЕМЫЕ ЧАСТИ ПРОГРАММ При написании программы часто нужно найти компромисс между временем, памятью и ясностью представления. Нельзя жертвовать ясностью программы, для того чтобы сэкономить несколько секунд на выполнение. Даже несмотря на то, что готовая программа редко оптимизируется программистом, необходимо хорошо знать методы оптимизации, поскольку это позволит лучше понять работу оптимизирующих компиляторов. Многие преобразования, выполняемые оптимизирующими компиля-
306 Глава 9 Таблица 9.1. Эффективность выражений Выражения 1) D = A + B; C=D*D—D + D 2)С = А*А + А*В + А*В + В*В 3) С = А * (А + В) + В * (А + В) 4)D = A+B;E = A + B;C = D*E 5)D = A + B;C = A*D + B*D 6) С = А*А + 2*А*В + В*В 7) С = А + В; С = С * С 8) С = (А + В) * (Л + В) 3) D = A + B; C = D*D 10) D = A + B; C = D**2 U) C = (A-t-B)**2 Мера Холстеда E 237,2 197,7 196,5 194,0 180,0 126,3 123,5 102,9 99,0 83,7 48,0 Число операторов «+,- * ./ •• 2 3 10 13 4 0 13 2 0 3 2 10 2 2 2 0 12 4 0 2 110 12 10 2 110 2 10 1 110 1 торами, могут быть испбльзованы программистом, применяющим язык высокого уровня, для того, чтобы создать более эффективную программу. Арифметические выражения. В табл. 9.1 приведено несколько групп операторов присваивания, вычисляющих одно и то же значение. Эти операторы располагаются в порядке убывания сложности, определяемой мерой Холстеда Е. В таблице также указано число базовых операций, используемых в каждой группе. Мера Холстеда Е служит характеристикой ясности программы, причем значения этой меры довольно хорошо согласуются с интуитивно определяемой относительной ясностью выражений. Скорость вычисления выражения зависит от числа содержащихся в нем операций каждого типа. Для присваивания значения переменной необходимо один раз обратиться к памяти, в то время как для выполнения арифметической операции может потребоваться одно-два или ни одного обращения к памяти. В среднем операция присваивания выполняется быстрее, чем сложение или вычитание. В свою очередь сложение и вычитание осуществляются быстрее, чем умножение и деление. Самой медленной является операция возведения в степень. Возведение в целую степень выполняется путем нескольких умножений. Поэтому можно сказать, что возведение в степень реализуется с помощью программы, включающей одно умножение, наращивание значения переменной цикла и проверку этого значения. Выражения 1—5 в меньшей степени похожи на выражения, предназначенные для вычисления (а+6)2, чем выражения 6— 11. Характерно, что наиболее эффективные выражения 6—10 имеют самые низкие значения меры Холстеда Е. Как показывают приведенные выше примеры, выражения можно сделать
Оптимизация программ 307 более эффективными, не жертвуя в то же время их ясностью» с помощью следующих приемов: объединения подобных членов, факторизации, устранения общих подвыражений, уменьшения числа сложных операторов, исключения различных типов данных, использования целых чисел в качестве показателей степени и изменения порядка следования членов в выражении. Даже частичная факторизация, проводимая по схеме Горнера, повышает эффективность вычисления полинома ax3 + bx2 + cx+d, поскольку выражение, используемое для непосредственного вычисления полинома А*Х**3 + В*Х**2 +C*X-f-D, может быть преобразовано к виду X*(X*(A*X + B) + C) + D. Первое выражение содержит две операции возведения в степень, три операции умножения и три операции сложения. Второе выражение содержит три операции умножения и три операции сложения. Поскольку первая форма записи полинома является наиболее распространенной, то, несмотря на большую эффективность второй формы, первая форма все же более предпочтительна. Устранение общих подвыражений, появляющихся в одном или соседних операторах, в настоящее время выполняется оптимизирующими компиляторами. Все остальные случаи появления общих подвыражений могут быть учтены самим программистом. Предположим, что задано выражение С(*+1)-л(*+1)+в(н-1). Более предпочтительная запись будет включать новый оператор, вычисляющий индекс только один раз: 6 = 1+1, C(k) = A(k)+B(k). В примере 9.1.а показано, как обычно используются формулы для определения корней квадратичных уравнений: #*.*, « л г, ^ЛЛ (Пример 9.1) (a)ifb**2-4.0*a *c>0.0 \ v v r then root-1 := (-b + sqrt(b ** 2 - 4.0 * a * c)) / (2.0 * a); root-2 := (-b - sqrt(b ** 2 - 4.0 * a * c)) / (2.0 * a) (b) discr := b * b - 4. * a * c; If discr > 0.0 then d := sqrt(discr);
308 Глава 9 two-a:=a + a; root-1 := (-b + d) /two-a; root-2 := (-b - d) / two-a Видно, что в операторах содержится много общих подвыражений. В примере 9.1.6 дан второй вариант программы вычисления корней квадратичного уравнения. В нем устранены все общие подвыражения, и функция, вычисляющая значение квадратного корня, вызывается только один раз. Также уменьшено число сложных операций за счет замены операции возведения в степень умножением b*b и умножение сложением а+а. Логические выражения можно оптимизировать, изменив порядок следования в них условий. Если р и q — условия и р принимает значение «истина» чаще, чем q, то if p or q выполнится быстрее, чем if q or p при условии, что ЭВМ прекращает вычисление составного условного выражения, когда результат уже ясен, и что ЭВМ просматривает выражение слева направо, т. е. в дизъюнктивном выражении простые условия должны стоять в порядке убывания вероятностей их появления. В конъюнктивном же выражении простые условия должны стоять в порядке возрастания вероятности их появления. Если р принимает значение «истина» чаще, чем q,.TO if q and p выполнится быстрее, чем if p and q поскольку, если q принимает значение «ложь», значения р уже можно не определять, a q с большей вероятностью, чем р, принимает значение «ложь». Следует учесть, что оптимальный порядок следования условий не всегда ускоряет вычисления. Повторения. Как уже говорилось выше, оптимизировать следует только часто выполняемые фрагменты программ, например циклы. При оптимизации циклов не следует жертвовать ясностью ради эффективности. Языковые конструкции должны использоваться только тогда, когда они являются наиболее подходящими. Если подходящих конструкций не существует, программист может использовать более эффективный способ управления циклом. Для любых способов управления циклом все стоящие внутри цикла операторы, выполнение которых не зависит от числа повторений цикла, должны быть выполнены до входа в цикл.
309 При программировании на языке Кобол часто встречаются такие ошибки, как заполнение пробелами строки, выводимой на АЦПУ, перед тем как записать в нее информацию в теле цикла. Но если все строки, выводимые на печать, имеют один и тот же формат, удобнее было бы заполнять пробелами область, состоящую из нескольких строк, один раз до входа в цикл. Примеры 9.2, а и б представляют собой два фрагмента программ, причем второй получен из первого с помощью удаления из цикла выражений 2*к и к—1. Вычисление значений этих выражений можно проводить за пределами цикла, поскольку они не зависят от управляющей переменной цикла: 'for I := 1 to 100 do n := k + к; (Пример 9.2) j := 2 * k + i; m := k- 1; A(j) :=k-1 for i := 1 to 100 do j := n + i; A(j) :=m а) Цикл б) Оптимизированный цикл Пример 9.3 показывает, как можно уменьшить число сложных операторов, стоящих внутри цикла, с помощью введения новой переменной: for i := 1 to 100 do j := 5 (Пример 9.3) A(i) :=5 *i far i := 1 to 100 do A(i):=j; J:=i + 5 а) Цикл б) Оптимизированный цикл Если цикл с управляющей переменной выполняется 1000 раз, столько же раз повторяется не только выполнение операций из тела цикла, но и наращивание управляющей переменной и проверка условия завершения. Число итераций можно существенно уменьшить, если воспользоваться методом, известным как метод развертывания цикла. Он заключается в том, что один и тот же оператор, стоящий в теле цикла, записывается для нескольких различных значений управляющей переменной и, таким образом, число итераций удается сократить в несколько раз. Этот метод иллюстрируется ниже: for i := 1 to 1000 do for i := 1 to 1000 step 2 do .(Пример 9.4) A(i) :=0 A(i) :=0; A(i + 1) :=0 а) Цикл б) Оптимизированный цикл
310 Глава 9 В примере 9.4, а неявно присутствуют 1000 операций сложения, проводимых с управляющей переменной и В примере 9.4, б также проводится 1000 операций сложения с управляющей переменной (500 неявных и 500 явных). Но число проверок условия завершения цикла в примере 9.4,6 в два раза меньше, чем в примере 9.4, а. Если тело цикла должно повторяться лишь несколько раз, когда цикл выполняется, более эффективно полностью развернуть цикл, т. е. записать непосредственно все выполняемые операторы. Для программ, большая часть времени выполнения которых приходится на циклы, оптимизация циклов может привести к значительной экономии времени. Хорошим примером является стандартный алгоритм умножения матриц. Если матрицы имеют размерность 50X50 (пример 9.5), внутренний цикл выполняется 125000 раз. Развертывание цикла приводит к тому, что тело цикла усложняется, но выполнение цикла будет осуществляться только 15625 раз, а число проверок условия завершения цикла уменьшится на 90%. for i := 1 to 50 do (Пример 9.5> for j := 1 to 50 do C(i,j):=0; for k := 1 to 50 do C(U):=C(i,j) + A(i,k)*B(k,j) а) Умножение матриц for i := 1 to 50 step 2 do ii:=i + 1; for j := 1 to 50 step 2 do ' . jj:=j + l; C(i,j):=0; C(i,jj):=0; C(ii,j):=0; C(ii,jj):=0; for k := 1 to 50 step 2 do kk:=k+1; C(i,j) :- C(i,j) + A(i,k) * B(kJ) + A{i,kk) * B(kkJ); C(i,Jj) := C(ijj) + A(i,k) * B(kjj) + A(ifkk) * B(kk.jj); C(iij) := C(ii,j) + A(ii,k) * B(k,j) + A(ii,kk) * B(kkJ); C(iijj) := C(iijj) + A(ii,k) * B(k,jj) + A(ii,kk) * B(kk,jj) б) Оптимизированное умножение матриц Если для выполнения программ использовать интерпретатор, а
Оптимизация программ 311 не компилятор, то первая программа будет выполняться быстрее второй, поскольку более компактная запись цикла позволит минимизировать время, требуемое для интерпретации операторов. К экономии времени приводит также переупорядочивание вложенных циклов таким образом, чтобы внешний цикл выполнялся как можно меньшее число раз. В примере 9.6. б внутренний цикл инициализируется 10 раз, а в примере 9.6, а — 20 раз: for j := 1 to 20 do for i := 1 to 10 do (Пример 9.6) for i := 1 to 10 do for j := 1 to 20 do A(i,|):«0 A(i,j):-0 а) Цикл б) Оптимизированный цикл Покажем, как можно уменьшить число инициализаций цикла на примере следующих друг за другом циклов. Слияние двух внешних циклов в примере 9.7 позволит уменьшить длину программы и снизить время выполнения второго внешнего цикла: iot i := 1 to 50 do for i := 1 to 50 do (Пример 9.7) for j := 1 to 50 do for j := 1 to 50 do A(u):=0; A(i,j):=0; for k := 1 to 50 do A(i,i) := 1 N A(k,k) := 1 -а) Цикл б) Оптимизированный цикл Выбор метода оптимизации зависит от вычислительной среды. Так, замена возведения в степень умножением оправдана, «ели компилятор не делает этого. При определенных способах представления чисел и аппаратной реализации умножение может выполняться так же быстро, как и сложение. Дублирование операторов в теле цикла для уменьшения числа итераций может использоваться только в тех случаях, когда программа компилируется; если же применяется интерпретатор, дублирование может привести к замедлению счета. Переупорядочивание вложенных циклов с целью уменьшения общего числа инициализаций циклов не всегда уменьшает время выполнения. Дело в том, что переупорядочивание может привести к тому, что элементы большого массива будут запрашиваться не в том порядке, в котором они хранятся. Тогда в виртуальной среде время выполнения может возрасти из-за дополнительных обращений к страницам памяти.
812 Глава 9 9.3.3. Повышение эффективности алгоритмов Предположим, что имеются два алгоритма. Время работы одного пропорционально полиномиальной функции, а время работы второго — линейной функции времени. В этом случае неоправданными будут попытки выиграть несколько миллисекунд путем изменения программы, реализующей первый алгоритм. Если лучшего алгоритма нет, целесообразно использовать эвристики для повышения эффективности работы алгоритма. Если и эта возможность уже исчерпана, можно проводить модификацию исходного текста программы (а не объектного). Если нет настоятельной необходимости в повышении эффективности программы, модификацию текста не следует проводить. Целью модификации должна быть экономия времени выполнения наиболее часто используемых частей программы без ущерба для ясности и общепринятых стандартов. Бессмысленно оптимизировать редко используемые части программы, поскольку удается сэкономить очень мало времени, но зато в текст программы могут быть внесены дополнительные ошибки. СТАРАЙТЕСЬ БЕЗ КРАЙНЕЙ НЕОБХОДИМОСТИ НЕ ИЗМЕНЯТЬ РАБОТАЮЩУЮ ПРОГРАММУ Изменения, вносимые в структуру данных или в алгоритм^ могут существенно улучшить программу. Переупорядочивая вложенные циклы, можно уменьшить число проверок условий завершения внутренних циклов. Этого можно достичь также путем изменения структуры данных и алгоритма. Для уменьшения числа проверок внутренних циклов в некоторых задачах можно использовать различные методы поиска. Линейный поиск в таблице заканчивается, когда нужный элемент найден или вся таблица просмотрена. Следовательно, необходимо постоянно проверять два условия: во-первых, является ли текущий элемент искомым и, во-вторых, есть ли еще элементы в таблице. Размещая искомый элемент непосредственно за последним элементом таблицы, можно гарантировать, что он будет найден, и отпадает необходимость проверять, все ли элементы таблицы просмотрены. В этом случае следует различать две ситуации, которые могут возникнуть после завершения выполнения цикла. Чтобы учесть их, необходимо сделать небольшие изменения в первоначальной программе, которая завершается при нескольких условиях. Если должно быть считано не более 100 значений и искомое значение ожидается, но может быть пропущено, все три условия можно использовать для присваивания значения 100 (или 101) управляющей переменной, поэтому в процессе контроля завершения выполнения цикла потребуется только одна проверка.
Оптимизация программ 31& В играх, использующих игральные доски с полями, подобная ситуация может возникнуть при проверке выхода за пределы доски. Если игровая часть доски обрамлена буферными полями, выход за пределы игровой части может обрабатываться подобно обычному движению по доске. На рис. 9.4, а показан лабиринт, движение по которому должно контролироваться- (например, путем проверки значений индексов), чтобы предотвратить выход за пределы доски. В лабиринте, показанном на рис. 9.4,6 выход за пределы контролируется так же, как и любое другое движение по лабиринту, т. е. очередная позиция анализируется и устанавливается, принадлежит ли она возможному пути, является ли целевой или нет. Рис. 9.4. Обрамление доски дополнительными полями, в —лабиринт; б — расширенный лабиринт. Изменив алгоритм таким образом, чтобы как можно большая часть работы выполнялась на стадии компиляции, можно сэкономить время. Однако, перекладывая на компилятор работу по инициализации переменных, удается сэкономить очень мало времени, так как инициализация является однократно выполняемой операцией. Если и константы, и переменные инициализируются во время компиляции, это приводит к достаточно запутанному коду. Кроме того, это является причиной трудностей, возникающих при реализации оверлейных структур. Разбиение программы на модули оказывает влияние на ее эффективность. Структура модулей и способ управления ими отражаются на эффективности страничной и оверлейной организации. От способа разбиения текста программы на модули зависит время выполнения. Если программа написана на языке Ассемблер, ее можно разбить на модули так, что дополнительного времени на выполнение не потребуется. Это достигается за счет использования макроопределений или вызовов модулей, не выполняющих вспомогательные функции. Последнее приводит к слабой модульной независимости. Большинство языков высокого уровня не имеет макросредств. Для передачи или приема управления частью программы, трактуемой как отдель- 21-399
314 Глава 9 «ый модуль, желательно не использовать оператор GOTO. В Коболе оператора GOTO нет. Поэтому ссылки к совместно используемым или независимым частям программы производятся с помощью оператора PERFORM. Параграф языка Кобол, который приводится в действие оператором PERFORM, можно считать, специальным типом внутренней процедуры. В зависимости от реализации использование внутренних процедур вместо внешних позволяет сэкономить время, требуемое на связь между модулями, не нарушая при этом модульности исходной программы. Правда, ошибки во внутренних процедурах более вероятны, поскольку в них обычно есть ссылки к глобальным данным. Если процедура вызывается из тела цикла, наилучший способ экономии времени заключается в организации цикла внутри процедуры, как показано в примере 9.8: procedure P(k); procedure P; (Пример 9.8) ... for i := 1 to 100 do end P; ... fori := 1 to 100 do end P; call P(i) call P а) Цикл . б) Оптимизированный цикл 9.4. Средства оптимизации Перед тем как начать проводить изменения файлов или текста программы, программист должен убедиться в том, что эти изменения действительно необходимы. Иногда лучшим выходом является изменение вычислительной среды. Обычные статистические характеристики, которые можно получить с помощью компиляторов и операционных систем, показывают, эффективно ли работает программа в данной вычислительной среде. К таким характеристикам относятся не только время, объем памяти, но и информация об использовании периферийных устройств. Если любые из этих величин оказываются слишком большими, следует проводить оптимизацию. Программные средства, позволяющие определять время выполнения программ и подсчитывать количество страничных прерываний, можно использовать для определения типа необходимой оптимизации. Глобальная оптимизация необходима в том случае, если программа, подготовленная для работы в режиме интерпретации, будет использоваться в режиме компиляции, поскольку в этом случае существенно уменьшается время выполнения и увеличивается объем требуемой оперативной и внешней памяти. Если используется виртуальная память или оверлейная организация, увеличение размера области памяти, выделяемой за-
Оптимизация программ 315 даче, и новая сегментация могут позволить уменьшить число обращений к дисковой памяти. Помещая все наборы данных и программу в один и тот же пакет дисков и выделяя память для них целыми цилиндрами, можно уменьшить сумму перемещений головок записи-считывания и, следовательно, уменьшить общее время доступа. Наилучшим способом повышения эффективности исходного текста программы является использование оптимизирующего компилятора. Наибольший выигрыш получается в результате оптимизации отдельных операторов исходной программы. Многие компиляторы стремятся получить такой объектный код, чтобы большая часть операций проводилась с быстродействующими регистрами, а не с оперативной памятью. Некоторые компиляторы проводят оптимизацию целиком всей программы. Они могут обнаруживать наличие неиспользуемых, но объявленных имен и удалять их, освобождая тем самым оперативную па~ мять. СТАРАЙТЕСЬ НЕ ОПТИМИЗИРОВАТЬ ТЕКСТ ПРОГРАММЫ СВОИМИ СИЛАМИ 9.5. Документирование показателей эффективности программы Оптимизация является попыткой повысить эффективность программы, т. е. уменьшить количество требуемых для ее выполнения ресурсов. Эти требования должны найти отражение в документации. Если для определения показателей эффективности выполняются контрольные прогоны, их результаты должны также быть включены в документацию. Если эффективность программы зависит от типа компилятора, системного программного обеспечения, описания и размещения наборов данных на магнитных носителях, это должно быть отражено в документации. Описание операционной среды, используемой для контрольных прогонов, должно быть включено в описание процедуры тестирования. На рис. 1.2 (гл. 1) для описания требований к операционной среде используется отдельный блок. Такие характеристики рабочей программы, как время, объем памяти, требуемое системное программное обеспечение, говорят о том, удовлетворяет ли программа этим требованиям. На рис. 1.2 приведен также блок, характеризующий надежность системы. Здесь под надежностью понимается в первую очередь нечувствительность к отказам, а также степень общности системы. Если система предназначена только для определенных данных, это должно быть отражено при описании характеристик надежности. В документацию следует включать описания возможных спо- 21*
316 Глава 9 собов оптимизации программы только тогда, когда программа используется неэффективно или из-за неудач может возникнуть неверное представление о проекте в целом. Если структура данных и алгоритмы ориентированы на конкретные данные, описание системы должно быть достаточно подробным, чтобы система могла легко адаптироваться к изменениям данных. 9.6. Упражнения 1. Если у вас нет виртуальной ЭВМ, возьмите программу, разбитую на модули, организуйте оверлейную структуру и прогоните программу. Сколько памяти удалось сэкономить? Как много времени затрачено на выполнение программы? 2. Если у вас виртуальная ЭВМ, измените модульную структуру уже существующей программы таким образом, чтобы уменьшить число страничных прерываний. Сколько времени удается сэкономить таким способом? 3. Напишите программу размещения 1000 записей, в последовательном файле. Сравните время, необходимое для выполнения программы, и память, необходимую для хранения как программ, так и файла, если записи а) не сблокированы, б) сблокированы с разными характеристиками блокировки. 4. Выполните упражнение 3 для файла с прямым доступом. Считайте, что записи могут поступать в произвольном порядке. Если ваша операционная система не позволяет блокировать файлы с прямым доступом, осуществите блокирование программным путем. 5. Проведите компиляцию программы с помощью обычного и оптимизирующего компиляторов. Используйте несколько типов оптимизации. Сравните память, необходимую полученным программам, а также время их выполнения. 6. Проведите компиляцию программы с помощью оптимизирующего компилятора. После этого проведите оптимизацию программы самостоятельно и откомпилируйте ее с помощью стандартного компилятора. Сравните результаты. 7. Приведите документацию, относящуюся к показателям эффективности «акой-нибудь программы.
СПИСОК ТЕРМИНОВ Абстрактная структура данных: структура данных, определенная функционально, т. е. посредством выполняемых над ней операций. Подобная структура не зависит от способа ее реализации. Агрегирование: группирование данных, при котором не обязательно наличие какой бы то ни было логической взаимосвязи данных. Ада: созданный в последние годы универсальный алгоритмический язык, применяемый при разработке как системного, так и прикладного программного обеспечения. Алгол: алгоритмический процедурно-ориентированный язык программирования. Получил более широкое распространение в европейских странах, чем в США. Алгоритм: формализованное описание содержания и последовательности выполнения операций, реализующих некоторый метод решения задачи. Данное описание может быть представлено в виде набора математических зависимостей, текста на псевдокоде или одном из языков программирования и т. д. База данных: совокупность структурированных данных, используемых в разнообразных приложениях. Бейсик: алгоритмический язык для научных расчетов, ориентированный на использование индивидуальных терминалов. Получил широкое распространение на малых ЭВМ. Виртуальная машина: вычислительная система, обеспечивающая выполнение крупных заданий при небольшом объеме оперативной памяти. Программы автоматически расчленяются на помещающиеся в памяти страницы, которые загружаются в нее по мере необходимости. Восходящий метод: метод разработки, реализации и тестирования программ, опирающийся на первоначальное изучение самых конкретных, частных деталей и последовательный переход к более общим аспектам. Граф-диаграмма: схема, на которой с помощью кружков и стрелок изображаются процессы прохождения данных через систему. Графическая схема: древоподобная схема, иллюстрирующая структуру документа или набора данных. Диаграмма Варнье — Орра: иерархическая система контурных изображений, опирающаяся на структуры входных и выходных данных. Каждое изображение детализирует предыдущее. Диаграмма потоков данных: схема, на которой указаны входные и выходные данные программного модуля, наборы данных и потоки данных. Драйвер: фиктивная управляющая программа, используемая при восходящем кодировании. Осуществляет вызовы тестируемых подпрограмм. Заглушка: фиктивная подпрограмма, используемая при нисходящем кодировании программного модуля. Ее вызов осуществляет тестируемый модуль. Защищенность: способность программы сохранять работоспособность при наличии системных отказов. Иерархическая диаграмма: древоподобная схема, показывающая, какие программные модули осуществляют вызовы других модулей, и каких именно. Интерпретатор: системная программа, осуществляющая пооператорное
818 t Список терминов выполнение программы на языке высокого уровня без перевода ее в машинный код. Исключительное состояние: ошибка обработки или необычное событие* которое должно быть опознано и соответствующим образом обработано. Исходный код: закодированный текст на языке высокого уровня, служащий входной информацией для компилятора или интерпретатора. Итерации: способ решения задачи, основанный на последовательном применении одного и того же алгоритма к ряду элементов данных. Для формального описания этого способа могут использоваться любые стандартные циклические конструкции. Индексированный файл: система хранения файлов на дисках, в которой применяются справочники, позволяющие реализовать как последовательный» так и прямой доступ к данным. При прямом доступе информация в поле данных используется в, качестве ключа. Кобол: наиболее широко применяемый для решения экономических и коммерческих задач язык программирования. Приобрел популярность благодаря использованию упрощенных конструкций английского языка. Компилятор: обрабатывающая программа, осуществляющая перевод программ с языка высокого уровня в машинный код. Конструкция case: стандартная логическая конструкция, предназначенная для одной из некоторой совокупности альтернатив. Конструкция if...then...else: стандартная условная конструкция, предназначенная для выбора одной из двух возможных альтернатив. Конструкция repeat...until: стандартная циклическая конструкция с проверкой условия на выходе. Последнее означает, что тело цикла должно выполниться по меньшей мере один раз. Конструкция while...do: стандартная циклическая конструкция с проверкой условия на входе. Последнее означает, что тело цикла может ни разу не выполниться. Модуль: базовый функциональный элемент программы. Модули разрабатываются, кодируются и испытываются по отдельности. Мультипрограммная система: вычислительная система, осуществляющая параллельную обработку нескольких заданий одновременно. Надежность: показатель, характеризующий степень повторяемости результатов, выдаваемых программой, при различных условиях функционирования. Нестандартная функция: функция, порождающая побочные эффекты. Она не только возвращает значение в точку вызова, но может также выполнять операции ввода/вывода, изменять значения некоторых внутренних параметров или глобальных данных. Нисходящий метод: метод разработки, реализации и тестирования программ, опирающийся на первоначальное изучение всей проблемы в целом и последовательный переход к более частным аспектам. Объектный код: обработанная компилятором программа, представленная в машинном коде. Оверлеи: указанные пользователем сегменты программы, загружаемые по мере необходимости из дисковой в быстродействующую оперативную память. Отладка: процесс локализации и идентификации ошибок в программе. Паскаль: компактный высокоструктурированный язык программирования, являющийся развитием Алгола. В основном используется на малых машинах и в сфере обучения. ПЕРТ-диаграмма: схема, состоящая из кружков и стрелок. Отображает временные связи между отдельными этапами обработки. ПЛ/1: мощный многоцелевой язык программирования, в котором оочета*
Список терминов 319 аотся и развиваются средства Кобола, Фортрана и Алгола. В основном применяется на вычислительных установках, выпускаемых фирмой IBM1^ Повторно используемый код: программный код, в котором предусмотрено >выполнение операций присваивания, обеспечивающее неизменность результатов при каждом вызове кода с одним и тем же набором входных данных. Погрешность: показатель, характеризующий отклонение числового значения результата от истинной величины. Подобное отклонение может быть обусловлено как особенностями используемого вычислительного алгоритма, так и ошибками, связанными с ограниченной разрядностью ЭВМ. Подпрограмма: отдельно компилируемая программа, содержащая один •или несколько модулей. Последовательный доступ: способ обращения к отдельным элементам файла или массива, основанный на последовательном просмотре всех предшествующих элементов. Последовательный файл: система хранения записей, обеспечивающая обращение к ним только с помощью последовательного доступа. Права: наличие разрешения на использование системных ресурсов для .выполнения определенных действий. Правильность: показатель, характеризующий степень соответствия программы требованиям технического задания. Предсказуемость: модуль называют предсказуемым, если его функционирование определяется только параметрами, в то время как внешняя программная среда не оказывает на него влияния. Прямой доступ: способ обращения к отдельным элементам файла или массива не путем последовательного просмотра, а непосредственно, с помощью соответствующих ключей. Псевдокод: подробное описание алгоритма на структурированном и частично формализованном подмножестве английского языка. Программа: функционально законченная совокупность модулей или подпрограмм. Реентерабельный код: программный код, не содержащий никаких локальных переменных или любых запоминаемых внутри него данных. Рекурсия: способ решения задачи, основанный на применении алгоритма к данным, полученным с помощью того же алгоритма. Для формального описания этого способа могут использоваться либо рекурсивные функции, либо вызовы программных модулей, в которых в качестве параметров фигурируют имена тех же модулей. Связность: мера внутренней целостности программного модуля. Показатель, характеризующий степень взаимосвязи отдельных частей программы. Сеть Петри: схема, позволяющая путем перемещения специальных значков показать, как синхронизованы между собой различные этапы обработки. Система: совокупность связанных друг с другом программ и наборов данных. Словарь данных: таблица, содержащая такие атрибуты элементов данных, как их имена, типы, размерности, форматы, диапазоны изменения значений, допустимые способы использования. Совместимость: показатель, характеризующий возможность правильной работы модуля в качестве составной части программной системы. Страничный обмен: автоматическое перемещение необходимых сегментов программы из дисковой памяти в оперативную. Страничное прерывание: в виртуальных машинах — вызов сегмента программы, который в данный момент отсутствует в оперативной памяти. Структурная схема: схема, на которой с помощью стрелок и блоков различной формы отображаются потоки данных, проходящих через систему, или передачи управления внутри программы. *> Язык ПЛ/1 входит в состав программного обеспечения ЭВМ Единой Серии, производимых в странах СЭВ. — Прим. перев.
320 Список терминов Схема «исток — преобразование — сток»: построенная по иерархическому принципу схема, получаемая на основе анализа процессов, в результате которых входные данные преобразуются в выходные. Схема Джексона: построенная по иерархическому принципу схема, получаемая на основе анализа структур входных и выходных данных. Схема Насси — Шнейдермана: схема, иллюстрирующая структуру передач управления внутри модуля с помощью вложенных друг в друга блоков. Сцепление: мера взаимонезависимости программных модулей. Показатель, определяемый характером межмодульных связей. Таблица решений: таблица, отображающая зависимость действий, подлежащих выполнению, от условий, при которых они могут выполняться. Точность: показатель, характеризующий возможность разделения близ> ких числовых значений. Часто он определяется как количество значащих цифр. Универсальность: показатель, характеризующий возможность правильной работы программы при обработке различных вариантов исходных данных. Управляющее поле: поле входных данных, используемое для группирования данных. Изменение содержимого этого поля вызывает управляющее прерывание. Управляющее прерывание: временное прерывание процесса обработки, обусловленное изменением значения определенного элемента данных. Файл с относительной нумерацией: система хранения файлов на дисках, обеспечивающая как последовательный, так и прямой доступ к данным. При прямом доступе ключом служит порядковый номер записи в файле. Фортран: язык программирования, получивший наибольшее распространение в области научно-технических расчетов. В этом языке предусмотрен ши* рокий набор разнообразных математических функций. Черный ящик: программный модуль, набор данных или устройство, о которых не известно ничего, кроме спецификаций входной и выходной информа* ции. Чистый код: программный код, в котором после его вызова не запоминаются никакие числовые значения. Шифрование: метод кодирования сообщений, позволяющий предотвратить их перехват пользователями, не обладающими соответствующими правами. Эвристика: неформальный метод преобразования алгоритма с целью повышения его эффективности. Эффективность: показатель, характеризующий быстродействие и объем используемой памяти.
ЛИТЕРАТУРА ПРЕДИСЛОВИЕ Brooks F. P., Jr., The Mythical Man-Month. Reading, Mass.: Addison-Wesley, 1975. [Имеется перевод: Брукс Ф. П., Как проектируются и создаются программные комплексы: мифический человеко-месяц. Очерки по системному программированию. — М.: Наука, 1979.] Maly К-, Hanson A. R., Fundamentals of the Computing Sciences, Englewood Cliffs, N. J.: Prentice-Hall, 1978. Weinberg G. M., The Psychology of Computer Programming, New York: Van Nostrand Reinhold, 1971. Глава 1 Bohl M., A Guide for Programmers, Englewood Cliffs, N. J.: Prentice-Hall, 1978. Glass R. L., Software Reliability Guidebook, Englewood Cliffs, N. J.: Prentice- Hall, 1979. [Имеется перевод: Гласе Р., Руководство по надежному программированию. — М.: Финансы и статистика, 1982.] Myers G. J., Software Reliability: Principles and Practices, New York: Wiley, 1976. [Имеется перевод: Майерс Г., Надежность программного обеспечения.—М.: Мир, 1980.] Tausworthe R. С, Standardized Development of Computer Software, Parts I and II. Englewood Cliffs, N. J.: Prentice-Hall, 1979. Van Duyn J., Documentation Manual, Philadelphia: Auerbach, 1972. Глава 2 Atwood J. W., The Systems Analyst, Rochelle Park, N. J.: Hayden, 1977. Bauer F. L., ed., Advanced Course of Software Engineering, New York: Sprin- ger-Verlag, 1973. Bergland G. D., Gordon R. D., eds., Software Design Strategies, New York: IEEE Computer Society, 1979. Freeman P., Wasserman A. I., Tutorial on Software Design Techniques, New York: IEEE Computer Society, 1980. Gane Ch., Sarson Т., Structured Systems Analysis: Tools and Techniques, Englewood Cliffs, N. J.: Prentice-Hall, 1979. Yeh R. Т., ed., Current Trends in Programming Methodology, Vols. I and II, Englewood Cliffs, N. J.: Prentice-Hall, 1977. Глава 3 Gane Ch., Sarson Т., Structured Systems Analysis: Tools and Techniques, Englewood Cliffs, N. J.: Prentice-Hall, 1979. Hoare C. A. R„ Notes on Data Structuring, in Structured Programming, O. J. Dahl, E. W. Dijkstra and C. A. R. Hoare, eds., New York: Academic Press, 1972. Kaimann R. A., Structured Information Files, Los Angeles: Melville, 1973. Preliminary ADA Reference Manual, SIGPLAN Notices, 14, № б (1979). Wirth N., Jensen K., Pascal —User Manual and Report, New York: Springer- Verlag, 1975. [Имеется перевод: Иенсеи К., Вирт Н., Паскаль: Руководство для пользователей и описание языка. — М.: Финансы и статистика, 1982.] Глава 4 Bergland G. D., Gordon R. D., eds., Tutorial on Software Design Strategies, New York: IEEE Computer Society, 1979. Freeman P., Wasserman A. I., eds., Tutorial on Software Design Techniques, New York: IEEE Computer Society, 1980. Jackson M., Principles of Program Design, London: Academic Press, 1975.
322 Литература Kernighan В. W., Plauger P. L., Software Tools, Reading, Mass.: Addison-Wes- ley, 1976. . ^ Maynard J., Modular Programming, Princeton, N. J.: Auerbach, 1972. Myers G. J., Reliable Software through Composite Design, New York: Petro- celli/Charter, 1975. Myers G. J., Software Reliability: Principles and Practices, New York: Wiley> 1976. [Имеется перевод: Майерс Г., Надежность программного обеспече* ния. —М.: Мир, 1980.] Nam P., Programming by action clusters, BIT, 9 (1969), pp. 250—258. Parnas D. L., On the criteria used in decomposing systems into modules, Communications of the ACM, 15, № 12 (1972), pp. 1053—1058. Parnas D. L„ A Technique for software module specification with examples* Communications of the ACM, 15, № 5 (1972), pp. 330—336. Yourdon E., Techniques of Program Structure and Design, Englewood Cliffs, N. J.: Prentice-Hall, 1975. Yourdon E., Constantine L. L„ Structured Design, Englewood Cliffs, N. J.: Prentice-Hall, 1979. Wirth N., Program development by stepwise refinement, Communications of the ACM, 14, № 4 (1971), pp. 221—227. Глава 5 Aho A. V., Hopcroft J. E., Ullman J. D., The Design and Analysis of Computer Algorithms, Reading, Mass.: Addison-Wesley, 1974fr [Имеется перевод: Ахо А. и др. Построение и анализ вычислительных алгоритмов. — Mr. Мир, 1979.] Ernst G. W., Newell A., GPS: A Case Study in Generality and Problem Solving^ New York: Academic Press, 1969. Floyd R. W., The paradigms of programming, Communications of the ACM, 22,. № 8 (1979). Goodman S. E., Hedetniemi S. Т., Introduction to the Design and Analysis of Algorithms, New York: McGraw-Hill, 1977. [Имеется перевод: Гудман С.„ Хидетниеми С, Введение в разработку и анализ алгоритмов. — М.: Мир,. 1981.] Jackson P., Introduction to Artificial Intelligence, New York: Petrocelli Books* 1974. Knuth D. E., The Art of Computer Programming, Vols. I—III, Reading, Mass.: Addison-Wesley, 1969. [Имеется перевод: Кнут Д., Искусство программирования для ЭВМ, т. 1. — М.: Мир, 1976; т. 2, Получисленные алгоритмы,. 1977; т. 3, Сортировка и поиск, 1978.] Polya G., How to Solve It, Garden City, N. Y.: Doubleday, 1957. Трахтенброт Б. А., Алгоритмы и машинное решение задач; Под ред. С. В. Яблонского— 2-е изд. — М.: Физматгиз, 1960. Wetherell Ch., Etudes for Programmers, Englewood Cliffs, N. J.: Prentice-Hall^ 1978. Глава 6 American National Standard Flowchart Symbols, ANSI-X 3.5-1970, New York: American National Standards Institute, 1970. Bohl M., Flowcharting Techniques, Chicago: Science Research Associates, 197L Boillot M. H., Gary M. G., Horn L. W., Essentials of Flowcharting, Dubuque, Iowa: William C. Brown, 1975. Farina M. V., Flowcharting, Englewood Cliffs, N. J.: Prentice-Hall, 1970. Ganapathy S., Rajaraman V., Information theory applied to the conversion ofc decision tables to computer programs, Communications of the ACM, 16, № 9 (1973), pp. 532—539. Humby £., Programs from Decision Tables, New York: American Elsevier,, 1973. Kernighan B. W., Plauger P. J., Programming styles, examples and counter* examples, ACM Computing Surveys, 6, № 4 (1974), pp. 303—319.
Литература 323 Kernighan В. W., The Elements of Programming Style, New York, McGraw-Hill, 1974. London K. R., Decision Tables, Princeton, N. J.: Auerbach, 1972. McDanie H., An Introduction to Decision Logic Tables, New York, Wiley, 1968. Nassi, I., Schneiderman В., Flowchart techniques for structured programming, SIGPLAN Notices, 8, № 8 (1973), pp. 12—26. Pooch U. W., Translation of decision tables, ACM Computing Surveys, 6, № 2 (1974), pp. 125—151. Wirth N., On the Composition of well-structured programs, ACM Computing Surveys, 6, № 4 (1979), pp. 247—259. Глава 7 Barron D. W., Recursive Techniques in Programming, New York, American Elsevier, 1968. [Имеется перевод: Баррон Д., Рекурсивные методы в программировании. — М.: Мир, 1974.] Gilb Т., Software Metrics, Englewood Cliffs, N. J.: Prentice-Hall, 1972. Dijkstra E. W., GOTO statement considered harmful, Communications of the ACM, И, № 3 (1968), pp. 147—148. ч Fitzsimmons A., Love Т., A review and evaluation of soitware science, ACM Computing Surveys, 10, № 1 (1978), pp. 3—18. Halstead M. H., Elements of Software Science, New York: Elsevier North-Holland, 1977. [Имеется перевод: Холстед М. X., Начало науки о программах.— М.: Финансы и статистика, 1981.] Kernighan В. W., Plauger P. J., The Elements of Programming Style, New York: McGraw-Hill, 1974. Knuth D. E., Structured programming with GOTO statements, ACM Computing Surveys, 6, № 4 (1974). Ledgard H. F., Programming Proverbs, Rochelle Park, N. J.: Hayden, 1975. Глава 8 Dodes I. A., Numerical Analysis for Computer Science, New York: North-Holland, 1978. Hantler S. L., King J., C, An Introduction to proving the correctness of programs, ACM Computing Surveys, 8, № 3 (1976), pp. 331—353. Hastings C., Jr., Approximations for Digital Computers, Princeton, N. J.: Princeton University Press, 1955. Hetzel W. C, ed., Program Test Methods, Englewood Cliffs, N. J.: Prentice- Hall, 1973. Hildebrand F. В., Introduction to Numerical Analysis, New York: McGraw-Hill, 1956. IBM System/360 Operating System FORTRAN IV Library: Mathematical and Service Subprograms, White Plains, N. J.: IBM. lempel A., Cryptology in transition, ACM Computing Surveys, 11, № 4 (1979), pp. 285—303. JVlyers G. J., Reliable Software through Composite Design, New York: Petrocelli/ /Charter, 1975. JVlyers G. J., Software Reliability: Principles and Practices, New York: Wiley, 1976. Myers G. J., The Art of Software Testing, New York: Wiley, 1979. [Имеется перевод: Майерс Г., Искусство тестирования программ. — М.: Финансы и ста- тистика, 1982.] Тлава 9 Allen F. Е., Cocke J., A catalogue of optimizing transformations, in Design and Optimizations of Compilers, Randall Rustin, ed. Englewood Cliffs, N. J.: Prentice-Hall, 1972. Denning P., Virtual memory, ACM Computing Surveys, 2, № 3 (1970).
ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ Абстрактная структура данных 317 Агрегирование 317 Ада 16, 81, 237, 317 Адаптация процедур ручной обработки 17 Адаптируемость 13 Алгол 301, 317 Алгоритм 317 — выбор 303—305 — детерминированный 140—143 ч — недетерминированный 140—143 —- повышение эффективности 312—316 — по природе недетерминированный 141 — эффективность 143 Анализ сообщений 107—110, 117 Аппроксимация 266—268 Архив данных 13 База данных 63—64, 317 Бейсик 79, 92, 189, 317 Блокирование 96, 153, 295 Ведущая группа 30 Виртуальные операционные системы 296, 297, 317 Внутренние процедуры 314 Возврат 191, 237—238 Восходящий метод 317 — кодирования 218 — проектирования 125, 126 Вспомогательные средства реализации 248—251 Вызов модуля 237—239 — по значению 237 — по значению-результату 237 — по имени 237 — по ссылке 237 Граф-диаграмма 43, 71, 123, 317 Графическая схема задания 25, 31, 77, 317 Граф потоков данных 43 Данные глобальные 126—127 — доступ 301, 302 — иерархическая схема 119—121 — информационные связи 132—134 — коллективный доступ 42 — оптимизация 303, 304 — проверка правильности 216, 225—226, 253-254 — разработка 42 — рекурсивное определение 59—60 — словарь 74, 75, 319 — структуры 58 — типы 83—87 — толерантность 256—257 Дейкстра 103 Дерево решений 131 Джексон 118 Диаграмма Варнъе — Орра 43, 131, 317 Диаграмма потоков данных 317 Диск 97, 301, 302 Документирование алгоритмов 179 — данных 98 — исходных текстов 244—248 — модулей 99, 214—216 — показателей эффективности 315 — программ 99, 135—137 — проекта 55 — пролог 244, 245 — системы 30 Заглушка 218, 317 Задачи (см. Примеры): библиотечная ев» стема регистрации 33, 56, 100 — вычисление синуса 156, 158, 180 среднего 180 функции Аккермана 180 числа перестановок 142—149, 252 — генерация случайных чисел 180 — десятичные значения римских цифр 205, 206, 216, 292 — задача о рюкзаке 180 — зондирование Венеры 249 — китайские шашки 137, 180, 252 — кратчайший путь через лабиринт 157— 159, 163, 180 — печать таблицы треугольников 138 — печать частот появления слов 138 — программы спулинга 56 — разделение на страницы 77, 78, 123— 125, 180 — система ведения документации 44, 56 — система предварительного заказа теннисных кортов 33, 100 — система регистрации избирателей 20—22, 100, 138 — система регистрации студентов 100 Защита информации 28, 50 Защищенность 12, 317 Значения ссылок 221 Иерархическая диаграмма 317 Изменение глобальных переменных 238 Инварианты цикла 279 Инвертирование программы 124, 125 Интерпретатор 317 Информационный поток 18 Исключительные состояния 181, 191, 200 Исходный код 318 Итерации 147, 151, 318 Качество программного обеспечения 10 — программной системы 11 Ключ доступа К файлу 93—95, 98 Кобол 79, 91, 187, 221, 228—229, 235—236, 247, 300, 309, 318 Комментарии 246—248 Компилятор 318 Константы 220—222 Конструкция следования 183—185, 196, 211 — case 184-191, 195, 212, 318 — if ... then ... else 184, 318 — repeat ... until 184, 318 — while ... do 184, 212, 318 Контрольная сумма 225, 226 Контрольная цифра 225 Координация считывания/записи 51 Критический путь 29 Линия перехода 199 Магнитная лента 46, 91, 97 Машина Тьюринга 177
Предметный1 указатель 32S Машинные средства 248—251 Меры Холстеда 241—244, 252, 285 Метод выделения подцелей 172—175 — динамического программирования 165— 168 — моделирования 175—179 — наискорейшего спуска 160—164 — Ньютона — Рафсона 151, 158 — обратного прохода 164, 165 — Парнаса 137 — поиска с возвратом 168—172 — последовательных приближений 158—160 — «разделяй и властвуй> 155—158 Мнемонические имена 223 Множество 79, 81—82 Моделирование дискретных событий 175— 179 Моделирование переходов состояний 175— 179 Модуль 318 — длина 240—244 — завершение выполнения 282 —• иерархическое проектирование 119—125 — независимый 113—117 — нумерация 109, 136 — объем 242 — ошибки объединения 285 — предсказуемый 115, 319 — сила связности 111 — сложность 243 — спецификация 118—119 — уровень записи 242, 243 Модуляризация 101 Мультипрограммная система 318 Набор данных 85 Надежность 12, 28, 253, 318 Неоднозначность имен 238 Нестандартная функция 238, 318 Нисходящий метод 318 — кодирования 217—218 — проектирования 103 Нормальная форма 66 Область управления 127—128 — влияния 127—128 Обработка входных данных 226, 227 Общие подвыражения 307 Объектный код 318 Объявления 221—223, 245 — типы 59 Оверлеи 101, 318 Оператор СОТО 235, 236 Оператор ON 192, 193 Оптимизация текста программы 305—311 — метод развертывания цикла 309 Оптимизирующие компиляторы 315 Отладка 318 Ошибки, намеренное внесение 283, 284 — обнаружение 253—255 — оценка 262—268, 283—286 — распространение 266 — типы 255, 256, 262—267, 268 — флаг 239 Пакет 81 Параллельная обработка 147, 152 Пароли 286, 287 Паскаль 92, 185, 187, 301, 318 Передача блоками 89 Перекрестное суммирование 226 Переменные 223, 224 Переход обработки 117, 120, 132, 133 Переходы 52 Перт-диаграмма 28, 51, 318 ПЛ/1 79, 91, 186, 246, 300, 318 Повторно используемый код 319 Погрешность 319 Подпрограмма 319 Полезность 3 Полиномы Чебышева 267 Постановка задачи 16—19 Поток данных 40, 126—129 совместно обрабатываемый 40, 48—55 схема 43, 71 таблица 71, 73 Потокоориентированная передача данных 89 Пошаговое уточнение 103—111 Права 287, 319 Правила оформления листингов 246 Правило 209 ГГравильность 11, 319 Предложение EXIT 189, 190 Предложение RETURN 191 Предсказуемость 319 Представление чисел 257—262 Примеры: биномиальные коэффициенты 149, 150 — вычисление площади треугольника 155 арифметического выражения 59, 170, 171, 180, 206 синуса 158, 159, 180 квадратного корня 151 числа перестановок 142—149, 252 — генератор отчетов 120, 124, 131, 138 Пробуксовка 297 Проверяемость 12 Программа выборки 49, 50 Программа удаления операций из циклам 309 Проектное решение 118, 119 Процессы 40 — управляемые данными 48 Псевдокод 106, 319 Пустая запись 96, 97 Путь доступа 64 Развертывание циклов 309, 310 Разделение на страницы 77, 78, 123—125* 319 Расширение ядра 117—125 Редактирование 227 Реентерабельный код 319 Рекурсия 147, 151, 232—235, 319 — многократная 234 — однократная 232 Сборник алгоритмов АСМ 154 Связанный список 234 Связность модулей 11—113, 139, 319 — временная (по классу) 112 — коммутативная 111—112 — логическая 113 — последовательная 111 — по совпадению 113 — процедурная 112 — функциональная 111 Секретарь проекта 30 Сеть Петри 52, 319 Символ ограничения/прерывания 197 Синхронизация обработки 48—53 Система 319 — испытания 19, 24 — качество 27 — определение 18, 35—39 — организация проектирования 28—30 — основные компоненты 18 — проект 32 — проектирование 19, 25—27 — развернутый план проекта 26—28
.326 Предметный указатель — реализация 19, 24, 35 — требования 31 — функциональная схема 45 — функциональное описание 22 — функциональные требования 15 — эксплуатация 19 Системные константы 221 Скаляры 57, 79—80 Сквозной анализ проекта 30 Слияние циклов 311 Сложность алгоритмов 143—146 — исходных текстов 240 —» модулей 243 — операторов 307 — программ 240—243 Смешанное программирование 218 Совместимость 12, 319 Сопрограммы 122, 153—154 Сортировка-обмен 144, 145 ^Сортировка-слияние 155 Сортировка Шелла 156 'Составной ключ 67 Специальные функции 290 Список клиентов 17 Спулинг 56, 88 Среда заказчиков 15, 16 — пользователей 13 — ЭВМ 14 Средства оптимизации 314 Стандарт шифрования данных 289 Стандарты 27 Стиль программирования 208 Страничное прерывание 297 Страничный обмен 319 Структурное проектирование 22 -Структурные схемы программ 195—202, 319 Структурный анализ 20 Схема Джексона 120—122, 320 Схема «исток — преобразование — сток» 107-110, 117, 320 Схема HIPO 53-55, 134 Схемы Насси — Шнейдермана 202, 320 Сцепление модулей 113—116, 320 — — по внешним ссылкам 115 данным 114 кодам 115—116 — образцу 114 общей области 114 управлению 115 -Счетчик записей 225 Таблица перекрестных ссылок 250 Таблица решений 130, 320 < вход действий 209 избыточная 211 полная 209 противоречивая 211 совмещение столбцов 211 с ограниченным входом 209 с расширенным входом 209 со смещенным входом 210 столбец действий 209 Таблица связей 110 Тестирование ветвлений 272—273 — нестандартных ситуаций 274 — путей 269—271 — путем фиктивного выполнения программы 275, 276 — специальных значений 273—275 — структур управления 271, 272 — текста программы 296—301 Точки ветвления 277 — слияния 277 Точность 11, 320 Указатель 80 Универсальная программа решения задач СР 174 Универсальность 12, 320 Уничтожение записи 96 Управляющая группа 104—105 — конструкция 183—185 Управляющее поле 77, 216, 320 Управляющее прерывание 77, 102, 117, 120, 131—133, 320 Управляющий граф 249—251 Условие несущественное 210 — неформальное 191 — «почти нормальное» 191—192 — составное 231 Утверждения 277—282 Файл вспомогательный 87 — выбор организации 97, 98 — доступ 90, 93, 95, 319 — копирование 87 — обработка 95—97 — организация индексированная 93—95, 318 последовательная 90—92, 97—98, 319 с относительной нумерацией записей 92, 97-98, 320 — регистрационный 48—50, 88 — сопровождение 43, 47, 130, 135 — типы 87, 89 Физическая организация внешних данных 87 Флаг отладки 239 Формальное определение 82, 83 Фортран 71, 91, 186, 222, 228, 246, 300, 320 Функциональная декомпозиция 103 Функциональная схема системы 45 учитывающая синхронизацию процессов 48 — условные обозначения 46 Хэширование 86 ЦикломатиЧеское число 240 Цикл со счетчиком индекса 200 Черный ящик 320 Численное интегрирование 159 Шифрование 288—290 Шифрование с общим ключом 290 Эвристики 142, 320 Экономия времени 301—309, 311 Экономия памяти 299—301 Эффективность 12 — модуля 215 — работы программиста 293 Язык управления заданиями 40, 56
ОГЛАВЛЕНИЕ Предисловие редактора перевода , 5 Предисловие автора 7 Глава 1. ВВЕДЕНИЕ 10 1.1. Качество программных систем 11 1.2. Постановка задачи 16 1.3. Проектирование системы .... 19 1.4. Вспомогательные средства проектирования 25 1.5. Системная документация 30 1.6. Упражнения 3$ Глава 2. ПРОЕКТИРОВАНИЕ СИСТЕМ 35 2.1. Определение основных компонентов системы .... 35 2.2. Методы разработки данных 48 2.3. Методы разработки средств управления 4& 2.4. Проектная документация 55 2.5. Упражнения 5$ Глава 3. МЕТОДЫ ОРГАНИЗАЦИИ ДАННЫХ 57 3.1. Типы данных 57 3.2. Уровни организации данных 61 3.3. Уровень логической организации данных 65 3.4. Представление данных 70 3.5. Физическая организация данных . . . . 83 3.6. Документирование данных 98- 3.7. Упражнения 100 Глава 4. ПРОЕКТИРОВАНИЕ ПРОГРАММ 101 4.1. Метод нисходящего проектирования 10S 4.2. Метод расширения ядра 117 4.3. Метод восходящего проектирования 125 4.4. Анализ внутреннего потока данных 126 4.5. Вспомогательные средства проектирования программ . . 1291 4.6. Программная документация 135 4.7. Упражнения , 137 Глава 5. АЛГОРИТМЫ 139 5.1. Типы алгоритмов 13$ 5.2. Способы реализации алгоритмов 146 5.3. Методы построения алгоритмов 154 5.4. Документация алгоритмов 179 5.5. Упражнения 179> Глава 6. ПРОЕКТИРОВАНИЕ МОДУЛЕЙ 181 6.1. Структурированные алгоритмы 183> 6.2. Схемы передач управления . 19$
328 Оглавление 6.3. Управляющие таблицы 203 6.4. Документация модулей 214 6.5. Упражнения 216 Глава 7. РЕАЛИЗАЦИЯ ПРОГРАММНОГО МОДУЛЯ .... 217 7.1. Подходы к реализации 217 7.2. Реализация данных 220 7.3. Реализация ввода-вывода 224 7.4. Реализация управления 228 7.5. Сложность программы 240 7.6. Оформление программы 244 7.7. Вспомогательные средства, используемые при реализации 248 7.8. Упражнения 251 Глава 8. ПРОВЕРКА ПРАВИЛЬНОСТИ ПРОГРАММ 253 8.1. Обнаружение ошибок 253 8.2. Тестирование модулей 268 8.3. Формальные методы доказательства правильности программ 276 8.4. Оценки ошибок 283 8.5. Средства защиты программных систем 286 8.6. Качество документации 291 8.7. Упражнения 291 Глава 9. ОПТИМИЗАЦИЯ ПРОГРАММ 293 9.1. Экономия памяти - 294 9.2. Экономия времени 301 9.3. Повышение эффективности программ 303 9.4. Средства оптимизации 314 9.5. Документирование показателей эффективности программы 315 9.6. Упражнения 316 Список терминов 317 Литература 321 Предметный указатель 324 УВАЖАЕМЫЙ ЧИТАТЕЛЬ! Ваши замечания о содержании книги, ее оформлении, качестве перевода и другие просим присылать по адресу: 129820, j Москва, И-И 0, ГСП, 1-й Рижский пер., д. 2, изд-во «Мир».