Middle: питання на співбесіді з теми «Pull request і code review»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
5 питань
Головна мета рев'ю - покращити здоров'я кодової бази, а не знайти ідеальне рішення. Корисно йти від загального до деталей: немає сенсу обговорювати назву змінної в коді, який узагалі не варто вливати.
Порядок перегляду:
- Опис і мета - чи зрозуміло, яку проблему розв'язує PR, і чи потрібна ця зміна взагалі;
- Дизайн - чи правильне місце для коду, чи вписується в архітектуру, чи не надто складно;
- Тести - чи покривають нову поведінку й крайні випадки, чи падали б вони без зміни;
- Логіка й коректність - граничні умови, null, порожні колекції, конкурентний доступ, обробка помилок;
- Безпека й продуктивність - авторизація, валідація вводу, N+1, запити в циклі, великі вибірки без пагінації;
- Читабельність - назви, зрозумілість, коментарі там, де «чому» неочевидне;
- Стиль - лише те, що не перевіряє автоматика.
На що звертати особливу увагу в Laravel:
- міграції - чи безпечні на великій таблиці, чи є відкат, чи сумісні зі старою версією коду під час деплою;
- масове присвоєння і
$fillable, перевірка прав (Gate, політики) у контролерах і Livewire-діях; - черги - ідемпотентність задач, що буде при повторі;
- запити -
with()для зв'язків, індекси під новіwhere.
Що віддати автоматиці:
- форматування - Pint;
- типові помилки й типи - PHPStan/Larastan;
- тести - CI.
Людина не повинна писати «тут пробіл» - на це є інструменти, а увага рецензента дорожча.
Чого не робити:
- вимагати переписати під свій смак, якщо рішення автора теж нормальне;
- рев'ювити диф без контексту - іноді треба відкрити весь файл чи запустити гілку локально (
gh pr checkout 412); - затягувати: швидкий відгук важливіший за ідеальний.
Докладніше в документації: Google: що шукати під час code review
Текстовий коментар не передає інтонації: те, що рецензент вважав дрібною порадою, автор може прочитати як різку вимогу. Тому важливо явно позначати вагу коментаря і писати про код, а не про людину.
Conventional Comments - домовленість про формат коментарів: мітка на початку показує, чого саме чекає рецензент.
issue: тут N+1 - для кожної вакансії окремий запит до company.
Додай ->with('company') у запит вище.
suggestion (non-blocking): можна винести умову в scope `published()`,
вона повторюється в трьох місцях.
nitpick: `$data` -> `$vacancyAttributes`, так зрозуміліше.
question: чи може тут прийти порожній масив? Якщо так - впаде на array_key_first.
praise: дуже зручно, що експорт іде потоково, - пам'ять не росте.
Мітки й значення:
| Мітка | Що означає |
|---|---|
issue |
проблема, яку треба виправити |
suggestion |
пропозиція покращення |
question |
рецензент не впевнений, хоче зрозуміти |
nitpick |
дрібниця на смак, не блокує |
praise |
щось зроблено добре |
(blocking) / (non-blocking) |
чи блокує злиття |
Принципи доброго коментаря:
- пояснювати чому: не «зроби інакше», а «так буде N+1, бо...»;
- питати, а не стверджувати, коли не впевнені: можливо, автор знає те, чого не знаєте ви;
- «ми» і «код», а не «ти»: «тут можна спростити», а не «ти ускладнив»;
- пропонувати рішення чи напрямок, а не лише вказувати на проблему;
- хвалити вдалі рішення - це теж інформація: що варто повторювати;
- не дублювати: однакова проблема в десяти місцях - один коментар «і так само нижче».
Автору: відповідати на кожен коментар (виправлено, не згоден - ось чому, винесу в окрему задачу) і не сприймати рев'ю особисто - рецензують код, а не людину.
Кнопка злиття pull request на GitHub має три варіанти, і вони по-різному формують історію main.
Create a merge commit (git merge --no-ff):
* Merge pull request #412 from acme/csv-export
|\
| * add tests
| * fix typo
| * export service
|/
* previous commit
- зберігаються всі коміти гілки й сам факт злиття;
- легко відкотити весь PR одним
git revert -m 1 <merge>; - історія з «fix typo» і «wip» потрапляє в
main.
Squash and merge:
* Експорт вакансій у CSV (#412)
* previous commit
- усі коміти PR стискаються в один новий коміт;
- лінійна чиста історія: один PR - один коміт, легко шукати й відкочувати;
- проміжні коміти автора губляться (лишаються на сторінці PR);
- автор після злиття не може просто продовжити роботу в тій самій гілці - коміт у
mainмає інший SHA, і наступний PR покаже старі зміни ще раз.
Rebase and merge:
- кожен коміт PR переноситься на
mainокремо, без коміту злиття - лінійна історія з усіма комітами; - GitHub завжди створює нові SHA й оновлює дані комітера, навіть якщо можна було б просто перемотати гілку, і відкидає порожні коміти;
- доречно, коли автор ретельно впорядкував коміти й кожен має сенс сам по собі.
Як обрати:
| Ситуація | Підходить |
|---|---|
| коміти в PR «брудні», потрібна чиста історія | Squash |
| коміти атомарні й осмислені | Rebase |
| важливо бачити межі PR у графі | Merge commit |
Політика команди: у налаштуваннях репозиторію можна залишити лише один дозволений спосіб, щоб історія була однорідною. Для squash варто налаштувати, що заголовок коміту береться із заголовка PR, - тоді якість історії залежить від якості заголовків PR.
CODEOWNERS - файл, що визначає людей чи команди, відповідальні за частини репозиторію. Коли PR змінює їхні файли, GitHub автоматично запитує в них рев'ю.
Де лежить: .github/CODEOWNERS, корінь репозиторію чи docs/ - GitHub шукає в такому порядку і бере перший знайдений. Файл діє для тієї гілки, в якій лежить.
# За замовчуванням - уся команда бекенду
* @acme/backend
# Міграції - обов'язково команда даних
/database/migrations/ @acme/data
# Платежі - конкретні люди
/app/Billing/ @olena @acme/payments
# Фронтенд
*.vue @acme/frontend
/resources/js/ @acme/frontend
# Захистити сам файл і конфіги CI
/.github/ @acme/leads
Синтаксис: шаблони як у .gitignore, останній збіг має пріоритет. Тому загальне правило * іде першим, а конкретніші - нижче. Якщо для файла останнє правило не містить власників, у нього їх немає.
Вимоги: власники мають мати права на запис у репозиторій; команда має бути видимою й теж мати права на запис.
Справжня сила - разом із захистом гілки: правило Require review from Code Owners (у branch protection чи ruleset) не дає злити PR, доки його не схвалить власник змінених файлів. Без цього CODEOWNERS - лише автоматичне запрошення.
Практичні поради:
- захистити
.github/: інакше будь-хто може в PR змінити CODEOWNERS чи workflow CI і обійти правила; - команди, а не люди, де можливо: людина йде у відпустку - рев'ю блокується;
- не робити власником усього одну людину - вона стане вузьким місцем;
- GitHub показує помилки синтаксису CODEOWNERS прямо в інтерфейсі файла - варто перевіряти після змін.
Перший рецензент PR - його автор. Перегляд власного дифу на GitHub (а не в редакторі) за кілька хвилин знаходить те, на що інакше пішов би раунд рев'ю.
Чеклист перед запитом рев'ю:
- переглянути весь диф у вкладці Files changed - так, як побачить рецензент;
- прибрати
dd(),dump(),console.log, закоментований код, випадкові файли; - CI зелений, тести на нову поведінку є;
- опис оновлено: що, навіщо, як перевірити;
- залишити власні коментарі до неочевидних місць: «тут свідомо без транзакції, бо...» - це зменшує кількість запитань;
- PR не містить сторонніх змін, що потрапили «заодно».
Як оновлювати PR після коментарів:
- нові коміти, а не переписування історії під час рев'ю: рецензент бачить лише те, що змінилося з останнього перегляду. Після
force pushGitHub губить зв'язок частини коментарів з кодом, і доводиться переглядати все заново; - fixup-коміти зручні, якщо історію треба буде впорядкувати перед злиттям:
git commit --fixup=a1b2c3d
# перед злиттям, коли рев'ю завершене:
git rebase -i --autosquash main
А якщо репозиторій вливає через squash - впорядковувати взагалі не потрібно;
- відповідати на кожен коментар: «виправлено в 4f2e1a0», «не згоден, бо...», «винесу в #430»;
- не закривати чужі обговорення без відповіді - рецензент має бачити, що його почули;
- повторно запросити рев'ю (Re-request review), коли все виправлено, - інакше рецензент не дізнається, що черга знову за ним.
Якщо коментарів дуже багато - часто це сигнал, що варто поговорити голосом чи переглянути підхід, а не виправляти по одному.