Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Senior: питання на співбесіді з теми «Pull request і code review»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

4 питання

Захист гілки перетворює домовленості («не пушимо в main», «зливаємо лише після рев'ю й зелених тестів») на правила, які платформа примусово виконує.

Типовий набір правил для main:

  • заборонити прямий push - зміни лише через PR;
  • обов'язкове схвалення (1-2 рецензенти) і рев'ю власників коду (CODEOWNERS);
  • скидати схвалення при нових комітах (dismiss stale approvals) - інакше після апруву можна дописати що завгодно;
  • обов'язкові перевірки статусу (required status checks): тести, Pint, PHPStan мають бути зеленими;
  • гілка має бути актуальною перед злиттям - або merge queue замість цього;
  • заборонити force push і видалення гілки;
  • розв'язані обговорення перед злиттям, за потреби - підписані коміти і лінійна історія.

Branch protection rules проти rulesets:

Branch protection Rulesets
скільки діє на гілку одне правило кілька наборів одночасно, правила агрегуються
конфлікт налаштувань - діє найсуворіший варіант
вимкнути тимчасово лише видалити статус Disabled без видалення
хто бачить адміністратори усі з доступом на читання
винятки обмежено явний список bypass (ролі, команди, GitHub Apps)
рівень організації ні так (на планах Team/Enterprise)

Обидва механізми працюють разом: застосовуються всі правила, що підходять.

Пастки:

  • назва обов'язкової перевірки має збігатися з назвою job у CI. Перейменували job - PR зависає в очікуванні перевірки, яка ніколи не прийде;
  • перевірка, що запускається не завжди (через paths у workflow), лишається «очікуваною» - PR не можна злити. Рішення - job, що завжди звітує успіхом, якщо змін немає;
  • адміністратори за замовчуванням можуть обходити правила - варто вирішити свідомо;
  • bypass для ботів (релізний бот) - лише конкретним GitHub Apps, не всім.

Правила як код: rulesets можна експортувати й імпортувати в JSON чи керувати ними через API й Terraform - однакові правила для десятків репозиторіїв.

Докладніше в документації: GitHub: про rulesets

Проблема «зелені окремо, червоні разом». PR A і PR B пройшли CI окремо, кожен відносно старого main. Їх злили один за одним - і main зламався: A перейменував метод, а B додав новий виклик старої назви. Конфлікту в Git немає, а код не працює.

Вимога «гілка має бути актуальною» це лікує, але в активному репозиторії перетворюється на гонку: кожне злиття робить усі інші PR застарілими, їх оновлюють, CI перезапускається, і хтось інший знову зливає першим.

Merge queue:

  1. PR схвалений і зелений - автор додає його в чергу замість натискання Merge;
  2. GitHub створює тимчасову гілку gh-readonly-queue/main/... з main + усіма PR, що стоять перед ним у черзі, + цим PR;
  3. на цій комбінації запускаються обов'язкові перевірки;
  4. перевірки пройшли - PR вливається; впали - PR вилучається з черги, а черга за ним перебудовується без нього.

Черга може перевіряти кілька PR групою паралельно - це прискорює потік, коли PR багато.

Що треба налаштувати в CI:

on:
  pull_request:
  merge_group:

Подія merge_group - окрема від pull_request і push. Якщо її не додати, обов'язкові перевірки для черги не запустяться, і злиття впаде через відсутній статус. Сторонні CI мають реагувати на push у гілки з префіксом gh-readonly-queue/.

Що змінюється для команди:

  • спосіб злиття визначає черга, а не автор;
  • main завжди зелений: те, що в нього потрапляє, перевірено саме в такій комбінації;
  • час до злиття зростає на тривалість CI - тому швидкий CI стає ще важливішим.

Коли не потрібна: невелика команда, кілька злиттів на день - достатньо вимоги актуальності гілки. Merge queue окупається там, де PR вливаються десятками на день і зламаний main блокує всіх.

Докладніше в документації: GitHub: merge queue

Проблема: велику фічу розбили на маленькі PR, але кожен наступний залежить від попереднього. Чекати злиття першого, щоб почати другий, - повільно; складати все в один PR - втрачаємо переваги маленьких рев'ю.

Stacked PRs - ланцюжок залежних pull request-ів, де кожен спрямований не в main, а в гілку попереднього:

feat/ui      → PR #3 (base: feat/api)    ← верх
feat/api     → PR #2 (base: feat/schema)
feat/schema  → PR #1 (base: main)        ← низ
main

Кожен PR показує лише свій шар змін: рецензент дивиться міграцію окремо від API і окремо від інтерфейсу. Принцип: якщо код залежить від іншого коду, залежність має бути в тій самій гілці чи нижче.

Головна складність - rebase. Після виправлення в нижній гілці всі верхні треба перебазувати. Вручну це втомливо, але Git уміє оновлювати весь ланцюжок одразу:

git switch feat/ui
git rebase --update-refs main

--update-refs під час rebase верхньої гілки пересуває й проміжні гілки (feat/schema, feat/api) на переписані коміти. Можна увімкнути за замовчуванням: git config rebase.updateRefs true.

Після злиття нижнього PR наступний треба переспрямувати на main і перебазувати. Якщо низ вливали через squash, у верхніх гілках лишаються «старі» коміти, яких у main вже немає за SHA, - тут допомагає git rebase --onto main feat/schema feat/api.

Інструменти: GitHub має нативну підтримку стеків (на момент написання - у публічному попередньому перегляді) з каскадним rebase на сервері і розширенням gh stack; є також сторонні інструменти на кшталт Graphite чи ghstack.

Коли варто: довга фіча, яку природно ділити на шари, і швидкий потік рев'ю. Коли ні: незалежні зміни - їм не потрібен стек, достатньо окремих PR від main; повільне рев'ю - стек з п'яти PR, що висять тиждень, перетворюється на постійний rebase.

Докладніше в документації: GitHub: stacked pull requests

Незгоди - нормальна частина рев'ю. Проблема не в них, а в тому, що вони тягнуться днями в коментарях і псують стосунки.

Як розв'язувати незгоду:

  1. розділити факти й смак. Баг, вразливість, порушення домовленостей команди - аргумент. «Я б написав інакше» - ні, якщо рішення автора теж коректне;
  2. спиратися на спільні правила, а не на авторитет: стайлгайд, архітектурні рішення (ADR), домовленості команди. Якщо правила немає - це привід його створити, а не виграти суперечку;
  3. перейти в розмову: після двох-трьох раундів коментарів 10 хвилин дзвінка вирішують більше, ніж ще десять повідомлень. Підсумок - записати в PR;
  4. ескалація до техліда чи ширшого обговорення, якщо згоди немає, - це нормальний механізм, а не поразка;
  5. «не блокує, але»: рецензент може погодитися злити зараз і створити задачу на покращення - якщо проблема не критична.

Принцип рецензента: схвалювати зміну, яка покращує стан кодової бази, навіть якщо вона не ідеальна. Вимога досконалості зупиняє розробку.

Як рев'ю стає вузьким місцем:

  • PR чекають рев'ю по кілька днів, автори перемикаються між задачами й втрачають контекст;
  • рев'ю робить одна людина;
  • великі PR, які ніхто не хоче брати.

Що допомагає:

  • домовленість про швидкість відгуку: перша реакція протягом робочого дня; не обов'язково повне рев'ю, але хоча б «подивлюсь після обіду»;
  • рев'ю - пріоритетна робота, а не те, що роблять «коли буде час»: незлитий PR - незавершена робота;
  • розподіл рев'юерів - автоматичне призначення команді за CODEOWNERS чи round-robin, щоб навантаження не падало на одних і тих самих;
  • маленькі PR - їх рев'юють швидко;
  • автоматика знімає з людей стиль і очевидні помилки;
  • вимірювати час до першого відгуку й до злиття - без цифр проблема помітна лише відчуттям;
  • парне програмування для складних змін - рев'ю відбувається під час написання.

Докладніше в документації: Google: як працювати з запереченнями в рев'ю