Skip to content

Latest commit

 

History

History
264 lines (161 loc) · 42.8 KB

File metadata and controls

264 lines (161 loc) · 42.8 KB

Функционально-легкий JavaScript

Глава 1: Почему Функциональное Программирование?

Функциональщик: (сущ.) Тот парень, который называет переменные "x", функции "f", а паттерны -- "Зигохистоморфный Препроморфизм"

Джеймс Ири @jamesiry 5/13/15

https://twitter.com/jamesiry/status/598547781515485184

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

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

Цель первой главы заключается в том, чтобы ответить на вопросы вроде "Почему я должен использовать ФП стиль для своего кода?" или "Как Функционально-легкий JavaScript соотносится с другими ФП подходами?" После завершения этой подготовительной работы, на протяжении оставшейся части книги мы будем постепенно раскрывать методы и шаблоны написания JS кода в Функционально-Легком стиле.

Взгляд "одним глазом"

Давайте кратко проиллюстрируем понятие "Функционально-Легкий Javascript" путем сравнения кода, написанного с использованием различных подходов. Рассмотрим следующий фрагмент:

var numbers = [4,10,0,27,42,17,15,-6,58];
var faves = [];
var magicNumber = 0;

pickFavoriteNumbers();
calculateMagicNumber();
outputMsg();                // The magic number is: 42

// ***************

function calculateMagicNumber() {
    for (let fave of faves) {
        magicNumber = magicNumber + fave;
    }
}

function pickFavoriteNumbers() {
    for (let num of numbers) {
        if (num >= 10 && num <= 20) {
            faves.push( num );
        }
    }
}

function outputMsg() {
    var msg = `The magic number is: ${magicNumber}`;
    console.log( msg );
}

А теперь взгляните на этот фрагмент, выполняющий ту же самую работу:

var sumOnlyFavorites = FP.compose( [
    FP.filterReducer( FP.gte( 10 ) ),
    FP.filterReducer( FP.lte( 20 ) )
] )( sum );

var printMagicNumber = FP.pipe( [
    FP.reduce( sumOnlyFavorites, 0 ),
    constructMsg,
    console.log
] );

var numbers = [4,10,0,27,42,17,15,-6,58];

printMagicNumber( numbers );        // The magic number is: 42

// ***************

function sum(x,y) { return x + y; }
function constructMsg(v) { return `The magic number is: ${v}`; }

Как только вы начнете понимать ФП и Функционально-Легкий подход, скорее всего во втором фрагменте вы прочтете следующие:

Сначала мы создаем функцию sumOnlyFavorites(..), являющуюся комбинацией трех других функций. Мы объединяем два фильтра, одно сравнение с 10 и 20. После этого, мы добавляем редьюсер sum(..) в композицию трансьдюcера. На выходе получаем функцию sumOnlyFavorites(..), являющуюся редьюсером, который проверяет, подпадает ли значение под оба фильтра, и если да, добавляет его к значению аккумулятора.

Потом мы создаем другую функцию printMagicNumber(..), которая применяет редьюсер sumOnlyFavorites(..) к списку чисел, получая на выходе только сумму искомых чисел. После чего printMagicNumber(..) пропускает это финальное значение через функцию constructMsg(..), которая создает строковое представление результата, в конечном счете попадающего в console.log(..).

Весь этот поток кода говорит с ФП программистом образом, который скорее всего ещё вам не знаком. Эта книга поможет вам начать говорить аналогичным образом, так что этот код станет для вас столь же читабельным, как любой ругой, если не более того!

