Императивное и декларативное программирование: в чем разница

Императивное и декларативное программирование: в чем разница Полезное

Императивное программирование — это стиль, при котором программу описывают как последовательность команд, шаг за шагом меняющих состояние, то есть отвечают на вопрос «как получить результат». Декларативное программирование — это стиль, где описывают желаемый результат («что получить»), а способ его достижения берет на себя исполнитель: компилятор, движок базы данных, браузер, библиотека.

Ниже разберем разницу на одном и том же примере в двух стилях с фактическим выводом, покажем декларативность SQL, HTML и React, разложим парадигмы внутри каждого подхода (процедурное, ООП, функциональное) и дадим таблицу сравнения с признаком выбора.

Как vs что: разводим два вопроса

Проще всего запомнить пару так: императивный код говорит машине «как», декларативный — «что».

  • Императивный: «заведи счетчик, пройди список, на четных прибавь квадрат к счетчику, верни счетчик».
  • Декларативный: «дай сумму квадратов четных чисел».

Во втором случае мы не пишем цикл и не заводим переменную-накопитель — за нас это делает встроенная функция или движок. Это не значит, что цикла «нет»: он есть, но спрятан в реализацию, и нам не важно, как именно он устроен.

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

Один пример в двух стилях

Возьмем задачу: посчитать сумму квадратов четных чисел списка. Сначала императивно, на Python — явно каждый шаг.

nums = [1, 2, 3, 4, 5, 6]

total = 0                 # заводим состояние
for n in nums:            # сами управляем обходом
    if n % 2 == 0:        # условие вручную
        total += n * n    # меняем состояние на каждом шаге

print(total)  # 56

Четные — это 2, 4, 6; их квадраты 4, 16, 36; сумма 56. Мы описали весь механизм: переменную, цикл, условие, накопление.

Теперь декларативно — то же самое, тем же языком, но описываем результат:

nums = [1, 2, 3, 4, 5, 6]

total = sum(n * n for n in nums if n % 2 == 0)

print(total)  # 56

Вывод тот же — 56. Мы не заводили total вручную и не меняли его в цикле; мы сказали «сумма квадратов четных», а перебор sum выполнил сам. Тот же результат дает вариант через map/filter:

nums = [1, 2, 3, 4, 5, 6]

total = sum(map(lambda n: n * n, filter(lambda n: n % 2 == 0, nums)))

print(total)  # 56

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

Декларативность SQL, HTML и React

SQL — декларативный язык запросов. Вы описываете, какие данные нужны, но не как их искать:

SELECT city, COUNT(*) AS users
FROM users
WHERE country = 'Mexico'
GROUP BY city;

Мы не пишем, как перебрать таблицу, использовать ли индекс и в каком порядке соединять данные — это решает оптимизатор СУБД. Императивный эквивалент «вручную» — тот самый цикл со счетчиком и условием, как в примере выше, только по строкам таблицы.

HTML — декларативный язык разметки (это разметка, а не язык программирования в полном смысле: без переменных и логики). Вы объявляете структуру, а как ее нарисовать и разложить по экрану, решает браузер:

<article>
  <h1>Заголовок</h1>
  <p>Абзац текста.</p>
</article>

React переносит декларативность на интерфейс. Сравним. Императивно на чистом JavaScript мы шаг за шагом меняем DOM — состояние живет в самой странице:

const btn = document.getElementById('btn');

btn.addEventListener('click', () => {
  if (btn.classList.contains('highlight')) {
    btn.classList.remove('highlight');
    btn.textContent = 'Add Highlight';
  } else {
    btn.classList.add('highlight');
    btn.textContent = 'Remove Highlight';
  }
});

Декларативно в React мы описываем, как интерфейс должен выглядеть при данном состоянии, а обновлением DOM занимается сам React:

function HighlightButton() {
  const [on, setOn] = React.useState(false);
  return (
    <button
      className={on ? 'highlight' : ''}
      onClick={() => setOn(!on)}
    >
      {on ? 'Remove Highlight' : 'Add Highlight'}
    </button>
  );
}

Разница по признакам: в императивном варианте состояние (класс и текст) хранится в DOM, и мы правим его руками; в React состояние on живет в компоненте, а разметка — функция от него. Глядя на return, сразу видно, каким будет интерфейс в каждом из двух состояний.

