Введение

Какие бывают языки программирования? Что за концепции в них заложены? Как они развивались? В данной статье рассмотрим виды языков программирования основываясь на так называемых уровнях — от машинных кодов (низкий уровень, приближённый к компьютерному "железу") до таких языков, как Java или С# (высокий уровень). Чем меньше преобразований пройдёт текстовый листинг программы по пути превращения в набор нулей и единичек – тем ниже уровень.
Далее мы рассмотрим:
  1. Языки низкого уровня (машинные коды и ассемблер)
  2. Средний уровень ( C, Фортран …. )
  3. Высокий уровень (C++, Java, Python, Ruby, JavaScript ...)
Уровень также характеризует насколько подробно нужно детализировать листинг будущей программы для воплощения реализации. Насколько этот процесс прост для человека. Не стоит считать уровень языка однозначным показателем его возможностей. Язык программирования – это инструмент, который эффективен в одной области и менее полезен в других. И столяр, и плотник работают с деревом. У первого основной инструмент – набор стамесок, у второго – топор. Однако резной шкаф изящнее сделает столяр, а дом быстрее поставит плотник. Хотя каждый и способен выполнить работу другого, но сделает это гораздо менее эффективно. Различные данные в компьютере представлены в виде наборов нулей и единиц. Управляющие команды для её обработки – те же данные, содержащие внутри себя инструкции, которые определяют местоположение необходимой информации и способ модификации.

Машинные языки (Самый низкий уровень)

Нам придётся совершить краткий визит из Software области в Hardware. Рассмотрим в упрощенном виде. Процессор – основной «мозг» компьютера. Материнская плата, на которой он установлен, содержит контроллеры, служащие для взаимодействия с прочими устройствами через шины (каналы данных для связи).
Некоторые работают с большой скоростью (красные стрелки): процессор черпает из памяти команды и манипулирует данными, видеокарта – особенно в 3D играх, потребляет огромные объёмы текстур, фигур, координат пикселей и прочих объектов для построения изображения на экране монитора. Другим (в силу ограничения скорости обмена информацией) столь высокие показатели и не нужны. Разнообразные внутренние и внешние устройства подключены на схеме зелёными стрелками.

Внутренний мир процессора

Все команды процессора поступают из памяти на выполнение в двоичном виде. Формат, количество, подмножество инструкций зависят от его архитектуры. Большинство из них несовместимо друг с другом и следуют разным идеологиям. А также вид команды сильно зависит от режима (8/16/32… разрядность) и источника данных (память, регистр, стек…), с которыми работает процессор. Одно и то же действие может быть представлено различными инструкциями. Процессор имеет команды сложения двух операндов (ADD X,Y) и прибавления единицы к указанному (INC X). Добавление тройки к операнду можно выполнить как ADD X,3 или троекратно вызвав INC X. И, в отношении разных процессоров, нельзя предсказать какой из этих способов будет оптимальным по скорости или объёму занимаемой памяти. Для удобства двоичную информацию записывают в 16-ричном виде. Рассмотрим часть привычной программы (язык C, синтаксис которого сходный с Java)
int func() {
    int i = getData("7") ;
    return ++i;
   ...
}
Код, реализующий те же действия в виде последовательности инструкций для процессора: ... 48 83 ec 08 bf bc 05 20 00 31 c0 e8 e8 fe ff ff 48 83 c4 08 83 c0 01 ... Вот так, собственно и выглядит низкоуровневый язык программирования для процессора intel. Фрагмент, вызывающий метод с аргументом и возвращающий увеличенный на единицу результат. Это и есть машинный язык (код), который передается непосредственно сразу, без преобразований, на исполнение процессору. Плюсы:
  • Мы полностью хозяева положения, имеем самые широкие возможности использования процессора и аппаратуры компьютера.
  • Для нас доступны все варианты организации и оптимизации кода.
Минусы:
  • Необходимо обладать обширными знаниями по функционированию процессоров и учитывать большое количество аппаратных факторов при выполнении кода.
  • Создание программ чуть более сложных чем приведенный пример приводит к резким увеличениям затрат времени по написанию кода и его отладку.
  • Платформозависимость: программа, созданная для одного процессора, как правило, не будет функционировать на других. Возможно, и для данного процессора, в остальных режимах его работы, потребуется редактирование кода.
