Теорія
Баг-репорт і GitHub issues: шаблони, мітки й майлстоуни
Як перетворити запит в один рядок на issues, з якими команда може працювати: як написати баг-репорт із кроками відтворення, запити на фічі з критеріями приймання, шаблон issue на GitHub, мітки, виконавці, sub-issues і майлстоун із дедлайном. А ще як ключові слова закриття й згадки пов'язують pull requests з issues і що кожен стан issue каже продакту.
~10 хв читання · 11 вправ · 250 балів
Перед початком
Один рядок від CEO
Понеділок третього тижня в Котокормі. Продуктова команда працює над балами лояльності, а робота розкидана по чатах, трьох pull requests і одному issue від CEO з назвою «Loyalty», де сказано лише, що це потрібно до Чорної п'ятниці.
Останній модуль — про планування роботи на GitHub. У першому уроці ти перетвориш запит в один рядок на issues, з якими команда може працювати: із шаблонами, мітками, відповідальними й майлстоуном із дедлайном.
Issues: запитання, яке йде перед кодом
У Котокормі робота над лояльністю розповзлася по чатах, трьох pull requests і одному issue, яке CEO назвав «Loyalty» й описав одним рядком: «Need this before Black Friday!!». Новий колега ніколи не знайшов би все це разом.
Issue — це місце на GitHub для всього, про що команда має знати або що має зробити. Більшість issues належать до трьох видів: баг-репорт (щось зламалося), запит на фічу (чогось бракує) і задача (справа з відповідальним і часто з датою). Issue зазвичай з'являється раніше за код, і читають його люди, яких не було на нараді, тож воно має бути зрозумілим без пояснень.
Loyalty#147
Відкрив CEO минулого тижня · без міток і виконавця
Test an orange checkout button#135
Мітка idea · відкрив Daniel
Checkout button stays grey after editing cart#131
Звіт про баг від підтримки · закрито через #134
Show prices in euros#138
Запит на фічу · закрито як незаплановане
Код, який відповідає на issue, приходить пізніше як pull request із посиланням на нього. Issue — це запитання, pull request — одна з відповідей. Pull request можна відкрити й без issue, але за місяць ніхто не згадає, яку проблему він розв'язував. Закрите issue теж зберігає причину: закрито як виконане або закрито як незаплановане.
- ТакIssue #131 «Checkout button stays grey after editing cart» — баг-репорт; pull request #134 його виправляє й посилається на нього.
Проблема і її виправлення лишаються пов'язаними.
- Не такГілка
fix-thingі повідомлення в чаті: «виправив той баг із вівторка».За місяць ніхто не знає, яку проблему це розв'язало.
Баг-репорт, який повторить будь-хто
Розробник може виправити лише те, що здатен відтворити на своєму екрані. Добрий баг-репорт дає йому чотири речі.
- Кроки, щоб відтворити: що робити й у якому порядку, починаючи з відомого стану (який акаунт, який флаг увімкнено).
- Очікуваний результат: що мало статися.
- Фактичний результат: що сталося натомість.
- Де: пристрій, браузер, акаунт, номер замовлення, а якщо є — скриншот.
Issue #159 у Котокормі зроблено саме так. З акаунта працівника замовляють товари на 600 грн, застосовують промокод на 100 грн і платять 500 грн. Очікувано: 50 балів, по одному за кожні 10 грн оплати. Фактично: 60 балів, пораховані до знижки. Будь-хто повторить це за дві хвилини, а два числа одразу показують розбіжність.