Парадигмы внутри подходов

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

Императивные:

  • Процедурное — код делится на процедуры и функции, состояние — в переменных. Типичный пример — язык C.
  • Объектно-ориентированное (ООП) — данные и поведение упакованы в объекты, методы которых меняют состояние объекта. Основа Java и C++. ООП обычно относят к императивному семейству, потому что объекты хранят и изменяют состояние.

Ближе к декларативному семейству:

  • Функциональное — программа строится из выражений и функций без изменения общего состояния (map, filter, reduce — как раз оттуда). Его часто относят к декларативному стилю; это упрощение: степень «чистоты» зависит от языка (в Haskell строже, в JavaScript и Python функциональный стиль соседствует с императивным).
  • Логическое — вы задаете факты и правила, а вывод ищет система (язык Prolog).

Большинство массовых языков — мультипарадигменные: Python, JavaScript, C# позволяют писать и императивно, и декларативно. Поэтому корректнее говорить о стиле кода, а не жестко клеить «язык X — только императивный». Даже у декларативных инструментов под капотом лежат императивные реализации (у SQL есть и процедурные расширения, например PL/pgSQL).

Таблица сравнения

Признак Императивный подход Декларативный подход
Что описываем последовательность шагов (как) желаемый результат (что)
Состояние заводим и меняем явно обычно не меняем напрямую
Управление потоком пишем сами (циклы, условия) берет на себя исполнитель или движок
Языки и инструменты C, ассемблер, ядро Java/C++ SQL, HTML, CSS, Prolog
Подпарадигмы процедурное, ООП функциональное, логическое
Сильная сторона контроль над каждым шагом, наглядная механика короче, читаемее, меньше ошибок состояния
Слабая сторона многословно, легче ошибиться в состоянии меньше контроля над «как», сложнее ручной тюнинг

Признак выбора простой: если важен полный контроль над шагами и производительностью узкого места — берите императивный стиль; если важнее краткость, читаемость и меньше ручного состояния — декларативный. На практике их смешивают: бизнес-логику часто пишут декларативно, а критичный по скорости участок — императивно.

Выводы

  • Императивный стиль отвечает на вопрос «как» (шаги и изменение состояния), декларативный — на вопрос «что» (описание результата); способ достижения берет на себя исполнитель.
  • Один и тот же результат (сумма квадратов четных = 56) выражается обоими стилями на одном языке — разница в том, пишете вы цикл сами или описываете итог.
  • SQL, HTML и React декларативны: вы объявляете нужные данные, структуру или вид интерфейса, а перебор, отрисовку и обновление DOM выполняет движок.
  • Внутри императивного лежат процедурное и ООП, ближе к декларативному — функциональное и логическое; отнесение функционального к декларативному — упрощение, зависящее от языка.
  • Большинство языков мультипарадигменны, поэтому выбирают стиль под задачу, а не «навсегда»: контроль — императивно, краткость и меньше состояния — декларативно.

Где применяется / связь с практикой

Освойте тему на практике

Оба стиля вы встретите с первых недель практики: цикл и условие — это императивный код, а map, генератор списка или SQL-запрос — декларативный. Научиться осознанно переключаться между ними проще на языке, который поддерживает оба, — например, на Python. Системно разобрать базовый синтаксис, циклы, функции и переход к декларативным конструкциям помогает курс Python Basic. Посмотреть формат занятий и разобрать тему вживую можно на открытых уроках до старта обучения.

Смежные темы: Объектно-ориентированное программирование, Модульное программирование, Программирование на C: базовые знания.

FAQ

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

Какой стиль работает быстрее?
Однозначного ответа нет. Императивный код дает контроль над шагами и позволяет вручную оптимизировать узкое место, но декларативный движок (например, оптимизатор SQL) нередко подберет план лучше ручного. Скорость определяется задачей, объемом данных и реализацией, а не самим стилем.

Что новичку учить первым — императивный или декларативный стиль?
Обычно сначала осваивают императивные основы (переменные, циклы, условия): они делают механику видимой. Декларативные конструкции (генераторы, map/filter, SQL) естественно добавляются следом, когда понятно, что именно они прячут внутри.

OTUS Журнал
Скидка 5% 14-20 сентября на курсы (popup)