Явный дизайн в разработке приложений. Часть 0

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

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

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

Сразу предупрежу:

Это не рекомендация, как писать или не писать код

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

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

Ограничения и условности

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

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

Наоборот, мы постараемся замешать как можно больше идей, чтобы протестировать их. Скорее всего, в результате мы напишем код, который будет не похож на «ортодоксальный» код React-приложения, но в нём мы попробуем найти связи и параллели с современными паттернами разработки.

О чём будем говорить

В этой серии (пока что) будет 11 постов, за время которых мы напишем приложение-конвертер вымышленных валют. Каждый пост мы посвятим одной конкретной теме, а план и содержание будет таким:

Кроме этого у меня есть идея написать несколько смежных постов на близкие темы:

  • Применимость всего описанного во фреймворках (например, Next)
  • Улучшение типобезопаности с помощью брендирования (например, для DDD)
  • Код-сплиттинг, маршрутизация и перформанс (например, с использованием Suspense и use)
  • Функциональная обработка ошибок (например, с Result<TOk, TErr>)
  • Применимость с другими библиотеками (например, Solid или Svelte)

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

Приложение-пример

В качестве примера мы напишем конвертер валют из вселенной «Звёздных войн»:

Скриншот главного экрана готового приложения
Скриншот главного экрана готового приложения

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

Но приложение же маленькое!

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

  1. Чем приложение меньше, тем проще сфокусироваться на технических аспектах и удобстве разработки.
  2. Я ленивый и писать большое приложение в стол мне не хотелось, а идей для полезного приложения с открытым кодом у меня не нашлось.

Возможно, в большом приложении пользу было бы видно лучше, но я посчитал, что для эксперимента будет достаточно простенького конвертера. Если у вас есть идеи, как ситуацию улучшить, напишите мне на почту или в ишью на Гитхабе — обсудим 👋

Исходники на Гитхабе

Мы будем развивать приложение поэтапно. Результаты проделанной на каждом этапе работы вы найдёте на Гитхабе. Каждый пост будет отсылать к отдельной папке в репозитории проекта, где вы сможете посмотреть, как всё устроено, и поиграться с кодом.

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

Функциональность приложения

В постах мы будем подразумевать, что у нас есть связь с продукт-оунером, и мы знаем, какую функциональность хотим видеть по итогу разработки. Будем считать, что среди пользовательских сценариев в планах такие:

  • Ввод значения RPC, который автоматически пересчитывает значение в квот-валюте.
  • Выбор альтернативной квот-валюты с автоматическим пересчётом её значения относительно RPC.
  • Обновление котировок по данным с сервера и сохранение этих данных локально на устройстве.
  • Простенькая аналитика действий пользователя, но, вероятно, не сразу.

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

Проблемы, которых мы хотим избежать

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

Растворение фич в технических деталях

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

В идеале проект должен помогать:

  • Упрощать онбординг, направляя новых разработчиков и давая им погружаться в код постепенно.
  • Ускорять поиск кода, отвечающего за конкретную часть приложения.
  • Добавлять и удалять функциональность без необходимости менять всё приложение.
  • Держать в голове ровно столько информации, сколько нужно для решения конкретной задачи в определённый момент.
  • Говорить с продукт-оунерами на одном языке и меньше терять при «переводе» с языка бизнеса на язык разработки.

Высокое зацепление модулей и “Ripple Effect” изменений

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

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

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

Неявное направление зависимостей

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

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

Протекающие абстракции и смешение ответственностей

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

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

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

Необходимость принимать решения «прямо сейчас»

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

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

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

Хрупкие тесты

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

Кода, который неясно, как тестировать, быть не должно. Каждая часть проекта должна решать чётко определённую задачу, а результат её должно быть просто и очевидно как протестировать.

Смешение бизнес-логики и инфраструктурного кода

Инфраструктурный код («клей», который держит всё вместе) не должен переплетаться с бизнес-логикой (которая содержит идеи, приносящие деньги).

Смешение бизнес-логики и побочного кода

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

Принципы разработки

Все перечисленные проблемы мы можем переформулировать в виде принципов, которых будем придерживаться при проектировании и разработке:

  • Главной частью приложения должна быть бизнес-логика.
  • Структура проекта должна отражать суть приложения.
  • Влияние инфраструктуры, UI и смежных задач должно быть минимально.
  • Код должно быть легко (и понятно как) тестировать.
  • Модули должны быть максимально независимы.
  • Код не должен принуждать к серьёзным решениям на ранних этапах.

Каждый из этих принципов мы развернём в последующих постах и испробуем на практике.

В следующий раз

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

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

Ссылки и дополнительные материалы

Другие части серии