В данной статье рассматриваются ключевые исторические этапы развития подходов к программированию, а также делается предположение о будущем пути их развития. Приведены проблемы, под влиянием которых языки изменялись на каждой эволюционной стадии, и трудности, возникающие вследствие этих изменений.
Ключевые слова: история, язык программирования, абстракция, параллелизм.
Уже на протяжении более чем полувека информационные технологии видоизменяют принципы взаимодействия и работы других сфер общества. Если в начале пути компьютер воспринимался, как вычислительное устройство, способное лишь ускорить вычисления для специалистов, то сейчас тысячи людей по всему миру, в чьи обязанности не входит проведение каких бы то ни было расчётов не только используют различные компьютеризированные устройства каждый день, но зависят от них тем или иным образом.
С момента появления персонального компьютера прошло всего несколько десятилетий, но произошедшие изменения коренным образом изменили не только то, как люди работают, но и то, как они общаются, взаимодействуют и размышляют. В самой же индустрии эти изменения также происходили, и проследить их можно через историю развития главного средства программиста — языка программирования, так как фактически язык программирования является не только набором правил, позволяющим выразить алгоритмическую составляющую задачи для компьютера, но и средством формирования мышления самого программиста.
Условно историю развития языков программирования можно разделить на шесть основных этапов или поколений, где первым будет этап специализированных машин. В этом поколении программирование не существовало, как таковое, однако программы уже существовали. Для каждой конкретной задачи строилась отдельная машина, которая умела исполнять только одну задачу, например, решать систему линейных уравнений заданного вида. Машина проводила вычисления и выдавала результат, однако, логика её работы, программа по сути, была обусловлена внутренней уникальной схемой, что не позволяло проводить различные вычисления на этой же машине. Важно заметить, что для программирования тогда использовались именно электротехнические элементы, то есть, программист-инженер по сути создавал программу посредством соединения непосредственно устройств, исполнявших операции.
Уникальность, и как следствие дороговизна, привели к тому, что было разработано новое поколение машин — эти устройства, по своей сути, стали первыми программируемыми в полном смысле слова. Языком программирования для этих машин в начале служила коммутационная панель, на которой, соединяя гнёзда различных устройств, программист мог задавать последовательность действий для машины — что уже было достаточно большим различием в синтаксисе языка. Если раньше программист исполнял роль инженера-схемотехника и применял сами компоненты для создания программы, то теперь при программировании он мог использовать универсальные блоки — заранее объединенные группы компонент, имеющие необходимые свойства и умеющие выполнять необходимые операции. В дальнейшем в связи с развитием оборудования панель была заменена на перфокарты, перфоленты, магнитные ленты и барабаны — однако, программирование всё ещё сводилось к формированию последовательности единиц и нулей. Несмотря на то, что процесс программирования для этих машины был долгим, трудоёмким и дорогостоящим — модифицируемость программ привела к стремительному изменению подхода — больше не было потребности в разработке новой машины для каждой задачи. Также необходимо обратить внимание на зарождение тенденции к повышению абстракции на этом этапе — ведь программист уже не занимался непосредственно разработкой схемы машины, его задачей стало именно выстраивание программы из уже существующих блоков.
Продолжившееся постепенное удешевление компьютеров и повышение производительности привело к кризису программирования. Индустрия могла производить новые компьютеры в некоторых случаях быстрее, чем группа инженеров успевала разработать и отладить достаточно сложную программу для прошлого поколения. Для решения данной проблемы был разработан язык программирования низкого уровня — ассемблер. Ассемблер позволил программисту использовать мнемонические команды, вместо машинных команд, что в значительной степени ускорило работу программистов. Однако, несмотря на наличие мнемонических команд, программа всё ещё являлась прямым указанием последовательности действий для машины, а не средством выражения алгоритма. Этот этап стал большим прорывом, так как сама программа перестала быть для программиста последовательностью нулей и единиц, а превратилась в набор команд. Однако стоит отметить, что любая команда ассемблера является лишь обёрткой над набором нулей и единиц, то есть, по сути, позволяет абстрагироваться программисту от некоторой командной последовательности, заменив её мнемонической командой.
На смену низкоуровневому ассемблеру пришли алгоритмические языки программирования высокого уровня. Эти языки программирования уже были достаточно далеки от конкретной аппаратной реализации, так как программирование велось в терминах, близких человеку. Целью появления этих языков стало формирование ещё одного уровня абстракции, программист теперь был свободен от решения задач переноса данных внутри машины и мог больше внимания уделять самому алгоритму программы. Именно на этом этапе языки программирования получили программы — компиляторы и интерпретаторы. Оба эти класса программ необходимы для перевода и трансформации текстового представления программы, которое удобно для программиста и построено согласно правилам языка в машинное представление — при этом управляющие команды в большей степени являются средствами языка и не привязаны к аппаратной реализации машины. Важным моментом здесь является то, что язык ассемблер не имеет компилятора, а использует ассемблер (утилита, проводящая сборку бинарной программы для машины из исходного кода, на языке ассемблера называется также) — программу, которая лишь ставит в соответствие мнемоническим символам бинарные последовательности, но не трансформирует программу. Эти изменения привели к значительному ускорению разработки и удешевлению программ, однако создание больших программ всё ещё оставалось трудоёмким. Одной из основных проблем стала не сложность программирования, а сложность самих программ. Большие программные продукты было очень трудно контролировать, так как не было единого стиля мышления и программирования.
Решением этой проблемы занялся ряд выдающихся учёных, в котором особо необходимо отметить имена Эдсгера Дейкстры, Коррады Бёма и Джузеппе Якопини. За несколько лет в результате работы многих учёных и практиков был сформирован базис структурного подхода к программированию. Отличительной особенностью стало выявление трёх базовых элементов любой программы: последовательность, ветвление и цикл. [1] Такая систематизация не позволяла программе превратиться в разрозненный набор команд, что в свою очередь в значительной мере повысило качество программ и скорость их разработки. Конечно же, формирование правил программирования мгновенно отразилось и на том, как о программе размышлял программист. Если на прошлом этапе программист был избавлен от необходимости думать непосредственно в командах машины и стал вести разработку с помощью команд языка, следя лишь за ходом программы, то теперь программа больше не воспринималась как набор приказов машине, она стала ещё более абстрактной, превратившись в укрупнённые блоки с хорошо видимой структурой, что позволило программистам сконцентрироваться на непосредственных алгоритмах, а не на отслеживании переходов между командами в памяти. Косвенным подтверждением можно считать резкое увеличение производительности алгоритмов и ряд открытий новых алгоритмов.
Однако задачи, которые решались с помощью компьютеров становились всё сложнее, а набор предметных областей — шире. По этой причине на рубеже 70-х годов возникла потребность вновь переработать подход к программированию. В результате на свет появилась новая концепция — объектно-ориентированный подход. Отличительной чертой данного подхода является возможность для программиста думать о программе, как о наборе объектов, которые могут взаимодействовать между собой и имеют собственное состояние. Такой подход упрощает восприятие и структуру программы, так как сводит многие трудные для понимания вещи к вещам из реального мира, думать о которых на этапе разработки проще. Стоит отметить, что объектно-ориентированный подход является именно подходом, а не самостоятельным языком. Объектно-ориентированный подход есть не что иное, как способ повышения уровня абстракции при работе над программой, а точнее, способ размышления о предметной области — то есть, полностью свести размышление о программе к абстрактным сущностям.
Именно такой подход является наиболее популярным, хотя и подвергается множественной критике и имеет ряд недостатков. Так, например, с каждым повышением уровня абстракции возникает дополнительная нагрузка, с которой приходится справляться вычислителю, что снижает эффективность программ. С другой стороны, повышение уровня абстракции позволяет избегать многих ошибок, так как программист избавлен от необходимости прямо указывать действия машине.
Краткое рассмотрение истории развития языков программирования позволяет очень чётко выделить общую тенденцию. Языки программирования с самого своего зарождения развиваются по направлению повышения абстрактности при программировании, что является упрощением взаимодействия между программистом и машиной. Таким образом, история развития языков программирования наглядно демонстрирует, что главной проблемой при программировании является наличие ошибок в программе, а не её вычислительная сложность. Действительно, с проблемой снижения эффективности можно бороться путём увеличения производительности самих машин, но подобный подход не позволяет бороться с ошибками в программах. Любая ошибка в программе стоит гораздо дороже, чем дополнительная память или процессорное время, так как сводит полезность программы к нулю.
Однако, подход наращения производительности был крайне эффективен в 90-х годах. На данный момент повышение производительности систем не является настолько простой задачей. С одной стороны, с ростом частоты процессора и уменьшения размера транзисторов повышается и рассеиваемая процессором мощность — что требует дополнительных затрат на поддержание процессора в рабочем режиме. С другой стороны, если проблему охлаждения тем или иным образом можно решить, то проблему теоретического минимального размера транзистора решить не представляется возможным без фундаментального изменения технологии производства. Таким образом дальнейшая миниатюризация становится всё более трудоёмкой и дорогостоящей. [2] Эти причины и привели в 2000-х годах к популяризации многопроцессорных систем, а как следствие, и к обострению проблемы параллельного программирования. Важно заметить, что эта проблема на данный момент всё ещё не является решённой в полном смысле слова, так как причиной её появления служит сама архитектура современных машин. [3]
В заключении хотелось бы попытаться предсказать, как будет выглядеть следующее поколение языков программирования — однако сделать точное предположение в такой области будет крайне затруднительно, так как уже сейчас ведутся работы в нескольких направлениях. Поэтому ограничимся лишь описанием наиболее важных проблем, которые возникнут перед авторами.
На данный момент очевидно, что решение проблемы параллелизма вычислений на этапе программирования должно быть найдено. При поисках решения разработчикам придётся столкнуться не только с трудностями реализации таких средств на современных компьютерах, но и с тем, что сама природа человеческого мышления линейна — то есть сам программист не способен мыслить параллельно. Необходимо также отметить, что решение проблемы абстрактности человеческого мышления в современных языках программирования является сдерживающим фактором для организации эффективных параллельных вычислений — так как не только человеческое мышление, но и сам инструментарий программиста во многом ориентирован на работу со строгой последовательностью действий, что усложняет разработку и отладку программ при работе с параллельными потоками вычислений. Вышесказанное приводит к мысли о том, что организации взаимодействия программиста и машины также потребует переработки. С самого зарождения синтаксис языков программирования эволюционировал по пути улучшения структурированности, читаемости и понятности программы для программиста. Однако, на данный момент улучшением синтаксиса добиться наглядности и удобства работы с параллельными программами пока не удаётся.
Вероятно, решение будет найдено в уже существующих концепциях. Так, например, ещё в 1974 году в Массачусетском технологическом институте был разработан MIT Static Dataflow Machine — аппаратная реализация машины с статической архитектурой потока данных [4]. В ней управление вычислениями проводилось при помощи самих данных, а не с помощью потока управления — то есть, была решена одна из основных проблем текущей архитектуры — наличие счётчика инструкций. Эта идея не получила развития в связи со сложностью реализации и программирования в 80-х годах и была постепенно забыта, однако с появлением графических ускорителей идея была вновь использована. Многие графические чипы последних лет используют в том или ином виде элементы архитектуры с потоком данных.
Другим направлением может стать разработка средств визуального программирования, которые уже применяются сегодня в некоторых специализированных областях программирования. Примерами успешных решений можно считать: LabView, Algorithm Builder и HiAsm — однако, каждый из этих инструментов сильно привязан к своему набору решаемых задач, что не позволяет говорить о необходимом уровне универсальности, сопоставимым, например с языком C.
Учитывая описанные выше проблемы, можно предположить, что будущее языков программирования будет связано с увеличением значимости средств автоматизации программирования. Такого рода инструментарий уже сейчас применяется в некоторых специализированных областях, так, например, при разработке интеллектуальных систем и систем принятия решений. В дальнейшем такого рода технологии, вероятно, позволят переложить большую часть работы программиста на машину [5] — что в свою очередь вновь повысит уровень абстракции, снижая эффективность и повышая качество программ.
Литература:
1. Роберт У. Себеста. Основные концепции языков программирования. — 5-е изд.. — М.: Вильямс, 2001. — 672 с.
2. Джефф Коч, Изобретение многоядерных процессоров: расширение преимуществ закона Мура, Журнал Technology@Intel, 2005
3. Wesley M. Johnson, J. R. Paul Hanna, Richard J. Millar. Advances in Dataflow Programming Languages. — NY.: ACM Computing Surveys (CSUR), 2004.
4. Jack B.Dennis, David P.Misunas. Preliminary Architecture for a Basic Data-Flow Processor. — NY.: ACM Sigarch Computer Architecture News, 1974.
5. Тыугу Э. Х. Концептуальное программирование М.: Наука. Главная редакция физико-математической литературы, 1984. — 256 с.