Назва описує симптом, а не емоцію: «Points counted before promo discount», а не «Points broken». Двом речам у репорті не місце. Терміновість узгоджують із командою, а не пишуть великими літерами. А шукати файл із помилкою — робота розробника; здогадка автора репорту лише збиває з пантелику.
- Так«Points counted before promo discount. Кроки: акаунт працівника, кошик на 600 грн, промокод 100 грн, оплата 500 грн. Очікувано: 50 балів. Фактично: 60. Де: десктоп, Chrome, замовлення 58213.»
Будь-хто повторить і побачить розбіжність.
- Не так«Points broken. Клієнт каже, що бали неправильні, виправте терміново!!»
Нічого відтворити й нічого порівняти.
- Не так«ТЕРМІНОВО: баг, мабуть, у файлі оформлення замовлення.»
Кричить про терміновість і вгадує код замість описати, що сталося.
Запити на фічі й шаблони issues
Запит на фічу відповідає на три запитання для тих, кого не було на нараді: чого хочуть, навіщо це і як зрозуміти, що роботу зроблено. У корисному запиті є:
- назва, що описує результат, а не тему: «Customers can reuse a saved delivery address», а не «Addresses»;
- кілька рядків контексту: у кого проблема і чого вона їм коштує;
- критерії приймання: перевірки, які мають справджуватися, коли роботу завершено, сформульовані так, щоб людина могла їх перевірити;
- що не входить у межі, щоб робота непомітно не розросталася.
GitHub може підштовхнути всіх до такого формату через шаблони issues: файли в папці .github/ISSUE_TEMPLATE/ репозиторію. Після цього New issue спершу питає, що саме відкрити, наприклад «Bug report» чи «Feature request», і заповнює заголовки й мітки; шаблон бага може сам додати мітку bug. Є й версія-форма з окремими полями, і деякі з них можна зробити обов'язковими.
Пам'ятай про межу: шаблон може вимагати поле, але не добру відповідь. Заповнити його добре — все одно завдання автора.
- ТакКритерій приймання: «Збережену адресу можна вибрати під час оформлення, не вводячи її заново.»
Людина перевірить це за хвилину.
- Не такКритерій приймання: «Адреси добре працюють.»
Ніхто не скаже, коли це справдилося.
- ТакШаблон «Bug report» із заголовками Steps to reproduce, Expected, Actual і Where, який сам додає мітку
bug.Заголовки підказують, що потрібно розробнику.
Мітки й виконавці
Бокова панель праворуч від issue має ті самі поля, що й pull request, і кожне відповідає на одне з повсякденних запитань продакта.
Labels(мітки) кажуть, що це за робота і якої частини продукту стосується:bug,content,loyalty. Відфільтруй список issues заlabel:loyalty, і вся програма поміститься на одному екрані.Assignees(виконавці) — ті, хто відповідає за те, щоб issue рухалося. Це не обов'язково автор: продакт пише баг #159, а виконавцем призначають розробника Daniel. Призначити можна кількох людей, але одне ім'я одразу показує, кого питати.Milestoneприв'язує issue до цілі з датою.Developmentпоказує гілки й pull requests, які над ним працюють.
Міток має бути мало, і кожна має щось означати. Якщо на кожному issue по шість міток, фільтр за будь-якою нічого не скаже, і ними перестануть користуватися. Невеликого узгодженого набору (тип роботи плюс частина продукту) більшості команд досить.
- Так#159: мітки
bugіloyalty, виконавець Daniel.Тип, частина продукту й один відповідальний.
- Не такКожне issue отримує
bug,urgent,frontend,loyalty,q4іimportant.Коли позначено все, фільтр нічого корисного не показує.
- Не такВиконавець: той, хто випадково написав issue.
Автор не завжди той, хто рухає справу далі.
Sub-issues: батьківське issue і його частини
Деяка робота завелика для одного issue. Програмі лояльності Котокорму потрібні виправлення бага, баланс у кабінеті, списання балів під час оформлення, сторінка програми й перемикач запуску. Десять не пов'язаних між собою issues знову все розпорошать, а один величезний опис сховає, хто що робить.
Рішення — батьківське issue із sub-issues. У батьківському — мета й те, що означає «запущено»: #147 перейменовують на «Launch loyalty points before Black Friday». Кожне sub-issue — звичайне issue, прикріплене до нього, з власним номером, виконавцем, мітками й станом, тож Daniel може закрити свою частину, не чіпаючи чужих. Батьківське issue показує їх списком і рахує прогрес, наприклад «закрито 1 з 4».

