Программирование торта

Как новичку в мире кодинга, одной из самых трудных вещей, которую нужно понять, является парадигма кодирования. Когда я только учился кодить, я помню, как думал: «О, Ruby — это язык ООП, поэтому, когда я пишу на Ruby, это ООП» или «JS — это императивный язык».

Оказалось, что в большинстве языков можно писать в совершенно разных парадигмах. На самом деле в Javascript нет недостатка в функциональном коде, объектно-ориентированном коде, императивном коде и декларативном коде. Это означает, что для начинающего понимание того, что означают эти различные парадигмы, может быть РЕАЛЬНО запутанным. Надеюсь, эта аналогия с тортом немного поможет.

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

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

get flour
get sugar
get whipped_cream
get cherries
get candle
get pan

mix = flour + sugar

put flour and sugar in pan

set oven to 400

wait for 5 minutes

put pan in oven

wait for 20 minutes

put on gloves

take out pan

add whipped cream to pan

add cherries to pan

add candle to pan

light candles
Войти в полноэкранный режим Выйти из полноэкранного режима

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

Процедурное программирование — это разновидность императивного программирования, но теперь вам разрешено использовать процедуры. Процедура — это просто способ сказать: «Выполните эти действия, но назовите их по-другому».
Давайте создадим процедуру под названием «Bake». Она позволит нам указать сковороду и время, но будет содержать некоторые мелкие детали.

procedure Bake(thing_to_bake, time, temperature){
    set oven to temperature
    wait for 5 minutes
    put thing_to_bake in oven
    wait time
    take out thing_to_bake 
}
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь мы можем упростить наш императивный код

get flour
get sugar
get whipped_cream
get cherries
get candle
get pan

mix = flour + sugar

put flour and sugar in pan

bake(pan, 20, 400)

add whipped cream to pan

add cherries to pan

add candle to pan

light candles
Войти в полноэкранный режим Выход из полноэкранного режима

Мы собираемся поговорить о противоположности императивного кода, но прежде чем мы это сделаем, просто знайте: ВЕСЬ КОД ТЕХНИЧЕСКИ ИМПЕРАТИВЕН, но точно так же, как мы написали процедуру, которая представляет императивный код, мы можем создавать абстракции, которые позволяют нам не думать о шагах, а думать о том, что эти шаги создают. Это приводит нас к…

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

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

<Cake
   toppings: [cherries, whipped_cream, chocolate_icing]
   candle_count: 1
/>
Вход в полноэкранный режим Выйти из полноэкранного режима

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

new Cake({
   toppings: [cherries, whipped_cream, chocolate_icing],
   candle_count: 1
})
Вход в полноэкранный режим Выход из полноэкранного режима

Функциональный декларативный пирог

createCake({
   toppings: [cherries, whipped_cream, chocolate_icing],
   candle_count: 1
})
Вход в полноэкранный режим Выход из полноэкранного режима

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

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

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

На фронтенде мы используем такие библиотеки, как React, Angular, Ember, Elm или Vue, чтобы вместо того, чтобы писать document.querySelector для всего, наш код больше походил на html, который он в итоге создает.

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

Итак, надеюсь, теперь вы начинаете понимать разницу между императивным и декларативным кодом. Теперь мы поговорим о функциональном торте. В функциональном программировании мы используем ФУНКЦИИ (шокирует). Примечание: Здесь мы говорим о функциях в их интеллектуально чистом смысле, но многие языки (например, javascript, ruby, python) на самом деле используют функции как процедуры. Для большинства языков функция на самом деле НЕ является функцией в классическом смысле. ПОЧЕМУ?
Технически, чистая функция принимает данные и возвращает преобразованную версию этих данных.

Например, вспомните алгебру. Y = X + 5. Мы говорим, что если вы подставите 2, то Y будет 2 + 5 или 7. Часть «return», о которой я говорил ранее, означает, что в чистом функциональном программировании функция ВСЕГДА будет равна некоторому математическому вычислению того, что вы ввели.

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

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

mixture = mix([flour, water, sugar, eggs])
plain_cake = bake(mixture)
final_cake = top(plain_cake, icing, cherries, candle)
Вход в полноэкранный режим Выйти из полноэкранного режима

Вы можете упростить все это в одну большую функцию

function getBakedCake(ingredients, toppings){
   top(
      bake(ingredients),
      toppings
   )
}
Вход в полноэкранный режим Выход из полноэкранного режима

Бонусный материал Чистое функциональное программирование

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

const prossessedByFiveFunctions = function5(
    function4(
        function3(
            function2(
                function1(
                    thing
                )
                function2SecondParam
            )
            function3SecondParam
        )
        function4SecondParam
    ),
    function5SecondParam    
)
Вход в полноэкранный режим Выход из полноэкранного режима

Это просто отстой!

Мы могли бы очистить его, сделав кучу промежуточных переменных типа

const processedByFirstFunction = function1(thing, function1SecondParam)
const processedBySecondFunction = function2(
    processedByFirstFunction, function2SecondParam
)
const processedByThirdFunction = function3(
    processedByFirstFunction, function3SecondParam
)
const processedByFourthFunction = function4(
    processedByFirstFunction, function3SecondParam
)
const processedByFiveFunctions = function5(
    processedByFourthFunction,
    function5SecondParam
)
Войти в полноэкранный режим Выход из полноэкранного режима