Машинные коды широко использовались на заре появления компьютеров, других способов программирования в эпоху пионеров ЭВМ не было. В данное время ими изредка пользуются инженера в области микроэлектроники при разработке или низкоуровневом тестировании процессоров.

Язык ассемблера (низкий уровень)

В отличие от компьютера мы с вами лучше воспринимаем информацию в текстовом/смысловом, а не цифровом виде. Вы с легкостью назовете полсотни имён контактов в вашем смартфоне, но вряд ли сможете наизусть написать соответствующие им номера телефонов. Аналогично и с программированием. На лестнице типов мы поднимемся выше, сделав три основных шага:
  • Сопоставим группам цифровых инструкций процессора, выполняющих соответствующие действия, одну символьную команду.
  • Выделим аргументы инструкций процессора отдельно.
  • Введем возможность именовать области памяти, переменные, местоположение отдельных команд.
Сравним фрагменты прошлой программы в машинных кодах (по центру) и на языке ассемблера (справа):
2004b0     48 83 ec 08      sub    $0x8,%rsp
2004b4     bf bc 05 20 00   mov    $0x2005bc,%edi
2004b9     31 c0            xor    %eax,%eax
2004bb     e8 e8 fe ff ff   callq  getData
2004c0     48 83 c4 08      add    $0x8,%rsp
2004c4     83 c0 01         add    $0x1,%eax
Как видим, процесс написания программы упростился: нет необходимости пользоваться справочниками формирования цифровых значений команд, рассчитывать длины переходов, распределение данных в памяти по её ячейкам и иные особенности процессора. Мы описываем нужное действие из набора символьных команд и необходимых для логики из выполнения аргументов, а далее программа-транслятор переводит текстовый файл на понятный процессору набор нулей и единиц. Плюсы:
  • Процесс написания и модификации кода упростился.
  • Сохранился контроль ко всем ресурсам аппаратуры.
  • Относительно легче переносить программу на другие платформы, но требуется их модификация в зависимости от аппаратной совместимости.
Минусы:
  • Ассемблер относится к низкоуровневым языкам программирования. Создание даже небольших участков кода затруднено. К тому же также необходимо учитывать специфику работы аппаратуры.
  • Платформозависимость.
Самый популярный демонстрационный Java пример:
public static void main(String[] args) {
    System.out.println("Hello World!");
}
будет выглядеть (NASM синтаксис, с использованием Windows API и kernel32.lib) следующим образом:
global _main
	extern  _GetStdHandle@4
	extern  _WriteFile@20
	extern  _ExitProcess@4

	section .text
_main:
	; DWORD  bytes;
	mov 	ebp, esp
	sub 	esp, 4

	; hStdOut = GetstdHandle( STD_OUTPUT_HANDLE)
	push	-11
	call	_GetStdHandle@4
	mov 	ebx, eax

	; WriteFile( hstdOut, message, length(message), &bytes, 0);
    push	0
	lea 	eax, [ebp-4]
	push	eax
	push	(message_end - message)
	push	message
	push	ebx
	call	_WriteFile@20

	; ExitProcess(0)
	push	0
	call	_ExitProcess@4

	; never here
	hlt
message:
	db  	'Hello, World', 10
message_end:
Как и машинные коды, ассемблер чаще используется инженерами и системными программистами. На нём пишут аппаратно-зависимые части ядра операционных систем, критические по времени или особенностям реализации драйвера различных периферийных устройств. Но в последнее время к нему прибегают всё реже и реже, так как его применение сильно сужает переносимость программ на другие платформы. Иногда используют процесс дизассемблирования – создают ассемблерный листинг программы из цифровых кодов для разбора логики выполнения небольших фрагментов. В редких случаях, если первоначальный высокоуровневый код недоступен: анализ вирусов для борьбы с ними или потере исходного текста. Язык ассемблера причисляют к первому/второму поколению (мы не будем рассматривать отдельно псевдокоды до возникновения ассемблера и их отличие от символьных команд). Хотелось бы выделить использование ассемблера в Demo Scene (демо-сцена): сплав искусства, математики и низкоуровневого кодирования, воплощающие художественные замыслы своих создателей в виде программ, генерирующих видеоклипы при ограничениях в ресурсах. Часто общий размер файла программы и данных не должен превышать 256 байт (также популярен и формат в 4/64 килобайта).

