В докладе Границы Гери Бернхардт рассказывает про типы в качестве границ приложения. А в Type-Driven Design in Swift Alex Ozun показывает конкретную реализацию через паттерны.
Для реализации этих идей на TypeScript есть библиотека True Myth.
Из их документации:
Quote
True Myth предоставляет стандартные, типобезопасные оболочки и вспомогательные функции, которые помогут вам справиться с двумя чрезвычайно распространенными случаями программирования:
- отсутствие значения;
- наличие значения result, в котором имеется дело либо с успехом, либо с неудачей.
Разработчики назвали библиотеку в дань уважения идеи, которую высказал Толкин о мифах.
Я подумал, что хочу последовать их примеру и для демонстрации базовых сценариев, можно реализовать один из артефактов из мира Средиземья. Ведь как сказал Артур Кларк:
Quote
Любая достаточно развитая технология неотличима от магии
Авторизация в Мории
В Морию через врата Дурина могут попасть только друзья. Гномы первой эпохи не особо запаривались над безопасностью, поэтому для прохода нужно просто сказать на эльфийском слово “друг”.
Я спроектировал тип странника так, чтобы он был либо другом, либо чужаком:
где Stranger
это пустой класс, потому что к нему нет пока никаких требований, им может быть любой:
а тип Friend
нельзя создать без передачи в него корректного кода
Убирая ts-синтаксис и оставив только логику можно сказать:
Friend = CorrectCode
Что собой представляет класс верного кода? В докладе Озуна такой класс называется Parsed Wrapper. Его нельзя создать, если не передана корректная строка.
Для этого нужно:
- Сделать приватным конструктор
- Реализовать статический метод, который по нашим условиям либо возвращает результат c экземпляром друга:
Result.ok(CorrectCode)
, либо ошибку:Result.err(AppError)
Result
ok(new CorrectCode(code))
- вернёт контейнерResult
с валидным объектом кода;err(error)
- вернёт контейнерResult
с ошибкой;
Но в обоих случаях вернётся Result, который дальше можно обработать.
Убирая синтаксическую мишуру:
CorrectCode = 'mellon'
Получается, что без корректной строки не создать тип валидного кода, а без него не создать друга, без друга не попасть в Морию:
'mellon' -> CorrectCode -> Friend -> entrance
Обработка Result
и создание инстанса друга:
Границы выставлены достаточно чётко и можно удобно работать отдельно над каждой из них, уточняя детали и меняя правила.
Предположим, что у гномов в Мории появился продукт овнер и вот он, попивая смузи, говорит:
Quote
Ребят, всё конечно хорошо, но почему мэллон можно сказать только на латинице? Где вторая по популярности в интернете система письма?
Это легко поправить, заменив условие внутри класса CorrectCode
:
на другое:
unit-тест будет выглядеть так:
Снова залетает гном-продукт овнер:
Quote
Ребят, круто работаете, всё классно, на ревью поставлю вам 10 дуринов из 10. Но это вообще норм, что мы своих друзей по именам не знаем?
И мы понимаем, что формально Friend
уже не просто CorrectCode
, теперь он:
Friend = CorrectCode & Name
Но имя это не любая строка. К имени тоже есть требования. Описать их можно, создав уже знакомый ParsedWrapper:
Теперь нужно добавить его как правило для создания друга:
В хэндлер входа добавляются соответственно строки с именем:
Теперь мы можем гарантировать, что если мы где-то в приложении встретили инстанс друга, то у него точно есть код и имя, причём с валидными данными ещё и этих инстансов.
У Озуна в докладе есть про это отличная красно-зелёная картинка с фазами.
Это можно использовать, создавая приветствие. В презентаторе страницы приветствия следующий код:
Обратите внимание, что в презентатор можно передать только друга, то есть его не создать без правильного инстанса.
Внедрение контейнера Result
позволяет повысить надёжность приложения и снять лишнюю когнитивную нагрузку. Валидации в логике больше нет.
Maybe
На этот раз мы общаемся с гномьим дизайнером, который, под эльфийским модным влиянием, набросал леттеринг, что должен светится только при звёздном и лунном свете, но скрываться при солнечном. Wft?
Техлид начинает умничать, что луна отражает свет солнца, а солнце это тоже звезда и то, что нельзя так просто взять и… В конце концов действуют рестрикции на экспорт магии из кап. стран.
Но дизайнер кидает ссылку на лукошко-экспериментального-энтузиазма.io с прототипом, где с помощью спектрометра, и не очень глубокого анализа можно понять, что сейчас светит на стену.
И вот уже крутится сервис, который может прислать данные с аналитикой, что за небесное тело сейчас освещает стену. Нам нужно только, оперируя абстракциями, принимать решения.
Контракт такой:
То есть мы можем по типу понять, что сейчас светит за объект, если объект не классифицирован или свет недостаточен для анализа, придёт null
.
Для таких ситуаций, когда значение может быть, а может и нет, разработан контейнер Maybe
.
Он позволяет обернуть возможное значение. И если оно есть, то обработчики, будут запущены.
of(light)
- возвращает объект типаMaybe
;map(handleLight)
- безопасно запускает функциюhandleLight
;unwrapOr(false)
- извлекает значение из контейнера, если значение не валидное, то вернётfalse
.
Это очень похоже на работу функции map
в js. Только в массивах она не запускается, если массив пустой, а тут, если значение null
.
Круто то, что handleLight
вообще не знает о том, что light
может не прийти, его опциональность не прорастает в код:
Думаю, на этом надо остановиться. В голову лезут идеи про гномов безопасников с предложением сделать двухфакторную аутентификцию через рунные камни с устаревающими ключами…
Мысли и комментарии
-
Maybe
иResult
- это классические специальные монады из функционального программирования. Их можно встретить под похожими или такими же именами в фп книжках; -
map
- это функтор. Тоже классическая штука из мира фп. В true-myth предоставлено ещё множество других со своими задачами; -
в книге “Функциональное программирование на JavaScript” в главе 5 приведена своя реализация
Result
иMaybe
. С методами, которые пересекаются с теми, что есть в библиотеке. Они настолько простые, что, как говорят сами создатели: “их может реализовать любой. Но зачем это делать, когда есть готовое протестированное решение?”; -
@tolstoymv скинул библиотеку с функциональными конструкциями для ts, которую использует он. Прикрепляю в качестве альтернативы;
-
описанные контейнеры имеют гораздо больше возможностей, чем я показал. С полным списком методов рекомендую ознакомиться в документации;
-
честно говоря, пока опасаюсь добавлять в рабочий код все конструкции, но например отделение
AnonymousUser
отSignedInUser
уже реализовано и отлично работает по тому же принципу в Ozon Acquiring; -
проблема интеграции функциональных решений не в том, что это сложно. Как раз таки очень просто и выразительно. Порог вхождения повышается. Проект начинает требовать повышения культуры разработки;
-
но всё же, любой джун повсеместно использует конструкции типа:
response.then((data) => { ... })
list.map(item => { ... })
arr.reduce(reducer)
Вдохновляет
-
То, что “Властелин Колец”, “Хоббит”, “Сильмариллион” и др. книги Толкина это побочный продукт от главного - создания языков. Любой язык это отражение производственных отношений, он не отделим от материального базиса. Толкин это прекрасно понимал и написал историю мира, в котором развивались его языки;
-
Отношение Питера Джексона к коллективной работе и творческому процессу. Вместо того, чтобы снять своё видение Средиземья, он обратился к фанбазе толкинистов, у которой уже было огромное множество наработок по тому, как должно всё выглядеть, звучать и работать. Именно поэтому получилось создать такое цельное и глубокое кино. Кстати, в титрах указали вообще всех причастных. Они идут почти 30 минут, Карл;
-
Идея сделать для разрабов то, что делают Awdee для дизайнеров.
Материалы
- Документация True Myth
- Функциональное программирование на JavaScript
- Доклад Boundaries
- Доклад Type-Driven Design in Swift
- Shadcn UI
Ссылки
Тэги
typeDrivenDesign,functionalProgramming,result,maybe,монады,функторы,ddd