tgoop.com/orgprog/356
Create:
Last Update:
Last Update:
Программирование на флагах
Недавно я упомянул этот термин в одном и постов и получил неожиданно большое количество комментариев "что это?". Тема заслуживает раскрытия, поэтому пост.
Возьмем пример с sql:
SELECT *
FROM users
WHERE active = 1;
Почти наверняка это поле из двух состояний активен/не активен (1/0), где активность определяется подтверждением емейла. В Postgresql это было бы true/false.
В целом, этот код выглядит совершенно нормально и очень хорошо работает. До поры до времени. А потом выясняется, что «неактивный» бывает как «удалённый», так и «заблокированный». Или, например, мы (бизнес) захотим давать работать на сайте тем кто зарегистрировался, но не подтвердил емейл. Подтверждение емейла будет работать как способ получить больше функций. В сумме мы получаем:
⁃ зарегистрированный
⁃ активный (подтвержден емейл)
⁃ удаленный
⁃ забаненный
Поскольку в базе уже есть active, то разарботчик, вероятно, пойдет по наиболее легкому пути - начнет добавлять новые флаги. Это просто и не требует хитрых миграций, чтобы обеспечивать обратную совместимость (для zero downtime). В итоге в базе появятся флаги:
active, email_convirmed, banned, deletedи с этого момента начинается то самое программирование на флагах.
Вместо одной колонки состояния появляется несколько несвязанных булевых полей, которые можно комбинировать как угодно:
⁃ Комбинаторный взрыв состояний - пользователь может быть
active = true, но при этом banned = true и deleted = true. Что это значит? Он активен или нет?⁃ Неявные правила - чтобы понять, что такое «активный пользователь», нужно лезть в код и смотреть, как именно проверяются флаги (active && !deleted && !banned && email_confirmed)
⁃ Сложность изменений - добавление нового флага или изменение бизнес-логики требует правок во множестве мест, потому что условия размазаны по коду и SQL-запросам.
⁃ Повышенный риск багов - достаточно забыть один флаг в проверке и все, приплыли.
Самое главное, что состояния в рамках одного процесса будут требовать синхронного изменения. Если меняется один, то надо не забыть поменять другой. Например если мы человека вводим в бан, то надо не забыть снять активность.
Более универсальный и безопасный для расширения способ в таком случае, это не флаги, а одно свойство определяющее состояние нашей сущности.
Мы кстати с этого и начали, когда перечисляли возможные состояния пользователя.
type UserState = 'active' | 'banned' | 'deleted' | 'waiting_email_confirmation'
Фактически такой подход гораздо лучше ложится на то, как мы думаем об этом, чем флаги. Более того, это и намного нагляднее с точки зрения кода. Таким образом мы сразу решаем проблему синхронизации, переход из одного состояния в другое это одно действие и отсутствие багов в стиле "флаги не согласованы"
Есть еще один немаловажный плюс. Допустим у нас изначально два состояния и мы решили, что не будем бежать впереди паровоза. Мы делаем в базе boolean и попадаем в ситуацию, что когда добавится третье состояние, придется менять тип колонки на строку и все значения внутри него. Опытные разработчики сразу будут этому сопротивляться, потому что ломать обратную совместимость в базе нельзя, поэтому правильным способом, будет создать новую колонку и выполнить миграцию туда. Оно нам надо? То есть использования состояния это безопасно и расширяемо в отличие от флагов.
Но даже этого может быть недостаточно. Следующим шагом будет использование конечных автоматов https://github.com/eram/typescript-fsm (в том случае если нужна реакция на переходы, а сама структура переходов не линейная)
Ссылки: Телеграм | Youtube | VK
BY Организованное программирование | Кирилл Мокевнин

Share with your friend now:
tgoop.com/orgprog/356
