4 курс (заочка) / Учебное пособие / Tekhnologii_programmirovania
.pdf- 33 -
класс ПМ с высшей степенью прочности. Информационно прочный модуль может реализовывать, например, абстрактный тип данных.
Сцепление модуля – это мера его зависимости по данным от других модулей. Характеризуется способом передачи данных. Чем слабее сцепление модуля с другими модулями, тем сильнее его независимость от других модулей. Для оценки степени сцепления Майерс предлагает упорядоченный набор из шести видов сцепления моду-
лей. Худшим видом сцепления модулей является сцепление по содержимому. Та-
ким является сцепление двух модулей, когда один из них имеет прямые ссылки на содержимое другого модуля (например, на константу, содержащуюся в другом модуле). Такое сцепление модулей недопустимо. Не рекомендуется использовать так же сцеплений по общей области – это такое сцепление модулей, когда несколько модулей используют одну и ту же область памяти. Единственным видом сцепления модулей, которое рекомендуется для использования современной технологией программирования, является параметрическое сцепление. Это случай, когда данные передаются модулю либо при обращении к нему как значение его параметров, либо как результат его обращения к другому модулю для вычисления некоторой функции. Такой вид сцепления модулей реализуется на языках программирования при использовании обращений к процедурам (функциям).
Рутинность модуля – это его независимость от предыстории обращений к нему. Модуль будем называть рутинным, если результат обращения к нему зависит только от значений его параметров (и не зависит от предыстории обращений к нему). Модуль будем называть зависящим от предыстории, если результат обращения к нему зависит от внутреннего состояния этого модуля, изменяемого в результате предыдущих обращений к нему. Майерс не рекомендует использовать зависящие от предыстории (непредсказуемые) модули, так как они провоцируют появление в программах хитрых (неуловимых) ошибок. Однако такая рекомендация является малоконструктивной, так как во многих случаях именно зависящий от предыстории модуль является лучшей реализацией информационно прочного модуля. Поэтому более справедлива следующая рекомендация:
-34 -
–всегда следует использовать рутинный модуль, если это не приводит к пло-
хим (не рекомендуемым) сцеплениям модулей;
–модули, зависящие от предыстории, следует использовать только в случае, когда это необходимо для обеспечения параметрического сцепления;
–в спецификации модуля, зависящего от предыстории, должна быть чётко сформулирована эта зависимость для возможного прогнозирования поведения данного модуля при разных последующих обращениях к нему.
7.2. Подходы к разработке структуры программы
По традиции в качестве модульной структуры программы принято использовать древовидную структуру. В узлах такого дерева размещаются программные модули, а направленные дуги (стрелки) показывают статическую подчинённость модулей, т.е. каждая дуга показывает, что в тексте модуля, из которого она исходит, имеется ссылка на модуль, в который она входит.
Спецификация ПМ содержит
–синтаксическую спецификацию его входов для построения на используемом языке программирования синтаксически правильного обращения к нему;
–функциональную спецификацию модуля (описание семантики функций, выполняемых этим модулем по каждому из его входов).
Функциональная спецификация модуля строится так же, как и функциональная спецификация ПС.
При разработке программы её модульная структура может по-разному формироваться и использоваться для определения порядка программирования и отладки модулей, указанных в этой структуре. Поэтому можно говорить о разных методах разработки структуры программы. В литературе рассматриваются два метода: ме-
тод восходящей разработки и метод нисходящей разработки [3].
При методе восходящей разработки (снизу вверх) сначала строится модульная структура программы в виде дерева. Затем поочерёдно программируются модули программы, начиная с модулей самого нижнего уровня, чтобы для каждого програм-
мируемого модуля были уже запрограммированы все модули, к которым он может обращаться. После этого производится поочерёдное тестирование и отладка модулей
-35 -
втаком же (восходящем) порядке, в каком велось их программирование. На первый
взгляд такой порядок разработки программы представляется вполне естественным: каждый модуль при программировании выражается через уже запрограммированные непосредственно подчинённые модули, а при тестировании используются уже отла-
женные модули. Однако современная технология программирования не рекомендует такой порядок разработки программы. Для этого есть несколько причин.
Первая причина, для программирования какого-либо модуля совсем не обязательно наличие текстов используемых им модулей – для этого достаточно спецификации используемого модуля для правильного обращения к нему, а для тестирования его можно заменять используемые модули их имитаторами или заглушками [6]. Вторая причина, каждая программа создаётся с учётом некоторых общих соображений (принципы реализации, структуры данных и т.п.). При восходящей разработке эта общая или глобальная информация для модулей нижних уровней ещё не выяснена полностъю. Поэтому очень часто приходится перепрограммировать и модули нижних уровней. При кодировании остальных модулей происходит уточнение этой глобальной информации. Третья причина, восходящее тестирование требует для каждого модуля (кроме головного) создания ведущей программы (модуля). Ведущий модуль должен подготовить для тестируемого модуля необходимое состояние информационной среды и произвести требуемое обращение к нему. Результатом является большой объём «отладочного» программирования.
При методе нисходящей разработки (сверху вниз), как и в предыдущем методе, сначала строится модульная структура программы в виде дерева. Затем, начиная с модуля самого верхнего уровня (головного), поочерёдно программируются модули программы, программируя какой-либо другой модуль только в том случае, если уже запрограммирован модуль, который к нему обращается. После того как все модули программы закодированы, они поочерёдно тестируются и отлаживают-
ся в том же (нисходящем) порядке. При этом те модули, к которым может обращаться головной, заменяются их имитаторами или заглушками. Имитатор представляет собой довольно простой программный фрагмент. Назначение имитатора, как правило, заключается в сигнализации обращений к имитируемому моду-
- 36 -
лю, обработке значений его входных параметров и выдаче, если нужно, заранее определённого результата или сообщения. После этого тестируются модули, которые в данный момент представлены имитаторами. Для чего имитатор выбранного для тестирования модуля заменяется этим модулем. При таком порядке каждый такой модуль будет тестироваться в «естественной» информационной среде. И, кроме того, большой объём «отладочного» программирования при восходящем тестировании заменяется программированием достаточно простых имитаторов ис-
пользуемых в программе модулей. Таким образом, вся необходимая глобальная информация для разработки программы формируется своевременно.
Рассмотренные методы восходящей и нисходящей разработок (их называют классическими) объединяет требование, чтобы модульная структура программы была разработана до начала программирования (кодирования) модулей. Это тре-
бование полностью соответствует водопадному подходу к разработке ПС (см. 3.1.), так как разработка модульной структуры программы и её программирование выполняются на разных этапах разработки ПС: на первом этапе завершается конструирование ПС, а на втором – начинается кодирование. Однако классические
методы вызывают ряд возражений: представляется маловероятным достаточно точно и содержательно до программирования модулей разработать структуру программы. Можно несколько модернизировать водопадный подход.
Для этого предлагаются конструктивный и архитектурный подходы к разработке программы [1], в которых модульная структура формируется в процессе программирования или кодирования модулей.
Конструктивный подход к разработке программы является модификацией метода нисходящей разработки, при которой модульная древовидная структура программы формируется в процессе кодирования модулей. Начинается всё с программирования головного модуля, исходя из спецификации программы в целом. При этом спецификация программы принимается в качестве спецификации её головного модуля. В процессе программирования головного модуля выделяются подзадачи, которые реализуют внутренние функции. То есть, для каждой выделяемой подзадачи (функции) создаётся спецификация реализующего её фрагмента программы, который в
- 37 -
дальнейшем может быть представлен некоторым поддеревом модулей. Таким образом, на первом шаге разработки программы (при программировании её головного модуля) формируется верхняя начальная часть дерева.
Аналогичные действия производятся при программировании любого другого модуля, который выбирается из текущего состояния дерева программы из числа специфицированных, но пока ещё не запрограммированных модулей. Таким образом производится очередное доформирование дерева программы [4].
Архитектурный подход к разработке программы является модификацией метода восходящей разработки, при которой модульная структура программы формируется в процессе кодирования модуля. Цель разработки при этом ставится другая: повышение уровня используемого языка программирования, а не разработка конкретной программы. Другими словами, для заданной предметной области выделяются типовые функции, каждая из которых может использоваться для разных задач этой области. Для данных функций создают спецификации и программируют отдельные ПМ, выполняющие эти функции. Данный процесс сопровождается накоплением и обобщением опыта решения задач в заданной предметной области, выделением и реализацией отдельными модулями более простых функций. В результате существенно сокращаются трудозатраты на разработку конкретной программы путём подключения к ней заранее заготовленных и отлаженных модульных структур на нижних уровнях (специфика восходящей разработки снизу вверх). Архитектурный подход может рассматриваться как метод борьбы с дублированием в программировании, так как созданные модульные структуры нижних уровней в составе библиотек предметных областей могут многократно использоваться в разных программах.
7.3. Методы контроля структуры программы
Для контроля структуры программы, как правило, используют следующие методы [6]:
–статический контроль;
–смежный контроль;
–сквозной контроль.
- 38 -
Статический контроль представляет собой оценку структуры программы, её разделения на модули с учётом значений рассмотренных выше основных характеристик ПМ.
Смежный контроль сверху представляет собой контроль со стороны разработчиков архитектуры и внешнего описания ПС; смежный контроль снизу – контроль спецификации ПМ со стороны их разработчиков.
Сквозной контроль является мысленной проверкой структуры программы при выполнении заранее разработанных тестов. Этот контроль является видом динамического контроля так же, как и ручная имитация.
8. Проектирование и разработка ПМ
При разработке ПМ рекомендуется придерживаться следующего порядка [6]:
–изучение и проверка спецификации модуля, выбор языка программирования;
–выбор алгоритма и определение структуры данных;
–программирование (кодирование) модуля;
–шлифовка или доводка текста модуля;
–проверка модуля;
–компиляция модуля.
Первый этап разработки программного модуля во многом является смежным контролем структуры программы снизу. Изучение спецификации модуля необходимо разработчику для проектирования этого модуля. После чего выбирается язык программирования, который может быть либо уже предопределён для всего ПС, либо может быть выбран другой язык, более подходящий для реализации данного модуля (например, язык ассемблера).
На втором этапе разработки ПМ необходимо определить, есть ли уже какиелибо алгоритмы для решения поставленной задачи. И если найдётся подходящий алгоритм, то целесообразно им воспользоваться. Определение подходящих структур данных во многом предопределяет логику и качественные характеристики разрабатываемого модуля.
- 39 -
На третьем этапе создаётся текст модуля на выбранном языке программирования. Необходимость учёта всевозможных деталей при реализации функций, указанных в спецификации модуля, легко может привести к созданию очень сложного и запутанного текста. Поиск ошибки в таком «непрозрачном» модуле и внесение в него изменений может оказаться трудоёмкой задачей. Поэтому для построения
текста модуля важно пользоваться обоснованной технологически и проверенной на практике дисциплиной программирования. Первым об этом заговорил Дейкстра, сформулировав и обосновав основные принципы структурного про-
граммирования [10]. Эти принципы лежат в основе многих дисциплин программирования. Наиболее распространена дисциплина пошаговой детализации [1].
На четвёртом этапе разработки ПМ на основе спецификации качества ПС текст модуля доводится до завершённого состояния. На третьем этапе основное внимание уделяется правильности реализации функций модуля. При доводке текста модуля разработчик редактирует имеющиеся комментарии и, возможно, включает дополнительные для обеспечения требуемых примитивов качества.
Пятый этап является ручной проверкой внутренней логики ПМ до начала его отладки («прогоны» его на компьютере).
На шестом этапе разработки завершается проверка модуля (с помощью компилятора) и становится возможным переход к процессу отладки модуля.
8.1. Структурное программирование
Программирование модуля предполагает, что программа должна быть понятной и компьютеру, и человеку. Современные языки программирования достаточно сложны, чтобы запутать логику работы модуля, тем самым, сделать модуль малопонятным для человека и ненадёжным. Поэтому Дейкстра [10] предложил строить программу в виде композиции нескольких типов управляющих конструкций, которые позволяют значительно повысить понимаемость логики работы программы. Программирование с использованием только таких конструкций назвали структурным.
На рис. 5 представлены основные конструкции структурного программирования: следование, разветвление и повторение. Компонентами этих конструкций яв-
ляются обобщённые операторы (узлы обработки) S, S1, S2 и условие (предикат) Р.
- 40 -
Обобщённым оператором может быть либо простой оператор какого-либо языка программирования (операторы присваивания, ввода/вывода, обращения к процедуре и т.п.), либо фрагмент программы в виде композиции основных управляющих конструкций структурного программирования. Каждая из этих конструкций име-
ет только один вход и один выход.
Важен тот факт, что эти конструкции представляют собой математические объекты. Этим объясняется успех структурного программирования. Доказано, что для каждой неструктурированной программы можно построить функционально эквивалентную структурированную программу. Для структурированных программ возможно математическое доказательство некоторых свойств, позволяющих обнаруживать в программе некоторые ошибки.
Следование Разветвление
S1 |
Да |
P |
Нет |
|
|
|
|
|
S1 |
|
S2 |
S2 |
|
|
|
Повторение
S |
Да |
P |
Нет |
|
|
|
Рис. 5. Основные управляющие конструкции структурного программирования
У структурного программирования есть ещё одно название − «программирование без GO TO». Но дело не в операторе GO TO, а в его неупорядоченном ис-
- 41 -
пользовании. Часто при структурном программировании с использованием некоторых языков программирования (например, ФОРТРАНа) оператор безусловного перехода (GO TO) используют для реализации структурных конструкций, что не нарушает принципов структурного программирования. Запутывают программу слу-
чаи «неструктурного» применения оператора перехода, особенно переход к оператору, расположенному в тексте модуля выше выполняемого оператора пере-
хода. Однако иногда желание избежать использования GO TO в некоторых простых случаях может привести к слишком громоздким программам, что ухудшает их ясность и повышает вероятность появления в тексте модуля дополнительных ошибок.
Поэтому рекомендуют избегать употребления оператора перехода всюду, где это возможно, но не ценой ясности программы.
К позитивным результатам использования оператора GO TO обычно относят выход из цикла или процедуры, срабатывающие по особому условию, «досрочно» прекращающие работу данного цикла или данной процедуры.
8.2. Пошаговая детализация. Псевдокод
Структурное программирование даёт рекомендации относительно текста модуля. Обычно программирование модуля начинают с построения блок-схемы, которая описывает в общих чертах логику его работы. Но современная технология разработки программ не рекомендует этого делать без соответствующей компьютерной поддержки. Несмотря на то, что блок-схемы позволяют достаточно наглядно представить логику работы модуля, но при их ручном кодировании возникают своеобразные ошибки. При отображении двумерных блок-схем на линейный текст модуля есть опасность искажения логики работы модуля. Исключением являются случаи, когда блок-схемы формализованы настолько, что по ним автоматически генерируется текст на требуемом языке программирования [4].
Современная технология программирования в качестве основного метода построения текста модуля рекомендует пошаговую детализацию [6]. Суть этого метода состоит в разбиении процесса разработки текста модуля на ряд шагов. Первый шаг заключается в описании общей схемы работы модуля в обозримой линейной текстовой форме с использованием укрупнённых понятий. Причём это описание является час-
- 42 -
тично формализованным и ориентировано на человеческое восприятие. Каждый следующий шаг заключается в уточнении и детализации одного из понятий (его называют уточняемым) какого-либо описания одного из предыдущих шагов. В результате получается описание выбранного уточняемого понятия либо в терминах базового языка программирования, либо в форме с использованием новых уточняемых понятий. Про-
цесс детализации завершается, когда все требующие уточнения понятия будут уточнены и детализированы. На последнем шаге получают текст модуля на базовом языке программирования с помощью замены всех вхождений уточняемых понятий задающими их описаниями и замещения всех вхождений конструкций структурного программированиясредствамитребуемогоязыкапрограммирования.
Псевдокод – частично формализованный язык, используемый при пошаговой детализации [6]. Он позволяет использовать все конструкции структурного программирования (рис. 6). Псевдокод состоит как из формализованных фрагментов, так и неформализованных фрагментов на естественном языке.
Головное описание на псевдокоде должно содержать:
–начало модуля на базовом языке;
–раздел описаний на базовом языке;
–неформальное обозначение тела каждого описания процедуры или функции как обобщённого оператора;
–конец модуля на базовом языке.
Следование:
Обобщённый_оператор Обобщённый_оператор
Разветвление:
ЕСЛИ условие ТО Обобщённый_оператор ИНАЧЕ Обобщённый_оператор ВСЁ ЕСЛИ
Повторение:
ПОКА условие ДЕЛАТЬ Обобщённый_оператор ВСЁ ПОКА
Рис. 6. Основные конструкции структурного программирования на псевдокоде
