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

Можем ли мы сделать это быстро?

Это распространенная просьба, которая может вызвать у разработчика чувство страха и поражения, часто потому, что слово «быстрый» ассоциируется с результирующим кодом, который в конечном итоге оказывается «грязным», «хакерским» или «рискованным». Делать что-то быстро может означать компромисс с хорошо продуманными принципами, отточенными годами опыта. Это может означать возвращение к коду через 6 месяцев и проклинание себя прежнего, когда вы изо всех сил пытаетесь понять закодированное представление ранее ясного и очевидного требования. Зная, что эти изменения зафиксированы в истории через исходный код; архив прискорбных решений, свободно доступный для тех самых людей, которых вы не хотели бы видеть — ваших сверстников.

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

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

Быстрая обратная связь

Если вы открываете кофейню, вы хотите, чтобы кофе нравился покупателям. И как владелец, вы, скорее всего, заручитесь помощью друзей, чтобы найти отличный вкус. Это не точная наука — вы не станете продавать кофе, основываясь только на теории, где рассчитаны правильные температуры и давления и предоставлена ​​рекомендуемая смесь. И это потому, что испытание вашего кофе на других — настоящее доказательство пудинга. Если вы получите положительные отзывы об этих испытаниях, это придаст вам больше уверенности, чем что-либо еще, чтобы начать продавать населению.

Тот же ход мыслей можно применить, когда дело доходит до создания программного обеспечения. Пробная версия программного обеспечения позволяет предприятиям получать ощутимую обратную связь от клиентов и, в свою очередь, дает уверенность в том, что ваше программное обеспечение предоставляет услуги, которые клиенты хотят или в которых нуждаются. Процессы CI/CD действительно воплощают в жизнь мечту создать-оценить-обучиться, где конечной целью являются более быстрые итерации этого цикла. Обеспечение эффективности части цикла создание-измерение-обучение на самом деле является обязанностью разработчиков программного обеспечения, так что просьба о том, чтобы что-то было сделано быстро, действительно такая неразумная просьба?

Переключатели функций могут стать действительно мощным инструментом для сокращения времени сборки в этом цикле. Они позволяют экспериментальному коду оставаться бездействующим, пока не будет включен переключатель. Как следствие:

  • При развертывании этого кода в реальной среде риск минимален, если функция выключена. Это означает, что код функции может быть включен в релизную ветку как можно скорее, даже до того, как он будет готов к испытанию, что сокращает время цикла разработки.
  • Если фича не работает и дальше ее развивать не нужно, мы можем выключить переключатель и удалить ее вместе с кодом фичи.
  • Если эта функция сработает, мы можем сделать код первоклассной частью кодовой базы посредством рефакторинга и просто убрать переключатель.

Представляем замены сервисов декораторами

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

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

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

В качестве примера мы можем смоделировать кофейню с закодированным представлением ее работы.

Кофе можно купить только там, где он есть в наличии.

Совершение покупки невозможно, если требуемый вес кофе (исходя из крепости кофе) превышает вес кофе на складе для определенной смеси. Это бизнес-правило, которое защищается созданием исключения в домене, но также проверяется перед запросом на покупку.

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

Эта простая реализация SupplierService представляет проблему : доступно 0 кг кофе для всех смесей. Поэтому покупка кофе больше невозможна.

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

Решение использовать другого временного поставщика записывается как переключатель функции «UseTemporarySupplier», но мы еще не знаем, какие подробности…

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

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

Думая немного дальше, в воздухе все еще есть несколько вопросов. Когда первоначальный поставщик вернется к работе? Будут ли они продолжать использовать этого нового поставщика или вернутся к старому поставщику? Пока мы не ответим на эти вопросы, мы проверим нового поставщика:

Добавление декоратора в службу ISupplierService позволяет использовать как обычных, так и пробных поставщиков кофе на основе переключателя UseTemporarySupplier, который мы сделали ранее. Мы можем оставить наш первоначальный код поставщика неактивным, но все еще доступным. Это также не затрагивает любой вызывающий код, поскольку декоратор действует как ISupplierService, который разрешается через контейнер внедрения зависимостей.

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

После опробования, если новый поставщик не работает и кофейня продолжает использовать исходного поставщика, достаточно просто удалить эти фрагменты кода. Если это сработает и станет постоянной заменой, достаточно просто удалить декоратор и заменить SupplierService на UnprovenSupplierService.

Введение замены методов делегатами

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

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

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

Быстро и просто. Но когда дело доходит до выпуска изменения, внезапно становится очень сложно посмотреть в глаза вашей команде QA и сказать: «Это изменение кода не несет никакого риска, поэтому нет необходимости тестировать». Кроме того, что, если экспериментальная цена сработает, и мы захотим убрать переключение функций? Это еще один абсолютно безопасный релиз? Риск связан с тем, что первоначальная реализация метода калькуляции была изменена с учетом изменений в ценообразовании.

Чтобы оставить первоначальную реализацию неизменной, мы можем ввести уровень делегирования в домен:

Разберем эти изменения:

  • GetCost был продублирован и переименован в GetExperimentalCost перед реализацией изменений в новой модели ценообразования. Это представляет собой альтернативный метод расчета стоимости, который будет использоваться, когда включено переключение функций.
  • И GetCost, и GetExperimentalCost затем были установлены как частные. Это необходимо для внутренней инкапсуляции принятия решения о переключении функций, чтобы ссылка на клиентский код не имела к этому никакого отношения.
  • В качестве общедоступной точки входа для расчета стоимости кофе был введен новый метод GetCost. Это возвращает делегата к любому из методов Costing на основе переданного переключателя функций.

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

Код, использующий GetCost (строка 17 ниже), также выглядит довольно чистым. В случае, если сигнатуры метода GetCost для старой и новой реализации совпадают, это простой способ убрать функцию переключения из параметров метода, чтобы их не нужно было менять:

Вы можете возразить, что реализация вызывающего метода (MakePurchase) изменилась, и будете правы. Однако изменение ограничивается передачей логического параметра «useExperimentalPricing» через серию связанных вызовов методов. Любые сбои в этом могут быть обнаружены во время компиляции, и, что важно, это не приравнивается к какому-либо изменению логического поведения непосредственно внутри этого метода.

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

Вывод

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