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

Вступление

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

Программа – запись алгоритма, выполнение которого приводит к достижению результата.

Если результат ожидаемый, то программа не содержит ошибок. С другой стороны, существуют два постулата. 

  1. Любая программа содержит как минимум одну ошибку. 
  2. Любую программу можно сократить как минимум на одну команду. 

Разумеется, это шутка, но, как в любой шутке, в ней есть доля правды.

Программирование – поиск и реализация алгоритма на языке программирования.

Алгоритмы формулируются на естественном языке или в лучшем случае – на алгоритмическом. И где-то в процессе перевода с одного языка на другой происходят ошибки и неточности. Отсюда можно сделать вывод, что

Ошибка в программе – это неточность реализации алгоритма на языке программирования.

Исходя из данных определений можно прийти к выводу, что чем ближе язык программирования к алгоритмическому языку, тем меньше вероятность ошибок в процессе перевода. В связи с этим сразу может возникнуть вопрос: а разве нельзя сразу использовать алгоритмический язык для написания программ? Тогда и проблем с переводом не будет. К сожалению, ответ будет отрицательным. И связано это на мой взгляд с тем, что в начале развития информационных технологий языки программирования “подстраивали” под возможности аппаратной части, а возможности трансляторов (компиляторов) были весьма ограничены. В настоящий момент разрыв между технологиями производства процессоров (для которых, собственно, пишутся программы) и технологиями развития математического и логического аппарата, лежащего в основе любых алгоритмических языков, настолько велик, что даже разговоры про нейронные сети и искусственный интеллект не смогут помочь преодолеть эту пропасть в обозримом будущем. Поэтому давайте с максимальной эффективностью использовать то, что у нас есть.

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

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

Нулевой уровень абстракции

При нулевом уровне абстракции программисты оперировали понятиями из физического мира: коды процессорных команд, регистры процессора, ячейки памяти, порты ввода-вывода и так далее. Первые языки программирования (языки программирования низкого уровня) позволяли использовать вместо машинных кодов их мнемонические имена, а вместо адресов памяти – текстовые метки. И хотя это давало полный контроль над производительностью кода, скорость самой разработки программы была очень низкой. С чем же приходилось иметь тогда дело?

Программный код

Программа — последовательность инструкций, выполняемые компьютером.

Подпрограмма — часть программы, выполняющая определенные действия.

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

На низком уровне для вызова подпрограммы используется команда CALL <адрес>, которая сначала записывает в стек вызовов (специальную область памяти компьютера) адрес следующей за ней команды, а затем передает управление по указанному адресу. Для возврата из подпрограммы используют команду RET, которая извлекает из стека вызовов адрес для перехода.

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

Данные

Переменная – поименованная область памяти. Значение переменной – это содержимое указанной области памяти. В процессе работы программы значение переменной может меняться.

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

Константа – поименованная область памяти. Значение константы – это содержимое указанной области памяти. В процессе работы программы значение константы не меняется.

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

Выражений на нулевом уровне абстракций не было – команды при необходимости включали в себя обрабатываемые данные.

Первый уровень абстракции

На смену командам пришли операторы – абстрактные инструкции, не связанные с кодами, выполняемыми процессором.

Оператор – минимальная структурная единица языка программирования.

Операторы можно разделить на императивные (выполняющие действия) и декларативные (объявляющие различные сущности языка: переменные, константы, процедуры и т.д.)

Ключевое слово – строковый идентификатор, имеющий специальное значение.

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

Типы данных

Тип данных (тип) — множество значений и операций над этими значениями.

Типы можно разделить на простые и составные (наборы элементов). Простые типы разделяются на стандартные и пользовательские. Пользовательские типы бывают перечисляемыми и интервальными. Составные типы бывают однородные(массивы) и разнородные (записи).

Перечисляемый тип — тип данных, чьё множество значений представляет собой ограниченный список идентификаторов.

Множественный тип – тип данных, который представляет собой набор элементов перечисляемого типа.

Интервальный тип – тип данных, множество значений которого представляет диапазон значений стандартного типа.

Массив – совокупность элементов одного типа, имеющая конечный размер. Доступ к элементам массива осуществляется по индексу.

Запись – структурированный комбинированный тип данных, состоящий из фиксированного числа компонент (полей) разного типа.

Типизация

Между типом, переменной и её значением имеются некоторые связи (зависимости), определяющие логику работы с данными сущностями в контексте конкретного языка программирования высокого уровня. 

