uzluga.ru
добавить свой файл
1 2 3
Ранние поколения

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

Первым шагом на пути к облегчению задачи программирования был отказ от использования цифр для записи команд и операндов непосредственно в той фор­ме, в которой они используются в машине. С этой целью при разработке про­грамм стали широко применять мнемоническую запись различных команд вме­сто их шестнадцатеричного представления. Например, вместо цифрового кода команды загрузки регистра программист мог теперь написать LD, а вместо кода команды копирования содержимого регистра в память мог использовать мнемо­ническое обозначение ST. Для записи операндов были разработаны правила, в соответствии с которыми программист мог присваивать некоторым областям па­мяти описательные имена (их часто называют идентификаторами) и использо­вать их при записи команд программы вместо адресов соответствующих ячеек памяти. Одним из специфических вариантов является присвоение мнемониче­ских имен регистрам центрального процессора, например RO, Rl, R2,... .

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

Вначале программисты использовали такие обозначения при разработке программ на бумаге, а затем переводили их на машинный язык. Однако вскоре стало понятно, что такой перевод может выполнить и сама машина. В результате были разработаны программы, названные ассемблерами и предназначенные для перевода записанных в мнемоническом виде программ на машинный язык. Название "ассемблер" (assembler — сборщик) эти программы получили потому, что их назначение заклю­чалось в сборке машинных команд из кодов команд и операндов, полученных в ре­зультате перевода мнемонических обозначений и идентификаторов. Мнемонические системы записи программ стали, в свою очередь, рассматриваться как особые языки программирования, именуемые языками ассемблера.

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

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

Кроссплатформенное программное обеспечение

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

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

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

Следуя такому подходу, специалисты по компьютерам стали разрабатывать языки программирования, которые больше подходили для целей разработки программного обеспечения, чем низкоуровневые языки ассемблера. В результате появились языки программирования третьего поколения, которые отличались от предыдущих поколений тем, что их языковые конструкции имели более высо­кий уровень и были машинно-независимыми. Наиболее известными примерами ранних языков третьего поколения являются FORTRAN (FORmula TRANslator — переводчик формул), который был предназначен для научных и инженерных расчетов, и COBOL (COmmon Business-Oriented Language — язык общего назначения деловой ориентации), разработанный специалистами военно-морского флота США для решения экономических задач.

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

После того как необходимый набор примитивов высокого уровня будет определен, пишется программа, называемая транслятором (translator — переводчик). Она пред­назначена для перевода программ, записанных с использованием примитивов языка высокого уровня, на машинный язык. Подобный транслятор похож на программу-ассемблер второго поколения, за исключением того, что ему часто приходится объе­динять (или компилировать, от англ., compile) несколько машинных инструкций в короткие последовательности команд, предназначенные для имитации выполнения отдельных примитивов высокого уровня. Именно поэтому подобные программы-переводчики часто называют компиляторами. Разработку первого компилятора при­писывают Грейс Хоппер (Grace Hopper), которая играла ведущую роль в продвиже­нии концепции языков программирования высокого уровня. Действительно, идея писать программы в форме, близкой к естественному языку, была настолько рево­люционной, что многие руководители поначалу отвергали ее.

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

Машинная независимость

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

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

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

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

assign Total the value Price + Tax,

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

Парадигмы программирования

Классификация языков программирования по поколениям требует распреде­ления их по линейной шкале (рис. 5.1) в соответствии с той степенью свободы от компьютерной тарабарщины, которую данный язык предоставляет программи­сту. Это позволяет ему мыслить понятиями, связанными непосредственно с ре­шаемой задачей. В действительности развитие языков программирования проис­ходило несколько иначе. Оно протекало по разным направлениям, связанным с альтернативными подходами к процессу программирования (называемыми пара­дигмами программирования). Таким образом, историческую схему развития языков программирования наиболее точно можно изобразить составной диа­граммой, показанной на рис. 5.2, на которой отдельные линии, символизирую­щие различные парадигмы программирования, появляются и развиваются неза­висимо друг от друга. В частности, на рисунке показаны четыре независимых направления, соответствующие функциональной, объектно-ориентированной, императивной и декларативной парадигмам программирования, а также пред­ставлены различные относящиеся к ним языки. Месторасположение названия языка на линии соответствует времени его появления относительно других язы­ков. Однако это вовсе не означает, что каждый последующий язык обязательно является наследником предыдущего.

LISP ML________Scheme_________

— функциональные

SIMULA C++ Ada 95

Объектно-ориентированные

Smalltalk Visual Basic Java Машинные FORTRAN BASIC С Ada

языки COBOL ALGOL APL Pascal

GPSS Prolog

Императивные Декларативные

Эволюция парадигм программирования

Императивная, или процедурная парадигма, представляет традиционный подход к процессу программирования. Действительно, именно в соответствии с этой парадигмой построен цикл обработки команды центрального процессора: "извлечь-декодировать-выполнить". Как следует из названия, императивная па­радигма определяет процесс программирования как запись последовательности команд, которая при выполнении выполнит обработку данных, необходимую для получения желаемого результата. Таким образом, для решения задачи импера­тивная парадигма предлагает попытаться найти алгоритм ее решения.

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

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

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

