Найти тему
Блокнот математика

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

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

И этот принцип я считаю единственно правильным! Компьютер должен освобождать нас от необходимости чем-то заниматься, не лишая возможности это делать.

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

Первые программы на языках программирования (ассемблер не рассматриваем) описывали поток команд, так, как они и выполнялись процессором. Разве что можно было написать x=y+z, и не думать о регистрах, адресации и выделении памяти и многом другом.

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

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

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

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

Квинтэссенцией процедурного программирования стало утверждение, что любая программа может (и должна) состоять из вызова трех процедур: INIT, RUN, DONE. Первая готовит все для работы, третья завершает работу, а вторая, собственно, работает.

Эта концепция дальше переросла в модульное программирование. Модули имеют свои области видимости и могут содержать процедуры и переменные, как видимые снаружи, так и приватные. Еще есть защищенные, которые можно прочитать извне, но нельзя изменить. В итоге сама программа загружает модули и вызывает init, run, done. А вся суть спрятана в модулях. Это не просто облегчает работу и снижает риск наделать ошибок, но и позволяет коллективу работать параллельно, а также повторно использовать код. А это уже важно.

Тенденция к инкапсуляции — сокрытию подробностей внутри и объединению данных и методов работы с ними внутри одной области видимости, закрытой от внешнего мира — привела к объектно-ориентированному программированию, которое превратилось в культ. К счастью, он отживает и концепция опять становится просто концепцией. "Специальные олимпиады" на тему "чей язык воистину ОО-ный" проходят всё реже.

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

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

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

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

Но современное высокопроизводительное программирование — это совсем другая история, о которой я тоже расскажу, если это интересно.

Навигатор по каналу