Йонатан Шарвит Санкт-Петербург «БХВ-Петербург» 2024
УДК 004.4'236 ББК 32.973.26-018.1 Ш25 Шарвит Й. Ш25 Дата-ориентированное программирование: Пер. с англ. — СПб.: БХВ-Петербург, 2024. — 464 с.: ил. ISBN 978-5-9775-1924-3 Книга посвящена парадигме DOP (дата-ориентированному программированию), являющейся расширением философии объектно-ориентированного программирования. Предлагается новый взгляд на формирование структур данных и операции над ними в высоконагруженных приложениях. Изложенный материал дает решения сложных проблем, возникающих при управлении состоянием, разделяемыми и распределенными данными, позволяет безопасно организовать параллелизм и конкурентность, а также адаптировать ключевые принципы объектноориентированного программирования (полиморфизм, композицию, наследование) к новым задачам, связанным с обработкой больших данных. Для аналитиков данных, программистов, руководителей команд и преподавателей УДК 004.4'236 ББК 32.973.26-018.1 Группа подготовки издания: Руководитель проекта Олег Сивченко Зав. редакцией Людмила Гауль Перевод с английского Юлии Спикиной Редактор Софья Исакова Компьютерная верстка Ольги Сергиенко Оформление обложки Зои Канторович © 2023 BHV Authorized translation of the English edition © 2022 Manning Publications. This translation is published and sold by permission of Manning Publications, the owner of all rights to publish and sell the same. Авторизованный перевод с английского языка на русский издания © 2022 Manning Publications. Перевод опубликован и продается с разрешения компании-правообладателя Manning Publications. Все права защищены. "БХВ-Петербург", 191036, Санкт-Петербург, Гончарная ул., 20 ISBN 978-1-61729-857-8 (англ.) ISBN 978-5-9775-1924-3 (рус.) © Manning Publications, 2022 © Перевод на русский язык, оформление. ООО "БХВ-Петербург", ООО "БХВ", 2024
Оглавление Вступительное слово .................................................................................................... 13 Введение .......................................................................................................................... 17 Благодарности ................................................................................................................................ 18 Об этой книге ................................................................................................................................. 19 Кто должен прочитать эту книгу? ........................................................................................ 19 Как организована эта книга: дорожная карта ...................................................................... 19 О коде...................................................................................................................................... 21 Дискуссионный форум liveBook ........................................................................................... 22 Об авторе ........................................................................................................................................ 23 Об иллюстрации на обложке ........................................................................................................ 23 Персонажи пьесы ........................................................................................................................... 23 ЧАСТЬ 1. ГИБКОСТЬ ................................................................................................. 25 Глава 1. Сложность объектно-ориентированного программирования ............... 27 1.1. Дизайн ООП: классический или традиционный? ................................................................ 28 1.1.1. Этап проектирования ................................................................................................... 28 1.1.2. UML 101 ....................................................................................................................... 30 1.1.3. Объяснение каждой части диаграммы классов ......................................................... 33 Класс Library ....................................................................................................................... 33 Классы Librarian, Member и User ...................................................................................... 33 Класс Catalog ...................................................................................................................... 35 Класс Book ........................................................................................................................... 35 Класс BookItem .................................................................................................................... 35 1.1.4. Этап реализации ........................................................................................................... 36 1.2. Источники сложности ............................................................................................................ 36 1.2.1. Множество отношений между классами ................................................................... 38 1.2.2. Непредсказуемое поведение кода............................................................................... 40 1.2.3. Нетривиальная сериализация данных ........................................................................ 42 1.2.4. Сложные иерархии классов ........................................................................................ 44 Итоги ............................................................................................................................................... 48
6 Оглавление Глава 2. Разделение кода и данных ........................................................................... 51 2.1. Две части системы ДОП......................................................................................................... 52 2.2. Объекты данных ..................................................................................................................... 54 2.3. Модули кода ............................................................................................................................ 56 2.4. Системы ДОП просты для понимания .................................................................................. 61 2.5. Системы ДОП являются гибкими ......................................................................................... 64 Итоги ............................................................................................................................................... 68 Глава 3. Основные манипуляции с данными .......................................................... 69 3.1. Разработка модели данных .................................................................................................... 70 3.2. Представление записей в виде карт ...................................................................................... 74 3.3. Манипулирование данными с помощью универсальных функций .................................... 81 3.4. Вычисление результатов поиска ........................................................................................... 85 3.5. Обработка записей различных типов .................................................................................... 93 Итоги ............................................................................................................................................... 98 Глава 4. Управление состоянием ............................................................................. 101 4.1. Несколько версий системных данных ................................................................................ 102 4.2. Структурное совместное использование ............................................................................ 104 4.3. Реализация структурного совместного использования ..................................................... 110 4.4. Безопасность данных ............................................................................................................ 112 4.5. Фиксационный этап изменения ........................................................................................... 114 4.6. Обеспечение целостности состояния системы ................................................................... 116 4.7. Восстановление предыдущих состояний ............................................................................ 117 Итоги ............................................................................................................................................. 120 Глава 5. Основы контроля конкурентности .......................................................... 123 5.1. Оптимистичный контроль конкурентности ........................................................................ 124 5.2. Согласование между конкурентными изменениями.......................................................... 126 5.3. Сокращение коллекций ........................................................................................................ 129 5.4. Структурная разница ............................................................................................................ 131 5.5. Имплементация алгоритма согласования ........................................................................... 140 Итоги ............................................................................................................................................. 142 Глава 6. Модульные тесты ........................................................................................ 145 6.1. Простота дата-ориентированных тестовых кейсов ............................................................ 145 6.2. Модульные тесты для кода манипулирования данными ................................................... 147 6.2.1. Дерево вызовов функций .......................................................................................... 149 6.2.2. Модульные тесты для функций вниз по дереву ...................................................... 150 6.2.3. Модульные тесты для узлов в дереве ....................................................................... 154 6.3. Модульные тесты для запросов ........................................................................................... 157 6.4. Модульные мутационные тесты .......................................................................................... 162 Движение вперед ......................................................................................................................... 171 Итоги ............................................................................................................................................. 172 ЧАСТЬ 2. МАСШТАБИРУЕМОСТЬ ...................................................................... 175 Глава 7. Основы валидации данных ....................................................................... 179 7.1. Валидация данных в ДОП .................................................................................................... 179 7.2. Суть JSON-схемы ................................................................................................................. 181
Оглавление 7 7.3. Гибкость и строгость схемы ................................................................................................ 188 7.4. Композиция схемы ............................................................................................................... 193 7.5. Сведения о сбоях при валидации данных ........................................................................... 197 Итоги ............................................................................................................................................. 202 Глава 8. Расширенный контроль конкурентности .............................................. 203 8.1. Сложность блокировок......................................................................................................... 203 8.2. Потокобезопасный счетчик с атомами ............................................................................... 205 8.3. Потокобезопасный кеш с атомами ...................................................................................... 211 8.4. Управление состоянием с помощью атомов ...................................................................... 213 Итоги ............................................................................................................................................. 215 Глава 9. Персистентные структуры данных ......................................................... 217 9.1. Потребность в персистентных структурах данных ............................................................ 217 9.2. Эффективность персистентных структур данных ............................................................. 221 9.3. Библиотеки персистентных структур данных .................................................................... 227 9.3.1. Персистентные структуры данных в Java ................................................................ 228 9.3.2. Персистентные структуры данных в JavaScript ...................................................... 229 9.4. Персистентные структуры данных в действии .................................................................. 232 9.4.1. Написание запросов с персистентными структурами данных ............................... 232 9.4.2. Операции изменения при работе с персистентными структурами данных .......... 235 9.4.3. Сериализация и десериализация ............................................................................... 236 9.4.4. Структурная разница ................................................................................................. 237 Итоги ............................................................................................................................................. 240 Глава 10. Операции с базой данных ........................................................................ 243 10.1. Извлечение данных из базы данных ................................................................................. 244 10.2. Хранение данных в базе данных ....................................................................................... 251 10.3. Простая манипуляция данными......................................................................................... 254 10.4. Продвинутая обработка данных ........................................................................................ 258 Итоги ............................................................................................................................................. 266 Глава 11. Веб-сервисы ................................................................................................ 269 11.1. Другой запрос функции ...................................................................................................... 270 11.2. Создание внутренностей, подобных внешностям ............................................................ 270 11.3. Представление запроса клиента в виде карты .................................................................. 273 11.4. Представление ответа сервера в виде карты .................................................................... 276 11.5. Дальнейшая передача информации ................................................................................... 281 11.6. Расширение результатов поиска в действии .................................................................... 284 11.7. Доставка в срок ................................................................................................................... 295 Итоги ............................................................................................................................................. 296 ЧАСТЬ 3. УДОБСТВО СОПРОВОЖДЕНИЯ ....................................................... 297 Глава 12. Расширенная проверка данных ............................................................. 299 12.1. Проверка аргументов функции .......................................................................................... 299 12.2. Проверка возвращаемого значения ................................................................................... 308 12.3. Расширенная проверка данных.......................................................................................... 310 12.4. Автоматическое создание диаграмм модели данных ...................................................... 314
8 Оглавление 12.5. Автоматическая генерация модульных тестов на основе схемы .................................... 316 12.6. Новый подарок .................................................................................................................... 324 Итоги ............................................................................................................................................. 326 Глава 13. Полиморфизм ............................................................................................. 327 13.1. Сущность полиморфизма ................................................................................................... 327 13.2. Мультиметоды с единичной отправкой ............................................................................ 332 13.3. Мультиметоды с множественной отправкой .................................................................... 337 13.4. Мультиметоды с динамической отправкой ...................................................................... 343 13.5. Интеграция мультиметодов: производство ...................................................................... 346 Итоги ............................................................................................................................................. 351 Глава 14. Расширенная обработка данных ............................................................ 353 14.1. Обновление значения на карте с помощью выразительности ........................................ 353 14.2. Манипулирование вложенными данными ........................................................................ 357 14.3. Использование наилучшего инструмента для работы ..................................................... 360 14.4. Легкое разматывание .......................................................................................................... 365 Итоги ............................................................................................................................................. 370 Глава 15. Отладка ....................................................................................................... 371 15.1. Детерминизм в программировании ................................................................................... 371 15.2. Репродуцируемость с числами и строками ...................................................................... 375 15.3. Репродуцируемость с любыми данными .......................................................................... 379 15.4. Модульные тесты ................................................................................................................ 383 15.5. Работа с внешними источниками данных ........................................................................ 392 Прощание ..................................................................................................................................... 394 Итоги ............................................................................................................................................. 394 Приложение A. Принципы дата-ориентированного программирования ........ 397 A.1. Принцип № 1: отделяйте код от данных ............................................................................ 398 A.1.1. Иллюстрация к принципу № 1 ................................................................................. 399 Нарушение принципа № 1 в ООП ................................................................................... 399 Нарушение принципа № 1 в ФП ..................................................................................... 400 Соблюдение принципа № 1 в ООП ................................................................................. 400 Соблюдение принципа № 1 в ФП ................................................................................... 401 A.1.2. Преимущества принципа № 1 .................................................................................. 401 Преимущество № 1: код может быть повторно использован в различных контекстах ......................................................................................................................... 402 Преимущество № 2: код может быть протестирован изолированно ........................... 405 Преимущество № 3: системы, как правило, менее сложны .......................................... 406 A.1.3. Издержки принципа № 1 .......................................................................................... 408 Издержка № 1: отсутствие контроля над тем, какой код к каким данным может получить доступ .................................................................................................... 408 Издержка № 2: отсутствие пакетирования ..................................................................... 408 Издержка № 3: системы состоят из большего количества объектов ........................... 409 A.1.4. Основная суть принципа № 1 ................................................................................... 409 A.2. Принцип № 2: представляйте данные с помощью обобщенных структур...................... 410 A.2.1. Иллюстрация к принципу № 2 ................................................................................. 410 A.2.2. Преимущества принципа № 2 .................................................................................. 411
Оглавление 9 Применение функций, которые не ограничены определенным случаем использования ................................................................................................................... 412 Гибкая модель данных ..................................................................................................... 412 A.2.3. Издержки принципа № 2 .......................................................................................... 413 Издержка № 1: снижение производительности ............................................................. 413 Издержка № 2: отсутствие схемы данных ...................................................................... 414 Издержка № 3: отсутствует проверка данных на валидность во время компиляции ....................................................................................................... 414 Издержка № 4: необходимость явного приведения типов ............................................ 415 A.2.4. Основная суть принципа № 2 ................................................................................... 416 A.3. Принцип № 3: данные неизменяемы .................................................................................. 417 A.3.1. Иллюстрация к принципу № 3 ................................................................................. 417 A.3.2. Преимущества принципа № 3 .................................................................................. 419 Преимущество № 1: надежный доступ к данным для всех ........................................... 419 Преимущество № 2: предсказуемое поведение кода ..................................................... 419 Преимущество № 3: быстрая проверка равенства ......................................................... 420 Преимущество № 4: безопасность конкурентности без затрат .................................... 420 A.3.3. Издержки принципа № 3 .......................................................................................... 420 Издержка № 1: снижается производительность ............................................................ 421 Издержка № 2: требуется библиотека для персистентных структур данных .............. 421 A.3.4. Основная суть принципа № 3 ................................................................................... 421 A.4. Принцип № 4: отделяйте схему данных от представления данных ................................. 422 A.4.1. Иллюстрация к принципу № 4 ................................................................................. 422 A.4.2. Преимущества принципа № 4 .................................................................................. 424 Преимущество № 1: возможность свободно выбирать, какие данные следует валидировать ....................................................................................................... 424 Преимущество № 2: наличие опциональных полей ...................................................... 426 Преимущество № 3: наличие расширенных условий валидации данных .................... 428 Преимущество № 4: возможность автоматического создания визуализации модели данных .................................................................................................................. 428 A.4.3. Издержки принципа № 4 .......................................................................................... 429 Издержка № 1: слабая связь между данными и их схемой ........................................... 430 Издержка № 2: небольшое снижение производительности .......................................... 430 A.4.4. Основная суть принципа № 4 ................................................................................... 430 Заключение ................................................................................................................................... 431 Приложение B. Обобщенный доступ к данным в статически типизированных языках ................................................................... 433 B.1. Динамические геттеры для строковых карт ...................................................................... 433 B.1.1. Доступ к невложенным полям карты с помощью динамических геттеров .......... 434 B.1.2. Доступ к вложенным полям карты с помощью динамических геттеров.............. 435 B.2. Геттеры значений для карт .................................................................................................. 436 B.2.1. Доступ к невложенным полям карты с помощью геттеров значений .................. 437 B.2.2. Доступ к вложенным полям карты с помощью геттеров значений ...................... 438 B.3. Типизированные геттеры для карт ..................................................................................... 440 B.3.1. Доступ к невложенным полям карты с помощью типизированных геттеров ......... 440 B.3.2. Доступ к вложенным полям карты с помощью типизированных геттеров ......... 441
10 Оглавление B.4. Обобщенный доступ к членам класса ................................................................................ 443 B.4.1. Обобщенный доступ к не вложенным членам класса ............................................ 443 B.4.2. Обобщенный доступ к членам вложенного класса ................................................ 447 B.4.3. Автоматическая сериализация объектов JSON ...................................................... 450 Итоги ............................................................................................................................................. 451 Приложение C. Дата-ориентированное программирование: звено в цепи парадигм программирования ........................................................... 453 C.1. Хронология ........................................................................................................................... 453 C.1.1. 1958 год: Lisp ............................................................................................................. 453 C.1.2. 1981 год: значения и объекты .................................................................................. 453 C.1.3. 2000 год: идеальные хеш-деревья ............................................................................ 455 C.1.4. 2006 год: «Из ямы со смолой» ................................................................................. 455 C.1.5. 2007 год: Clojure ........................................................................................................ 455 C.1.6. 2009 год: неизменяемость для всех ......................................................................... 455 C.2. Принципы ДОП как наилучший подход ............................................................................ 456 C.2.1. Принцип № 1: отделяйте код от данных ................................................................. 456 C.2.2. Принцип № 2: представляйте данные с помощью обобщенных структур ........... 456 C.2.3. Принцип № 3: данные неизменяемы ....................................................................... 456 C.2.4. Принцип № 4: отделяйте схему данных от представления данных ...................... 457 C.3. ДОП и другие парадигмы, связанные с данными ............................................................. 458 C.3.1. Дата-ориентированная разработка ........................................................................... 458 C.3.2. Дата-управляемое программирование ..................................................................... 458 C.3.3. Дата-ориентированное программирование (ДОП) ................................................. 459 Итоги ............................................................................................................................................. 459 Приложение D. Ссылки на Lodash........................................................................... 461
Карин, которая ежедневно за мной присматривает
Вступительное слово Каждый принцип программирования, каждый метод проектирования, каждый архитектурный стиль и даже большинство языковых функций связаны с организацией сложности и возможностью адаптации. Две характеристики — неизменяемые данные и превращение частей программы в данные внутри самой программы — привлекли меня к Clojure в 2009 году, а совсем недавно к дата-ориентированному программированию Йехонатана Шарвита. В 2005 году я работал над одним из моих любимых проектов с некоторыми из моих любимых людей. Это был проект Java, но мы сделали две вещи, которые в то время не были распространены в мире Java. Во-первых, мы сделали наши основные значения данных неизменяемыми. Это было непросто, но сработало необыкновенно хорошо. Мы вручную накрутили методы clone и deepClone во многих классах. Отдача была огромной. В качестве примера предположим, что вам нужны шаблонные документы для создания экземпляров пользователями. Когда вы можете делать копии целых деревьев объектов, самим объектам не нужно «знать», являются ли они данными шаблона или данными экземпляра. Это решение зависит от того, какой объект содержит ссылку. Еще одно большое преимущество было получено от сравнения: когда значения неизменны, равенство идентичности указывает на равенство значений. Это может привести к очень быстрой проверке на равенство. Наш второй метод заключался в том, чтобы использовать общие данные, хотя и не в той степени, в какой Йехонатан покажет вам в этой книге. Там, где один слой имел иерархию классов, соседний слой представлял бы их как экземпляры более общего класса. То, что было бы переменной-членом в одном слое, будет описано полем в словаре в другом слое. Я уверен, что на этот стиль повлияли несколько болтунов в нашей команде. Это также сразу же окупилось, так как мы смогли компоновать и перекомпоновывать объекты в разных конфигурациях. Дата-ориентированное программирование, как вы увидите, обещает уменьшить случайную сложность и повысить уровень абстракции, с которым вы работаете. Вы начнете рассматривать повторяющееся поведение в своих программах как искусственное, результат разделения общих функций на классы, которые действуют как маленькие пространства имен, которые работают только с подмножеством значений вашей программы (их экземплярами). Мы можем «сложить вместе» почти все
14 Вступительное слово эти значения в карты и списки. Мы можем превратить имена участников (данные с трудом доступны через рефлексивные API) в ключи карты. Когда мы это делаем, код просто тает. Это первый уровень просветления. На этом этапе вы можете возразить, что компилятор использует эти имена членов во время компиляции для проверки правильности. Действительно так. Но верьте, потому что Йехонатан проведет вас к следующему уровню понимания: эти проверки во время компиляции являются небольшим подмножеством возможных проверок правильности значений. Мы также можем превратить проверки правильности в данные! Мы можем преобразовать схемы в значения внутри наших программ. Более того, мы можем применять критерии, которые все еще пытаются выяснить исследователи систем типов. Это второй уровень просветления. Дата-ориентированное программирование особенно ярко проявляется при работе с веб-API. В проводной сети нет типа системы, поэтому попытка сопоставить полезную нагрузку запроса непосредственно с классом предметной области гарантирует ненадежную и сложную реализацию. Если мы позволим данным быть данными, мы получим более простой код и гораздо меньше зависимостей от стомегабайтных фреймворковых библиотек. Итак, что случилось с такими достоинствами ООП, как инкапсуляция, наследование и полиморфизм? Оказывается, мы можем разобрать их и получить каждый из них по меню. (По моему мнению, наследование реализаций является наименее важным из них, хотя его часто преподают в первую очередь. Теперь я предпочитаю наследование интерфейсов через протоколы и сигнатуры общих функций.) Датаориентированное программирование предлагает полиморфизм «традиционного» типа: отправка одной из многих функций на основе типа первого аргумента (в объектно-ориентированном языке это маскировка первого аргумента метода. Просто бывает, что он стоит перед «.»). Однако, как и в случае проверки схемы, ДОП допускает больший динамизм. Представьте диспетчеризацию на основе типов первых двух аргументов. Или на основе того, есть ли в аргументе поле «день рождения» с сегодняшней датой! Это третий уровень просветления. А что касается инкапсуляции, мы все равно должны применить ее к организующей логике нашей программы. Мы инкапсулируем подсистемы, а не значения. Эта инкапсуляция воплощает сокрытие решений Дэвида Парнаса. Внутри подсистемы мы можем перестать отгораживать наши данные стеной от разрозненных пространств имен, навязываемых классами. По словам Алана Перлиса, «лучше иметь сто функций, работающих с одной структурой данных, чем десять функций с десятью структурами данных». В нашей бесконечной битве с энтропией мы можем использовать дата-ориентированное программирование, чтобы уменьшить объем кода, чтобы не отставать и повысить уровень абстракции, чтобы сделать логику и смысл нашей программы точными и очевидными. Наслаждайтесь путешествием и останавливайтесь на каждом новом плато, чтобы насладиться видом и сказать себе: «Это просто данные!» Майкл Т. Нигард, автор книги «Release It!: Design and Deploy Production-Ready Software»
Вступительное слово 15 Эта книга поразила меня в нужное время. Я создавал веб-приложения почти 20 лет в объектно-ориентированной среде. Я никогда не считал себя опытным программистом, но я достаточно хорошо знал свои инструменты, чтобы рассмотреть типичную бизнес-задачу, набросать модель данных и создать приложение в стиле MVC, чтобы выполнить свою работу. Проекты были захватывающими в начале. Мне нравилось ощущение соединения частей воедино и наблюдение за тем, как приложение оживает. Но как только я заставил его работать, я столкнулся с проблемами. Я не мог изменить одну деталь, не имея в виду все остальные модели. Я знал, что должен писать тесты, но мне нужно было настроить так много состояний для тестирования вещей, что оно того не стоило: я не хотел писать больше кода, который было бы трудно изменить. Даже запуск фрагментов кода в консоли был утомительным, потому что мне приходилось создавать состояние базы данных для вызова метода. Я думал, что, вероятно, делаю это неправильно, но решения, о которых я знал, такие как сложные среды тестирования, казалось, усложняли, а не облегчали задачу. Затем однажды я увидел на YouTube выступление Рича Хики, создателя Clojure. Он объяснял функциональное программирование и противопоставлял его объектноориентированному программированию, которое он насмешливо называл «пространственно-ориентированным программированием». Я не был уверен, прав ли он, но услышал скрытое сообщение, которое меня заинтриговало: «Дело не в тебе, дело в твоем языке». Я посмотрел все видео, которые смог найти, и начал думать, что Clojure может быть ответом. Прошли годы. Я продолжал смотреть видеоролики о Clojure и пытался применять функциональные принципы, когда мог. Но всякий раз, когда приходило время начинать новый проект, я возвращался к своему знакомому фреймворку. Переход на другой язык с совершенно другой экосистемой библиотек был слишком большим скачком. Затем, когда я собирался начать работу над новым продуктом, я нашел эту книгу. Слова «дата-ориентированное» в заголовке прозвучали звоночком. Я слышал, как программисты в тех видеороликах Clojure использовали эти слова раньше, но я действительно не понимал, что они означают. Кое-что о том, как проще создавать системы, которые манипулируют литералами данных (например, картами и массивами) вместо пользовательских объектов. Языки, которые я знал, хорошо поддерживали литералы данных, поэтому я подумал, что могу научиться чему-то, что поможет мне продержаться до того волшебного дня, когда я смогу переключиться на Clojure. Мое первое озарение произошло прямо во вступлении. На первых нескольких страницах Йехонатан объясняет, что, хотя он пишет на Clojure уже 10 лет, книга не привязана к конкретному языку, а примеры будут на JavaScript. «Подожди! — подумал я. — Неужели мне не нужно менять языки, чтобы значительно улучшить способ написания программ?» Я был так взволнован этой перспективой, что проглотил книгу в один присест. Мои глаза открылись и увидели то, что все это время было прямо передо мной. Конечно,
16 Вступительное слово мой код было трудно протестировать! Из-за ORM, который я использовал, вся моя функциональность была написана в объектах, которые предполагали множество состояний базы данных! Когда я увидел, как это прописано с примерами в книге, я не мог развидеть это. Мне не нужен был новый язык, мне просто нужно было по-другому подойти к программированию! Все разработчики, которых я считаю великими, указывают на одно и то же: хорошая разработка заключается в разделении вещей. Дело не только в том, чтобы заставить код работать, каким бы уродливым он ни был. Речь идет о распутывании частей друг от друга, чтобы вы могли изменить одну вещь, не нарушая все остальное. В этой книге код и данные разбираются на части с неожиданными и захватывающими результатами. Шарвит отделил способ программирования от определенного языка. Возможно, я никогда не перейду на Clojure, да и не чувствую, что мне это нужно. Дата-ориентированное программирование помогло мне увидеть новые возможности языков, которые я знаю, и множество новых фреймворков, появляющихся каждый день. Райан Сингер, автор книги «Shape Up: Stop Running in Circles and Ship Work that Matters»
Введение Я работаю инженером-программистом с 2000 года. Для меня очевидно, что есть «до» и «после» 2012 года. Почему именно 2012 год? Потому что 2012 год — это год, когда я открыл для себя Clojure. До Clojure программирование было моей работой. После Clojure программирование стало моей страстью. Несколько лет назад я задавался вопросом, какие особенности Clojure сделали этот язык программирования таким большим источником удовольствия для меня. Я поделился своими вопросами с другими членами сообщества Clojure, которые испытывают к нему ту же страсть, что и я. Вместе мы обнаружили, что особенным в Clojure были не функции, а принципы. Когда мы решили выделить основные принципы Clojure, мы поняли, что они, по сути, применимы и к другим языкам программирования. Именно тогда начала зарождаться идея этой книги. Я хотел поделиться тем, что мне так нравится в Clojure, с мировым сообществом разработчиков. Для этого мне понадобилось бы средство четкого выражения идей, которые в основном не знакомы разработчикам, не знающим Clojure. Мне всегда нравилось придумывать истории, но будут ли программисты воспринимать мои придуманные диалоги всерьез? Конечно, Платон придумывал истории в сократических диалогах, чтобы передать учение своего учителя. Точно так же раввин Иуда Халеви придумал историю о царе хазар, чтобы объяснить основы иудаизма. Но эти две работы относятся к области мысли, а не практики! Затем я вспомнил книгу по менеджменту, которую прочитал несколько лет назад, под названием «Цель» (North River Press, 2014). В этой книге Элияху Голдратт рассказывает историю директора завода, которому удается спасти свою фабрику благодаря принципам, вытекающим из теории ограничений. Платон, Иуда Халеви и Элияху Голдратт узаконили мое безумное желание написать историю, чтобы поделиться идеями.
18 Введение Благодарности Прежде всего, я хочу поблагодарить мою возлюбленную Карин. Ты верила в меня с самого начала этого проекта. Тебе всегда удается увидеть свет, даже когда он скрывается за несколькими слоями тьмы. Моим замечательным детям — Одайе, Орлу, Адваху, Нехораю и Яиру, которые были первыми слушателями историй, которые я придумал, когда был молодым папой. Вы самая прекрасная история, которую я когда-либо писал! Есть много других людей, которым я также хочу выразить свою благодарность, в том числе Джоэлу Кляйну за все увлекательные и обогащающие дискуссии об искусстве и душе; Меир Армон за то, что помогла мне уточнить, какой контент не следует включать в книгу; Ричу Хикки за изобретение Clojure, такого прекрасного языка, который охватил дата-ориентированное программирование еще до того, как у него появилось название; Кристофу Гранду, чьи ценные советы помогли мне выделить первые три принципа дата-ориентированного программирования; Марку Шампайну за тщательный анализ рукописи и множество ценных предложений; Эрику Норманду за поддержку и в частности советы по применению дата-ориентированного программирования в Java; Берту Бейтсу за то, что научил меня секретам написания хорошей книги, и Бену Баттону за ознакомление с главами, посвященными схеме JSON. Моя благодарность всем сотрудникам издательства Manning Publications, особенно Майку Стивенсу, за согласие продолжать работать со мной, несмотря на провал моей первой книги; Элеше Хайд за доступность и внимание к мельчайшим деталям; Мариусу Бутуку за восторженные положительные отзывы от прочтения первой главы; Линде Котлярски за то, что описания глав были составлены в такой увлекательной форме, и Фрэнсис Буран за улучшение ясности текста и истории. Всем рецензентам: Алексу Гауту, Аллену Дингу, Андреасу Шабусу, Эндрю Дженнингсу, Энди Киршу, Энн Эпштейн, Бертольду Франку, Кристиану Крейцер-Беку, Кристоферу Карделлу, Дейну Балии, доктору Давиду Кадамуро, Элиасу Илмари Лиинамаа, Эзре Симелоффу, Джорджу Томасу, С. Гири, Джулиано Араужо Бертоти, Грегору Рейману, Дж. М. Боровина Йоско, Джероми Мейеру, Хесусу А. Хуаресу Герреро, Джону Д. Льюису, Джону Гюнтеру, Келуму Прабату Сенанаяке, Келвину Джонсону, Кенту Р. Спиллнеру, Ким Габриэлсен, Константину Еремину, Маркусу Гезелле, Марку Элстону, Мэтью Проктору, Маурицио Томази, Майклу Айдинбасу, Милораду Имбра, Озайу Думану, Раффаэллу Вентальо, Раманану Нарараджану, Рамбабу Поза, Саурабху Сингху, Сету Макферсону, Шайло Моррис, Виктору Дюрану, Винсенту Терону, Уильяму Э. Уилеру, Йогешу Шетти и Ивану Фелизоту, ваши предложения помогли сделать эту книгу лучше. Наконец, я хотел бы упомянуть моего двоюродного брата Ниссима, которому банда варваров не позволила процветать.
Введение 19 Об этой книге Дата-ориентированное программирование было написано для того, чтобы помочь разработчикам снизить сложность создаваемых ими систем. Идеи, изложенные в этой книге, в основном применимы к системам, которые управляют информацией, — таким как интерфейсные приложения, внутренние веб-серверы или вебслужбы. Кто должен прочитать эту книгу? Дата-ориентированное программирование предназначено для разработчиков frontend, backend и full stack с парой лет опыта работы на языках программирования высокого уровня, таких как Java, C#, C++, Ruby или Python. Для разработчиков объектно-ориентированного программирования некоторые идеи, представленные в этой книге, могут вывести их из зоны комфорта и потребовать от них отучиться от некоторых парадигм программирования, с которыми они чувствуют себя легко. Разработчикам функционального программирования эту книгу будет немного легче переварить, но она также преподнесет несколько приятных сюрпризов. Как организована эта книга: дорожная карта В этой книге рассказывается история, иллюстрирующая дата-ориентированное программирование (ДОП) и то, как применять его принципы в реальных производственных системах. Мое предложение состоит в том, чтобы следить за историей и читать главы по порядку. Однако, если некоторые главы вызывают у вас большее любопытство, чем другие, имейте в виду, что материал из части 1 и главы 7 необходим для понимания частей 2 и 3. На протяжении всей книги мы используем Lodash (https://lodash.com /), чтобы проиллюстрировать, как манипулировать данными с помощью универсальных функций. В случае если вы читаете фрагмент кода, который использует незнакомую вам функцию Lodash, вы можете обратиться к приложению D, чтобы понять поведение функции. Часть 1 «Гибкость» содержит шесть глав, освещает проблемы традиционного объектно-ориентированного программирования (ООП) и ставит в центр внимания дата-ориентированное программирование, показывая, как создавать гибкие системы, используя основные принципы ДОП. Главы выстраиваются таким образом: В главе 1 «Сложность объектно-ориентированного программирования» мы рассмотрим сложность ООП. Тогда начинается наша ДОП-сага! Послушайте разговор между Тео, старшим разработчиком, и его многообещающим коллегой Дейвом. Почувствуйте сочувствие к Тео, борющемуся со сложностью ООП, и найдите отличную причину для того, чтобы попробовать другую парадигму программирования. В главе 2 «Разделение кода и данных» наш друг Тео ищет решение, которое уменьшит сложность и повысит гибкость систем. На кону его работа. Входит
20 Введение Джо, опытный разработчик, у которого есть для него ответ — ДОП. Узнайте, как принцип ДОП № 1 помогает снизить сложность информационных систем. В главе 3 «Основы манипулирования данными» исследуется, как мы можем освободить наши данные от инкапсуляции в жесткость класса и свободно манипулировать ими с помощью универсальных функций, применяя принцип ДОП № 2. Да здравствует революция! Глава 4 «Управление состоянием» исследует управление состоянием с помощью мультиверсионного подхода, который позволяет нам вернуться назад во времени, восстановив систему до предыдущего состояния, потому что в ДOП состояние — это не что иное, как данные. Путешествие во времени реально — в ДОП! Глава 5 «Основы управления параллелизмом» помогает нам получить высокую пропускную способность операций чтения и записи в параллельной системе, применяя оптимистичную стратегию управления параллелизмом. Розовые очки не требуются! Глава 6 «Модульные тесты» предлагает выпить чашечку кофе... с Джо! Наш друг Джо доказывает, что модульное тестирование кода, ориентированного на данные, настолько просто, что вы можете заняться им в кафе. Возьмите чашечку кофе и узнайте, почему это так просто (даже для изменений!), когда вы пишете модульный тест ДОП вместе с Джо. Это по-настоящему круто! Часть 2 «Масштабируемость» иллюстрирует, как создать масштабируемую систему ДОП с акцентом на проверку данных, многопоточные среды, большие коллекции данных, а также доступ к базам данных и веб-службам. Вам нужно увеличить размер вашей системы? Нет проблем! Глава 7 «Базовая проверка данных» учит нас, как убедиться, что данные, поступающие в наши системы и из них, являются достоверными, на всякий случай... потому что, как говорит Джо, вас не заставляют проверять данные в ДОП, но вы можете, когда вам нужно. Проверять или не проверять — вот в чем вопрос! В главе 8 «Расширенное управление параллелизмом» обсуждается, что после того как наш друг Джо разберет детали реализации механизма «атом», мы научимся управлять всем состоянием системы потокобезопасным способом без использования блокировок. Вы не узнаете сложность от атома до атома! Глава 9 «Постоянные структуры данных» переносится в более академическую среду, где наш друг Джо раскрывает внутренние детали более безопасного и масштабируемого способа сохранения неизменности данных, а также того, как его эффективно реализовать независимо от размера данных. Урок уже начался! Глава 10 «Операции с базами данных» учит нас, как представлять данные из базы данных, получать к ним доступ и управлять ими таким образом, чтобы обеспечить дополнительную гибкость и (как вы уже догадались) меньшую сложность. Глава 11 «Веб-службы» позволяет нам открыть для себя простоту взаимодействия с веб-службами. Мы узнаем, что Джо имеет в виду, когда говорит: «Мы
Введение 21 должны строить внутреннюю часть наших систем так же, как мы строим внешнюю». Часть 3 «Поддерживаемость» включает в себя такие методы ДОП, как расширенная проверка данных, полиморфизм, красноречивый код и методы отладки, которые жизненно важны, когда вы работаете в команде. Добро пожаловать в команду! Глава 12 «Расширенная проверка данных» позволяет нам определить форму будущих событий. Здесь вы узнаете, как проверять данные, когда они поступают внутри системы, что позволяет упростить разработку, определяя ожидаемую форму аргументов функции и возвращаемых значений. Глава 13 «Полиморфизм» переносит нас вместе с Тео и Дейвом на занятия в сельской местности — подходящее место, чтобы поиграть с животными и узнать о полиморфизме без объектов с помощью мультиметодов. Глава 14 «Расширенные возможности обработки данных» позволяет нам увидеть, как Дейв и Тео применяют мудрый совет Джо, чтобы превратить утомительный код в красноречивый код, создавая свои собственные инструменты для обработки данных. «Поставь телегу впереди лошади» — это еще одна жемчужина от Джо! Глава 15 «Отладка» приводит Дэйва и Тео в музей, чтобы в последний раз сказать «ура», поскольку они создают инновационное решение для воспроизведения и исправления ошибок. Эта книга также имеет четыре приложения. Приложение А «Принципы дата-ориентированного программирования» обобщает каждый из четырех принципов ДОП, которые подробно рассматриваются в части 1, и иллюстрирует, как каждый принцип может быть применен как к языкам FP, так и к ООП. В нем также описываются преимущества каждого принципа и затраты на соблюдение каждого из них. В приложении В «Общий доступ к данным в статически типизированных языках» представлены различные способы обеспечения общего доступа к данным в статически типизированных языках программирования, таких как Java и C#. Приложение C «Дата-ориентированное программирование: звено в цепи парадигм программирования» исследует идеи и тенденции, которые вдохновили ДОП. Мы рассматриваем открытия, которые делают его применимым в производственных системах в больших масштабах. Приложение D «Ссылки на Lodash» содержит краткое описание функций Lodash, которые мы используем на протяжении всей книги, чтобы проиллюстрировать, как манипулировать данными с помощью универсальных функций, не изменяя их. О коде Большинство фрагментов кода в этой книге написаны на JavaScript. Мы выбрали JavaScript по двум причинам:
22 Введение JavaScript поддерживает как функциональное программирование, так и объектноориентированный стиль программирования. Синтаксис JavaScript легко читается в том смысле, что даже если вы не знакомы с JavaScript, вы можете прочитать фрагмент кода JavaScript на высоком уровне, как если бы это был псевдокод. Чтобы читателям с любого языка программирования было легко читать фрагменты кода, мы ограничились базовым синтаксисом JavaScript и избежали использования расширенных языковых функций, таких как функции со стрелками и асинхронная нотация. Там, где возникла концептуальная проблема в применении идеи к статически типизированному языку, мы добавили фрагменты кода на Java. Код отображается по всему тексту и в виде отдельных фрагментов кода шрифтом фиксированной ширины, подобным этому. Во многих случаях исходный код был переформатирован. Мы добавили разрывы строк и переработали отступы, чтобы вместить доступное пространство страницы в книге. Аннотации к коду также сопровождают некоторые списки, выделяя важные концепции. Вы можете получить исполняемые фрагменты кода из онлайн-версии этой книги в liveBook по адресу https://livebook.manning.com/book/data-oriented-programming, или по ссылке на Github книги здесь: https://github.com/viebel/data-orientedprogramming. Дискуссионный форум liveBook Покупка «Дата-ориентированного программирования» включает бесплатный доступ к liveBook, онлайн-платформе для чтения издательства «Мэннинг». Используя эксклюзивные функции обсуждения liveBook, вы можете прикреплять комментарии к книге глобально или к определенным разделам или абзацам. Легко делать заметки, задавать технические вопросы и отвечать на них, а также получать помощь от автора и других пользователей. Чтобы получить доступ к форуму, перейдите по ссылке https://livebook.manning.com/book/data-Oriented-programming/discussion. Вы также можете узнать больше о форумах издательства «Мэннинг» и правилах поведения на https://livebook.manning.com/discussion. Обязательство издательства «Мэннинг» перед читателями состоит в том, чтобы предоставить место, где может состояться содержательный диалог между отдельными читателями и между читателями и автором. Это не обязательство какой-либо конкретной суммы участия со стороны автора, чей вклад в форум остается добровольным (и неоплачиваемым). Мы предлагаем вам попробовать задать автору несколько сложных вопросов, чтобы его интерес не потерялся! Форум и архивы предыдущих обсуждений будут доступны на веб-сайте издателя, пока книга находится в печати.
Введение 23 Об авторе Йехонатан Шарвит имеет более чем 20-летний опыт работы в качестве инженера-программиста, программируя на C++, Java, Ruby, JavaScript, Clojure и ClojureScript, как в бэкенде, так и во внешнем интерфейсе. На момент написания этой книги он работал архитектором программного обеспечения в Cyclognito, создавая программные инфраструктуры для крупномасштабных конвейеров данных. Он делится своей страстью к программированию в своем блоге (https://blog.klipse.tech/) и на технических конференциях. Вы можете следить за ним в «Твиттере»: https://twitter.com/viebel. Об иллюстрации на обложке Рисунок на обложке «Fille de l'Isle Santorin», или «Девушка с острова Санторини», взят из сборника Жака Грассе де Сен-Совера, опубликованного в 1797 году. Каждая иллюстрация там нарисована и раскрашена вручную. В те дни было легко определить, где живут люди и каково их ремесло или положение в жизни, просто по их одежде. На своих обложках издательство Manning подчеркивает творческую жилку и инициативность, присущие всем, кто занят в сфере IT. Хочется напомнить, как разнообразны были региональные культуры еще пару веков назад и возродить память о тех временах, украшая книги картинками из подобных этнографических коллекций. Персонажи пьесы ТЕО, старший разработчик НЭНСИ, предпринимательница МОНИКА, менеджер, босс Тео ДЕЙВ, младший разработчик, коллега Тео ДЖО, независимый программист КЕЙ, психотерапевт, жена Джо ДЖЕЙН, жена Тео НЕРАЙЯ, сын Джо АУРЕЛИЯ, дочь Джо Действие этой истории происходит в Сан-Франциско.
24 Введение
Часть 1 Гибкость Утро понедельника. Теодор сидит с Нэнси на террасе La Vita è Bella, итальянской кофейни рядом с зоопарком Сан-Франциско. Нэнси — предпринимательница, которая ищет агентство по развитию для своей стартап-компании Klafim. Тео работает в агентстве по разработке программного обеспечения Albatross, которое стремится вернуть доверие стартапов. Нэнси и ее партнер по бизнесу собрали начальный капитал для Klafim, социальной сети для книг. Уникальное ценностное предложение Klafim заключается в объединении онлайн-мира с физическим миром, позволяя пользователям брать книги из местных библиотек, а затем встречаться онлайн для обсуждения книг. Большая часть продукта основана на интеграции существующих онлайн-сервисов. Единственное, что требует разработки программного обеспечения, — это то, что Нэнси называет глобальной системой управления библиотеками. Их беседу на мгновение прерывает официант, который приносит Тео его крепкий эспрессо, а Нэнси американо с молоком. Тео: По-твоему, что такое глобальная система управления библиотеками? Нэнси: Это программная система, которая выполняет основные функции ведения библиотечных дел, в основном связанные с каталогом книг и читателями. Тео: Не могла бы ты немного конкретизировать? Нэнси: Конечно. На данный момент нам нужен быстрый прототип. Если реакция рынка на Klafim будет положительной, мы будем продвигаться вперед уже с большим проектом. Тео: Какие функции тебе нужны для этапа создания прототипа? Нэнси берет салфетку из-под кофейной кружки и пишет на ней пару маркированных пунктов. ТРЕБОВАНИЯ К ПРОТОТИПУ KLAFIM Два типа пользователей библиотеки — это читатели и библиотекари. Пользователи входят в систему с помощью электронной почты и пароля. Читатели могут брать книги напрокат.
26 Часть 1. Гибкость Читатели и библиотекари могут искать книги по названию или по автору. Библиотекари могут блокировать и разблокировать читателей (например, когда они запаздывают с возвратом книги). Библиотекари могут перечислить книги, которые в настоящее время предоставлены читателям. У книги может быть несколько экземпляров. Книга принадлежит физической библиотеке. Тео: Что ж, все это довольно понятно. Нэнси: Сколько времени потребуется вашей компании, чтобы сделать прототип? Тео: Я думаю, мы могли бы сделать его в течение месяца. Скажем, в среду, 30-го. Нэнси: Это слишком долго. Нам он нужен через две недели! Тео: Это тяжело! Можешь ли ты вырезать одну или две функции? Нэнси: К сожалению, я не могу сократить какие-либо функции, но если хочешь, ты можешь сделать поиск очень простым. (Тео действительно не хочет терять этот контракт, поэтому он готов усердно работать и ложиться спать позже.) Тео: Я думаю, что это может быть выполнено к среде 16-го. Нэнси: Супер!
Сложность объектно-ориентированного программирования Капризный предприниматель В ЭТОЙ ГЛАВЕ РАССМАТРИВАЮТСЯ Тенденция ООП к увеличению сложности системы. Что делает ООП-системы трудными для понимания. Стоимость смешивания кода и данных в объекты. В этой главе мы рассмотрим, почему системы объектно-ориентированного программирования (ООП), как правило, сложны. Эта сложность не связана с синтаксисом или семантикой конкретного языка ООП. Это то, что присуще фундаментальному пониманию ООП: программы должны состоять из объектов, которые состоят из некоторого состояния, вместе с методами для доступа к этому состоянию и управления им. На протяжении многих лет экосистемы ООП облегчали эту сложность, добавляя новые функции в язык (например, анонимные классы и анонимные функции) и разрабатывая фреймворки, которые скрывают часть этой сложности, предоставляя более простой интерфейс для разработчиков (например, Spring и Jackson в Java). Внутри фреймворки полагаются на расширенные возможности языка, такие как отражение и пользовательские аннотации. Эта глава не предназначена для прочтения как критический анализ ООП. Ее цель — повысить вашу осведомленность о тенденции к усложнению ООП как парадигмы программирования. Надеюсь, это побудит вас открыть для себя другую парадигму программирования, в которой сложность системы имеет тенденцию к снижению. Эта парадигма известна как дата-ориентированное программирование (ДОП).
28 Часть 1. Гибкость 1.1. Дизайн ООП: классический или традиционный? ПРИМЕЧАНИЕ. Тео, Нэнси и их новый проект были представлены в начале первой части. Прочитайте вступительную часть, если вы ее пропустили. Тео возвращается в офис с салфеткой Нэнси в кармане и с большим беспокойством в душе, потому что он знает, что поставил себе жесткий дедлайн. Но у него не было выбора! На прошлой неделе Моника, его босс, совершенно ясно сказала ему, что он должен заключить сделку с Нэнси, несмотря ни на что. Albatross, где работает Тео, — консалтинговая компания по программному обеспечению, имеющая клиентов по всему миру. Изначально у него было много клиентов среди стартапов. Однако за последний год многие проекты плохо управлялись, и отдел стартапов потерял доверие своих клиентов. Вот почему руководство перевело Тео из корпоративного отдела в отдел стартапов в качестве старшего технического руководителя. Его работа заключается в том, чтобы заключать сделки и выполнять их вовремя. 1.1.1. Этап проектирования Прежде чем броситься к своему ноутбуку, чтобы закодить систему, Тео берет лист бумаги, намного больше салфетки, и начинает рисовать UML-диаграмму классов системы, которая будет реализовывать прототип Klafim. Тео объектно-ориентированный программист. Здесь для него нет никаких вопросов — каждая бизнессущность представлена объектом, а каждый объект сделан из класса. ТРЕБОВАНИЯ К ПРОТОТИПУ KLAFIM Существуют два типа пользователей: читатели1 и библиотекари. Пользователи входят в систему с помощью электронной почты и пароля. Читатели могут брать книги напрокат. Читатели и библиотекари могут искать книги по названию или по автору. Библиотекари могут блокировать и разблокировать читателей (например, когда они запаздывают с возвратом книги). Библиотекари могут составить список книг, которые в настоящее время предоставлены читателю. У книги может быть несколько экземпляров. Книга принадлежит физической библиотеке. Тео тратит некоторое время на размышления об организации системы. Он выделяет основные классы для глобальной системы управления библиотеками Klafim. 1 Английское слово Member переведено как «читатель» в контексте системы библиотеки; в листингах представлен как member.
Глава 1. Сложность объектно-ориентированного программирования 29 ОСНОВНЫЕ КЛАССЫ СИСТЕМЫ УПРАВЛЕНИЯ БИБЛИОТЕКОЙ Library — центральная часть системного проектирования. Book — книга. BookItem — книга может иметь несколько копий, и каждая копия рассматривается как элемент книги. BookLending — при предоставлении книги создается объект предоставления книги. Member — читатель. Librarian — библиотекарь. User — базовый класс для библиотекаря и читателя. Catalog — содержит список книг. Author — автор книги. Это была самая легкая часть. Теперь начинается самое сложное: отношения между классами. Примерно через два часа Тео предлагает первый набросок проекта глобальной системы управления библиотеками. Это выглядит как схема на рис. 1.1. ПРИМЕЧАНИЕ. Представленный здесь дизайн не претендует на звание самого умного дизайна ООП: опытные разработчики ООП, вероятно, использовали бы пару шаблонов проектирования, чтобы предложить гораздо лучший дизайн. Этот дизайн должен быть наивным и ни в коем случае не охватывает все функции системы. Это служит двум целям: для Тео, разработчика, он достаточен, чтобы начать кодить; для меня, автора книги, он достаточен, чтобы проиллюстрировать сложность типичной системы ООП. Тео гордится собой и только что созданной им дизайнерской схемой. Он определенно заслужил чашечку кофе! Возле кофемашины Тео знакомится с Дейвом, младшим разработчиком программного обеспечения, который присоединился к Albatross пару недель назад. Тео и Дейв ценят друг друга, поскольку любопытство Дейва заставляет его задавать сложные вопросы. Встречи у кофемашины часто превращаются в интересные дискуссии о программировании. Тео: Привет, Дейв. Как день? Дейв: Сегодня? Не очень. Я пытаюсь исправить ошибку в своем коде, но не могу понять, почему состояние моих объектов всегда меняется. Но я уверен, что разберусь с этим. А как проходит твой день? Тео: Я только что закончил дизайн системы для нового клиента. Дейв: Круто! Можно ли мне посмотреть? Я пытаюсь улучшить свои дизайнерские навыки. Тео: Конечно! Схема у меня на столе. Если хочешь, мы можем взглянуть на нее прямо сейчас.
30 Часть 1. Гибкость Рис. 1.1. Диаграмма классов для глобальной системы управления библиотеками Klafim 1.1.2. UML 101 С латте в руке Дейв следует за Тео к его столу. Тео с гордостью показывает Дейву свое произведение искусства: UML-диаграмму для системы управления библиотекой (см. рис. 1.1). Дейв, кажется, очень взволнован. Дейв: Ух ты! Такая подробная диаграмма классов. Тео: Да. Я очень доволен этим. Дейв: Но дело в том, что я никогда не могу вспомнить значение разных стрелок.
Глава 1. Сложность объектно-ориентированного программирования 31 Тео: На моей диаграмме классов есть четыре типа стрелок: композиция, ассоциация, наследование и использование. Дейв: В чем разница между композицией и ассоциацией? ПРИМЕЧАНИЕ. Не волнуйтесь, если вы не знакомы с жаргоном ООП. Мы оставим это для следующей главы. Тео: Все дело в том, могут ли объекты жить друг без друга. В случае с композицией, когда умирает один объект, умирает и другой. Находясь в ассоциативном отношении, каждый объект имеет независимую жизнь. СОВЕТ. В отношении композиции, когда умирает один объект, умирает и другой. Находясь в ассоциативном отношении, каждый объект имеет независимый жизненный цикл. На диаграмме классов есть два типа композиций, обозначенных стрелкой с простым ромбом на одном краю и необязательной звездой на другом краю. На рис. 1.2 показано соотношение между: Library, которой принадлежит Catalog, — композиция «один к одному». Если объект Library умирает, то вместе с ним умирает и его объект Catalog; Library, которая владеет многими Members, — это композиция «один ко многим». Если объект Library умирает, то все его объекты Members умирают вместе с ним. Рис. 1.2. Два вида композиции: «один к одному» и «один ко многим». В обоих случаях, когда объект умирает, составной объект умирает вместе с ним СОВЕТ. Композиционное соотношение представлено простым ромбом на одном краю и необязательной звездой на другом краю. Дейв: Есть ли ассоциативные связи на твоей диаграмме? Тео: Взгляни на стрелку между Book и Author. У нее есть пустой ромб и звезда по обоим краям, так что это ассоциативное отношение «многие ко многим». Книга может быть написана несколькими авторами, и один автор может написать несколько книг. Более того, объекты Book и Author могут жить независимо. Связь между книгами и авторами представляет собой ассоциацию «многие ко многим» (рис. 1.3).
32 Часть 1. Гибкость СОВЕТ. Ассоциативное отношение «многие ко многим» представлено пустым ромбом и звездой по обоим краям. Дейв: Я также вижу кучу пунктирных стрелок на вашей диаграмме. Тео: Пунктирные стрелки обозначают отношения использования: когда класс использует метод другого класса. Рассмотрим, например, метод Librarian:: blockMember. Он вызывает Member::block. СОВЕТ. Пунктирные стрелки указывают на отношения использования (рис. 1.4), например когда класс использует метод другого класса. Рис. 1.3. Отношения ассоциации «многие ко многим»: каждый объект живет независимо Рис. 1.4. Отношения использования: класс использует метод другого класса Дейв: Ага. И я предполагаю, что простая стрелка с пустым треугольником, как между Member и User, представляет наследование. Тео: Совершенно верно! СОВЕТ. Простые стрелки с пустыми треугольниками представляют наследование классов (рис. 1.5), где стрелка указывает на суперкласс. Рис. 1.5. Отношения наследования: класс является производным от другого класса
Глава 1. Сложность объектно-ориентированного программирования 33 1.1.3. Объяснение каждой части диаграммы классов Дейв: Спасибо за то, что освежил мои знания об UML! Теперь я думаю, что могу вспомнить, что означают разные стрелки. Тео: С удовольствием. Хочешь посмотреть, как все это сочетается? Дейв: На какой класс нам следует обратить внимание в первую очередь? Тео: Я думаю, нам следует начать с Library. Класс Library Library — это корневой класс библиотечной системы. На рис. 1.6 показана структура системы. Рис. 1.6. Класс Library С точки зрения кода (поведения) объект Library ничего не делает сам по себе. Он делегирует все объектам, которыми обладает. С точки зрения данных объект Library владеет: несколькими объектами Member; несколькими объектами Librarian; одним объектом Catalog. ПРИМЕЧАНИЕ. В этой книге мы используем термины код и поведение как взаимозаменяемые. Классы Librarian, Member и User Оба класса Librarian и Member происходят от User. На рис. 1.7 показано это соотношение.
34 Часть 1. Гибкость Рис. 1.7. Librarian и Member происходят от User Класс User представляет собой пользователя библиотеки. Что касается данных читателя, то они представляют собой абсолютный минимум — у читателя есть идентификатор (id), адрес электронной почты (email) и пароль (password) (на данный момент без защиты и шифрования). С точки зрения кода читатель может войти в систему, используя логин. Класс Member представляет собой читателя. Он наследуется от User. Что касается элементов данных, то в нем нет ничего, кроме User. С точки зрения кода он может: • проверить книгу через checkout; • вернуть книгу через returnBook; • заблокировать себя с помощью block; • разблокировать себя с помощью unblock; • ответить, если он заблокирован через isBlocked; Он владеет несколькими объектами BookLending. Он использует BookItem для реализации checkout. Класс Librarian представляет собой библиотекаря. Он происходит от User. С точки зрения элементов данных в нем нет ничего кроме User. С точки зрения кода он может: • блокировать и разблокировать Member; • перечислить книги, выданные читателю, через getBookLendings; • добавлять книги в библиотеку через addBookItem. Он использует Member для реализации blockMember, unblockMember и getBookLendings.
Основы валидации данных Торжественный подарок В ЭТОЙ ГЛАВЕ РАССМАТРИВАЮТСЯ Важность валидации данных на границах системы. Валидация данных с использованием языка JSON-схемы. Интеграция валидации данных в существующую кодовую базу. Получение подробной информации о сбоях валидации данных. На первый взгляд может показаться, что применение ДОП означает доступ к данным без их валидации, а также погружение в сказочную реальность, где данные всегда валидны. На самом деле валидация данных не только возможна, но и рекомендована, когда мы следуем принципам дата-ориентированного программирования. В этой главе показано, как валидировать данные, когда они представлены с помощью общих структур данных. Основное внимание в ней уделяется валидации данных, происходящей на границах системы, в то время как в третьей части книги мы будем заниматься валидацией данных по мере их прохождения через систему. Эта глава представляет собой глубокое погружение в четвертый принцип ДОП. ПРИНЦИП № 4. Отделяйте схему данных от представления данных. 7.1. Валидация данных в ДОП Тео перенес свои встречи. Сроки намечены грозные, поэтому он все еще не уверен, верно ли было дать ДОП второй шанс. ПРИМЕЧАНИЕ. Причина, по которой Тео перенес свои встречи, объясняется в начале второй части. Найдите минутку, чтобы прочитать вступительную часть, если вы ее пропустили.
180 Часть 2. Масштабируемость Джо: Как вы думаете, какого аспекта ООП вам будет не хватать больше всего в вашем большом проекте? Тео: Валидации данных. Джо: Расскажите-ка поподробнее. Тео: В ООП есть надежная гарантия того, что при создании экземпляра класса его поля-элементы будут иметь правильные имена и правильные типы. Но с ДОП слишком легко допустить ошибки в именах и типах полей. Джо: Что ж, у меня для вас прекрасные новости! Существует способ валидации данных и в ДОП. Тео: А как это будет работать? Я думал, что ДОП и проверка данных — это две взаимоисключающие вещи! Джо: Вовсе нет. ДОП и в самом деле не вынуждает вас валидировать данные, но это не мешает вам делать это самостоятельно. В ДОП схема данных отделена от представления данных. Тео: Я не понимаю, устранит ли это проблемы с согласованностью данных. Джо: Согласно принципам ДОП наиболее важные для валидации данные — это данные, которые пересекают границы системы. Тео: Какие границы вы имеете в виду? Джо: В случае веб-сервера это будут области, в которых веб-сервер взаимодействует со своими клиентами и со своими источниками данных. Тео: А можно посмотреть на диаграмму? Джо подходит к доске и берет ручку. Затем он рисует диаграмму, подобную приведенной на рис. 7.1. Рис. 7.1. Высокоуровневая архитектура современного веб-сервера Джо: Эта архитектурная диаграмма определяет то, что мы называем границами системы с точки зрения обмена данными. Сможете ли вы назвать три границы системы? ПРИМЕЧАНИЕ. Границы системы определяются как области, в которых система обменивается данными.
Глава 7. Основы валидации данных 181 Тео: Сейчас подумаю. Первая — это граница с клиентом, затем идет граница с базой данных и, наконец, граница с веб-сервисом. Джо: Именно! Важно определить границы системы, потому что в ДОП мы разграничиваем два вида валидации данных: валидация, которая происходит на границах системы, и валидация, которая происходит внутри системы. Сегодня мы сосредоточимся на валидации, которая происходит на границах системы. Тео: Означает ли это, что валидация данных на границах системы более важна? Джо: Конечно! Как только вы убедитесь, что данные, поступающие в систему и из нее, являются валидными, вероятность появления неподходящих данных внутри системы довольно мала. СОВЕТ. После валидации данных на границах системы повторная валидация данных внутри системы не является обязательной. Тео: Тогда зачем нам нужна проверка данных внутри системы? Джо: Это связано с упрощением кодирования системы по мере роста кодовой базы. Тео: И какова же основная цель валидации данных на границах? Джо: Чтобы предотвратить ввод невалидных данных и их вывод из системы, а также для отображения полезной информации об ошибках при столкновении с невалидными данными. Я нарисую на доске таблицу, и вы сможете увидеть отличия (табл. 7.1). Таблица 7.1. Два вида валидации данных Вид валидации данных Цель Среда На границах Защита Использование Внутри Облегчение разработки Разработка Тео: А когда вы расскажете мне о валидации данных внутри системы? Джо: Позже, когда кодовая база станет больше. 7.2. Суть JSON-схемы Тео: На данный момент Система управления библиотекой — это приложение, которое выполняется в памяти, без базы данных и подключенных к нему HTTPклиентов. Но Нэнси, вероятно, захочет, чтобы я превратил систему в настоящий веб-сервер с клиентами, базой данных и внешними сервисами. Джо: Хорошо. Давайте представим, как будет выглядеть запрос клиента на поиск книг. Тео: По сути, поисковый запрос состоит из строки и полей, которые вы хотели бы получить для книг, название которых содержит эту строку. Таким образом, запрос состоит из двух полей: title, который является строкой, и fields, который является массивом строк.
182 Часть 2. Масштабируемость Тео быстро записывает что-то на доске. Закончив, он отходит в сторону, чтобы Джо смог взглянуть на получившийся код для поискового запроса. Листинг 7.1. Пример поискового запроса { "title": "habit", "fields": ["title", "weight", "number_of_pages"] } Джо: Понятно. Позвольте, я покажу вам, как выразить схему поискового запроса отдельно от представления данных поискового запроса. Тео: Что именно вы подразумеваете под «отдельно»? Джо: Представление данных выступает само по себе, а схема данных — сама по себе. Вы можете проводить проверку соответствия фрагмента данных схеме данных, как захотите и когда захотите. СОВЕТ. В ДОП схема данных отделена от представления данных. Тео: Как по мне, так это довольно абстрактно. Джо: Это точно. Но через мгновение все прояснится. А пока я покажу вам, как создать схему данных для поискового запроса на языке схем, называемом JSONсхемой. Тео: Я обожаю JSON! ПРИМЕЧАНИЕ. Информацию о языке JSON-схема можно найти по адресу https://jsonschema.org. Для схем в этой книге используется JSON-схема версии 2020-12. Джо: Во-первых, мы должны указать тип данных запроса. Каким будет тип данных в случае запроса на поиск книг? Тео: Карта. Джо: В схеме JSON тип данных для карт называется object. Я покажу вам базовую канву карты. Это карта с двумя полями: type и properties. Джо подходит к доске. Он набрасывает код для карты с двумя полями. Листинг 7.2. Схема канвы карты { "type": "object", "properties": {...} } Джо: Значение поля type — «object», а значение поля properties — карта со схемой для полей карты.
Расширенная обработка данных Всё, что хорошо задумано, ясно сказано В ЭТОЙ ГЛАВЕ РАССМАТРИВАЮТСЯ Манипулирование вложенными данными. Написание четкого и лаконичного кода для бизнес-логики. Разделение бизнес-логики и общих манипуляций с данными. Создание пользовательских инструментов для манипулирования данными. Использование лучшего инструмента для работы. Когда наша бизнес-логика включает расширенную обработку данных, общих функций манипулирования данными, предоставляемых языком run time и сторонними библиотеками, может оказаться недостаточно. Вместо того чтобы смешивать детали манипулирования данными с бизнес-логикой, мы можем написать наши собственные общие функции манипулирования данными и реализовать нашу пользовательскую бизнес-логику, используя их. Отделение бизнес-логики от внутренних деталей манипулирования данными делает код бизнес-логики кратким и легким для чтения другими разработчиками. 14.1. Обновление значения на карте с помощью выразительности Дейв становится все более и более автономным в проекте Klafim. Он может реализовать большинство функций самостоятельно, обращаясь к Тео только для проверки кода. Стандарты качества кода Дейва довольно высоки. Даже когда его код функционально надежен, он, как правило, недоволен его удобочитаемостью. Сегодня он просит Тео помочь улучшить читаемость кода, который исправляет ошибку, привнесенную Тео давным-давно.
354 Часть 3. Удобство сопровождения Дейв: Я думаю, что нашел ошибку в коде, который возвращает информацию о книге из Open Library API. Тео: Какую ошибку? Дейв: Иногда API возвращает повторяющиеся имена авторов, и мы передаем дубликаты клиенту. Тео: Похоже, эту ошибку несложно исправить. Дейв: Верно, я исправил это, но я не удовлетворен удобочитаемостью кода, который я написал. Тео: Критическое отношение к собственному коду — важное качество для прогресса разработчика. Что именно тебе не нравится? Дейв: Взгляни на код. Листинг 14.1. Удаление дубликатов простым, но утомительным способом function removeAuthorDuplicates(book) { var authors = _.get(book, "authors"); var uniqAuthors = _.uniq(authors); return _.set(book,"authors", uniqAuthors); } Дейв: Я использую _.get для получения массива с именами авторов, затем _.uniq для создания версии массива без дубликатов и, наконец, _.set для создания новой версии книги без повторяющихся имен авторов. Тео: Код утомительный, потому что следующее значение authorNames должно быть основано на его текущем значении. Дейв: Но это обычный вариант использования! Разве нет более простого способа написать такой код? Тео: Твое удивление определенно делает честь тебе как разработчику, Дейв. Я согласен, должен быть более простой способ. Позволь мне позвонить Джо и узнать, доступен ли он для звонка. Джо: Как дела, Тео? Тео: Отлично! Вы вернулись со своей технической конференции? Джо: Я только что приземлился. Сейчас я еду домой на такси. Тео: Как прошел ваш разговор о ДОП? Джо: Вполне нормально. Сначала люди были немного подозрительны, но, когда я рассказал им историю Albatross и Klafim, она оказалась вполне убедительной. Тео: Да, в этом взрослые похожи на детей; они любят истории. Джо: Что насчет вас? Удалось ли вам добиться полиморфизма с помощью мультиметодов?
Приложение А Принципы дата-ориентированного программирования Дата-ориентированное программирование (ДОП) — это парадигма программирования, направленная на упрощение проектирования и реализации программных систем, в которых в центре внимания находится информация, таких, например, как клиентские или серверные веб-приложения и веб-сервисы. Вместо того чтобы проектировать информационные системы на основе программных концепций, объединяющих код и данные (например, экземпляры объектов, созданные из классов), ДОП предписывает отделять код от данных. Кроме того, методология ДОП содержит рекомендации о том, как представлять данные и манипулировать ими. СОВЕТ. В ДОП к данным относятся как к привилегированным гражданам. Суть ДОП заключается в том, что к данным в этой парадигме относятся как к привилегированным гражданам. Это дает разработчикам возможность манипулировать данными внутри программы с той же простотой, с какой они манипулируют числами или строками. Обращаться с данными как с привилегированными гражданами становится возможным благодаря соблюдению четырех основных принципов. Код (поведение) отделен от данных. Данные представлены с помощью обобщенных структур. Данные рассматриваются как неизменяемые. Схема данных отделяется от представления данных. Когда эти четыре принципа работают сообща, они образуют единое целое, как показано на рис. А.1. Системы, построенные с использованием ДОП, проще и понятнее, поэтому жизнь разработчиков значительно улучшается. СОВЕТ. В системе, ориентированной на данные, код отделен от данных. Данные представлены с помощью обобщенных структур, которые являются неизменяемыми и имеют отдельную схему. Обратите внимание, что принципы ДОП не связаны с языком. Их можно придерживаться (или нарушать) на: языках объектно-ориентированного программирования (ООП), таких как Java, C#, C++ и других;
398 Приложение А Рис. А.1. Принципы дата-ориентированного программирования языках функционального программирования (ФП), таких как Clojure, OCaml, Haskell и других; языках, поддерживающих как ООП, так и ФП, таких как JavaScript, Python, Ruby, Scala и других. СОВЕТ. Принципы ДОП не связаны с языком. ПРИМЕЧАНИЕ. Разработчикам ООП для перехода на ДОП может потребоваться сильнее перепрограммировать свое сознание, чем разработчикам ФП, поскольку ДОП запрещает инкапсуляцию данных в классах с сохранением состояния. Данное приложение кратко иллюстрирует, как эти принципы могут быть применены или нарушены на языке JavaScript. Вкратце будут упомянуты преимущества соблюдения каждого принципа и издержки, связанные с получением этих преимуществ. В этом приложении принципы ДОП проиллюстрированы с помощью простых фрагментов кода. На протяжении всей книги подробно рассматривается применение принципов ДОП к готовым информационным системам. A.1. Принцип № 1: отделяйте код от данных Принцип № 1 — это принцип проектирования, который рекомендует четкое разделение между кодом (поведением) и данными. Может показаться, что эта идея лежит в основе ФП, но на самом деле такого принципа можно придерживаться (или нарушать его) либо в ФП, либо в ООП.
Принципы дата-ориентированного программирования 399 Соблюдать этот принцип в ООП означает агрегировать код в виде методов статического класса. Нарушать этот принцип в ФП означает скрывать состояние в лексической области действия функции. Кроме того, этот принцип не относится к способу представления данных. Представление данных регулируется принципом № 2. ПРИНЦИП № 1. Отделяйте код от данных таким образом, чтобы код заключался в функциях, поведение которых не зависит от данных, инкапсулированных в контексте функции. A.1.1. Иллюстрация к принципу № 1 Наше исследование принципа № 1 начинается с иллюстрации того, как он может быть применен к ООП и ФП. Следующие разделы иллюстрируют, как этот принцип может быть соблюден или нарушен в простой программе, которая работает с: объектом для имени автора с полями firstName и lastName, а также количеством книг books, которые это автор написал; фрагментом кода, вычисляющим полное имя автора; фрагментом кода, определяющим, является ли автор плодовитым, основываясь на количестве написанных этим автором книг. Нарушение принципа № 1 в ООП Нарушение принципа № 1 в ООП происходит, когда мы пишем код, объединяющий данные и код вместе в одном объекте. Следующий листинг демонстрирует, как это выглядит. Листинг A.1. Нарушение принципа № 1 в ООП class Author { constructor(firstName, lastName, books) { this.firstName = firstName; this.lastName = lastName; this.books = books; } fullName() { return this.firstName + " " + this.lastName; } isProlific() { return this.books > 100; } } var obj = new Author("Isaac", "Asimov", 500); ❶ obj.fullName(); // → "Isaac Asimov" ❶ Да-да, Айзек Азимов написал около 500 книг!
400 Приложение А Нарушение принципа № 1 в ФП Нарушить этот принцип без классов в ФП означает скрыть данные в лексической области действия функции. В следующем листинге приведен пример такого нарушения. Листинг A.2. Нарушение принципа № 1 в ФП function createAuthorObject(firstName, lastName, books) { return { fullName: function() { return firstName + " " + lastName; }, isProlific: function () { return books > 100; } }; } var obj = createAuthorObject("Isaac", "Asimov", 500); obj.fullName(); // → "Isaac Asimov" Соблюдение принципа № 1 в ООП В листинге A.3 показан пример, в котором код придерживается принципа № 1 ООП. Исполнение этого принципа может быть достигнуто даже с классами путем написания программ таким образом, чтобы: код состоял из статических методов; данные инкапсулировались в классах данных (классах, которые являются только лишь контейнерами для данных). Листинг A.3. Следование принципу № 1 в ООП class AuthorData { constructor(firstName, lastName, books) { this.firstName = firstName; this.lastName = lastName; this.books = books; } } class NameCalculation { static fullName(data) { return data.firstName + " " + data.lastName; } }
Принципы дата-ориентированного программирования 401 class AuthorRating { static isProlific (data) { return data.books > 100; } } var data = new AuthorData("Isaac", "Asimov", 500); NameCalculation.fullName(data); // → "Isaac Asimov" Соблюдение принципа № 1 в ФП В листинге A.4 показан пример, в котором код придерживается принципа № 1 в ФП. Исполнение этого принципа означает необходимость отделения кода от данных. Листинг A.4. Следование принципу № 1 в ФП function createAuthorData(firstName, lastName, books) { return { firstName: firstName, lastName: lastName, books: books }; } function fullName(data) { return data.firstName + " " + data.lastName; } function isProlific (data) { return data.books > 100; } var data = createAuthorData("Isaac", "Asimov", 500); fullName(data); // → "Isaac Asimov" A.1.2. Преимущества принципа № 1 Мы проиллюстрировали, как можно следовать принципу № 1 или нарушать его в ООП и ФП. Теперь обсудим преимущества, которые принцип № 1 привносит в наши программы. Вот как тщательное отделение кода от данных идет на пользу программам: код может быть повторно использован в различных контекстах; код может быть протестирован изолированно; системы, как правило, получаются менее сложными.
402 Приложение А Преимущество № 1: код может быть повторно использован в различных контекстах Представьте, что помимо объекта для сведений об авторе книги существует объект для читателя библиотеки, который не имеет никакого отношения к авторам, но имеет два таких же поля данных, что и объект для автора: firstName и lastName. Логика вычисления полного имени одинакова для авторов и читателей: получить значения двух полей с одинаковыми названиями. Однако в традиционном ООП, как и в версии с createAuthorObject в листинге A.5, код для вычисления fullName не может быть использован для имени читателя библиотеки непосредственно, поскольку он заблокирован внутри класса Author. Листинг A.5. Код для fullName заблокирован в классе Author class Author { constructor(firstName, lastName, books) { this.firstName = firstName; this.lastName = lastName; this.books = books; } fullName() { return this.firstName + " " + this.lastName; } isProlific() { return this.books > 100; } } Один из способов получить возможность повторно использовать код при смешивании кода и данных — это применить механизмы ООП, такие как наследование или композиция, чтобы разрешить классам User и Author использовать один и тот же метод для fullName. Эти методы подходят для простых случаев, но в реальных системах обилие классов (либо базовых, либо композитных) приводит к увеличению сложности. В листинге A.6 показан простой способ избежать наследования. В этом листинге мы дублируем код fullName внутри функции createUserObject. Листинг A.6. Дублирование кода в ООП во избежание наследования function createAuthorObject(firstName, lastName, books) { var data = {firstName: firstName, lastName: lastName, books: books}; return { fullName: function fullName() { return data.firstName + " " + data.lastName; } }; }
Приложение D Ссылки на Lodash На протяжении всей книги мы использовали Lodash (https://lodash.com/), чтобы проиллюстрировать, как манипулировать данными с помощью универсальных функций. Но в Lodash нет ничего уникального. Точно такой же подход можно реализовать с помощью других библиотек обработки данных или пользовательского кода. Более того, мы использовали Lodash FP (https://github.com/lodash/lodash/wiki/ FPGuide) для манипулирования данными без их изменения. По умолчанию порядок аргументов в неизменяемых функциях перемешивается. Код в листинге D.1 необходим при настройке Lodash, чтобы гарантировать, что сигнатура неизменяемых функций точно такая же, как и у изменяемых функций. Листинг D.1. Настройка неизменяемых функций _ = fp.convert({ "cap": false, "curry": false, "fixed": false, "immutable": true, "rearg": false }); В этом коротком приложении перечислены 28 функций Lodash, используемых в книге, чтобы помочь вам, если вы смотрите на фрагмент кода в книге, который использует функцию Lodash, которую вы хотите понять. Функции разделены на три категории: функции для карт в табл. D.1; функции для массивов в табл. D.2; функция для коллекций (как массивов, так и карт) в табл. D.3. Каждая таблица состоит из трех столбцов: функция показывает функцию с ее сигнатурой; описание содержит краткое описание функции; глава — это номер главы, в которой функция появляется впервые.
462 Приложение D Таблица D.1. Функции Lodash для карт Функция Описание Глава at(map, [paths]) Создает массив значений, соответствующих paths карты 10 get(map, path) Получает значение в path карты 3 has(map, path) Проверяет, есть ли на карте поле в path 3 merge(mapA, mapB) Создает карту, полученную в результате рекурсивного слияния между mapA и mapB 3 omit(map, [paths]) Создает карту, состоящую из полей карты, которых нет в paths 10 set(map, path, value) Создает карту с теми же полями, что и map, но с добавлением поля <path, value> 4 values(map) Создает массив значений map 3 Таблица D.2. Функции Lodash для массивов Функция Описание Глава concat(arrA, arrB) Создает новый массив, объединяющий arrA и arrB 5 flatten(arr) Выравнивает arr на один уровень в глубину 14 intersection(arrA, arrB) Создает массив уникальных значений как в arrA, так и в arrB 5 nth(arr, n) Получает элемент с индексом n в arr 10 sum(arr) Вычисляет сумму значений в arr 14 union(arrA, arrB) Создает массив уникальных значений из arrA и arrB 5 uniq(arr) Создает массив уникальных значений из arr 14 Таблица D.3. Функции Lodash для коллекций (как массивов, так и карт) Функция Описание Глава every(coll, pred) Проверяет, возвращает ли pred значение true для всех элементов coll 14 filter(coll, pred) Итерирует элементы coll, возвращая массив всех элементов, для которых pred возвращает true 3 find(coll, pred) Итерирует элементы coll, возвращая первый элемент, для которого pred возвращает true 15 forEach(coll, f) Перебирает элементы массива и вызывает f для каждого элемента 14 groupBy(coll, f) Создает карту, состоящую из ключей, сгенерированных по результатам выполнения каждого элемента coll через f. Соответствующее значение для каждого ключа представляет собой массив элементов, отвечающих за генерацию ключа 10
Ссылки на Lodash 463 Таблица D.3 (окончание) Функция Описание Глава isEmpty(coll) Проверяет, пуст ли coll 5 keyBy(coll, f) Создает карту, состоящую из ключей, сгенерированных по результатам выполнения каждого элемента coll через f. Соответствующее значение для каждого ключа — это последний элемент, отвечающий за генерацию ключа 11 map(coll, f) Создает массив значений, запуская каждый элемент в coll через f 3 reduce(coll, f, initVal) Сокращает coll до значения, которое является накопленным результатом выполнения каждого элемента в coll через f, где каждому последующему вызову предоставляется возвращаемое значение предыдущего 5 size(coll) Получает размер coll 13 sortBy(coll, f) Создает массив элементов, отсортированных в порядке возрастания по результатам запуска каждого coll в столбце через f 14 isEqual(collA, collB) Выполняет глубокое сравнение между collA и collB 6 isArray(coll) Проверяет, является ли coll массивом 5 isObject(coll) Проверяет, является ли coll коллекцией 5