awesome-everything EN
↑ Обратно к восхождению

Инженерная практика

Trunk-based: чтение кода и конфигов

Суть Читайте реальные диффы и CI-конфиг, предсказывайте поведение по trunk-based и выбирайте фикс с наибольшим рычагом — обёртка флагом, branch by abstraction и зелёный гейт.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min

Дисциплина trunk-based видна в диффе и CI-конфиге, а не в манифесте. Читайте каждый сниппет, предсказывайте, как он поведёт себя на общем trunk, и выбирайте фикс, который senior-инженер сделает первым.

Цель

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

Сниппет 1 — отправка незавершённой работы в trunk

// Новый checkout-флоу не готов, но должен влиться в trunk сегодня.
export async function handleCheckout(cart: Cart, user: User) {
  if (flags.isEnabled("checkout-v2", { userId: user.id })) {
    return newCheckout(cart, user);   // недостроен, ещё нет расчёта налога
  }
  return legacyCheckout(cart, user);  // текущий продакшен-путь
}
Викторина

Флаг 'checkout-v2' по умолчанию выключен в продакшене. Что верно, если этот код вливается в trunk ежедневно?

Сниппет 2 — шестинедельная замена ORM на trunk

interface UserStore {                 // абстракция
  find(id: string): Promise<User>;
  save(u: User): Promise<void>;
}

class LegacyOrmStore implements UserStore { /* текущий, в работе */ }
class NewOrmStore    implements UserStore { /* строится инкрементально, dark */ }

// связано один раз, переключается на границе, когда NewOrmStore готов
export const userStore: UserStore =
  flags.isEnabled("orm-v2") ? new NewOrmStore() : new LegacyOrmStore();
Викторина

Почему такая форма branch by abstraction — это trunk-based ответ на миграцию, которую нельзя выпустить за один день?

Сниппет 3 — конфиг CI-гейта

# .ci/trunk-gate.yml — запускается на каждый PR
on: pull_request
jobs:
  gate:
    steps:
      - run: make unit-tests          # ~2 мин, блокирует merge
      - run: make lint typecheck      # ~1 мин, блокирует merge
  e2e:
    steps:
      - run: make e2e-suite           # ~40 мин
        if: github.event_name == 'schedule'   # только ночью, никогда не блокирует
Викторина

Читая этот гейт, какая senior-оценка верна?

Сниппет 4 — оставленный устаревший флаг

// выпущен 14 месяцев назад, "search-v2" всё это время был на 100%
function rankResults(q: Query, results: Result[]) {
  if (flags.isEnabled("search-v2")) {
    return rankV2(q, results);        // живой путь
  }
  return rankV1(q, results);          // не запускается, не обновлялся с запуска
}
Викторина

search-v2 был на 100% уже 14 месяцев. Какое действие имеет наибольший рычаг и почему?

Итог

Дисциплина trunk-based читаема в артефактах. Условие с флагом на trunk позволяет незавершённой работе ехать dark и вливаться ежедневно без дрейфа; интерфейс-абстракция позволяет шестинедельной миграции ехать на trunk как маленькие зелёные коммиты с одним flip-and-delete; многоуровневый CI-гейт остаётся быстрым, блокируя на unit/lint/typecheck и гоняя медленный e2e вне критического пути; а release flag, всё ещё включённый на стабильном 100%, — это долгоживущая ветка, спрятанная в if, которую вы удаляете (флаг и мёртвый путь вместе) как закрывающий шаг раскатки.

Продолжить восхождение ↑Trunk-based: один полный цикл
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.