Но функциональные программисты придумали умный хак, чтобы убрать это. Что, если мы сделаем новый оператор под названием pipe, который позволит нам подключать функции в обратном направлении. Многие функциональные языки используют |>, в javascript его нет, но если бы он был, мы могли бы рефакторить наш код, чтобы он выглядел так (без дурацких имен промежуточных переменных)

const processedByFiveFunctions = thing
    // leave () blank if you don't want to pass anything in as a 2nd param
    |> function1(function1SecondParam) 
    |> function2(function2SecondParam)
    |> function3(function3SecondParam)
    |> function4(function4SecondParam)
    |> function5(function5SecondParam)

Вход в полноэкранный режим Выйти из полноэкранного режима

Вот это сексуальный, читабельный код функций (хотя к нему нужно привыкнуть). Поскольку в JS нет оператора pipe, вы можете попробовать использовать сторонние библиотеки, чтобы сделать что-то похожее на это.

const processedByFiveFunctions = pipe(
    thing,
    (x) => function1(x, function1SecondParam),
    (x) => function2(x, function2SecondParam),
    (x) => function3(x, function3SecondParam),
    (x) => function4(x, function4SecondParam),
    (x) => function5(x, function5SecondParam)
)
Вход в полноэкранный режим Выход из полноэкранного режима

Это подводит нас к нашему ультра-красивому функциональному пирогу

const getBakedCake = 
   ingredients => 
   toppings =>
      ingredients
      |> bake
      |> top(toppings) 
// can be called like `getBakedCake(["flour", "water"])(["candles", "icing"])`
Вход в полноэкранный режим Выход из полноэкранного режима

Некоторые важные выводы:

  • Мы не изменили ни одной переменной (никаких мутаций)
  • Вся система превращается в одно значение (ссылочная прозрачность)
  • Никакие другие части кода не были затронуты или вызваны (никаких побочных эффектов).

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

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

  • Записать что-то на экран? Побочный эффект
  • Изменить состояние счетчика? Побочный эффект, и мутация
  • Изменить запись в базе данных? Побочный эффект
  • Генерировать случайное число? Уже не чистая функция

Но если вы не можете постоянно работать на 100% функционально, это не значит, что вы не получите огромные преимущества, пытаясь минимизировать хаос, когда это возможно. Сторонники функциональности думают о состоянии приложения так, как Циклоп (Люди Икс) видит свои силы. СДЕРЖИВАТЬ ЭТО ДЕРЬМО! Наличие глазных яблок, которые могут стрелять массивным лазерным лучом сквозь что угодно, полезно только в том случае, если вы точно знаете, когда вы его включаете и на что нацеливаете. Мы хотим, чтобы наши приложения были супергероями, а не случайно проделывали дыры в зданиях.

OOP Cake

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

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

class Cake {
    initialIngredients = [];
    toppings = [];
    isDone = false;

    consructor(ingredients, toppings){
        this.initialIngredients = ingredients
    }

    async bake(){
        await this.putInOven();
        await wait();
    }

    async top(toppings){
        // do something in herek
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

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

const chocolateCake = new Cake(["brownie mix", "water"],["icing", "cherries"])
console.log(chocolateCake.isDone) // false
Войти в полноэкранный режим Выйти из полноэкранного режима

выглядит довольно элегантно и сексуально! Но на самом деле я совершил ошибку, мой торт еще не готов, я забыл его испечь. NBD, давайте это исправим

const chocolateCake = new Cake(["brownie mix", "water"],["icing", "cherries"])
chocolateCake.bake()
console.log(chocolateCake.isDone) // true
Вход в полноэкранный режим Выход из полноэкранного режима

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

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

Не хочу заходить слишком далеко в кроличью нору «срать на ООП». Множество НАСТОЯЩИХ замечательных программ было написано в стиле ООП и множество НАСТОЯЩИХ отстойных программ было написано на FP и наоборот.

Резюме

Новичку или даже опытному разработчику бывает очень трудно сориентироваться во всех существующих стилях кодирования. По правде говоря, если вы похожи на меня, вы не сможете понять эти стили, пока не займетесь этим некоторое время. Я не понимал, что такое декларативный код, пока не начал писать код на react примерно через год. После того, как я услышал, как его объясняют 1000 раз, и увидел примеры, я начал мысленно представлять, что это на самом деле значит. То же самое касается ООП, FP и многого другого.

Лучший совет, который я могу дать, — это немного читать и много кодить. Я думаю, что тратить 20% времени на чтение / подкасты — это довольно здоровый баланс. Надеюсь, эта маленькая аналогия с тортом упростит все это в вашей голове. В конечном итоге понимание того, что означают эти стили, не только поможет вам писать код, но и лучше общаться как разработчик. Я рекомендую вам попробовать решить одну и ту же задачу в разных парадигмах, если у вас будет такая возможность. У меня есть свое мнение, сформируйте свое собственное! Вы можете полностью ненавидеть торты ООП или торты FP и все равно зарабатывать хренову тучу денег как разработчик программного обеспечения.

ИДИТЕ И СДЕЛАЙТЕ НЕСКОЛЬКО ТОРТОВ!

Оцените статью
Procodings.ru
Добавить комментарий