Автор работы: Пользователь скрыл имя, 24 Ноября 2011 в 18:44, курсовая работа
Несомненно, работа на ЭВМ с использованием ее истинного языка требует довольно-таки высокой квалификации программиста. По мере того как компьютеров становилось все больше и больше, возникла необходимость облегчить общение с ними. Первое, что было сделано, — автоматизировали распределение памяти и ввели понятные человеку обозначения для машинных команд. Например, умножение сумматора на ячейку ОП обозначили MULT (от английского multiply — умножить) и т. п. Получившийся язык общения с ЭВМ назвали языком Ассемблера.
История
возникновения
Несомненно,
работа на ЭВМ с использованием ее
истинного языка требует Так, программа умножения числа 2 на 2 записывается так.
Естественно,
потребовалась специальная программа,
переводящая вышеописанные предложения
на родной язык компьютера. Такая программа
называется транслятором с языка Ассемблера. Не случайно инициатором этой работы стала знаменитая фирма IBM. С опозданием выйдя на рынок компьютеров, фирма испытывала трудности с их продажей на рынке. Для нее было крайне заманчиво предложить компьютер, доступный не только профессиональным программистам, но и научным работникам и инженерам, привыкшим к алгебраическим формулам. В 1953 году
Джон Бэкус предложил разработать язык,
позволяющий резко упростить программирование
модели IBM-704. Делать от i:=
l до 10 { ... } Успех системы
превзошел все ожидания: уже к 1958 году
более половины всех машинных команд на
компьютерах IBM-704 было получено не вручную,
а с помощью транслятора с языка Фортран. Таким образом,
Фортран выполнил и другую не менее важную
роль: он стал тем средством, с помощью
которого самые различные БИ — компьютеры
нашли общий язык между собой. Несмотря
на девиз разработчиков «Лучшее — враг
хорошего», разработка нового языка, названного
Алгол (ALGOrith-mic Language), заняла более двух
лет, и он использовался в основном на
Европейском континенте, скорее для того,
чтобы подтвердить существование в Европе
специалистов по языкам программирования. В 1964 году
ею был предложен язык PL/1. (Programming Language
One — Язык Программирования НОМЕР ОДИН!) Семилетней
девочкой Грейс проявила черты суперпрограммиста:
она разобрала в доме все будильники, желая
узнать, как они устроены, и не смогла собрать
их обратно. Целью Грейс
Хоппер стала возможность программировать
на английском языке. При этом тяжелая
история с банковским счетом заставила
ее ориентироваться не на физиков или
на создание универсального языка программирования,
а на такой язык, который бы облегчил в
первую очередь экономические расчеты. Часть 2 В предыдущем параграфе мы описали бурные события, сопровождавшие рождение языков программирования. Хотелось бы обратить ваше внимание на факт, который, быть может, не бросился в глаза. Если первые
разработчики не различали такие вещи,
как разработка языка и написание для
него транслятора, то в дальнейшем эти
два процесса силами теоретиков, для которых
наконец-то появилась работа, были совершенно
отделены друг от друга. Языки разрабатывали
одни люди, а трансляторы писали совершенно
другие.
К тому же стало ясно, что для каждого вида человеческой деятельности, связанного с обработкой информации, желательно иметь свой собственный язык программирования (вот они, долгожданные мячики для каждого игрока!):
Достаточно
сказать, что только в военном ведомстве
США в начале семидесятых годов использовалось
до 450 языков высокого уровня и их диалектов
(Это, кстати, послужило причиной появления
языка ADA с его архаичной претензией на
универсальность). Поработав
с Паркетчиком, вы, наверное, даже не заметили,
что он, в отличие от «Малютки», не допускает
передачи управления из любого места программы
в любое другое. Отсутствие так называемого
оператора GOTO — оператора передачи управления
— характерная черта хорошо структурированных
программ. Структурно-ориентированным
стал даже Basic в варианте Quick Basic фирмы Microsoft,
с вариантом которого вы имели дело, когда
писали программы на языке QBasic. Разумеется,
теоретики программирования не могли
смириться с тем, что компьютеру, словно
малолетке, нельзя сказать: «Подумай сам
и получи результат, связанный с исходными
данными известными тебе соотношениями». Другой подход,
начавший развиваться практически одновременно
с логическим программированием, опирался
на математическое понятие функции и получил
название функционального. Такой акцент
на описание свойств объектов получил
название объектно-ориентированного.
К объектно-ориентированным относятся
языки C++ и Smalltalk. В дальнейшем
стало возможным с помощью одного оператора
заставить машину складывать сотни и тысячи
чисел, находить в массивах чисел максимумы,
производить сортировку и т. п. Массив
чисел стал в программистском мышлении
восприниматься как абстрактный объект,
над которым можно производить те или
иные действия. Формализованный
язык описания таких структур в единстве
с действиями, которые над этими структурами
можно выполнять, состоит из имен объектов
и имен свойств этих объектов. Эти свойства
сигнализируют компьютеру, как можно преобразовывать
данный объект. Программирование на таком
языке сводится к описанию объектов и
структурированию исходных данных и результатов
в такие объекты. |
7.1. Основные понятия
Модульная программа
— это такая программа, в которой любую
часть логической структуры можно изменить,
не вызывая изменений в остальных частях
программы. Однако, что же это на самом
деле означает? Каким образом программист
узнает, что он написал модульную программу?
Важнее понять, каким образом узнает руководитель,
что программист написал модульную программу?
Модуль характеризуют:
• один вход и один
выход — на входе программный модуль получает
определенный набор исходных данных, выполняет
содержательную обработку и возвращает
один набор результатных данных, т.е. реализуется
стандартный принцип IPO (Input-Process-Output) —
вход-процесс-выход;
• функциональная
завершенность — модуль выполняет перечень
регламентированных операций для реализации
каждой отдельной функции в полном составе,
достаточных для завершения начатой обработки;
• логическая независимость
— результат работы программного модуля
зависит только от исходных данных, но
не зависит от работы других модулей;
• слабые информационные
связи с другими программными модулями
— обмен информацией между модулями должен
быть по возможности минимизирован;
• обозримый по размеру
и сложности программный элемент.
7.2. Свойства модулей
7.2.1. Размеры модулей
в модульной программе
В своих попытках определить
характерные черты модульных программ
специалисты предлагают следующие стандарты
размеров модуля:
1. Некоторые пользователи
и программисты фирмы IBM предлагают определить
модуль как нечто, укладывающееся в 4096
байтах памяти.
2. Аналогично некоторые,
программирующие для систем фирмы Honeywell
UNIVAC и других, предлагают считать модулем
всякую последовательность кодов, занимающую
512 слов памяти, или 1024 слова, или 2048 слов
и т. д.
3. Некоторые руководители
отделов программирования высказывали
пожелания, чтобы модуль определялся как
раздел программы, который может быть
написан и отлажен одним программистом
за один месяц. Считая приближенно, что
средний программист может написать от
10 до 15 отлаженных команд вдень, получим
отсюда размер модуля равным 200—300 программным
командам.
4. Л. Констентайн высказал
предположение, что большинство хороших
модульных программ не содержит модулей,
превышающих 100—200 команд.
5. Р. Хенри, предположил,
что модуль не должен состоять более чем
из 20 операторов на
языке высокого уровня. У него такое чувство,
что если программист записывает что-то
с помощью более чем 20 операторов, то скорее всего он программирует
не один функциональный элемент, а в этом
случае следует употреблять больше одного
модуля.
6. Сотрудник фирмы
IBM Бейкер в своей статье (Baker F. Т. «Chief Programmer
Team Management of Production Programming», IBM Systems Journal, v.
11, №2 ) рекомендует своим программистам
ограничивать размеры модулей 50 операторами
на языке ПЛ/1.
Должно быть ясным,
что наложение ограничений на размер программы
не гарантирует того, что она будет модульной.
Один из программистов проекта для ВВС
США сделал интересное замечание по поводу
правила о 500 Кобол - операторах в модуле:
«Черт с ними, я соглашусь со всеми глупыми
ограничениями вроде этого, большинство
из них можно довольно просто обойти. Предлагаемое
правило модульности слишком просто. Я
пишу программу, не обращая никакого внимания
на это правило 500 операторов, а затем,
если окончательная программа получается
длиной в 3000 операторов, я просто делаю:
чик! чик! чик! — получаю шесть модулей!
Вот и все!».
С другой стороны,
приведенные выше положения не лишены
определенного смысла. Один руководитель
сказал как-то: «Слушай, я знаю, что модульная
программа должна поддаваться разбиению
на несколько подпрограмм, но у меня нет
времени, чтобы убедиться в том, что программисты
обеспечивают это должным образом. Они
способны принести мне главную программу,
состоящую из 500 операторов, к которой
добавлены два-три модуля по 10 операторов,
и всю эту чепуху назвать модульной программой!
Единственное средство, которое может
меня убедить в том, что они правильно
представляют себе саму идею модульности
и правильно применяют это понятие, состоит
в том, чтобы определить жесткий стандарт
в 50 операторов на модуль. Если у них появляется
необходимость в каком-либо исключении,
они сначала должны разъяснить мне его
суть».
7.2.2. Независимость
Одним из наиболее
абстрактных понятий, связанных с модульностью,
является понятие независимости: в модульной
программе каждый модуль не зависит от
других. Под этим, конечно, подразумевается,
что его можно изменить или модифицировать,
не вызывая каких-либо последствий в других
модулях. Должно быть ясным, однако, что
понятие независимости не является абсолютным.
Нам было бы очень трудно сказать, что
модуль Х на 99% или 44% независимее всех
других модулей (если бы нам это удалось,
то, может быть, в конечном счете мы сказали
бы, что программа Z модульна на 99% или 44%).
С другой стороны,
может иметь смысл представление о независимости
по отношению к другим факторам, например
к базе данных, объему рабочей памяти,
другим модулям и т. д. На самом деле мы
хотим задать следующий вопрос: если изменить
один фактор в программе, то как это повлияет
на данный модуль? Так, по-видимому, разумно
рассматривать независимость некоторого
модуля (и в конечном счете его модульность)
по отношению к таким факторам, как:
1. Логическая структура
программы, т. е. алгоритм. Если вся программа
(или система) зависит от некоторого специального
подхода (например, в задаче обхода сети
построение программы зависит от выбора
эвристического алгоритма обхода сети),
то в скольких модулях потребуется внести
изменения при изменении алгоритма?
2. Аргументы, или параметры,
модуля. В этом отношении зависимость
модуля может быть довольно сильной: если
изменяется число, тип или формат аргументов,
то не следует слишком удивляться, если
это потребует больших изменений в модуле.
3. Внутренние переменные
таблиц и константы. Многие модули -зависят
от общих таблиц (например, от разделов
протоколов и сообщений в ряде экономических
систем и систем коммутации сообщений);
если изменяется структура таких таблиц,
то мы можем ожидать, что модули также
изменятся.
4. Структура и формат
базы данных. В большей степени эта зависимость
аналогична зависимости от общих переменных
и таблиц, упомянутой выше, с той разницей,
что с практической точки зрения базу
данных удобнее считать независимой от
программы.
5. Модульная структура
управления программой. Некоторые пишут
модуль не особенно задумываясь над тем,
каким образом он будет использоваться.
Допустим, что, после того как мы записали
модуль X, мы вдруг узнаем, что необходимо
обеспечить его реентерабельность. Какую
часть логической структуры модуля нам
придется изменить? Какие бы возникли
трудности, если бы вдруг нам захотелось
использовать модуль рекурсивно?
Предполагая эти факторы
неизменными, мы могли бы установить независимость
отдельных модулей программы. Если интерфейсы
между модулями определены, то в этом случае
можно было бы заменять модуль функционально
ему эквивалентным (т. е. таким, который
принимает те же самые входные данные
и вырабатывает те же значения выходных
данных), не вызывая при этом никаких последствий
ни в каком другом модуле программы. В
зависимости от того, насколько возможны
такие замены, имеет смысл говорить о том,
что мы имеем модульную программу.
Заметим, что определенное
таким образом свойство независимости
нарушается, если некоторые модули могут
произвольно передавать или получать
управление от одного к другому или если
они могут модифицировать друг друга.
Таким образом, следующее условие модульности
и составляющая часть независимости —
условие «один вход — один выход», т. е.
модульная программа, должна состоять
из модулей, которые имеют одну точку входа
и одну точку выхода. Мы можем несколько
ослабить это требование, допуская, быть
может, существование модулей с более
чем одним входом; важно при этом, что точки
входов должны быть строго определены
и другие модули не могут входить в данный
в произвольной точке.
7.2. Преимущества и
недостатки модульности
Прежде чем обсудить
некоторые методы написания модульных
программ, мы должны привести некоторые
доводы в пользу модульности, а также ряд
контрдоводов.
7.2.1. Доводы в пользу
модульности
Мы уже упоминали в
этой главе достоинства модульности; здесь
достаточно было бы просто их повторить.
Тем не менее всякому программисту следует
подумать о каждом из нижеследующих пунктов
в связи с его собственной программой
— просто, чтобы убедиться, что они имеют
к ней отношение.
1. Модульные программы
легко составлять и отлаживать. Функциональные
компоненты такой программы могут быть
написаны и отлажены порознь.
2. Модульную программу
легче сопровождать и модифицировать.
Функциональные компоненты могут быть
изменены, переписаны или заменены без
изменений в остальных частях.
3. Руководству легче
управлять разработкой модульной программы.
Более сложные модули могут быть переданы
более опытным программистам; простые
модули могут быть написаны младшими программистами.
Разбивая программу на модули, которые
могут быть созданы за; один месяц, руководитель
может быть уверен, что ни один из программистов
не окажется слишком перегруженным сложными
элементами программы.
7.2.2. Доводы против
модульности
Напомнив многие достоинства
модульности, мы можем теперь поставить
вопрос: почему же многие программы не
отличаются большей степенью модульности.
С уверенностью можно сказать, что большинство
программ пишется немодульными. Многие
крупные фирмы расходуют до 50% бюджета,
выделенного на обработку данных, на сопровождение,
модификацию и совершенствование программ,
и большую часть этих действий можно было
бы выполнять совершенно тривиально, если
бы с самого начала проектировалась модульная
программа.
Большинство программистов
не понимают смысла модульности. Вероятно,
основная причина недостаточной модульности
программ состоит в том, что большинство
программистов в действительности не
знают, что такое модульность. У них есть
интуитивное представление о значении
этого слова и методах достижения модульности,
но, по-видимому, они не задумывались над
формализованными теоремами и алгоритмами.
Не многие программисты согласятся, что
именно в этом заключена действительная
причина недостаточной модульности их
программ; они обычно приводят какие-нибудь
другие доводы из числа перечисленных
ниже. Больше всего озадачивает то, что
они представляют себе модульность абсолютным
понятием и что, вводя несколько подпрограмм,
они смогут ее обеспечить.
Модульность требует
большей дополнительной работы. Чтобы
писать модульные программы, программист
должен быть значительно более аккуратным
на этапе проектирования программной
разработки. Он должен проектировать свои
программы по нисходящей схеме, начиная
с верхних уровней всей программы (системы)
и затем продвигаясь вниз к более детальному
проектированию отдельных подпрограмм.
На каждом шаге он должен спрашивать себя,
легко ли будет внести изменения в проект
и просто ли его модифицировать. В больших
разработках каждый шаг проектирования
должен сопровождаться составлением соответствующей
документации, с тем чтобы различные пользователи
и руководство могли выразить свое понимание
и одобрение проводимой работы.
Все это требует огромного
терпения и заметного объема довольно
кропотливой и усердной работы, и все это
—до начала написания программы. Во многих
случаях программист оказывается в условиях
(часто им же и созданных!) необходимости
писать программу как можно скорее, и он
может отказаться от этой дополнительной
работы.
К сожалению, существующие
методы решения этой проблемы не вполне
удовлетворительны. Руководитель, имеющий
дело с непокладистым программистом, может
просто приказать ему писать программу
модульно. В иных случаях руководитель
может попытаться убедить его, показав,
что модульность позволит в дальнейшем
облегчить изменение программы. Однако
часто руководителю очень трудно доказать,
что модульный подход определенно облегчит
модификации программ; во всяком случае,
это может не заинтересовать программиста,
поскольку он полагает, что за модификацию
программы будет отвечать кто-нибудь другой.
Модульный подход
иногда требует большего времени ЦП. Эта
проблема возникает прежде всего в тех
случаях, когда программа отличается,
наличием большого числа подпрограмм,
написанных на языках высокого уровня.
Кобол, Фортран и ПЛ/1 упоминаются в этой
связи в первую очередь; так, в одной версии
ПЛ/1 требуется 198 мкс только на то, чтобы
войти в подпрограмму и выйти из нее! Для
обеспечения модульности может также
потребоваться больше времени ЦП, если
части программы с командами ввода-вывода
совершенно отделены от ее вычислительных
частей; входная запись может быть несколько
раз передана подпрограммам, прежде чем
начнется ее фактическая обработка.
Если этот вопрос может
оказаться критическим, программисту
(или его руководителю) следует сделать
оценку, позволяющую установить, действительно
ли требование модульности существенно
увеличивает время прохождения программы.
Жесткие правила, безусловно запрещающие
программистам пользоваться операторами
вызова подпрограмм (которые часто встречаются
в стандартных учебниках Кобола для некоторых
малых ЭВМ), вообще говоря, заслуживают
порицания. В большинстве случаев модульный
под
ход требует дополнительно
5—10% времени ЦП; представляется, что это
приемлемая плата за возможность легко
изменять программу; исключение составляют
очень специальные случаи (например, некоторые
прикладные системы реального времени
или программы, расходующие по нескольку
часов машинного времени).
В модульном подходе
может потребоваться несколько больший
объем памяти. Если каждой подпрограмме
отводится отдельная часть рабочей памяти,
то всей программе может потребоваться
несколько больший объем памяти; однако
это не обязательно так, если промежуточные
результаты хранятся в списке, располагаемом
в памяти магазинного типа (исключение
составляют случаи вызова многократно
вложенных подпрограмм). В тех случаях,
когда программа реализуется на основе
большого числа подпрограмм, для обеспечения
связей между ними также может потребоваться
дополнительная память.
И в этом случае программисту
следует получить надежную оценку того,
что модульный подход требует существенно
большего объема памяти. В большинстве
случаев модульность не приводит к увеличению
объема программ более чем на 5—10%. Это
не может приводить к каким-либо осложнениям,
за исключением случаев, когда на длину
программы накладывается произвольное
ограничение, или при использовании мини-ЭВМ
из-за чисто физических ограничений.
ТЕМА 8. СТРУКТУРНОЕ
ПРОГРАММИРОВАНИЕ
Несмотря на довольно
широкое освещение структурного программирования
в мировой литературе, этот подход до сих
пор остается неизвестным многим программистов.
И если они все-таки что-то слышали о нем
(обычно в крайне упрощенной форме), то
склонны его отвергать как неоправданно
ограничительный и нецелесообразный.
8.1. Основные предпосылки
структурного программирования
Профессор Э. Дейкстра
был одним из первых инициаторов структурного
программирования. В 1965 г. на конгрессе
он высказал предположение, что оператор
GO TO мог бы быть исключен из языков программирования.
Больше того он заявил, что "квалификация
программиста обратно пропорциональна
числу операторов GO TO в его программах!"
Несмотря на то что конгресс 1965 г. происходил
в Нью-Йорке и отличался многочисленностью
участников из целого ряда стран, это заявление
вызвало сравнительно слабую реакцию.
Многие программисты только что отказались
от пользования Фортраном II и связывали
свои дальнейшие планы с переходом на
Фортран IV, одним из краеугольных камней
которого является почтенный оператор
GO TO.
Будучи не из тех, с
кем можно не посчитаться, Дейкстра повторно
изложил свои идеи в письме редактору
Communications of ACM в марте 1968 г. Он сформулировал
также некоторые из своих идей по нисходящему
проектированию систем (которые, по-видимому,
развиваются параллельно и в согласии
с идеей структурного программирования)
в статье, представленной Первому симпозиуму
по основам операционных систем,— статье,
перепечатанной в последствии в майском
номере Communications of ACM за 1968 г.
Цели, которые преследует
Дейкстра во всей своей работе, представляются
весьма постоянными. Основным был вопрос:
возможно ли повысить на порядок уровень
наших способностей в программировании
и какие средства (теоретические, организационные
и технические) можно было бы использовать
в процесс создания программы, чтобы достичь
этого уровня. Больше, чем что-либо другое,
Дейкстру, по-видимому, волновала проблема
доказательства правильности программы
для ЭВМ, т. е. разработка математически
строгих методов доказательства правильности
программ, которые бы устранили необходимость
в дорогостоящих, кропотливых и чаще всего
недостаточных специальных приемах их
испытаний. В той же статье он формулирует
это так:
«...Я сосредоточил
свое внимание не на вопросе «как мы доказываем
правильность данной программы», а на
вопросе «какими должны быть структуры
программ, чтобы без чрезмерных усилий
мы могли находить- доказательство их
правильности, если даже программы оказываются
большими?», и как следствие этого на вопросе
«как нам составить для данной задачи
такую хорошо структурированную программу?».
Мое стремление рассматривать лишь такие
«хорошо структурированные» программы
(как подмножество всех возможных программ)
основывается на уверенности в том, что
мы можем найти такое подмножество, которое
удовлетворяет нашим нуждам программирования,
т. е. что для всякой программируемой задачи
в этом подмножестве найдутся в достаточном
количестве реалистические программные
решения».
На той же самой конференции
по методам программного обеспечения,
на которой Дейкстра сообщил некоторые
из своих идей по структурному программированию,
Дж. Арон, фирма IBM, докладывал об эксперименте,
известном под названием «проект суперпрограммиста».
В этом эксперименте одному программисту,
д-ру X. Миллсу, было предложено проделать
нечто почти невозможное — выполнить
за шесть месяцев работу, представляющую,
как оказалось, проект на 30 человеко - лет.
Хотя было сказано лишь то, что эксперимент
был успешным (д-р Миллс, например, был
скрыт от пользователей, чтобы их не смущал
тот факт, что две группы программистов
работали над одним и тем же проектом!),
его результатом явились некоторые новые
идеи в организации разработки, проектировании
систем и в проектировании программ, прежде
всего — идеи нисходящего проектирования.
Успех этого эксперимента
побудил IBM испробовать те же идеи в более
крупной разработке — проекте информационно-поисковой
системы для газеты Нью-Йорк, Тайме [10].
Так же как и в предыдущем случае, основные
усилия в этом проекте были посвящены
поиску новых эффективных методов организации
и управления разработкой, хотя значительная
роль отводилась также структурному программированию
и нисходящему программированию. Этот
проект был весьма интересен тем, что он
отличался сравнительно большим масштабом
(около 83 000 операторов исходной программы),
практическим характером (реальный заказчик
платил реальные деньги за работающую
систему) и успешной реализацией (производительность
программистов оказалась приблизительно
в пять раз выше производительности среднего
программиста).
После успешного завершения
проекта для Нью-Йорк Тайме отделение
федеральных систем фирмы IBM, в котором
впервые зародилась идея использования
«суперпрограммиста», начало внедрять
свои идеи в других отделениях IBM и в организациях
нескольких крупных пользователей продукции
фирмы IBM.
8.2. Цели и задачи структурного
программирования
Структурное программирование
представляет собой нечто большее, чем
один лишь отказ от оператора GO TO, и мы
намерены обсудить это более подробно.
Тем не менее изложение этого предмета
различным группам безразличных к нему
программистов показывает, что сначала
важно подчеркнуть позитивные цели этого
метода программирования. Мы уже рассмотрели
один негативный аспект структурного
программирования (по крайней мере, с точки
зрения среднего программиста), т. е. исключение
GO TO.
Суть структурного
программирования состоит в ряде ограничений
и правил программирования, которые обеспечивают
соответствие программы очень строгому
образцу, исключая тем самым бессистемность,
не удобочитаемость и запутанность, которые
порождают ошибки и затрудняют тестирование
и сопровождение.
Это также может показаться
нежелательным аспектом, заставляющим
программиста выразить свое неудовольствие
следующими словами: "Еще правила и
ограничения в дополнение к стандартам,
которые мне уже приходится соблюдать.
Программировать стало совсем неинтересно;
у меня нет возможности для творчества".
В то же время, если мы сможем показать,
что вводимые ограничения позволяют программисту
удвоить число команд, которые он пишет
за день, то это, быть может, вовсе не так
уж плохо!
Основные проблемы,
на решение которых направлено структурное
программирование.
8.2.1. Уменьшение трудностей
тестирования
Мотивы, побудившие
первоначально Дейкстру к разработке
структурного программирования, остаются
определяющими и сегодня. Этот подход
дает возможность тестировать большие
программы и программные системы тщательно
и в. полном объеме. Имеющиеся здесь трудности
хорошо известны многим программистам:
1. Трудоемкость и стоимость
тестирования больших программ возрастает
экспоненциально с увеличением их размеров.
Стоимость проверки изменения в большой
системе может быть в десятки раз больше
стоимости внесения самого изменения.
2.Ошибки всегда остаются
в больших системах, особенно в таких,
которые требуют постоянного сопровождения,
усовершенствования и других изменений.
В каждой новой версии операционной системы
имеются сотни ошибок и это число остается
относительно неизменным. Изучение операционных
систем разных изготовителей подтверждают
это явление; число ошибок в разных системах
может быть большим или меньшим, но каждый
изготовитель, по-видимому, характеризуется
своей константой.
3. Издержки от плохо
тестированных программ неуклонно возрастают
по мере того, как общество возлагает на
электронные вычислительные системы все
более и более ответственные функции.
В случае больших систем коллективного
пользования, всегда есть опасность, что
какая-нибудь не выявленная ошибка может
быть причиной огромных человеческих
жертв, денежных потерь или неуправляемости
систем принятия решений (например, систем
управления воздушным транспортом).
Стандартная процедура
тестирования заключается в том, что программист
выбирает то, что он считает хорошим тестовым
случаем, и проводит его испытание. После
того как опробовано достаточное количество
случаев, программист прекращает эту работу
и заявляет, что программа испытана. Но
как заметил проф. Дейкстра, «тестирование
доказывает наличие ошибок, но не их отсутствие».
Другой возможный
путь спасения лежит в области автоматического
доказательства правильности программ.
Несколько исследователей изучали проблему
автоматического доказательства правильности
произвольной программы. В одном из случаев,
выполненных Хоором, доказательство правильности
программы, содержащей 12 операторов, включает
восемнадцать лемм. Более того, все еще
сохраняется такое чувство, что число
операторов «доказательства» может возрастать
с увеличением длины проверяемой программы
быстрее, чем по линейному закону. Одной
из причин разработки Дейкстрой структурного
программирования было то, что автоматическое
доказательство программ, отвечающих
некоторому структурному образцу, может
быть значительно упрощено.
Вместе с тем программы,
написанные на основе принципов структурного
программирования с использованием подхода
«сверху - вниз», оказываются более простыми
в тестировании.
8.2.2. Более высокая
производительность программистов
Хотя это может казаться
очевидным, стоит заметить, что облегчение
тестирования обычно повышает производительность
программиста, т. е., используя этот подход,
программист может написать большее число
отлаженных команд программы в день.
Повышение производительности
более существенно, чем может это казаться
на первый взгляд. Если каждый программист
может сделать вдвое больше, то для выполнения
данного проекта требуется лишь половина
программистов; часто это позволяет уменьшить
число уровней в организационной структуре
управления и значительно улучшить психологическую
атмосферу и общение между программистами.
Среди N программистов отдельный программист
чувствует себя как малая спица в большом
колесе, среди N12 программистов он может
почувствовать себя более важной составляющей
и работать с большим упорством и научиться
значительно большему в искусстве (а может
науке?) программирования.
8.2.3. Ясность и читабельность
программ
Структурное программирование
обладает тем дополнительным преимуществом,
что повышает читабельность программ.
Поведение многих неструктурированных
программ часто ближе к броуновскому движению,
чем к сколько-нибудь организованному
процессу. Всякая попытка прочесть листинг
приводит человека в отчаяние тем, что
в такой программе обычно исполняются
несколько операторов, после чего управление
передается в некоторую точку несколькими
страницами ниже, где исполняются еще
несколько операторов и управление снова
передается в какую-то случайную точку,
там используются еще какие-то операторы
и т. д. После нескольких таких передач
читатель забывает, с чего все началось,
и теряет ход мысли.
Структурированным
программам, напротив, свойственна тенденция
к последовательной организации и исполнению.
Еще в большей степени это справедливо
в отношении программ, отвечающих определенным
форматам и соглашениям, выделяющим вложенные
уровни циклов и операторов IF-THEN.
8.2.4. Эффективность
Один из наиболее распространенных
доводов против структурного программирования
сводится к утверждению, что оно приводит
к менее эффективным программам. Повышенное
внимание уделяется тому обстоятельству,
что использование операторов вызова
подпрограмм как альтернативы операторов
GO TO увеличивает время ЦП, требуемое для
прохождения всей программы, и в ряде случаев
связано с необходимостью значительного
увеличения памяти. В других ситуациях
программисту проще повторить небольшие
фрагменты программы (предпочитая этот
прием обращению к «общему» разделу программы),
дабы не нарушать правил структурного
программирования. Хотя обычно это не
приводит к увеличению времени ЦП, ясно,
что на это требуется дополнительная память.
В настоящее время
признано, что программы, написанные на
языках высокого уровня, таких, как Си,
Basic, Java потенциально оказываются более
эффективными при использовании принципов
структурного программирования. В конечном
счете эффективность программы, написанной
на языке высокого уровня, зависит от качества
объектного кода, формируемого компилятором
с такого языка (если, конечно, не принимать
во внимание эффективность или неэффективность
программы, заложенные в ней как внутренние
свойства проекта, что часто оказывается
значительно важнее рассматриваемых деталей
кодирования). Таким образом достигаемый
при оптимизации выигрыш значительно
превышает все виды не эффективностей,
внутренне присущих принципам структурного
программирования.
8.3. Теория и методы
структурного программирования
Итак, понятие структурного
программирования представляет собой
некоторые принципы написания программ
в соответствии с набором жестких правил
и имеет целью облегчение процессов тестирования,
повышение производительности программистов
и улучшение читабельности программ. Прежде
всего, к методам структурного программирования
относится отказ от оператора GO TO и замена
его рядом других более структурированных
операторов передачи управления. Сюда
относятся также идеи нисходящего проектирования,
а также ряд других менее важных ограничений
и соглашений, касающихся программирования.
8.3.1. Теоретические
основания структурного программирования
Известно, что формальные
системы теории вычислимости не требуют
понятия GO TO. Так, общие рекурсивные функции
Клини, системы Поста, алгоритмы Маркова
и лямбда-исчисление Чёрча строятся без
использования механизма GO ТО. В области,
более практической, ряд исследователей
пытались найти решение проблемы написания
программ таким образом, чтобы их правильность
поддавалась доказательству. Эти усилия
были приложены в направлении анализа
программ, организованных по нисходящей
схеме. В этой схеме вся программа (или
система) сначала рассматривается как
независимый «вызываемый» модуль (и действительно,
часто она именно так и функционирует,
поскольку всякая прикладная программа
вызывается операционной системой как
подпрограмма, и при этом она отрабатывает
возврат управления операционной системе
по окончании или сбою). На следующем этапе
проектирования исходная (уровня 0) программа
расчленяется на подпрограммы модулей
уровня 1; эти последние подвергаются декомпозиции
на подмодули уровня 2; процесс декомпозиции
продолжается до тех пор, пока проектировщик
не придет к настолько малым составляющим
блокам, которые могут быть легко закодированы.
При тестировании
всей программы важно иметь возможность
определить поведение подмодулей уровня
k независимо от конкретных условий на
более высоком уровне. Это позволило бы
нам доказывать правильность подмодулей
уровня (k+1) независимо от смысла, придаваемого
им на k-м шаге декомпозиции. Это в свою
очередь с необходимостью приводит к требованию,
чтобы всякий модуль проектировался с
единственным входом и единственным выходом;
что опять ведет к представлению о программе
как множестве вложенных модулей, каждый
из которых имеет один вход и один выход.
По Бому и Джакопини
для построения программы нам требуется
три основных составляющих блока:
1. Функциональный
блок.
2. Конструкция обобщенного
цикла.
3. Конструкция принятия
двоичного, или дихотомического, решения.
Функциональный блок,
показанный на рис. 8.1, можно представлять
как отдельный вычислительный оператор
(или команду в машинном коде) или как любую
другую реальную последовательность вычислений
с единственным входом, и единственным
выходом, как в подпрограмме.
Рис. 8.1. Функциональный
блок.
Так, функциональным
блоком может быть
Ø
команда «считать в сумматор» в языке
ассемблера,
Ø
оператор MOVE в Коболе, или
Ø
типичное вычисляемое выражение в Visual
Basic.
Организация цикла,
показанная на рис. 8.2а, в литературе часто
упоминается как элемент DO-WHILE. Конструкция
принятия двоичного решения показана
на рис. 8.2б; по очевидным причинам его
часто называют элементом IF-THEN-ELSE.
Заметим, что конструкции,
показанные на рис.8.2а, б, могут сами рассматриваться
как функциональные блоки, поскольку они
обладают только одним входом и одним
выходом. Таким образом, мы можем ввести
преобразование операции цикла в функциональный
блок, как это показано на рис. 8.3а, и в последующем
рассматривать всякий такой оператор
цикла эквивалентом (несколько более сложного)
функционального блока. Аналогично мы
можем ввести преобразование конструкции
принятия решения вида, представленного
на рис. 8.3б, к функциональному блоку, как
это показано на рис. 8.3б. Наконец, мы можем
привести всякую последовательность функциональных
элементов к одному функциональному элементу,
как это показано на рис. 8.3в.
а) б)
Рис. 8.2. Две логические
конструкции. а — циклическая конструкция;
б — конструкция IF-THEN-ELSE.
Всякая программа,
составленная из функциональных блоков,
операторов цикла и элементов IF-THEN-ELSE (а
в соответствии с результатами Бома и
Джакопини программа может быть построена
с использованием только этих элементов),
поддается последовательному преобразованию,
как это иллюстрируется рис. 8.3. а—в, к
единственному функциональному блоку.
В то же время обратная последовательность
преобразований может быть использована
в процессе проектирования программы
по нисходящей схеме, т. е. исходя из единственного
функционального блока, который постепенно
раскрывается в сложную структуру основных
элементов.
Рис. 8.3. Преобразования.
Отметим также взаимосвязь
между этой идеей «вложенной структуры»
и идеей модульности. Программа, построенная
путем применения приведенных выше преобразований,
является модульной в строгом смысле слова.
Любой из функциональных элементов может
быть заменен эквивалентным, не вызывая
никаких последствий в остальной части
программы. Еще важнее то, что преобразования
Бома — Джакопини могут быть применены
к исходной программе, разбивая ее тем
самым на меньшие модули, которые в свою
очередь могут быть разбиты на еще меньшие
модули и т. д. В соответствии с этой схемой
процесс преобразований может продолжаться
до тех пор, пока мы не достигнем уровня
«атомарных» модулей т. е. отдельных вычислительных
операторов, операторов IF-THEN-EISE и элементарных
циклов типа DO-WHILE.
В этом, таким образом,
состоит существенное различие в подходах
структурного программирования и модульного
программирования. Попытки применить
модульное программирование обычно характеризуются
хорошим началом: программист решает разбить
большую программу на модули. Часто, однако,
он не пытается разбить каждый из полученных
модулей на меньшие модули. В результате
получается программа, содержащая главный
модуль и небольшое число больших (например,
в несколько сот операторов) модулей первого
уровня, которые не легко разделить на
составляющие. Отметим важность этого
обстоятельства: если модули не поддаются
дальнейшему разбиению, то они должны
рассматриваться как неделимые структуры,
а это означает, что тестирование, отладка,
сопровождение или понимание таких модулей
будут затруднены в связи с необходимостью
целостного восприятия больших блоков
программного кода. Ситуация обычно осложняется
еще тем, что эти модули, введенные неформально
в соответствии с общими представлениями
модульного программирования, часто оказываются
зависимыми один от другого — они изменяют
логику друг друга, пользуются общими
областями рабочей памяти и т. д.
8.3.2. Реализация структурного
программирования
Теоретическая основа
структурного программирования допускает
реализацию его принципов на многих современных
языках программирования. Соответствующие
правила очень просты: все операции в программе
должны представлять собой либо непосредственно
исполняемые в линейном порядке выражения
(например, обычные арифметические операторы),
либо одну из следующих трех управляющих
конструкций:
1. Вызовы процедур,
подпрограмм и функций—любое допустимое
обращение к замкнутой подпрограмме с
одним входом и одним выходом. Заметим,
что подпрограммы не являются абсолютно
необходимым условием возможности реализации
структурного программирования.
2. Вложенные на произвольную
глубину операторы IF-THEN-ELSE.
3. Некоторые циклические
структуры. Чаще всего в таком качестве
используются конструкции DO-LOOP.
Хотя перечисленных
средств достаточно для построения произвольной
программы для ЭВМ, в ряде организаций
находят удобным использовать дополнительно
некоторые их «расширения».
1. Конструкция CASE.
Встречается во многих языках и может
быть представлена в нескольких формах
2. Конструкция цикла
FOR-NEXT
Основные вопросы
1. Программа, предметная
(прикладная) область, постановка задачи
-
2. Алгоритм, свойства
алгоритм решения задачи
3. Классификация специалистов
занимающиеся разработкой программ
4. Программный продукт,
сопровождение программного продукта
5. Мобильность, надежность,
эффективность, модифицируемость, коммуникативность
программных продуктов
6. Типичные методы
структурного проектирования
7. Этапы создания программных
продуктов
8. Нисходящего проектирования,
основное назначение нисходящего проектирования,
основные проблемы нисходящего проектирования
9. Нисходящее кодирование,
основные довода в пользу нисходящего
кодирования
10. Идея нисходящего
тестирования, схема восходящее тестирование,
схема нисходящее тестирование, преимуществом
нисходящего тестирования, этапы нисходящего
тестирования, достоинства нисходящего
тестирования
11. Метод структурированного
разбора, его достоинства, основные правила
метода структурированного разбора
12. Модульное программирование,
важнейшие характеристики модуля
13. Доводы в пользу
модульности программ, довода против модульности
программ
14. Структурное программирование,
основная идея структурного программирования,
основные проблемы, на решение которых
направлено структурное программирование,
основные трудности тестирования больших
программ
15. Кому важен исход
тестирования программы
16. Случаи получения
бесполезных программ
17. Порочные приемы
написания программ, отладка которых вызывает
дополнительные трудности
ЛИТЕРАТУРА
1. Иодан Э. Структурное
проектирование и конструирование программ.
М.: Мир. 1979.
2. Боэм Б, Браун Дж.,
Каспар X. и др. Характеристики качества
программного обеспечения / Пер. с англ.
Е.К. Масловского. - М.: Мир, 1981.
3. Липаев В.В. Проектирование
программных средств. -М.: Высшая школа,
1990.
4. Майерс Г. Надежность
программного обеспечения / Пер. с англ.
Ю.Ю.Галимова: Под ред. В.Ш. Кауфмана. - М.:
Мир, 1980.
Три основных принципа языков объективно-ориентированного программирования
По Бьерну
Страуструпу, автору C++, язык может называться
объектно-ориентированным, если в нем
реализованы три концепции: объекты,
классы и наследование. Однако теперь
принято считать, что такие языки
должны держаться на других трех китах: инкапсуляции,
наследовании иполиморфизме. Эт
Инкапсуляция
Как я уже говорил, инкапсуляция, или утаивание информации (information hiding), — это возможность скрыть внутреннее устройство объекта от его пользователей, предоставив через интерфейс доступ только к тем членам объекта, с которыми клиенту разрешается работать напрямую. Поскольку в том же контексте я говорил также об абстрагировании, то считаю нужным пояснить разницу между этими похожими понятиями. Инкапсуляция подразумевает наличие границы между внешним интерфейсом класса (открытыми членами, видимыми пользователям класса) и деталями его внутренней реализации. Преимущество инкапсуляции для разработчика в том, что он может открыть те члены класса, которые будут оставаться статичными, или неизменяемыми, скрыв внутреннюю организацию класса, более динамичную и в большей степени подверженную изменениям. Как уже говорилось, в С# инкапсуляция достигается путем назначения каждому члену класса своего модификатора доступа — public, private или protected.
Абстрагирование
Абстрагирование связано с тем, как данная проблема представлена в пространстве программы. Во-первых, абстрагирование заложено в самих языках программирования. Постарайтесь вспомнить, давно ли вам приходилось заботиться о стеке или регистрах процессора. Возможно, когда-то вы изучали программирование на ассемблере, но держу пари, что много воды утекло с тех пор, когда вас занимали детали реализации программы на низшем, машинно-зависимом уровне. Причина проста: большинство языков отстраняют вас (абстрагируют) от таких подробностей, позволяя сосредоточиться на решении прикладной задачи.
При объявлении
классов в объектно-
Однако язык — это один уровень абстрагирования. Если вы пойдете дальше, то, как разработчику класса, вам нужно придумать такую степень абстрагирования, чтобы клиенты вашего класса могли сразу сосредоточиться на своей задаче, не тратя время на изучение работы класса. На очевидный вопрос — какое отношение интерфейс класса имеет к абстрагированию? — можно ответить так: интерфейс класса и есть реализация абстрагирования.
Чтобы обсуждаемые здесь идеи были понятней, воспользуюсь аналогией с работой внутренних устройств торговых автоматов. Описать подробно, что происходит внутри торгового автомата, довольно трудно. Чтобы выполнить свою задачу, автомат должен принять деньги, рассчитать, дать сдачу, а затем — требуемый товар. Однако покупателям — пользователям автомата видно лишь несколько его функций. Элементы интерфейса автомата: щель для приема денег, кнопки выбора товара, рычаг для запроса сдачи, лоток, куда поступает сдача, и желоб подачи товара. Торговые автоматы остаются без изменений (более или менее) со времени их изобретения. Это связано с тем, что их внутренняя организация совершенствовалась по мере развития технологии, а основной интерфейс не нуждался в больших переменах. Неотъемлемой частью проектирования интерфейса класса является достаточно глубокое понимание предметной области. Такое понимание поможет вам создать интерфейс, предоставляющий пользователям доступ к нужной им информации и методам, но изолирующий их от "внутренних органов" класса. При разработке интерфейса вы должны думать не только о решении текущей задачи, но и о том, чтобы обеспечить такое абстрагирование от внутреннего представления класса, которое позволит неограниченно модифицировать закрытые члены класса, не затрагивая существующего кода.
При определении
нужной степени абстрагирования
класса важно помнить и о программисте
клиентского кода. Представьте, что
вы пишете основное ядро базы данных. Возможно,
вы прекрасно разбираетесь в таких
понятиях БД, как курсоры (cursors), управле
Кроме того, решая, какие члены класса сделать открытыми, надо опять вспомнить о клиенте. Это еще раз подтверждает необходимость иметь хотя бы начальное представление о предметной области и клиентах вашего класса. Так, в случае с БД ваши клиенты, наверное, не должны иметь прямого доступа к членам, представляющим внутренние буферы данных. Ведь структура этих буферов может когда-нибудь измениться. Кроме того, от целостности этих буферов зависит вся работа ядра БД, и поэтому операции по их изменению следует выполнять только вашими методами. Только после этого можно сказать, что предприняты все меры предосторожности.
ПРИМЕЧАНИЕ Может показаться, что применение объектно-ориентированных технологий главным образом исчерпывается более упрощенным созданием классов. При этом на самом деле достигается некоторый выигрыш в производительности, однако долговременные выгоды вы получите, поняв, что основное назначение ООП в облегчении программирования клиентам классов. При разработке своих классов вы всегда должны ставить себя на место программиста, которому предстоит работать либо с экземплярами этих классов либо с производными от них классами.
О пользе абстрагирования
Наличие в
классах абстрагирования, которое
максимально удобно для программистов,
работающих с этими классами, имеет
первостепенное значение при разработке
повторно используемого ПО. Если вы
выстроите интерфейс, на который не влияют
изменения в реализации, то вашему приложению
долгое время не понадобятся никакие модификации.
Вспомните пример с расчетом зарплаты.
При работе с объектом Employee и функциями,
обеспечивающими расчет зарплаты, клиенту
нужны лишь несколько методов, таких как CalculatePay,
GetAddress и GetEmployeeType.
В результате отстранения пользователя от деталей реализации система в целом становится понятнее, а значит, и удобнее в работе. Иначе обстоит дело с такими процедурными языками как С, в которых нужно показать явно каждый модуль и предоставить доступ к элементам структуры. И при каждом ее изменении нужно редактировать строки кода, имеющие отношение к данной структуре.
Наследование
Наследованием называют возможность при описании класса указывать на его происхождение (kind-of relationship) от другого класса. Наследование позволяет создать новый класс, в основу которого положен существующий. В полученный таким образом класс можно внести свои изменения, а затем создать новые объекты данного типа. Этот механизм лежит в основе создания иерархии классов. После абстрагирования наследование — наиболее значимая часть общего планирования системы. Производным(derived class) называется создаваемый класс, производный от базового(base class). Производный класс наследует все методы базового, позволяя задействовать результаты прежнего труда.