От switch к стратегии (Refactor to Strategy)
Расчёт доставки с
switchпо способу иifпо стране за три шага превращается в тарифы-стратегии и правила из конфига, а тесты всё это время не меняются.
Исходная задача
Магазин считает стоимость доставки на кассе. Покупатель выбирает способ: пункт выдачи, курьер или почта. Цена зависит от способа, страны, веса и суммы заказа: пункт выдачи только по России и бесплатно от 3000, курьер по России и Беларуси с ценой за килограмм, почта везде, но за границу дороже. Недоступный способ - ошибка, корзина показывает его как "not available".
Сначала это одна функция на сорок строк, и для трёх способов она нормальная. Потом маркетинг просит экспресс по России.
Что болит
- Новый способ доставки - правка функции, которую трогают все. Старые способы приходится перепроверять, потому что правка шла в том же месте.
- Цена, доступность по странам и выбор способа перемешаны: курьера нельзя проверить отдельно от почты.
- Тарифы зашиты в ветки. Поменять цену - релиз, хотя логика не менялась.
- Ответ на вопрос "что доступно в Беларуси" надо собирать по всей функции.
План
Каждый шаг - отдельная рабочая копия кода на PHP, Go, Python и TypeScript со своими тестами и примером сборки. Таблицы тестов одинаковые во всех шагах: шесть посылок с ценами и три отказа. Меняется только то, как тест создаёт калькулятор. Зелёные тесты на каждом шаге - доказательство, что рефакторинг не изменил поведение.
- Шаг 1: одна функция на все способы доставки - исходный код и тесты, которые фиксируют поведение до того, как его трогать.
- Шаг 2: способы доставки за общим интерфейсом - каждая ветка становится классом за контрактом
ShippingMethod, выбор пока остаётсяswitch. - Шаг 3: реестр тарифов и правила из конфига - стратегии отвечают только за цену (
FlatRate,PerKilogram,FreeOver), доступность и выбор тарифа уходят в список правил, экспресс добавляется строкой конфига.
Итог
За два хода код пришёл к стратегии, и каждый ход менял одну вещь: сначала устройство расчёта, потом устройство выбора. Поэтому каждый промежуточный шаг рабочий и его можно выкатить, а не держать ветку на неделю.
Главный результат не в классах, а в разделении вопросов: "сколько стоит" отвечает тариф, "что доступно" - правила, "как это связано" - модуль сборки. Каждый меняется отдельно и с разной частотой.
Остановиться можно на любом шаге. Если способов три и новые не планируются, честнее оставить шаг 1 или 2: конфиг и реестр окупаются, только когда правки идут постоянно.
Связанные темы
- Стратегия - сам паттерн на простом примере со скидками
- Практика стратегии: расчёт доставки - похожая задача, где способ выбирается правилами автоматически