Кодовые базы разрастаются, становясь всё сложнее и запутаннее, что не может не пугать разработчиков. Как обнаружить код, изменяющий состояние вашей системы? Как сделать код таким, чтобы он не увеличивал сложность и запутанность кодовой базы? Большую часть «действий», изменяющих состояние, можно превратить в «вычисления», чтобы ваш код стал проще и логичнее. Вы научитесь бороться со сложными ошибками синхронизации, которые неизбежно проникают в асинхронный и многопоточный код, узнаете, как компонуемые абстракции предотвращают дублирование кода, и откроете для себя новые уровни его выразительности. Книга предназначена для разработчиков среднего и высокого уровня, создающих сложный код. Примеры, иллюстрации, вопросы для самопроверки и практические задания помогут надежно закрепить новые знания.
Author(s): Эрик Норманд
Series: Библиотека программиста
Publisher: Питер
Year: 2023
Language: Russian
Pages: 608
City: СПб.
Оглавление
Предисловие
Вступление
Благодарности
О книге
Для кого написана эта книга
Структура издания
О примерах кода
Другие ресурсы в интернете
Об авторе
От издательства
Глава 1. Добро пожаловать в мир функционального мышления
Что такое функциональное программирование
Недостатки определения при практическом применении
Определение ФП сбивает с толку руководителей
Функциональное программирование рассматривается как совокупность навыков и концепций
Действия вычисления и данные
Функциональные программисты особо выделяют код для которого важен момент вызова
Функциональное программирование отличает инертные данные от работающего кода
Функциональные программисты разделяют действия вычисления и данные
Три категории кода в ФП
1. Действия (А)
2. Вычисления (C)
3. Данные (D)
Как нам помогают различия между действиями вычислениями и данными
Чем эта книга отличается от других книг о ФП
Используется практический подход к программированию
Описываются реальные ситуации
Основное внимание уделяется проектированию программных продуктов
Книга передает богатство возможностей ФП
Книга нейтральна по отношению к языку
Что такое функциональное мышление
Часть I. Различия между действиями, вычислениями и данными
Часть II. Первоклассные абстракции
Основные правила для идей и навыков представленных в книге
1. Навыки не могут базироваться на возможностях языка
2. Навыки должны иметь непосредственную практическую ценность
3. Навыки должны применяться независимо от текущего состояния кода
Итоги главы
Резюме
Что дальше?
Глава 2. Функциональное мышление в действии
Добро пожаловать в пиццерию Тони!
Часть 1. Проведение различий между действиями вычислениями и данными
Организация кода по частоте изменений
Первый взгляд на многоуровневое проектирование
Часть 2. Использование первоклассных абстракций
Применительно к роботизированной кухне
Временные линии наглядно представляют работу распределенных систем
Действия на временных линиях могут выполняться в разном порядке
Особенности распределенных систем: урок полученный дорогой ценой
Сегментация временной линии: заставляем роботов ожидать друг друга
Положительные уроки
Итоги главы
Резюме
Что дальше?
Часть I. Действия, вычисления и данные
Глава 3. Действия вычисления и данные
Действия вычисления и данные
1. Анализ задачи
2. Программирование решения
3. Чтение кода
Действия вычисления и данные применимы в любых ситуациях
Что мы узнали при моделировании процесса покупки
Применение функционального мышления в новом коде
Наглядное представление процесса рассылки купонов по электронной почте
Реализация процесса отправки купонов
Информация о подписчике из базы данных
Категория купона представлена строкой
Определение категории купона реализуется функцией
Информация о купонах из базы данных
Вычисление для выбора купонов по категории реализуется функцией
Сообщение — тоже данные
Вычисление для планирования одного сообщения для подписчика
Планирование всех сообщений
Отправка сообщений является действием
Применение функционального мышления в существующем коде
Распространение действий в коде
Действия могут принимать разные формы
Итоги главы
Резюме
Что дальше?
Глава 4. Извлечение вычислений из действий
Добро пожаловать в MegaMart.com!
Когда покупательская корзина всегда полна
MegaMart показывает вам свой секретный код
Вычисление бесплатной доставки
Новое поручение
Императивное решение
Вычисление налога
Следующее поручение
Необходимо упростить тестирование
Код содержит бизнес-правила, которые нелегко тестировать
Из заметок Джорджа по коду
Рекомендации Джорджа из отдела тестирования
Необходимо улучшить возможности повторного использования кода
Бухгалтерия и отдел доставки хотят использовать наш код
Заметки Дженны из команды разработки в коде
Дженна о предложениях команды разработки
Различия между действиями вычислениями и данными
У функций есть ввод и вывод
Ввод и вывод бывают явными и неявными
Неявный ввод и вывод превращают функцию в действие
Тестирование и повторное использование связаны с вводом и выводом
Джордж 1: Отделите бизнес-правила от обновлений DOM
Джордж 2: Избавьтесь от глобальных переменных
Дженна 1: Устраните зависимость от глобальных переменных
Дженна 2: Не надейтесь, что ответ попадет в DOM
Дженна 3: Возвращайте ответ из функции
Извлечение вычислений из действий
Извлечение другого вычисления из действия
Весь код в одном месте
Итоги главы
Резюме
Что дальше?
Глава 5. Улучшение структуры действий
Согласование структуры с бизнес-требованиями
Выбор уровня абстракции в соответствии с целями
Приведение функции в соответствие с бизнес-требованиями
Принцип: минимизация неявного ввода и вывода
Сокращение неявного ввода и вывода
Проверим код еще раз
Классификация наших расчетов
Принцип: суть проектирования в разделении
Простота повторного использования
Простота сопровождения
Простота тестирования
Улучшение структуры за счет разделения add_item()
Выделение паттерна копирования при записи
Использование функции add_item()
Классификация вычислений
Уменьшение функций и новые вычисления
Итоги главы
Резюме
Что дальше?
Глава 6. Неизменяемость в изменяемых языках
Может ли неизменяемость применяться повсеместно
Классификация операций чтения и/или записи
Три этапа выполнения копирования при записи
Преобразование записи в чтение с использованием копирования при записи
Сравнение двух версий
Операции копирования при записи обобщаются
Знакомство с массивами в JavaScript
Что делать с операциями чтения/записи
Разделение функции выполняющей чтение и запись
Разделение операции на чтение и запись
Преобразование записи в копирование при записи
Возвращение двух значений одной функцией
Упаковка операции
Преобразование чтения и записи в чтение
Операции чтения неизменяемых структур данных являются вычислениями
Приложения обладают состоянием которое изменяется во времени
Неизменяемые структуры данных достаточно быстры
Оптимизацией всегда можно заняться позднее
Сборщики мусора работают очень быстро
Копирования не так много, как может показаться на первый взгляд
В языках функционального программирования используются быстрые реализации
Операции с копированием при записи для объектов
Кратко об объектах JavaScript
Преобразование вложенных операций записи в чтение
Что же копируется?
Наглядное представление поверхностного копирования и структурного совместного использования
Итоги главы
Резюме
Что дальше?
Глава 7. Сохранение неизменяемости при взаимодействии с ненадежным кодом
Неизменяемость при работе с унаследованным кодом
Наш код копирования при записи должен взаимодействовать с ненадежным кодом
Защитное копирование позволяет сохранить неизменяемый оригинал
Реализация защитного копирования
Правила защитного копирования
Правило 1. Копируйте данные, выходящие из вашего кода
Правило 2. Копируйте данные, входящие в ваш код
Упаковка ненадежного кода
Защитное копирование которое вам может быть знакомо
Защитное копирование в программных интерфейсах (API) веб-приложений
Защитное копирование в Erlang и Elixir
Сравнение копирования при записи с защитным копированием
Копирование при записи
Защитное копирование
Глубокое копирование затратнее поверхностного
Трудности реализации глубокого копирования в JavaScript
Диалог между копированием при записи и защитным копированием
Итоги главы
Резюме
Что дальше?
Глава 8. Многоуровневое проектирование: часть 1
Что такое проектирование программной системы
Что такое многоуровневое проектирование
Развитие чувства проектирования
Проклятие эксперта
Ввод в контексте многоуровневого проектирования
Вывод в контексте многоуровневого проектирования
Паттерны многоуровневого проектирования
Паттерн 1. Прямолинейная реализация
Паттерн 2. Абстрактный барьер
Паттерн 3. Минимальный интерфейс
Паттерн 4. Удобные уровни
Паттерн 1. Прямолинейная реализация
Операции с корзиной
Проверка наличия товара в корзине может быть полезной
Визуализируем нашу функцию с помощью графа вызовов
Прямолинейные реализации вызывают функции примерно одного уровня абстракции
Добавление функции remove_item_by_name()
Все функции уровня должны иметь некое предназначение
Три уровня детализации
1. Глобальный масштаб
2. Масштаб уровня
3. Масштаб функции
В масштабе уровня сравниваются стрелки между функциями
В масштабе функции сравниваются стрелки, ведущие от одной функции
Выделение цикла for
Обзор паттерна 1. Прямолинейная реализация
Итоги главы
Резюме
Что дальше?
Глава 9. Многоуровневое проектирование: часть 2
Паттерны многоуровневого проектирования
Паттерн 1. Прямолинейная реализация
Паттерн 2. Абстрактный барьер
Паттерн 3. Минимальный интерфейс
Паттерн 4. Удобные уровни
Паттерн 2. Абстрактный барьер
До абстрактного барьера
После абстрактного барьера
Абстрактные барьеры скрывают реализацию
Игнорирование подробностей симметрично
Замена структуры данных корзины
Повторная реализация корзины в виде объекта
Абстрактный барьер позволяет игнорировать подробности
Когда следует (или не следует!) использовать абстрактные барьеры
1. Для упрощения изменений реализации
2. Для упрощения чтения и написания кода
3. Для сокращения координации межу командами
4. Для умственной концентрации на имеющейся задаче
Обзор паттерна 2. Абстрактный барьер
Код становится более прямолинейным
Паттерн 3. Минимальный интерфейс
Отдел маркетинга хочет предоставить скидку на часы
Два варианта реализации
Реализация кампании над барьером лучше
Отдел маркетинга хочет сохранять в журнале товары, добавленные в корзину
Последствия выбора
Более правильное место для сохранения добавлений товаров в корзину
Обзор паттерна 3. Минимальный интерфейс
Паттерн 4. Удобные уровни
Паттерны многоуровневой архитектуры
Паттерн 1. Прямолинейная реализация
Паттерн 2. Абстрактный барьер
Паттерн 3. Минимальный интерфейс
Паттерн 4. Удобные уровни
Что можно узнать из графа о коде?
Код в верхней части графа проще изменять
Важность тестирования кода нижних уровней
Код нижних уровней лучше подходит для повторного использования
Итоги: что можно узнать о коде по графу вызовов
Удобство сопровождения
Удобство тестирования
Удобство повторного использования
Итоги главы
Резюме
Что дальше?
Часть II. Первоклассные абстракции
Глава 10. Первоклассные функции: часть 1
Отдел маркетинга все еще должен согласовываться с разработчиками
Результаты поиска: 2343 запроса на изменения от отдела маркетинга
Признак «кода с душком»: неявный аргумент в имени функции
Рефакторинг: явное выражение неявного аргумента
Определение того что является и что не является первоклассным значением
Не приведут ли строки с именами полей к новым ошибкам?
Усложнят ли первоклассные поля изменения API?
Мы будем использовать множество объектов и массивов
Первоклассные функции могут заменить любой синтаксис
Пример цикла for: еда и уборка
Рефакторинг: замена тела обратным вызовом
Что это за синтаксис
1. Глобальное определение
2. Локальное определение
3. Встроенное определение
Почему мы упаковали код в функцию
Итоги главы
Резюме
Что дальше?
Глава 11. Первоклассные функции:часть 2
Одна проблема два метода рефакторинга
Признак «кода с душком»: неявный аргумент в имени функции
Рефакторинг: явное выражение неявного аргумента
Последовательность действий
Рефакторинг: замена тела обратным вызовом
Последовательность действий
Рефакторинг копирования при записи
Рефакторинг копирования при записи для массивов
1. Определение частей: предшествующей, тела и завершающей
2. Извлечение функции
3. Извлечение обратного вызова
Возвращение функций функциями
Итоги главы
Резюме
Что дальше?
Глава 12. Функциональные итерации
Один признак «кода с душком» и два рефакторинга
Признак «кода с душком»: неявный аргумент в имени функции
Рефакторинг: явное выражение неявного аргумента
Рефакторинг: замена тела обратным вызовом
MegaMart создает группу взаимодействия с клиентами
map() в примерах
Инструмент функционального программирования: map()
Три способа передачи функций
Глобальное определение
Локальное определение
Встроенное определение
Пример: адреса всех клиентов
Будьте внимательны!
filter() в примерах
Инструмент функционального программирования: filter()
Пример: клиенты без покупок
reduce() в примерах
Инструмент функционального программирования: reduce()
Пример: конкатенация строк
Что можно сделать с reduce()
Отмена/повторение
Воспроизведение операций пользователя для тестирования
Отладчик с перемещением во времени
Контрольный след
Сравнение трех инструментов функционального программирования
Итоги главы
Резюме
Что дальше?
Глава 13. Сцепление функциональных инструментов
Группа взаимодействия с клиентами продолжает работу
Улучшение цепочек способ 1: присваивание имен шагам
Улучшение цепочек способ 2: присваивание имен обратным вызовам
Улучшение цепочек: сравнение двух способов
Пример: адреса клиентов с одной покупкой
Преобразование существующих циклов for в инструменты функционального программирования
Стратегия 1. Понимание и переписывание
Стратегия 2. Рефакторинг по признакам
Совет 1. Создавайте данные
Совет 2. Работайте с целыми массивами
Совет 3. Используйте много мелких шагов
Совет 4. Используйте много мелких шагов
Сравнение функционального кода с императивным
Скользящее среднее
Советы по сцеплению
Создавайте данные
Работайте с целыми массивами
Используйте множество мелких шагов
Дополнительно: заменяйте условия вызовом filter()
Дополнительно: извлекайте вспомогательные функции
Дополнительно: экспериментируйте, чтобы совершенствоваться
Советы по отладке
Действуйте конкретно
Включайте команды вывода
Следите за типами
Другие функциональные инструменты
pluck()
concat()
frequenciesBy() и groupBy()
reduce() для построения значений
Творческий подход к представлению данных
О выравнивании точек
ES6
Классический JavaScript с Lodash
Потоки данных в Java 8
C#
Итоги главы
Резюме
Что дальше?
Глава 14. Функциональные инструменты для работы с вложенными данными
Функции высшего порядка для значений в объектах
Явное выражение имени поля
Построение update()
Использование update() для изменения значений
Рефакторинг: замена схемы «чтение — изменение — запись» функцией update()
Последовательность действий для замены схемы «чтение, изменение, запись» вызовом update()
Функциональный инструмент: update()
Наглядное представление значений в объектах
Наглядное представление обновлений вложенных данных
Применение update() к вложенным данным
Построение updateOption()
Построение update2()
Наглядное представление update2() с вложенными объектами
Четыре варианта реализации incrementSizeByName()
Построение update3()
Построение nestedUpdate()
Анатомия безопасной рекурсии
1. Базовый случай
2. Рекурсивный случай
3. Продвижение к базовому случаю
Наглядное представление nestedUpdate()
Сила рекурсии
Конструктивные особенности при глубоком вложении
Абстрактные барьеры для глубоко вложенных данных
Сводка использования функций высшего порядка
Замена циклов for при переборе массивов
Эффективная работа с вложенными данными
Применение копирования при записи
Закрепление политики регистрации ошибок try/catch
Итоги главы
Резюме
Что дальше?
Глава 15. Изоляция временных линий
Осторожно ошибка!
Пробуем кликать вдвое чаще
Временные диаграммы показывают что происходит с течением времени
Два фундаментальных принципа временных диаграмм
Две неочевидные детали порядка действий
1. ++ и += в действительности состоят из трех шагов
2. Аргументы выполняются перед вызовом функции
Построение временной линии добавления товара в корзину: шаг 1
1. Идентификация действий
Асинхронным вызовам необходимы новые временные линии
Разные языки разные потоковые модели
Однопоточная синхронная модель
Однопоточная асинхронная модель
Многопоточная модель
Процессы с передачей сообщений
Поэтапное построение временной линии
Изображение временной линии добавления товара в корзину: шаг 2
2. Рисование всех действий (как последовательных, так и параллельных)
Временные диаграммы отражают две разновидности последовательного кода
Выполнение с чередованием
Выполнение без чередования
Временные диаграммы отражают неопределенность в упорядочении параллельного кода
Принципы работы с временными линиями
1. Чем меньше временных линий, тем проще
2. Чем короче временные линии, тем проще
3. Чем меньше совместного использования ресурсов, тем проще
4. Координируйте совместное использование ресурсов
5. Рассматривайте время как первоклассную концепцию
Однопоточная модель в JavaScript
Асинхронная очередь JavaScript
Что такое задание
Кто ставит задания в очередь
Что делает ядро при отсутствии заданий
AJAX и очередь событий
Если не ожидать завершения запроса, то как получить ответ?
Полный пример с асинхронными вызовами
Упрощение временной линии
1. Объединение всех действий на одной временной линии
2. Объединение завершаемых временных линий с созданием одной новой временной линии
Чтение завершенной временной линии
Упрощение временной диаграммы добавления товара в корзину: шаг 3
1. Объединение всех действий на одной временной линии
2. Объединение завершаемых временных линий с созданием одной новой временной линии
Рисование временной линии (шаги 1–3)
Резюме: построение временных диаграмм
Идентификация действий
Рисование действий
Упрощение временной линии
Чтение временных линий
Сопоставление временных диаграмм помогает выявить проблемы
Два медленных клика приводят к правильному результату
Два быстрых клика приводят к неправильному результату
Временные линии с совместным использованием ресурсов создают проблемы
Преобразование глобальной переменной в локальную
1. Определение глобальной переменной, которая заменяется локальной
2. Замена глобальной переменной на локальную
Преобразование глобальной переменной в аргумент
1. Идентификация неявного ввода
2. Замена неявного ввода аргументом
Расширение возможностей повторного использования кода
Принцип: в асинхронном контексте в качестве явного вывода вместо возвращаемого значения используется обратный вызов
Итоги главы
Резюме
Что дальше?
Глава 16. Совместное использование ресурсов между временными линиями
Принципы работы с временными линиями
1. Чем меньше временных линий, тем проще
2. Чем короче временные линии, тем проще
3. Чем меньше совместного использования ресурсов, тем проще
4. Координируйте совместное использование ресурсов
5. Рассматривайте время как первоклассную концепцию
Корзина все еще содержит ошибку
Необходимо гарантировать порядок обновлений DOM
Реализация очереди в JavaScript
В JavaScript нет готовой структуры очереди, поэтому нам придется реализовать ее самостоятельно
Замена работы добавлением в очередь
Обработка первого товара в очереди
Изменение обратного вызова calc_cart_total() для запуска обработки следующего товара
Остановка перебора при отсутствии элементов
Упаковка переменных и функций в области видимости функции
Принцип: берите за образец решения по совместному использованию из реального мира
Совместное использование очереди
Выделение функции done()
Извлечение специализированного поведения работника
Получение обратного вызова при завершении задачи
Активизация обратного вызова при завершении задачи
Функция высшего порядка расширяет возможности действия
Анализ временной линии
Принцип: чтобы узнать о возможных проблемах проанализируйте временную диаграмму
Пропуск задач в очереди
Итоги главы
Резюме
Что дальше?
Глава 17. Координация временных линий
Принципы работы с временными линиями
1. Чем меньше временных линий, тем проще
2. Чем короче временные линии, тем проще
3. Чем меньше совместного использования ресурсов, тем проще
4. Координируйте совместное использование ресурсов
5. Рассматривайте время как первоклассную концепцию
Ошибка!
Как изменился код
Идентификация действий: шаг 1
Представление каждого действия: шаг 2
Упрощение диаграммы: шаг 3
Действия по упрощению для потоковой модели JavaScript
Анализ возможных вариантов упорядочения
Почему эта временная линия выполняется быстрее
Ожидание двух параллельных обратных вызовов
Примитив синхронизации для нарезки временных линий
Использование Cut() в коде
Анализ неопределенных упорядочений
Анализ параллельного выполнения
Анализ для нескольких кликов
Примитив для однократного вызова
Неявная и явная модель времени
Резюме: манипулирование временными линиями
Итоги главы
Резюме
Что дальше?
Глава 18. Реактивные и многослойные архитектуры
Два архитектурных паттерна
Реактивная архитектура
Многослойная архитектура
Связывание причин и эффектов изменений
Что такое реактивная архитектура
Плюсы и минусы реактивной архитектуры
Отделение эффектов от причин
Последовательность шагов рассматривается как конвейер
Гибкость временных линий
Ячейки как первоклассное состояние
Переменную ValueCell можно сделать реактивной
Обновление значков доставки при изменении ячейки
FormulaCell и вычисление производных значений
Изменяемое состояние в функциональном программировании
Как реактивная архитектура изменяет конфигурацию систем
Отделение эффектов от причин
Центр связей между причинами и эффектами
Интерпретация последовательности шагов как конвейера
Гибкость временной линии
Второй архитектурный паттерн
Реактивная архитектура
Многослойная архитектура
Что такое многослойная архитектура
Краткий обзор: действия вычисления и данные
Данные
Вычисления
Действия
Краткий обзор: многоуровневое проектирование
Традиционная многоуровневая архитектура
Функциональная архитектура
Упрощение изменений и повторного использования
Понятия используемые для размещения правила в слое
Анализ удобочитаемости и громоздкости решения
Удобочитаемость кода
Скорость разработки
Быстродействие системы
Итоги главы
Резюме
Что дальше?
Глава 19. Путешествие в мир функционального программирования продолжается
План главы
Полученные профессиональные навыки
Часть I. Действия, вычисления и данные
Часть II. Первоклассные абстракции
Главные выводы
В действиях часто скрываются вычисления
Функции высшего порядка помогают достичь новых высот абстракции
Вы можете управлять временной семантикой вашего кода
Приобретение навыков: победы и разочарования
Параллельные пути к мастерству
Путь 1: песочница
Путь 2: реальный код
Песочница: создание побочного проекта
Ограничьте масштаб проекта
Выберите экстравагантный побочный проект
Используйте знакомые навыки плюс один новый навык
Расширяйте проект так, как считаете нужным
Песочница: практические упражнения
Edabit (https://edabit.com/challenges)
Project Euler (https://projecteuler.net)
CodeWars (https://codewars.com)
Реальный код: устранение ошибок
Уменьшите количество глобальных изменяемых переменных на 1
Уменьшите количество аномальных временных линий на 1
Реальный код: постепенное улучшение проекта
Выделите одно вычисление из действия
Преобразуйте один неявный ввод или вывод в явный
Замените один цикл for
Популярные функциональные языки
Функциональные языки с наибольшим количеством вакансий
Функциональные языки на разных платформах
Возможность получения знаний
Статическая типизация
Функциональные инструменты и преобразования данных
Параллелизм и распределенные системы
Математическая основа
Лямбда-исчисление
Комбинаторы
Теория типов
Теория категорий
Системы эффектов
Литература
Итоги главы
Резюме
Что дальше?