Типизация – способ, которым в языке программирования определяется тип данных. Типизация имеет три аспекта.

  • Статическая динамическая типизация. Статическая определяется тем, что конечные типы устанавливаются на этапе компиляции.. В динамической типизации все типы выясняются уже во время выполнения программы
  • Сильная слабая типизация (также иногда говорят строгая / нестрогая). Сильная типизация выделяется тем, что язык не позволяет смешивать в выражениях различные типы и не выполняет автоматические неявные преобразования, например нельзя вычесть из строки множество. Языки со слабой типизацией выполняют множество неявных преобразований автоматически, даже если может произойти потеря точности или преобразование неоднозначно
  • Явная неявная типизация. Явно-типизированные языки отличаются тем, что тип новых переменных (а также функций и их аргументов, о которых будет сказано ниже) нужно задавать явно. Соответственно языки с неявной типизацией перекладывают эту задачу на компилятор / интерпретатор.

Выражения

Выражение – это последовательность операндов и операторов, приводящая к вычислению значения определенного типа.

Операнд – это сущность, с которой выполняются операции: переменные, константы, функции, выражения.

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

Если тип переменной и тип выражения не совпадают, то требуется выполнение приведения типа.

Приведение типа – это преобразование значения одного типа в значение другого типа.

Приведение типа бывает явным (задается программистом) и неявным (регулируется правилами приведения языка программирования).

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

Функции

С появлением первого уровня абстракции в языках программирования у подпрограмм появились параметры (формальные и фактические), а сами подпрограммы разделились на процедуры и функции.

Функция – это подпрограмма, возвращает результат (значение) определенного типа.

Функции обычно используются в выражениях и по своим свойствам напоминают переменные (функции имеют название и тип), значение которых вычисляется в момент вызова функции. Поэтому всё, что было сказано о типизации, относится и к функциям.

Процедура – это функция, которая не возвращает результата.

Параметр — принятый функцией аргумент определенного типа.

Формальный параметр — аргумент, указываемый при объявлении функции.

Фактический параметр — аргумент, передаваемый в функцию при её вызове.

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

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

Модули

С ростом сложности и увеличением объема исходных кодов программы возникла необходимость разделять код на модули.

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

Модуль имеет имя (уникальный в пределах приложения строковый идентификатор), который может быть использован в качестве префикса при написании оператора вызова функции. Это может понадобиться в случае, если разные модули проекта содержат функции с одинаковыми названиями

Константы

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

Чистые константы – константы, чьё объявление содержит только идентификатор и значение. Тип такой константы определяется по типу значения. Значение константы может быть задано константным выражением.

Константное выражение – это выражение, значение которого компилятор может определить без выполнения программы, в которую оно включено. Константные выражения включают числа, символьные строки, чистые константы, значения перечисляемых типов, специальные константы True, False и nil и выражения, построенные на базе этих элементов с использованием операторов, преобразования типов и конструкторов множеств. 

Константные выражения не могут включать переменных, указателей и вызовов функций, зa исключением: Abs(), High(), Low(), Pred(), Succ(), Chr(), Length(), Odd(), Round(), Swap(), Hi(), Lo(), Ord(), SizeOf(), Trunc() (приведены встроенные функции языка Паскаль)

Типизированные константы – константы, чьё объявление содержит явное указание на её тип.

Частный случай типизированных констант – константные массивы, значение которых задается специальным образом. Такие массивы могут быть многомерными.

Также можно объявлять константы – записи

Существуют так называемые процедурные константы, назначение которых – это создание альтернативного имени для функции. 

Область видимости

Область видимости (доступности) переменных, констант, процедур и функций зависит от места, где они были объявлены. Если они объявлены внутри процедуры или функции, то область видимости ограничивается данной процедурой и функцией. Если они объявлены в заголовке модуля в разделе реализации (implementation), то область видимости расширяется до размеров модуля. Если они объявлены в заголовке модуля разделе интерфейса (interface), то область видимости расширяется до всех модулей, которые ссылаются на данный модуль в секции использования ( uses).

Второй уровень абстракции

Тип данных эволюционировал в понятие класс, функция – в метод, поле – в свойство, переменная – в объект. Но прежние понятия не исчезли, они продолжили своё существование вместе с новыми.

Класс

Класс – это данные (поля и свойства) и методы их обработки.

Инкапсуляция – объединение данных вместе с методами их обработки и сокрытие информации о том, как именно осуществляется эта обработка.

Наследование – создание нового класса на основе другого (родительского) с сохранением доступа к данным и методам родительского класса.

Иерархия классов – иерархическая совокупность классов, с помощью которых создаётся программа. В вершине дерева класса находится класс TObject.

Полиморфизм — возможность объектов с одинаковой спецификацией иметь различную реализацию. В частности, способность функций обрабатывать данные различных типов.

