Суть Читаем реальный код вычисления флага и правило targeting, предсказываем поведение или баг и выбираем самую рычажную правку, которую senior делает первой.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Проверка флага, хеш бакетинга и правило targeting — места, где системы флагов тихо ломаются. Прочитайте сниппет, предскажите, что сделает с ним прод, и выберите правку, которую senior сделает первой.
Цель
Отработайте цикл, который вы прогоняете на каждом ревью флагов: прочитать путь вычисления, заметить баг консистентности или fail-open и потянуться к структурной правке — sticky-bucketing, безопасный дефолт, тикет на удаление — прежде чем добавлять новые флаги.
Сниппет 1 — проверка раскатки
function inRollout(flag, user) { // раскатка 25%: разыгрываем свежее случайное число на каждый вызов return Math.random() * 100 < flag.rolloutPercent;}if (inRollout(newCheckout, user)) { return renderNewCheckout(user);}return renderOldCheckout(user);
Викторина
Completed
Раскатка на 25% использует эту проверку. Что ломается в проде и в чём правка?
Heads-up Ошибка на единицу на границе сдвигает одну корзину, но не вызывает симптом. Дефект в том, что назначение случайно на каждый вызов, а не детерминированная функция пользователя, поэтому оно не sticky.
Heads-up Random дёшев; дело не в скорости. Дело в корректности: недетерминированный розыгрыш на каждый вызов означает отсутствие per-user stickiness. Правка — стабильный хеш, а не кэширование розыгрыша.
Heads-up Сетевой round-trip на каждое вычисление добавил бы задержку и жёсткую зависимость. Stickiness берётся из локального детерминированного хеширования стабильного ключа пользователя — без сетевого round-trip на проверку.
Сниппет 2 — путь kill switch
async function chargeCard(order) { const enabled = await flags.evaluate("payments-enabled", order.user); if (!enabled) { throw new Error("payments disabled by kill-switch"); } return gateway.charge(order);}// flags.evaluate бросает исключение, если сервис флагов недоступен
Викторина
Completed
Здесь kill switch обёрнут вокруг платежей. В чём продовый риск и какова senior-правка?
Heads-up Полярность верна — disabled означает не списывать. Риск в том, что сетевой сбой в evaluate бросает исключение и блокирует путь, превращая сервис флагов в единую точку отказа.
Heads-up Намеренный путь отключения вполне законно отклоняет списание. Настоящий дефект — сетевая зависимость на каждый вызов, которая роняет путь, когда сервис флагов лежит.
Heads-up Вторая проверка добавляет ещё одну сетевую зависимость, не починив первую. Структурная правка — локальное вычисление с безопасным дефолтом, чтобы сервис флагов никогда не был на критическом пути.
Сниппет 3 — правило targeting
flag: new-dashboardrules: - if: user.plan == "enterprise" serve: on - if: user.betaOptIn == true serve: on - rollout: percent: 10 stickyBy: user.iddefault: off
Викторина
Completed
Enterprise-пользователь, который вдобавок подписался на бету, открывает дашборд. Какое правило решает его вариант и на какое общее свойство опирается этот конфиг?
Heads-up Targeting не объединяет все правила по И. Правила вычисляются по порядку, и побеждает первое совпадение; enterprise-пользователь получает on независимо от последующих правил беты и rollout.
Heads-up Неявного приоритета у rollout-правил нет. Приоритет чисто позиционный — enterprise-правило идёт первым и замыкает вычисление до достижения rollout.
Heads-up Несколько совпадений не неоднозначны; побеждает первое совпадение, а дефолт применяется, только когда не совпало ни одно правило. Этот пользователь сначала совпадает с enterprise-правилом.
Сниппет 4 — застойный флаг в коде
// добавлено 2023-02 для релиза checkout-v2, доведено до 100% в 2023-03if (flags.isEnabled("checkout-v2")) { return renderCheckoutV2(cart);} else { return renderCheckoutV1(cart); // V1 больше не поддерживается}
Викторина
Completed
Прошёл год; checkout-v2 отдаёт on всем с 2023-03, а V1 не поддерживается. Какое действие правильно и почему?
Heads-up Это не безвредно. Ветка всё ещё вычисляется, засоряет код, а неподдерживаемый путь V1 может быть переиспользован или случайно включён — ровно так застойный флаг разбудил мёртвый код в Knight Capital.
Heads-up Включение годовалого неподдерживаемого пути воскрешает мёртвый код против устаревших допущений. Release-флаг не следует молча перепрофилировать в ops; это требует осознанного решения, владельца и поддерживаемого кода с обеих сторон.
Heads-up Отправлять трафик на неподдерживаемый путь, чтобы держать его тёплым, институционализирует долг. Правильный ход — удалить V1 и флаг, а не сохранять путь, который никто не поддерживает.
Итог
Каждое ревью флагов читается одинаково: проверка раскатки обязана хешировать стабильный ключ в фиксированную корзину, иначе она мигает; kill switch обязан вычисляться локально с безопасным дефолтом, иначе сервис флагов становится жёсткой зависимостью, роняющей ваш горячий путь; правила targeting чувствительны к порядку и первое совпадение замыкает вычисление, так что порядок правил — это приоритет; а release-флаг на 100% с неподдерживаемой второй веткой — это flag debt на удаление, а не конфиг на сохранение. Прочитать путь вычисления, починить структуру, затем пере-ревьюить.