В течение многих лет программное обеспечение строилось на основе операционных и процедурных языков, таких как Фортран, Бейсик, Паскаль, Ада, Си. Сегодня современные версии этих и им подобных языков (Модула, Форт) доминируют при разработке прикладных программных средств. Однако по мере эволюции языков программирования получили широкое распространение и другие, принципиально другие подходы.
Классическое операционное программирование требует от программиста детального описания того, как решить задачу, то есть формулировки алгоритма и его специальные записи. При этом ожидаемые свойства результата обычно не указываются. При процедурном подходе операторы объединяются в группы – процедуры. Структурное программирование не выходит за рамки этого направления, оно лишь дополнительно фиксирует некоторые полезные приемы технологии программирования.
Модульное программирование является развитием и совершенствованием процедурного программирования и библиотек специальных программ. Основная черта модульного программирования — стандартизация интерфейса между отдельными программными единицами. Модуль — это отдельная функционально-законченная программная единица, которая структурно оформляется стандартным образом по отношению к компилятору и по отношению к объединению ее с другими аналогичными единицами и загрузке. Как правило, каждый модуль содержит паспорт, в котором указаны все основные его характеристики: язык программирования, объем, входные и выходные переменные, их формат, ограничения на них, точки входа, параметры настройки и т.д. Объем модуля обычно не превышает 1000 команд ЭВМ или операторов языка программирования. В противном случае модуль становится громоздким и трудным к восприятию и использованию.
Модульное программирование — это искусство разбиения задачи на некоторое число различных модулей, умение широко использовать стандартные модули путем их параметрической настройки, автоматизация сборки готовых модулей из библиотек, банков модулей.
Основные концепции модульного программирования:
· каждый модуль реализует единственную независимую функцию;
· каждый модуль имеет единственную точку входа и выхода;
· каждый модуль имеет единственную точку входа и выхода;
· размер модуля по возможности должен быть минимизирован;
· каждый модуль может быть разработан и закодирован различными членами бригады программистов и может быть отдельно протестирован;
· вся система построена из модулей;
· модуль не должен давать побочных эффектов;
· каждый модуль не зависит от того, как реализованы другие модули.
При таком подходе сложная система разделяется на несколько частей, одновременно создаваемых различными программистами. Каждый модуль реализует единственную функцию. Размер модуля невелик, поэтому тестирование управляемо и может быть проведено тщательным образом. После кодирования и тестирования всех модулей происходит их интеграция, и тестируется вся система.
При сопровождении тестируется и отлаживается только тот модуль, который плохо работает. Очевидны преимущества в облегчении написания и тестирования программ, уменьшается стоимость их сопровождения.
Концепция модульного программирования реализована в ряде языков, таких как Modula 2, Turbo Pascal 5.0 и выше, C, Python,Perl.
Отличие в реализации процедурного программирования от модульного состоит в том, что модуль не виден программе. В отличие от стандартных языков процедурного программирования, в модульных языках лишние модули просто не прикомпановываются на этапе сборки.
Принципиально иное направление в программировании связано с парадигмами непроцедурного программирования. К ним можно отнести объектно-ориентированное и декларативное программирование.
Объектно-ориентированный язык создает окружение в виде множества независимых объектов. Каждый объект ведет себя подобно отдельному компьютеру, их можно использовать для решения задачи как «черные ящики», не вникая во внутренние механизмы их функционирования.
Из языков объектного программирования, популярных среди профессионалов, следует назвать прежде всего C++, для более широкого круга программистов предпочтительны среды типа Delphi и VisualBasic.
При использовании декларативного языка программист указывает исходные информационные структуры, взаимосвязи между ними и то, какими свойствами должен обладать результат. При этом алгоритм программист не строит. То есть при использовании декларативного языка в программах описывается способ решения поставленной задачи, а не предписываются шаги для получения результата.
Декларативные языки подразделяются на два класса: функциональные и логические.
Функциональное программирование — парадигма программирования, в которой процесс вычисления трактуется как вычисление значений функций в математическом понимании (то есть тех, чей единственный результат работы заключается в возвращаемом значении, или другими словами, вычисление которых не имеет побочного эффекта); способ решения задачи описывается при помощи зависимости функций друг от друга (в том числе возможны рекурсивные зависимости), но без указания последовательности шагов.
Типичным представителем функциональных языков программирования является Лисп. В основе языка Лисп лежит лямбда-исчисление. Лямбда-исчисление – формализм для представления функций и способов их комбинирования. Вместе со своим эквивалентом –комбинаторной логикой, в которой не используются переменные, – предложено около 1930 г. логиками Черчем, Шейнфинкелем и Карри.
В лямбда-исчислении Черча функция записывается в виде l (x1,x2, … , xn).fn
В Лиспе лямбда-выражение имеет вид:
(LAMBDA(x1,x2, … , xn).fn).
Символ LAMBDA означает, что мы имеем дело с определением функции. Символы xiявляются формальными параметрами, они образуют список, называемый лямбда-списком; fn – тело функции, которое может иметь произвольную форму, допускаемую интерпретатором Лиспа. Телом функции может быть константа или композиция из вызовов функций.
Программы на языках логического программирования выражены как формулы математической логики, а компилятор пытается получить следствия из них. Так же как в функциональном программировании, программист остается в неведении о методах, применяемых при вычислении, и последовательности исполнения элементарных действий. Большая часть ответственности за эффективность вычислений в логическом и функциональном программировании перекладывается на «плечи» транслятора используемого языка программирования.
Все сказанное выше можно отобразить следующей схемой:
Приведем другие классификации языков программирования.
Одной из наиболее примечательных является классификация моделей языков, предложенная Дж. Бэкусом в 1977 г. В соответствии с ней выделяются три категории языков:
A. Простые операционные модели (языки, основанные на конечных автоматах, машине Тьюринга);
B. Аппликативные модели (языки на основе лямбда-исчисления Чёрча, системы комбинаторов Карри, чистого Лиспа);
C. Модели фон Неймана (традиционные языки программирования).
Джон Устерхаут предложил принцип классификации языков, в соответствии с которым высокоуровневые языки делятся на языки системного программирования и на скриптовые.
Скриптовый язык (англ.scriptinglanguage, также называют язык сценариев) — язык программирования, разработанный для записи «сценариев», последовательностей операций, которые пользователь может выполнять на компьютере. Простые скриптовые языки раньше часто называли языками пакетной обработки (batch languages или job control languages). Сценарии всегда интерпретируются, а не компилируются.
В прикладной программе, сценарий (скрипт) — это программа, которая автоматизирует некоторую задачу, которую без сценария пользователь делал бы вручную, используя интерфейс программы.
Поскольку сценарии интерпретируются из исходного кода динамически при каждом исполнении, они выполняются обычно значительно медленнее готовых программ, оттранслированных в машинный код на этапе компиляции. Поэтому сценарные языки не применяются для написания программ, требующих оптимальности и быстроты исполнения. Но из-за простоты они часто применяются для написания небольших, одноразовых («проблемных») программ. Также, в плане быстродействия скриптовые языки можно разделить на языки динамического разбора (sh, command.com) и предварительно компилируемые (Perl). Языки динамического разбора считывают инструкции из файла программы минимально требующимися блоками, и исполняют эти блоки, не читая дальнейший код. Предкомпилируемые языки вначале считывают всю программу, компилируют её всю либо в машинный код, либо в какой-то внутренний формат, и лишь затем — исполняют получившийся код.
Вегнер сгруппировал некоторые из наиболее известных языков высокого уровня в четыре поколения в зависимости от того, какие языковые конструкции впервые в них появились:
FORTRAN I | Математические формулы |
ALGOL-58 | Математические формулы |
Flowmatic | Математические формулы |
IPL V | Математические формулы |
Первое поколение (1954-1958)
Второе поколение (1959-1961)
FORTRAN II | Подпрограммы, раздельная компиляция |
ALGOL-60 | Блочная структура, типы данных |
COBOL | Описание данных, работа с файлами |
Lisp | Обработка списков, указатели, сборка мусора |
Третье поколение(1962-1970)
PL/I | FORTRAN+ALGOL+COBOL |
ALGOL-68 | Более строгий приемник ALGOL-60 |
Pascal | Более простой приемник ALGOL-60 |
Simula | Классы, абстрактные данные |
· Потерянное поколение (1970-1980)