С начала развития программирования подходы к написанию кода менялись по разным причинам: разработчики находили более удобные решения, появлялись новые технологии и задачи. Условно, в одной и той же сфере код 20 лет назад писали иначе, чем сейчас.
На разных этапах формировались и продолжают формироваться стили написания кода и поиска решений. Официально они называются парадигмами программирования, и сегодня рассказываем про них.
Что такое парадигмы программирования
Парадигмы — это не какие-то строгие правила и законы, а просто стили написания кода и использования разных возможностей.
Работа в рамках парадигмы означает, что разработчик придерживается использования определённых архитектуры и инструментов языка. Поэтому понятие «парадигма программирования» говорит, скорее, не что нужно делать, а чего делать нельзя (или очень не рекомендуется).
Здесь есть два главных определения:
- Парадигма программирования — устоявшаяся система взглядов и подходов, в рамках которых идёт разработка. Это определение сформировал историк и философ Томас Кун.
- Парадигма — запрет конкретных действий при работе над программой. Автор этого тезиса — инженер и программист Роберт Мартин.
Небольшое количество языков программирования написаны в строгом соответствии с определённой парадигмой, и в таких языках часть возможностей отсутствует совсем. Но среди самых популярных языков общего назначения на сегодняшний день такого нет. Сегодня мы можем писать программу и использовать несколько парадигм в одном языке одновременно или по очереди, в зависимости от задачи.
Парадигм, как и языков программирования, успело появиться много. Новые парадигмы продолжают возникать из старых в ответ на обновления в инструментах и появление новых задач.
Мы рассмотрим несколько основных, которые используются сегодня, — и вспомним, как на появление разных парадигм повлияли разные виды программирования.
Императивная парадигма
Первая появившаяся из парадигм сформировалась, когда программы записывались на машинном языке. Написанный в соответствии с этой парадигмой код будет отвечать на вопрос: «Как именно должна работать программа и что она должна делать?»
Для императивного программирования характерны следующие черты:
- Компьютер выполняет последовательно записанные команды.
- Инструкции могут использовать результаты, полученные после выполнения предыдущих команд.
- Данные могут сохраняться в памяти.
- Состояние программы может изменяться после каждой инструкции.
- Основа императивного программирования — операторы присваивания, которые управляют состоянием переменных.
Эти признаки довольно абстрактны, и сразу видно, что они могут использоваться почти в любом коде.
Чистый императивный код — машинный язык. Писать на нём программы неудобно: код сложно воспринимать, приходится дублировать отдельные фрагменты скриптов и часто пользоваться операторами присваивания.
Как правило, императивное программирование решает небольшие подзадачи для поддержания работы основной программы. Использовать эту парадигму (но не ограничиваться ею) можно почти во всех современных языках: C, C#, C++, Python, Java, Ruby.
Чем больше становились программы, тем труднее было работать с императивной парадигмой, поэтому появились новые.
Декларативная парадигма
Если императивное программирование подразумевает максимально подробные инструкции для машины, то декларативная парадигма работает по-другому: описывает не способ решения, а желаемый результат. Она отвечает на вопрос: «Что делает программа и что должно получиться в итоге?»
Как это выглядит:
- Компьютер сам выбирает действия для достижения результата.
- Состояние программы не меняется и не отслеживается в процессе работы.
- Декларативное программирование не использует переменные и операторы присваивания.
Звучит проще императивной парадигмы, и декларативный подход действительно серьёзно облегчает работу. Преимущества декларативной парадигмы можно свести к двум основным:
- Точная адаптация под конкретные задачи. Декларативное программирование создаёт высокоуровневый слой функциональности, который выполняет заранее продуманные инструкции.
- Защита от ошибок. Скрытая реализация не даёт что-то поломать в системе. Например, мы можем неправильно использовать запросы SQL, но не можем испортить сами запросы.
Пример декларативного языка программирования — язык запросов SQL. Он точно адаптирован к разным аспектам работы с базами данных, это простой и удобный интерфейс для взаимодействия с ними. Мы не знаем, как именно работают конкретные запросы, мы просто задаём конечную цель и ограничения. Например: «Возьми базу клиентов и выбери оттуда всех покупателей за последний месяц, при этом они должны быть мужского пола, не женаты, но иметь детей».
Как это работает внутри и что делает база данных в этот момент — большинство разработчиков не знают, но это и не обязательно. Достаточно знать, что это просто работает и этим можно пользоваться. В этом вся сила декларативного программирования.
Звучит удобно, но полностью на компьютер полагаться нельзя. Может получиться что-то вроде такого: мы сказали программе выдавать купоны на скидку на определённые товары после регистрации в сервисе. Товары закончились, а компьютер этого не понимает и продолжает выдавать бесполезные купоны. Клиент регистрируется и получает скидку на то, что купить всё равно уже не может. В итоге злится, расстраивается, теряет доверие.
А ещё декларативная парадигма просто не может работать без императивной, потому что кто-то должен объяснить, как будут работать готовые декларативные шаблоны кода и что с ними делать.
Сравнение императивной и декларативной парадигм
Императивное и декларативное программирование — два основных направления, из которых появились остальные парадигмы. Поэтому сейчас мы коротко остановимся и сравним их, чтобы дальше было проще понимать плюсы и минусы других парадигм.
Синтаксис и структура кода. Декларативные языки программирования чаще всего высокоуровневые, легкочитаемые и выразительные. С их помощью можно кратко и лаконично описать, чего мы ожидаем от работы программы. Примеры декларативных языков — SQL и HTML.
Императивное программирование требует подробного пошагового синтаксиса и указания каждой инструкции для компьютера. Примеры императивных языков — C, Java и Python.
Уровень абстрагирования. В программировании много вещей, которые сложно визуализировать или представить в виде метафор. Благодаря тому что декларативная парадигма скрывает значительную часть работы, разработчикам не нужно детально вникать в устройство инструментов, а можно просто пользоваться ими.
Императивные языки программирования требуют большего контроля и понимания работы своих концепций и технологий. Такое сложнее освоить, но это позволяет настраивать программы более гибко и делать их быстрее и производительнее.
Управление состоянием. В декларативном программировании состояние программы обычно контролируется автоматически при помощи языка программирования или фреймворка. Мы описываем нужное состояние, а реализация инструмента сама позаботится о том, чтобы всё работало в соответствии с запросом.
Императивное программирование так не умеет, и разработчику всё нужно продумать самому: самостоятельно отслеживать и обновлять состояние приложения или сервиса по мере выполнения программы.
Порядок выполнения команд. После запуска программа следует определённому алгоритму и списку инструкций.
Декларативное программирование чаще всего делает этот порядок неявным, потому что вся декларативная парадигма предполагает большое количество работы, проделанное за пользователя.
В императивной парадигме порядок управления системой явный. Мы указываем точный порядок выполнения всех инструкций, что даёт больший контроль, но и требует более глубокого понимания принципов работы.
Какого-то твёрдого решения, какой парадигмой стоит пользоваться, на сегодня нет. На самом деле в современной работе их нужно совмещать и дополнять другими.
Все остальные парадигмы происходят из императивной и декларативной. Если посмотреть на основные существующие направления, получится такая схема:
Модульная парадигма
Если при работе с программой нужно использовать какие-то части повторно, их целесообразно вынести отдельно и подключать в нужные моменты. Такие наборы инструкций называются модулями, или библиотеками.
Модули часто выносятся в отдельные файлы и папки, которые лежат вместе с основным проектом. Ещё при импорте библиотек в проекте часто нужна не вся библиотека, а только её часть — это тоже будет один из модулей.
Процедурная парадигма
Процедуры в программировании — почти то же самое, что и функции, но парадигмы этих подходов различаются. Процедурное программирование появилось из императивного, а функциональное — из декларативного. Функциональное программирование мы тоже рассмотрим ниже.
Процедурный код делится на более мелкие функции. Эти функции будут использовать много переменных и операторов присваивания, а сам код будет подробным и детальным. Такая сложная программа требует времени на создание и опыта от разработчика, но, скорее всего, будет быстрее и менее ресурсоёмкой, чем при функциональном программировании.
Логика такая: мы пишем много функций, каждая из которых делает что-то своё, а потом объединяем их в коде для решения задачи.
Объектно-ориентированная парадигма
Объектно-ориентированный код собирает функции в классы и их экземпляры — объекты. Можно создать класс и уже внутри него прописать функции, которые смогут использовать все объекты класса. Такие функции будут называться методами.
ООП — это императивное программирование с тремя дополнительными абстракциями: инкапсуляцией, наследованием и полиморфизмом.
- Наследование означает, что мы можем создавать классы, которые будут перенимать все свойства и функции от другого класса, то есть наследовать их.
- Полиморфизм — возможность создать класс-наследник и переписать его методы так, чтобы они назывались по-старому, но работали по-новому.
- Инкапсуляция защищает данные от внешних действий, ограничивая или запрещая доступ к содержимому объектов.
Структурная парадигма
Структурная парадигма делит код на модули и подразумевает управление ими через три основных принципа — структуры управления:
- Код должен быть разделён на небольшие логические модули-последовательности. Каждая часть таких последовательностей возвращает простой предсказуемый результат и может быть использована многократно.
- Для управления ходом программы должны быть использованы условия, через которые работающий скрипт понимает, какую из последовательностей запустить в конкретный момент.
- Чтобы запускать одну и ту же последовательность несколько раз, нужно использовать циклы.
В первых языках программирования часто использовался оператор goto
, который мог передавать управление программы в любое место кода. Это казалось удобным решением, но при масштабировании программы становилось сложно отследить, какая строка кода и в какой момент сработает. Получалось то, что сейчас называют спагетти-кодом.
Императивная парадигма сам по себе не запрещала использование оператора goto
, а структурная — запретила. Код стал более надёжным и легкочитаемым.
И структурное, и модульное программирование разбивает код на составные части, но первое делает это через структуры управления, а второе — через модули. Внешне похоже, но логика работы всё же отличается.
Логическая парадигма
Это продолжение декларативной парадигмы, поэтому при логическом программировании мы описываем, что нужно получить, и стараемся дать подходящие входные данные.
В логической парадигме программы описываются как логические факты и выводы на их основе, а их выполнение строится на следовании этим фактам и выводам. Например, у нас есть данные о расстояниях между разными городами, и на основе этой информации нужно проложить самый короткий маршрут между двумя из них.
Решением будет являться вывод, который программа сделает на основе фактов сама, поэтому такой подход относится к декларативному.
Функциональная парадигма
Это процедурное программирование, но на декларативный лад.
В процедурной парадигме все функции тщательно описаны и у каждой есть переменные, в которые мы что-то присваиваем. При функциональном программировании так подробно работу определять не нужно, мы пользуемся уже готовыми методами.
Смысл функционального программирования в том, что мы не задаём последовательность нужных нам команд, а описываем взаимодействие между ними и подпрограммами. Это похоже на то, как работают объекты в объектно-ориентированном программировании, только здесь это реализуется на уровне всей программы. В функциональном программировании весь код — это правила работы с данными. Вы просто задаёте нужные правила, а код сам разбирается, как их применять.
Например, при работе со строками мы можем читать их, вычислять длину, заменять символы — и делать всё это через готовые команды, которые реализованы кем-то другим через императивное программирование.
Что учить сначала
Одни парадигмы не хуже других, нет смысла изучать какие-то определённые.
Если хотите начать программировать, выбирайте не по парадигмам, а по задачам и языкам разработки. Изучайте то, чем хотите заниматься в будущем, а правила и направления определятся сами.
Если хотите более быстрого погружения, попробуйте курсы Практикума. Со сложными задачами помогут наставники, можно будет тренироваться на тренажёре для программирования, а в портфолио наберётся несколько проектов. У курсов есть бесплатная часть, которую можно попробовать — и так понять общее устройство учёбы и насколько вы хотите этим заниматься.