Несколько замечаний по поводу данного сравнения фрагментов кода:

  • Вполне вероятно, что для многих читателей первый фрагмент выглядит более удобным/читаемым/поддерживаемым, чем второй. Абсолютно нормально, если это так. Я уверен, что если вы найдете в себе силы дочитать эту книгу и попробовать применить все практики, которые будут рассмотрены, второй фрагмент кода станет для вас гораздо более естественным, и возможно даже предпочтительным!

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

  • Также возможно, что вы уже опытный разработчик FP, который просматривает начало этой книги, чтобы узнать, есть ли что-нибудь полезное для вас. И этот второй фрагмент конечно же уже выглядит для вас знакомым. Но я также готов поспорить, что вы вы пару раз подумали: «Хм, я бы этого не сделал * таким * образом ...». Это нормально и вполне разумно.

    Это не традиционная, каноническая книга по Функциональному Программированию. И временами используемые в ней подходы могут казаться весьма еретичными. Мы стремимся к достижению прагматического баланса между очевидными неоспоримыми преимуществами ФП и необходимостью поставки работоспособного, поддерживаемого JS кода без необходимости одолевать гору терминов и математических выражений. Это не традиционное ФП, это "Функционально-Легкий JavaScript".

Но независимо от причин, по которым вы читаете эту книгу, я говорю вам "Добро пожаловать"!

Надежность

У меня есть очень простая точка зрения, которая лежит в основе всего, что я делаю как преподаватель разработки программного обеспечения (на JavaScript): код, которому нельзя доверять - это код, который ты не понимаешь. Обратное тоже верно: код, который не понятен - не заслуживает доверия. Больше того, если код непонятен и ему нельзя доверять, то нельзя быть уверенным, что этот код способен решить проблему, для решения которой он был написан. Запуск такой программы равноценен участию в лотерее.

Но что я имею в виду под "доверием"? Я имею в виду, что путем чтения кода и логических рассуждений вы можете понять то, что будет делать программа, не запуская её; избавляя себя от необходимости полагаться на то, что она должна делать. Возможно, мы чаще необходимого склонны полагаться на тесты и уровень покрытия ими кода чтобы убедиться в работоспособности программы. Я не хочу сказать, что тесты - это плохо. Но я думаю, что мы должны стремиться к тому, чтобы понимать наш код достаточно хорошо, чтобы быть уверенными, что тесты проходят, ещё до их запуска.

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

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

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

Общение

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

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

Я горячо верю в то, что у кода есть куда более важная роль - это средство коммуникации с другими людьми.

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

По общим оценкам, разработчики тратят примерно 70% своего времени на то, чтобы прочитать и разобраться с имеющейся кодовой базой. Это открывает глаза. 70%. Не удивительно, что средний программист пишет лишь около 10 строк в день. И мы убиваем до 7 часов в день, просто на чтение кода и попытки осознать, что же на самом деле должны делать эти 10 строк!

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

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

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

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

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

Читабельность

Читабельность - это не бинарная характеристика. Это в значительной степени субъективный человеческий фактор, описывающий наше отношение к коду. И он, естественно, будет меняться со временем по мере развития наших навыков и понимания. У меня был опыт подобный показанному на нижеприведённом графике, и, как ни странно, многие другие, с которыми я разговаривал, испытывали тоже самое.

Возможно, вы будете испытывать похожие ощущения во время прочтения этой книги. Однако наберитесь мужества. Если вы продержитесь до конца, кривая снова пойдёт вверх!

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

Давайте вернёмся к двум ранее представленным в этой главе фрагментам кода.

Первый фрагмент - императивный, он почти полностью сосредоточен на том, КАК выполнять задачи; он усеян операторами if, циклами for, временными переменными, переназначениями, мутациями значений, вызовами функций с побочными эффектами и неявным потоком данных между функциями. Вы, конечно, можете проследить его логику, чтобы увидеть, как числа текут и переходят в конечное состояние, но это совсем не ясно и не прямолинейно.

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

Вместо того, чтобы возиться с оператором if для проверки числа, мы делегируем это хорошо известной служебной программе ФП, такой как gte (..) (greater-than-or-equal-to) (больше или равно), а затем фокусируемся на более важной задаче объединения этого фильтра с другим фильтром и функция суммирования.

