Декларативный код против императивного

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

Императивный код

Императивное программирование началось еще с ассемблера (1949 г.) и продолжилось на таких языках, как C, C++, C#, PHP и Java. Процедурное и объектно-ориентированное программирование относятся к императивной парадигме.

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

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

Процедурный код

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

#include <stdio.h>
int main()
{
    int num1, num2, num3;
    int *p1, *p2, *p3;

    //taking input from user
    printf("Enter First Number: ");
    scanf("%d",&num1);
    printf("Enter Second Number: ");
    scanf("%d",&num2);
    printf("Enter Third Number: ");
    scanf("%d",&num3);

    //assigning the address of input numbers to pointers
    p1 = &num1;
    p2 = &num2;
    p3 = &num3;
    if(*p1 > *p2) {
	if(*p1 > *p3){
		printf("%d is the largest number", *p1);
	}else{
		printf("%d is the largest number", *p3);
	}
    }else{
	if(*p2 > *p3){
		printf("%d is the largest number", *p2);
	}else{
		printf("%d is the largest number", *p3);
	}
    }
    return 0;
}

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

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

Примерами процедурных языков программирования служат Pascal (1970) и C (1972). Процедурное программирование пользуется сильной поддержкой. Линус Торвальдс, «отец» Linux, достаточно открыто критиковал C++ и объектно-ориентированное программирование.

Объектно-ориентированный код

Объектно-ориентированное программирование  —  это моделирование объектов, которые сохраняют внутреннее состояние. Код в таком программировании основан на взаимоотношениях между объектами. Классы (для языков, основанных на классах)  —  это частный случай объектов.

Код в объектных методах по-прежнему императивный и основан на утверждениях, которые меняют состояние.

public interface Retile {
   void walk();
}

public class Turtle implements Reptile {
   @Override
   public void walk() {
      System.out.println("Turtle is walking!");
   }
}

public class Tortoise implements Reptile {
   @Override
   public void walk() {
      System.out.println("Tortoise is walking!");
   }
}

public class ReptileFactory {
   public Reptile getReptile(String reptileType){
      if(reptileType == null){
         return null;
      }		
      if(reptileType.equalsIgnoreCase("TURTLE")){
         return new Turtle();
      } else if(shapeType.equalsIgnoreCase("TORTOISE")){
         return new Tortoise();
      }
      return null;
   }
}

public class ReptileDemo {
   public static void main(String[] args) {
      ReptileFactory reptileFactory = new ReptileFactory();

      Reptile reptile = Reptile.getReptile("TURTLE");

      reptile.walk();
   }
}

Пример выше  —  образец архитектурного шаблона “Фабрика”, который применяется в Java. Заметьте: весь код фокусируется на том, чтобы определять классы и использовать взаимоотношения между ними через интерфейс. Как правило, все эти классы выделяются в собственные файлы.

Примерами объектно-ориентированных языков служат Smalltalk (1972), C++ (1985), Python (1991), Java (1995), JavaScript (1995), Ruby (1995), C# (2000), Scala (2004) и Swift (2014).

JavaScript начинался как основанный на прототипировании, объектно-ориентированный язык без классов. Классы были добавлены в ECMAScript в 2015.

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

В JavaScript фреймворк Angular представляет собой отличный пример влияния C# на разработчиков, которые хотели принести в веб-разработку более четкие объектно-ориентированные принципы.

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

Декларативный код

Декларативный код очень распространен и представлен предметно-ориентированными, логическими и функциональными языками программирования. Примерами таковых служат HTML, SQL, F#, Prolog и Lisp.

Декларативный код фокусируется на выражениях, которые говорят «что делать», не добавляя «как». Например, HTML-код <img src=”./turtle.jpg” /> говорит браузеру вывести изображение черепахи, не сообщая браузеру, как ему это делать. Также для декларативного кода типично избегать изменений состояний и переменных.

Предметно-ориентированный код

Предметно-ориентированные языки не обладают полнотой по Тьюрингу, что означает  —  они не могут делать всё то, на что способны другие, тьюринг-полные языки. Например, C# (императивный) и F# (декларативный) оба  —  тьюринг-полные языки, и всё, что поддается разработке в одном из них, возможно разработать и на другом. HTML не обладает полнотой по Тьюрингу и позволяет делать только что-то конкретное.

Пример SQL-кода, который находит сотрудников и их менеджеров в базе данных:

SELECT e.name, m.name FROM Employee e, Employee m WHERE e.Employee_id=m.Manager_id;

На предметно-ориентированных языках обычно очень просто как писать, так и читать. Благодаря этому они  —  популярный выбор для пользовательского интерфейса. Например, в библиотеке React для JavaScript для определения компонентов применяется JXC:

const myComponent = props => (
   <h1>Hello, {props.userName}</h1>
};

Примеры предметно-ориентированных языков  —  SQL (1974), HTML (1993), CSS (1996) и XML (1996).

Логический код

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

Наиболее типичный логический язык программирования, обладающий полнотой по Тьюрингу  —  Prolog (1972). Это означает, что всё, что возможно написать на языке C, также можно, теоретически, написать на Prolog.

Prolog основан на определении предикатов, которое, в свою очередь, определяет отношения между аргументами:

food(salad). % <- salad is food fact
food(pizza). % <- pizza is food fact

?- food(salad). % <- is salad food? True
?- food(turtle). % <- is turtle food? False

В вышеприведенном примере сначала определяется факт, что результат верен, а затем задаются вопросы, которые дают в результате «истину» или «ложь» в булевых переменных.

При работе на нем Prolog представляет собой сущую магию, а если вы не согласны, то тем хуже для вас.

Функциональный код

Функциональное программирование  —  декларативная парадигма, основанная на композиции чистых функций. Языки функционального программирования обладают полнотой по Тьюрингу и опираются на лямбда-исчисление  —  систему математической логики, созданную в 1930-х.

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

Функциональное программирование стабильно набирает популярность, и оно проникло в императивные языки программирования. Такие языки, как Python, C++ и JavaScript  — мультипарадигматичны, поскольку поддерживают написание кода в более чем одной парадигме.

Вот пример функционального кода, написанного на JavaScript с использованием библиотеки @7urtle/lambda:

import {upperCaseOf, trim, map, compose, Maybe} from '@7urtle/lambda';

const getElement = selector => Maybe.of(document.querySelector(selector));
const getText = element => element.textContent;
const transformer = compose(trim, upperCaseOf);
const getElementText = compose(map(transformer), map(getText), getElement);

getElementText('#myTurtle'); // => Maybe('SHELLY')

Функциональное программирование вносит новые концепции, которых нет в объектно-ориентированном программировании  —  к примеру, чистые функции, функции высшего порядка, неизменяемость, функторы, частичная применяемость, бесточечные функции, и так далее. Из-за этого порог входа может показаться высоким, в особенности с учетом того, что многие статьи по функциональному программированию очень глубоко погружаются в его математические корни. Я рекомендую для начала поглядеть на статьи попроще.

Только злые волшебники думают, будто функциональное программирование  —  это плохо. Помните: если кто-то говорит, будто монады  —  это сложно, он врет.

Другие примеры функциональных языков программирования  —  LISP (1984), Haskell (1990) и F# (2005).

Изучайте искусство программирования

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

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Martin Novak: Declarative Versus Imperative Code

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