Языки группы C/Фортран (средний/высокий уровень)

С развитием возможностей вычислительной техники объём функциональности и сроки реализации кода на ассемблере уже не устраивали. Затраты для написания, тестирования и сопровождения программ росли на порядок быстрее их возможностей. Необходимо было снизить требования от программиста в плане знаний функционирования аппаратуры, дать ему инструмент, позволяющий писать на языках, приближенных к человеческой логике. Перейти к новому уровню типов языков программирования. Предоставить возможность разбивать на разнообразные модули с дальнейшим последовательным вызовом (парадигма процедурного программирования), предоставить различные типы данных с возможностью их конструирования и т. п. Дополнительно эти меры привнесли улучшенную переносимость кода на другие платформы, более комфортную организацию командной работы. Одним из первых языков, поддерживающий всё вышеперечисленное был разработанный в 50-е годы прошлого века Фортран. Возможность создавать в текстовом виде с описанием логики выполнения используя циклы, ветвления, подпрограммы и оперируя массивами и представляя данные в виде вещественных, целых и комплексных чисел приводила инженеров и учёных в восторг. За короткое время были созданы научные «фреймворки» и библиотеки. Всё это и стало следствием того, что Фортран и поныне имеет актуальность, пусть и в узкой научной среде, и развивается, так как багаж наработок очень велик, одна только библиотека IMSL активно развивается с 1970 (!) года, много ли вспомните подобных актуальных software-старожилов? Другая ветка развития языков этого уровня – C. Если Фортран стал инструментом учёных, то C создавался в помощь программистам, создающим прикладное ПО: операционные системы, драйвера и т. д. Язык позволяет вручную управлять распределением памяти, даёт прямой доступ к аппаратным ресурсам. C-программистам приходится контролировать низкоуровневые сущности, поэтому многие придерживаются мнения, что язык C – усовершенствованный ассемблер и его часто называют языком «среднего» уровня. Привнеся в ассемблер типизацию данных, элементы процедурного и модульного программирования язык C и сегодня является одним из основных для системного программирования, чему также способствует и бурное развитие микроэлектроники в последнее время. Всевозможные гаджеты, контроллеры, сетевые и прочие устройства нуждаются в драйверах, реализации протоколов для совместной работы и прочем относительно низкоуровневом ПО для реализации взаимодействия с аппаратурой. Все вышеперечисленное способствует востребованности языка и в настоящее время. Объектно-ориентированные и функциональные принципы получили дальнейшее развитие в виде C++, C#, Java, взяв многое от синтаксиса C. Плюсы:
  • Упрощение процесса создания кода: введение типов, разбивка на модули, сокращение листинга программ.
  • Прозрачная логика заложенного алгоритма вследствие ухода от машинных кодов к более понятным для человека командам в семантически описательном стиле.
  • Переносимость. Стало достаточно перекомпилировать текст программы для выполнения на другой платформе (возможно, с небольшой модификацией).
  • Скорость откомпилированных программ.
Минусы:
  • Отсутствие автоматического управления памятью и необходимость постоянного её контроля.
  • Отсутствие реализации концепций объектно-ориентированного и функционального программирования.

Развитие языков высокого уровня