Метод

Методы – это процедуры и функции, принадлежащие данному классу.

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

Конструктор – функция, которая создает экземпляр данного класса и возвращает его в качестве результата.

Деструктор – процедура, которая уничтожает объект и освобождает занимаемую им память.

Методы могут быть объявлены с различными атрибутами, определяющими область их видимости и функциональность.

Виртуальный (virtual) метод – реализация метода в классе-наследнике будет полностью скопирована из реализации метода класса-предка.

Динамический (dynamic) метод – реализация метода ссылается на реализацию метода класса-предка.

Виртуальные методы используют, если требуется высокая производительность (частые вызовы, методы перекрываются), динамические позволяют экономить память (редкие вызовы, много наследников без перекрытия метода).

Перекрытый (override) метод – метод, унаследованный от родительского класса, реализация которого в классе-наследнике изменена. Перекрыть можно виртуальный или динамический метод.

Перегруженные (overload) методы – методы с одинаковым названием, но с разными параметрами. Используются для реализации полиморфизма.

Статический (static) метод – метод, который можно вызвать без создания экземпляра объекта.

Статические методы используются для создания библиотечных функций, объединяя их для удобства в класс.

Абстрактный (abstract) метод – метод, который не содержит реализации в классе, в котором он объявлен.

Реализация абстрактного метода должна быть осуществлена в классе-наследнике. Попытка вызова абстрактного метода в режиме выполнения программы вызовет ошибку Abstract error.

Обработка сообщений

С приходом операционной системы Windows стал использоваться событийно-ориентированный подход в программировании. Он основан на том, что у программы имеются специальные процедуры – обработчики событий. 

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

Обработчик сообщений – процедура, предназначенная для обработки сообщений от операционной системы и других компонентов программы.

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

Свойство

Свойство – это атрибут класса, доступ к значению которого осуществляется через методы.

Свойство скрывает поля класса, в которых обычно хранятся данные объекта. Более того, так как доступ к данным осуществляется через методы, то полей может не быть вовсе.

Область видимости

Область видимости класса зависит от места его объявления: в секции interface или implementation.

Иногда возникает необходимость общедоступные классы объявлять в секции implementation для того, чтобы избежать циклических ссылок uses в секции interface. Тогда используется предварительное объявление класса.

Для задания различных областей видимости членов класса (методов и свойств) предназначены различные секции внутри описания класса.

Приватные (private). Область видимости членов класса ограничивается самим классом.

Защищенные (protected). Члены класса, объявленные в данной секции, видны в пределах самого класса и его наследникам.

Публичные (public). Члены класса доступны всем, кому доступен класс. Это область по умолчанию, то есть, если не указывать раздел, то считается, что свойства и методы будут общедоступными.

Опубликованные (published). Это специальный раздел для свойств и обработчиков событий, которые должны быть опубликованы в редакторе свойств компонента среды разработки. Помещение свойств, методов и полей в секцию published заставит компилятор генерировать мета-информацию (RTTI) для них, что позволит объекту “узнавать о самом себе” во время выполнения.

Автоматизированные (automated). Этот раздел содержит свойства, которые доступны всем. Используется в наследниках класса TAutoObject при создании серверов OLE Automation.

Дружественные классы – это классы, расположенные в одном модуле. Дружественные классы могут иметь доступ ко всем членам классов, расположенных в этом же модуле. Если требуется ограничить “дружественность”, то в объявлении секций private и protected добавляется ключевое слово strict.

Объект

Объект – экземпляр класса, созданный его методом-конструктором.

Третий уровень абстракции

Класс эволюционировал в интерфейс.

Интерфейс

Интерфейс – это класс, который не может содержать поля, а также не имеет конструктора или деструктора. Все методы являются абстрактными, а доступ к свойствам идет строго через методы интерфейса.

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

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

OLE (Object Linking and Embedding) — технология связывания и внедрения объектов в другие объекты, разработанная корпорацией Майкрософт. OLE позволяет передавать часть работы от одной программы к другой и возвращать результаты назад.

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

Что дальше?

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

Немаловажную роль в этом процессе играет и война стандартов – часть корпоративных войн и противостояний брендов. 

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

Много разговоров ведётся об искусственном интеллекте и самообучающихся системах, которые не требуют программирования. Но это миф, служащий с одной стороны для придания пафосности весьма прозаичным задачам, которые решают разработчики подобных систем, а с другой дающий повод для мечтаний о мире, где “вкалывают роботы, а не человек”. 

Но, возможно, у вас есть другая информация на тот счет, пишите в комментариях – обсудим.

Один комментарий к “Эволюция абстракций”

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *