Н. Вирт - Программирование на языке Модула-2 (1160777), страница 17
Текст из файла (страница 17)
ЭтотФакт совсем не следует из содержания модуля определений: обычно семантика упоминается ввиде комментария или при помощи какой-либо другой формы документирования. Такиекомментарии объясняют то, что делает модуль, но не то, как это делается. Следовательно,различные модули реализации могут иметь один и тот же модуль определений.
Различия могутзаключаться в конкретном механизме реализации: например, буфер представлен как связанныйсписок элементов, а не массив (причем элементы под буфер выделяются по мере необходимости, азначит, размер буфера не ограничен). Но различия могут быть и в семантике. Следующаяпрограмма реализует не очередь, а стек (последним пришел -первым обслужен) и тем не менее кней подходит тот же модуль определений.
Любые изменения в семантике требуютсоответствующей адаптации пользовательских модулей, следовательно, такие изменения должныосуществляться с предельной осторожностью.IMPLEMENTATION MODULE Буфер:……………….PROCEDURE занести(х: CARDINAL);BEGINIF n < N THEN82буф[n] := x; n := n + 1;неполон := n < N; непуст := TRUEENDEND занести;PROCEDURE взять(х: CARDINAL);BEGINIF n > 0 THENn := n - 1; x := буф[n];непуст := n > 0; неполон := TRUEENDEND взять;BEGINn := 0; непуст := FALSE; неполон := TRUEEND Буфер.Очевидно, что условие "непуст" является предусловием для процедуры взять, а "неполон" для занести.
На этом завершим вводный пример.Синтаксис модуля определений:$МодульОпределений =$"DEFINITION" "MODULE" Идентификатор ":"${Импорт} {Определение}$"END" Идентификатор ".".$Определение = "CONST" {ОписаниеКонстанты ":"} |$"TYPE" {Идентификатор ["=" Тип] ";"} |$"VAR" {ОписаниеПеременной ":"} |$ЗаголовокПроцедуры ":".Синтаксис разделов реализации совпадает с синтаксисом главной программы, заисключением ключевого слова IMPLEMENTATION, добавляемого в начале для указания того, чтодля этого модуля существует модуль определений, описания которого автоматически считаютсяпринадлежащими и данному модулю.$ПрограммныйМодуль =$"MODULE" Идентификатор [Приоритет] ";"${Импорт} Блок Идентификатор.83$$ЕдиницаКомпиляции = МодульОпределений |["IMPLEMENTATION"] ПрограммныйМодуль.Как раздел определений, так и раздел реализации могут содержать списки импорта (одинили несколько).
В модуль определений следует импортировать лишь те объекты, которыедействительно требуются в нем самом. Это уменьшает его зависимость от других модулей.$$Импорт - ["FROM" Идентификатор]"IMPORT" СписИдент ";".Идентификатор, следующий за ключевым словом FROM, - имя модуля, из которогоимпортируются объекты. Без использования такого квалифицирования мы можем импортироватьтолько имена модулей (о том, как ослабить это правило, будет сказано в разделе, посвященномлокальным модулям). Если импортируется имя модуля, то автоматически импортируются всеимена, находящиеся в его разделе определений.
Однако в этом случае они должныквалифицироваться именем модуля подобно компоненте записи. Например, если модульМ'экспортирует объекты a, b, с, то запись IMPORT M, встретившаяся в модуле N, означает, что кобъектам можно обращаться с помощью обозначений М.а, M.b, М.с. Это средство позволяетимпортировать из различных модулей объекты с совпадающими именами и при этом избежатьконфликта имен. В данном случае М действует как квалифицирующий идентификатор.Возможность оформить внешние связи модуля в виде раздела определений и скрытьподробности функционирования в разделе реализации особенно удобна при организациибиблиотек подпрограмм.
Такие наборы стандартных подпрограмм имеются в любой программнойсреде. Они, как правило, включают программы ввода-вывода, операции работы с файлами ипроцедуры вычисления математических Функций. Хотя в Модуле и не существует жесткихстандартов, тем не менее модули InOut, ReallnOut, LineDrawlng, MathLib0 и Streams (или ихэквиваленты) можно считать стандартными, имеющимися во всех реализациях языка. Этимодули вводятся в книге далее. Здесь же в качестве первого примера приведем разделопределений модуля MathLib0.DEFINITION MODULE MathLib0;PROCEDURE sqrt(x: REAL): REAL;PROCEDURE exp(x: REAL): REAL;PROCEDURE ln(x: REAL): REAL;PROCEDURE sin(x: REAL): REAL;PROCEDURE cos(x: REAL): REAL;PROCEDURE arctan(x: REAL): REAL;PROCEDURE real(x: INTEGER): REAL;PROCEDURE entler(x: REAL): INTEGER;END MathLib0.25. РАЗБИЕНИЕ ПРОГРАММЫ НА МОДУЛИПод качеством программы подразумеваются мнопие аспекты, и эта сторона дела оченьрасплывчата и неуловима.
Пользователь может судить о качестве программы по ееэффективности, надежности или удобству диалога. Эффективность в принципе можно выразить наязыке цифр, но, например, удобство использования - это скорее дело личного вкуса, и впреобладающем большинстве случаев способ взаимодействия с программой считается удобным,84если он общепринят. Разработчик может судить о качестве по ясности и понятности программы,,свойствам тоже весьма расплывчатым.
Однако если некоторое свойство и не может бытьвыражено на языке точных цифр, то это еще не причина считать его несущественным. На самомделе ясность программы - чрезвычайно важная характеристика, ведь демонстрация(доказательство?) правильности в конечном счете сводится к убеждению человека в том, чтопрограмма надежна. Как нам достигнуть этой цели? Ведь, в конце концов, сложные задачи посамой своей природе требуют сложных алгоритмов, которые подразумевают гигантскоемножество деталей. И эти детали вырастают в целый лес, полный привидений.Единственное спасение - организация соответствующей структуры программы.
Программадолжна быть разбита на части так, чтобы их можно было рассматривать по отдельности, почтиполностью независимо друг от друга. На самом нижнем уровне элементами структуры являютсяоператоры, на следующем - процедуры, а на верхнем уровне - модули. Параллельноструктуризации программ идет структуризация данных. На нижнем уровне она осуществляетсяпри помощи массивов, записей и т.д., а на последующих уровнях это происходит за счетобъединения переменных с процедурами и модулями.
Сущность программирования заключается впоиске правильной (или, по крайней мере, подходящей) структуры, и опытный программист - какраз тот человек, который обладает интуитивной способностью найти такую структуру именно наэтапе первоначальной разработки программы, а не в процессе ее постепенных улучшений имодификаций. Однако программист, имеющий смелость поменять структуру своей программы вслучае обнаружения более удачного решения, гораздо лучше того, кто занимается подчисткой ивылизыванием программы, в основе которой лежит явно неадекватная структура, поскольку этоведет к программному продукту, "непонятному" ни для кого, кроме его создателя (а в конечномсчете и для него самого).Хотя и не существует рецепта определения оптимальной структуры программы, тем неменее выработались некоторые критерии для руководства процессом поиска хорошей структуры идля предотвращения получения плохой. Основное правило требует производить разбиение так,чтобы связи между частями были простыми и "слабыми".
Возможно, наипростейшим критериемслабости связи (называемой также интерфейсом.) между двумя частями является число объектов, вней участвующих. В частности, интерфейс двух модулей описывается с помощью списковимпорта модуля, и мерой слабости интерфейса можно считать число импортируемых объектов.Следовательно, мы должны выбрать такое разбиение на модули, которое делает списки импортакороткими. Естественно, оптимум найти трудно, поскольку списки импорта были бы самымикороткими, т.е.
совсем исчезли, если бы мы всю программу объединили в один модуль, что,очевидно, нежелательно. Способность модулей как наибольших структурных единиц разделятьпрограмму на относительно независимые части определяется их свойством скрывать детали итем самым образовывать новый уровень абстракции.
Это свойство используется по-разному.Можно выделить следующие типичные случаи:1. Модуль связывает две формы представления данных и содержит набор процедур,которые осуществляют преобразование между этими Формами. Типичный пример - перевод чиселиз атомарной, неделимой Формы в последовательность десятичных цифр и наоборот. Такиемодули не содержат собственных данных, обычно это просто наборы процедур.2. Основное содержание модуля - некоторая совокупность данных. Подробностипредставления данных скрыты, и доступ к ним осуществляется лишь с помощью экспортируемыхмодулем процедур.
Примером может служить модуль, содержащий множество отдельныхэлементов, организованных так, что доступ к ним по ключу происходит очень быстро. Другойпример - модуль, скрытой совокупностью данных которого является дисковая память: он скрываетспецифические детали, необходимые для работы котроллера диска.3. Модуль экспортирует тип данных и набор связанных с этим типом операций. В Модуле2 обычно такой модуль экспортирует один или несколько типов в скрытом режиме (иногда этитипы называются приватными.)- Тем самым скрывается структура типа и детали выполненияопераций.
Такое упрятывание информации позволяет дать гарантию истинности постулированныхинвариантных свойств каждой переменной приватного типа. Отличие от случая 2 состоит в том,что здесь переменные приватного типа описаны в модуле пользователя, а в случае 2 сама85переменная скрыта. Типичные примеры - очередь и стек. Возможно, наиболее удачный примертакой абстракции данных - это последовательный Файл, называемый также потоком.Такая классификация не абсолютна. Да абсолютной и не может быть, так как всеуказанные случаи могут совмещаться. Модуль InOut, уже использовавшийся в примерахпрограмм, совмещает черты модулей первого и второго видов: скрывает детали представлениячисел и их преобразования, а также скрывает две потоковые переменные In и Out.