У старіших репозиторіях роблять щось схоже чеклистом в описі: рядки, що починаються з - [ ], показуються як прапорці. Це працює, але їх ставлять вручну, а в рядка чеклиста немає власного виконавця чи стану. Sub-issues — це ті частини, які люди справді беруть у роботу.
- ТакБатьківське #147 «Launch loyalty points before Black Friday» із sub-issues #159, #162, #163 і #164, у кожного свій відповідальний.
Мета вгорі, частини, які можна взяти, нижче.
- Не такДесять окремих issues від «Loyalty 1» до «Loyalty 10», ні з чим не пов'язані.
Ніхто не бачить усієї програми та її прогресу.
Майлстоуни й смуга прогресу
Майлстоун об'єднує issues і pull requests під однією метою, з необов'язковим дедлайном і смугою прогресу. Мітки відповідають на запитання «що це за робота?», а майлстоун — «до чого ми йдемо і до коли?». Майлстоун Котокорму «Loyalty launch» має дедлайн 13 листопада, за два тижні до Black Friday, і містить батьківське #147, його sub-issues і pull requests лояльності.
Смуга — проста арифметика: закриті елементи ÷ усі елементи. Змерджений pull request рахується як закритий. Так само й issue, закрите як незаплановане: відкинута ідея рухає смугу так само, як завершена робота. Коли закрито 2 елементи з 8, «Loyalty launch» показує 25%.
Launch loyalty points before Black Friday#147
Батьківське · закрито 0 з 4 sub-issues · Sofia
Add loyalty points behind a flag#155
Earn points on paid orders#156
Write loyalty programme page#157
Emma
Points counted before promo discount#159
bug, loyalty · Daniel
Show points balance in the account#162
loyalty · Daniel
Spend points at checkout#163
loyalty · Daniel
Switch on loyalty points for all customers#164
loyalty · Sofia
Через це відсоток легко прочитати неправильно. Він рахує елементи, а не зусилля: виправлення одного рядка тексту і вся зміна оформлення замовлення важать однаково. І половинок у ньому немає: відкритий pull request просто відкритий. Перш ніж звітувати про прогрес, прочитай список під смугою: що ще відкрито, наскільки воно велике і чи не закрито щось як відкинуте замість зробленого.