Более того, поток данных во второй программе является явным:

  1. Список чисел переходит в printMagicNumber(..).
  2. Один за другим эти числа обрабатываются с помощью sumOnlyFavorites(..), в результате чего получается единое число, состоящее только из наших любимых видов чисел.
  3. Эта сумма преобразуется в строку сообщения с constructMsg(..).
  4. Строка сообщения выводится на консоль с помощью console.log(..).

Возможно, вам всё ещё кажется, что этот подход является запутанным, и что императивный фрагмент легче понять. Он (императивный стиль) более привычен вам; знакомство оказывает глубокое влияние на наши суждения о читабельности. Хотя, к концу прочтения этой книги, вы усвоите преимущества декларативного подхода второго фрагмента, и это знакомство пробудит читабельность.

Я знаю, просить вас поверить в это на данный момент - это вопрос доверия.

Требуется гораздо больше усилий, а иногда и больше кода, чтобы улучшить его читабельность как я предлагаю, и свести к минимуму или устранить многие ошибки, которые приводят к багам. Честно говоря, когда я начал написание этой книги, я никогда бы не смог бы написать (или даже полностью понять!) этот второй фрагмент. Сейчас, когда я более знаком с функциональным подходом, он выглядит даже более естественным и удобным.

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

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

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

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

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

Перспектива

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

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

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

Я называю здесь менее формальную практику "Функционально-лёгким программированием", потому что я думаю, что формализм истинного ФП страдает от того, что он может быть довольно подавляющим, если вы еще не привыкли к формальному мышлению. Я не просто предполагаю; это моя собственная личная история. Даже после преподавания ФП и написания этой книги, я всё ещё могу сказать, что формализм терминов и обозначений в ФП мне очень, очень трудно усвоить. Я пытался, и пытался, и, похоже, не могу справиться со многим из этого.

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

Итак, эта книга знакомит с концепциями, на которых, как я полагаю, построен ФП, но при этом даёт вам толчок снизу, чтобы подняться по отвесной стене, вместо того, чтобы снисходительно кричать на вас сверху, подталкивая подсказывает вам, как карабкаться по ходу дела.

Как найти баланс

Если ты уже достаточно времени в программировании, есть возможность, что ты слышал фразу "YAGNI" раньше: "You Ain't Gonna Need It" ("Тебе это не понадобится"). Этот принцип в первую очередь исходит из экстремального программирования и подчёркивает высокий риск и стоимость создания функциональности до того, как она понадобится.

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

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

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

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

Как правило, я бы рекомендовал стремиться к балансу в том, какой код вы пишите, и быть консервативным в применении концепций ФП по мере того, как вы осваиваете их. По умолчанию используйте принцип YAGNI при принятии решения о том, поможет ли определённый шаблон или абстракция сделать эту часть кода более читабельной или они просто добавляют умную изощренность, которая (пока) не оправдана.

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

Jeremy D. Miller @jeremydmiller 2/20/15

https://twitter.com/jeremydmiller/status/568797862441586688

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

Лучший код - это самый читабельный код в будущем, потому что он обеспечивает точно правильный баланс между тем, каким он может быть (идеализм), и тем, каким он должен быть (прагматизм).

Ресурсы

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

Книги

Некоторые книги по ФП/JavaScript, которые вам точно нужно прочитать:

Блоги/сайты

Другие авторы и материалы, с которыми вам следует ознакомиться:

Библиотеки

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

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

Вот, некоторые популярные ФП библиотеки для JavaScript, с которых можно начать своё исследование:

В приложении C более подробно рассматриваются указанные библиотеки и другие.

Заключение

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

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

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

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


1Buse, Raymond P. L., and Westley R. Weimer. “Learning a Metric for Code Readability.” IEEE Transactions on Software Engineering, IEEE Press, July 2010, dl.acm.org/citation.cfm?id=1850615.