Функциональная парадигма рассматривает процесс разработки программ как конструирование ее из неких "черных ящиков", каждый из которых получает некоторые исходные данные (на входе) и вырабатывает соответствующий резуль­тат (на выходе). Математики называют такие "ящики" функциями, поэтому этот подход называется функциональной парадигмой. Языковые конструкции функ­циональных языков программирования состоят из элементарных функций, на основе которых программист должен создавать более сложные функции, необхо­димые для решения поставленной задачи. Таким образом, согласно функцио­нальной парадигме, программист решает задачу, рассматривая исходные данные, требуемые результаты и преобразование, которое необходимо выполнить, чтобы получить результаты из исходных данных. Решение требуемой задачи, вероятнее всего, можно получить, разбивая исходное преобразование на более простые пре­образования, порождающие промежуточные результаты, служащие, в свою оче­редь, исходными данными для других простых преобразований. Короче говоря, в соответствии с функциональной парадигмой процесс программирования заклю­чается в конструировании требуемых функций в виде вложенных друг в друга совокупностей более простых функций.

Например, на рис. 5.3 показано, как можно построить функцию вычисления среднеарифметического нескольких чисел из трех более простых функций. Пер­вая из них — Sum — получает на вход список чисел и вычисляет их сумму; вто­рая — Count — получает список чисел и подсчитывает их количество; третья —

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

(Divide (Sum Numbers) (Count Numbers))

Использование в этом выражении вложенных структур отражает, что исход­ные данные для функции Divide являются результатами выполнения функций Sum и Count. В качестве другого примера предположим, что у нас есть функция Sort, которая сортирует список чисел, и функция First, которая находит пер­вое число в этом списке. В этом случае приведенное ниже выражение позволяет извлечь из списка List наименьшее из чисел:

(First (Sort List))

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

Превосходство функциональной парадигмы программирования над императивной проявляется в том, что она стимулирует модульный подход к конструи­рованию программ. Действительно, то, что программы рассматриваются как функции, которые, в свою очередь, должны состоять из других функций, выну­ждает программиста думать в терминах модулей. По этой причине сторонники функционального программирования утверждают, что этот подход приводит к созданию более высокоорганизованных программ, чем в случае применения им­перативной парадигмы. Более того, многие утверждают, что функциональная парадигма является естественной средой для метода, предусматривающего по­строение программ из "строительных блоков". Данный подход напоминает ско­рее конструирование программ из заранее подготовленных блоков, нежели вы­полнение всей работы с нуля. Такому способу программирования отдают пред­почтение в основном специалисты по разработке больших пакетов программ. Эти же аргументы приводятся и в защиту объектно-ориентированной парадигмы.

Объектно-ориентированная парадигма, которая предполагает применение ме­тодов объектно-ориентированного программирования (ООП), — это еще один подход к процессу разработки программного обеспечения. В рамках этого подхо­да элемент данных рассматривается как активный "объект", а не как пассивный элемент, как это принято в традиционной императивной парадигме. Поясним это на примере списка имен. В традиционной императивной парадигме этот список рассматривается просто как совокупность некоторых данных. Любая программа, получающая на вход этот список, должна содержать алгоритм выполнения над ним требуемых действий. Таким образом, список является пассивным объектом, поскольку он обрабатывается управляющей программой, а не обрабатывает себя сам. Однако при объектно-ориентированном подходе список рассматривается как объект, содержащий некоторую совокупность данных вместе с набором процедур для их обработки. Этот набор может включать процедуры для вставки в список нового элемента, удаления элемента из списка или сортировки списка. Поэтому программа, получающая доступ к списку для его обработки, не обязана содер­жать алгоритм для выполнения указанных действий. При необходимости она просто выполняет процедуры, предоставляемые самим объектом. В этом смысле объектно-ориентированная программа вместо сортировки списка (как при импе­ративной парадигме) скорее просит список отсортировать самого себя.

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

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

Еще одним преимуществом модульной структуры, полученной в результате при­менения объектно-ориентированной парадигмы, является то, что все взаимодействия модулей осуществляются посредством пересылки сообщений — основного способа взаимодействия машин в компьютерных сетях. Таким образом, концепция объектов, посылающих друг другу сообщения, — это естественный подход к разработке про­граммного обеспечения, распределенного по сети. Реализация такой сетевой системы объектов является целью технологии CORBA (Common Object Request Broker Architecture — архитектура с брокерами запросов к общим объектам) — открытой системы спецификаций для реализации механизмов передачи сообщений между объ­ектами в сети. В заключение следует заметить, что хотя мы называем рассмотренные выше концепции парадигмами программирования, они имеют ответвления и за рамка­ми собственно процесса программирования. Эти парадигмы представляют совер­шенно разные подходы к методам решения задач вообще и потому оказывают влияние на всю область разработки программного обеспечения. В этом смысле термин парадигма программирования является неверным. Правильно было бы назвать эти понятия парадигмами разработки программного обеспечения.



следующая страница >>