- Так«Закрито 60% елементів, але один із них відкинули, а найбільша частина, набори, ще відкрита.»
Читає список, а не лише смугу.
- Не так«Майлстоун на 60%, тож ми зробили більше половини.»
Смуга рахує елементи, а не зусилля, і враховує відкинуті.
Ключові слова закриття й згадки
Робота над issue може початися з самого issue. У розділі Development є Create a branch: GitHub пропонує назву з номера й заголовка issue, а Daniel змінює її за звичкою команди на fix/points-after-discount. Відтак issue показує цю гілку, а згодом і її pull request.
В описі pull request два посилання схожі на вигляд, але поводяться по-різному:
Fixes #159містить ключове слово закриття. Коли pull request змерджать в основну гілку (main), GitHub закриє #159 як виконане. Ключові слова — close, fix і resolve з їхніми звичними закінченнями: closes, closed, fixes, fixed, resolves, resolved.Part of #147— проста згадка. У хронології #147 з'явиться позначка, що pull request на нього посилався, і нічого не закриється. «Related to» чи «See» працюють так само.
e5f81a3Hide loyalty menu link when flag is off (#158)v1.4.1b2e6f10Earn points on paid orders (#156)6e0b4d8Add issue templates (#160)4f8c2e1Count points from the paid amountfix/points-after-discount7a1d9c3Count points after discounts (#165)main
Граф комітів, від найстарішого: Від issue #159 до main 1. коміт e5f81a3 у main "Hide loyalty menu link when flag is off (#158)", тег v1.4.1 2. коміт b2e6f10 у main "Earn points on paid orders (#156)" 3. коміт 6e0b4d8 у main "Add issue templates (#160)" 4. гілка fix/points-after-discount починається від коміту 6e0b4d8 5. коміт 4f8c2e1 у fix/points-after-discount "Count points from the paid amount" 6. коміт 7a1d9c3: fix/points-after-discount змерджено в main через squash "Count points after discounts (#165)" (виділено)
Саме тому частина великої програми ніколи не пише Fixes #147: перший змерджений шматок закрив би все батьківське issue. Закривай своє sub-issue, батьківське лише згадуй.

- Так
Fixes #159. Part of #147.Закриває баг, батьківське лише згадує.
- Не так
Fixes #147у pull request для однієї частини програми.Перша ж змерджена частина закрила б усю програму.
Одне ключове слово на issue, закриття лише після мерджу
У ключових слів закриття є два правила, на яких часто спотикаються.
Одне ключове слово на кожен номер. Fixes #162 and #163 закриє лише #162; #163 отримає згадку й лишиться відкритим. Щоб закрити обидва, повтори ключове слово: Fixes #162, fixes #163. А батьківське issue додай простою згадкою, Part of #147, щоб ця частина з'явилася в хронології програми, але не закрила її.
Нічого не закривається до мерджу. Відкритий pull request із Fixes #159 лише пов'язує їх; issue закриється, коли pull request змерджать в основну гілку, у Котокормі це main. Pull request, змерджений в іншу гілку, нічого не закриває. Ті самі ключові слова працюють і в повідомленні коміту, щойно цей коміт потрапить в основну гілку.
Після мерджу issue отримує фіолетову позначку «закрито як виконане», а розділ Development зберігає посилання на pull request, тож будь-хто бачить, що його закрило і коли. Якщо виправлення не спрацювало, issue можна відкрити знову, а не заводити нове.
- Так
Fixes #162, fixes #163. Part of #147.Два ключові слова для двох sub-issues, згадка для батьківського.
- Не так
Fixes #162 and #163.Закриється лише #162; #163 випадково лишиться відкритим.
- ТакВиправлення не пройшло тест працівниками: знову відкрити #159 і пов'язати новий pull request.
Історія лишається в одному місці.
Закрито — ще не означає, що клієнти це бачать
Коли змерджено #165, pull request від Daniel з Fixes #159 для бага з балами, #159 закривається само й отримує фіолетову позначку: закрито як виконане. У Котокормі мердж у main викладають за кілька хвилин, і тест працівників після обіду проходить: оплачено 500 грн, нараховано 50 балів. Баг виправлено в робочому коді.
Але клієнти досі не бачать жодних балів. Програма схована за фіча-флагом loyalty-points, а він увімкнений лише для працівників. Стан issue і флаг відповідають на різні запитання:
- Стан issue (відкрите, закрите як виконане, закрите як незаплановане): чи зроблено роботу?
- Деплой: чи працює код на сайті?
- Фіча-флаг: хто бачить фічу?
Тримай ці три речі окремо, коли звітуєш підтримці, продажам чи CEO. «#159 закрито» — правда, але вона вводить в оману, якщо слухач почує «клієнти тепер отримують правильні бали». Кажи, що вже працює, хто це бачить і коли це зміниться. А сторінка релізу — лише опис змін; її публікація нічого не викладає.
- Так«Виправлення вже на сайті. Клієнти побачать бали, коли ми ввімкнемо флаг для всіх; це заплановано до 13 листопада.»
Розділяє «зроблено», «викладено» і «видно».
- Не так«#159 закрито, тож клієнти вже отримують правильні бали.»
У клієнтів ще взагалі немає балів: флаг увімкнено лише для працівників.
Готові потренуватися?
Виконайте вправи уроку й отримайте 250 балів, коли пройдете його вперше.
Розпочати вправи