Высокоуровневые языки программирования, в плане создания ПО, стали всё по большей части удаляться от машинных кодов и реализовывать различные, помимо процедурного, парадигм программирования. К ним относят также и реализацию объектно-ориентированных принципов. C++, Java, Python, JavaScript, Ruby… – спектр языков данного типа наиболее популярен и востребован сегодня. Они предоставляют больше возможностей для реализации разнообразного ПО и нельзя однозначно определить «специализацию» каждого из них. Но популярность применения в соответствующих областях обусловлена библиотеками/фреймворками для работы с ними, например: JavaScript – Frontend. Язык был разработан для взаимодействия клиентского веб-браузера с пользователем и удалённым сервером. Наиболее популярные библиотеки: Angular, React и VUE. В данное время относительно активно употребляется и на web и т. п. серверах (backend), особенно популярен Node.js. Ruby – Backend. Применяется для создания скриптов (служебных сервисных файлов) и на web серверах. Основной фреймворк - Ruby On Rails. Python – научная и инженерная сфера (помимо веб-области). Является альтернативой стандартным вычислительным и математическим пакетам (Mathematica, Octave, MatLab…), но имеет привычную семантику языка и большое число библиотек. Имеет много поклонников в области систем машинного обучения, статистики и искусственного интеллекта. Из часто используемых библиотек необходимо упомянуть django, numpy, pandas, tensorflow. С++ – Универсал, эволюционное развитие языка C. Предоставляет возможности функционального и объектно-ориентированного программирования и не потеряв при этом способность низкоуровневого взаимодействия с аппаратным обеспечением. За счёт чего реализуется производительность и гибкость при создании ПО, но и цена соответствует: высокий порог вхождения за счёт сложной спецификации языка, необходимости самостоятельного контроля за ресурсами при выполнении программы. Многие однопользовательское и системное ПО написано с его применением: модули операционных систем (Windows, Symbian…), игры, редакторы (Adobe Photoshop, Autodesk Maya…), базы данных (MSSQL, Oracle…), проигрыватели (WinAmp…) и т. д. Следует отметить, что современное ПО является сложным продуктом, в разработке которого используется сразу несколько языков программирования и расставлять степень участия каждого из них в общий результат бывает весьма затруднительно.

Дальнейший прогресс

В последнее время набирает популярность и иной вид программирования - функциональное (дальнейшее развитие уровня языка). Здесь уже другой вид абстракции для вычислений – функции, которые берут в качестве аргументов набор функций и возвращают другую. Роль переменных играют те же функции (привычные нам переменные – просто константные выражения, аналогичные final перед объявлением типа в Java). Собственно функция замкнута в своей области видимости, результат её работы зависит только от переданных аргументов. Отсюда вытекают два замечательных свойства:
  • Для тестирования нам необходимы только аргументы функций (результат работы не зависит от внешних переменных и т. п.).
  • Программа в функциональном стиле чудесным образом готова к параллельной работе: последовательные вызовы функций можно пускать в соседних потоках (так как на них не действуют внешние факторы) и им не нужны блокировки (то есть, проблемы синхронизации отсутствуют). Хороший стимул уделить время этой теме, учитывая повальное распространение многоядерных процессоров.
Однако и порог вхождения выше, чем для ООП: для эффективного кода необходимо строить программу, описывая в виде функций алгоритм выполнения. Но также для чистого функционального стиля неплохо бы знать азы логики и теории категорий. Наиболее популярные – Haskell, Scala, F#. Но не бойтесь, в Java (как и в других современных языках третьего поколения) появились элементы функционального программирования и их возможно комбинировать вместе с ООП. Более подробно вы познакомитесь со всеми этими подробностями на онлайн-стажировке JavaRush. Область логического программирования (следующий уровень языков) пока не нашла широкого практического применения в силу малой востребованности. Построение программ требует знание основ дискретной математики, логики предикатов, средств ограничений и других разделов математической логики. Наиболее популярный активный язык – Prolog.

Заключение

В настоящее время самые распространённые – языки ООП. Java, с момента возникновения, всегда находится в топе, обычно в тройке, востребованных языков. Помимо ООП, содержит элементы функционального программирования, и вы можете комбинировать разные стили составления ваших программ. Спектр применения Java весьма широк – это бизнес задачи, реализация веб-серверов (backend), основной язык создания Android-приложений, кроссплатформенные среды программирования и рабочих мест (IDE/АРМ) и моделирования и многое другое. Особенно сильны позиции Java в Enterprise секторе – области корпоративного программного обеспечения, которая требует качественный и долгоживущий код, реализацию самых сложных бизнес-логик.