1. Навіщо потрібен демо‑сценарій на 5–10 хвилин
Якщо вам колись показували проєкт у стилі «зараз я вам усе поясню» і через 20 хвилин ви все ще дивилися на структуру папок, ви добре розумієте цей біль. Демо‑сценарій не дає перетворити показ результату на екскурсію кодом. Код ми й так любимо, але не всі зобовʼязані поділяти це захоплення. Формат «5–10 хвилин» дисциплінує: ви показуєте лише те, що справді дає цінність і викликає довіру.
Ключова думка: хороше демо — це не «що в нас є», а «що користувач може зробити й на що може розраховувати». Тому демо майже завжди будується в порядку поведінка → контракти → сигнали якості. Поведінка — це спостережувана дія: команда CLI або HTTP-запит. Контракти — це стабільні правила: stdout/stderr, коди виходу, статуси HTTP, error envelope. Сигнали якості — це відповідь на запитання «чому цьому можна довіряти»: стабільність виводу, тести, відсутність витоку внутрішніх помилок, request_id тощо.
Щоб не бути голослівними, у цій лекції вважатимемо, що в нас є навчальний застосунок Tasker: менеджер завдань із CLI та HTTP API. Ми не будемо «дописувати функціональність» — курс уже фіналізується. Натомість навчимося показувати те, що ви вже побудували.
Міні‑схема: що саме ми показуємо
flowchart TD
A[Демо починається з поведінки] --> B[CLI: команди та коди виходу]
B --> C[HTTP: статуси та JSON-контракт]
C --> D[Сигнали якості: тести, стабільність, request_id]
D --> E[Питання й фінальна перевірка очікувань]
2. Збираємо демо як одну історію
Найчастіша проблема початківців — бажання показати весь проєкт одразу, бо «я ж старався». Це нормально: ми всі хочемо, щоб наш код оцінили. Але демо працює інакше: це коротка історія з початком, дією і кінцем. Як у хорошому анекдоті: якщо треба пояснювати 10 хвилин, це вже не анекдот, а лекція з історії гумору.
Сценарій зручно тримати в голові як міні‑протокол:
- у вас є початковий стан: порожній список завдань або кілька завдань,
- ви виконуєте 2–4 дії,
- кожна дія дає спостережуваний результат,
- ви один раз показуєте, як система поводиться з помилкою,
- ви закінчуєте на «успішному» фіналі: завдання створено, виконано або отримано через API.
Щоб це було простіше повторювати, зручно оформити демо у вигляді «таймлайна». Не в голові, а в таблиці — вона дисциплінує і вас, і розповідь.
| Час | Крок демо | Що запускаємо | Що перевіряємо очима |
|---|---|---|---|
| 0:00–0:30 | Пітч | коротка промова (3–5 речень) | зрозуміло, що це за проєкт і для чого він потрібен |
| 0:30–2:30 | CLI: успішний сценарій | |
stdout = результат, exit code = 0 |
| 2:30–3:30 | CLI помилка | неправильна команда або аргумент | stderr = помилка/usage, exit code = 2 |
| 3:30–6:30 | HTTP: успішний сценарій | |
статуси 200/201, JSON коректний |
| 6:30–8:00 | HTTP помилка | + error envelope |
єдиний формат помилок, fields для валідації |
| 8:00–10:00 | Сигнали якості | |
проєкт «дорослий», а не просто «працює» |
У цій лекції ми розберемо, як швидко підготувати кожну частину, і які невеликі шматочки коду допомагають утримувати контракт. Так ваш проєкт не перетвориться на «воно сьогодні так поводиться, а завтра — подивимось».
3. Пітч: короткий вступ
Смішно, але факт: люди найчастіше «провалюють» демо не на коді й навіть не на багу, а на першому поясненні. Коли ви починаєте з архітектури й папок, слухач не розуміє, що взагалі відбувається, і мозок переходить у режим енергозбереження. Пітч потрібен, щоб мозок слухача сказав: «Ага, я зрозумів задачу та критерії якості».
Пітч має бути коротким і навіть трохи нудним — у хорошому сенсі. Приклад для Tasker:
Tasker — це маленький менеджер завдань. У нього є CLI та HTTP API.
У CLI я покажу контракт stdout/stderr і коди повернення 0/1/2, а в HTTP — статуси й єдиний JSON‑формат помилок.
Для неочікуваних проблем назовні не виходять внутрішні деталі: 500‑клас віддає стабільне повідомлення.
До того ж покажу request_id як мінімальний сигнал спостережуваності.
Зверніть увагу: ви заздалегідь називаєте «сигнали якості». Це не хизування, а навігація: слухач починає дивитися туди, куди потрібно. І вам легше не збитися в режим «зараз ще ось це покажу».
4. Демо CLI: stdout/stderr і коди виходу
CLI‑демо зручно робити першим: воно швидке, локальне й зрозуміле. Тут ви виграєте тим, що одразу показуєте дисципліну: stdout — результат, stderr — помилки/usage, exit codes — контракт. У реальному світі це дає змогу людям писати скрипти, а тестам — бути стабільними. У навчальному світі це показує, що ви не просто перевіряєте все «очима», а проєктуєте інтерфейс.
Ключовий трюк — виділити центральну функцію run(args) і в main() лише викликати os.Exit(code). Це банально, але дуже корисно: так ви не розпорошуєтеся на log.Fatal і виходи з середини програми, а тримаєте керування в одному місці.
Міні‑приклад: каркас CLI main → run → exit
package main
import (
"os"
)
func main() {
os.Exit(run(os.Args[1:]))
}
Цей фрагмент маленький, але він задає стиль: усе, що друкує, парсить або обробляє помилки, має бути всередині run.
Міні‑приклад: базовий диспетчер команд
package main
import (
"fmt"
"os"
)
func run(args []string) int {
if len(args) == 0 {
fmt.Fprintln(os.Stderr, "usage: tasker <command>")
return 2
}
if args[0] == "help" || args[0] == "-h" || args[0] == "--help" {
fmt.Println("usage: tasker <command>") // stdout: це нормальний результат
return 0
}
fmt.Fprintln(os.Stderr, "unknown command:", args[0])
return 2
}
Тут ви демонструєте одразу два контракти: help — це успішний сценарій (код 0), а помилка введення — це usage (код 2). Це здається дрібницею, доки ви не починаєте писати тести на CLI й запускати команди в bash. Тоді раптом виявляється, що «дрібниці» — це і є UX.
Що показувати в демо CLI
Не показуйте десять команд — вистачить трьох:
- «Створити завдання» (успішний сценарій).
- «Показати список» (стабільний вивід, сортування або порядок — якщо є).
- «Зламати введення» (помилка, exit code 2, stderr).
Приклад того, як це може виглядати, — це не код, а сценарій показу:
- tasker add -title "Buy milk" → stdout: created task id=1
- tasker list → stdout: таблиця або рядки
- tasker add (без title) → stderr: title is required, exit code 2
У цей момент ви словами фіксуєте контракт: «Результат я друкую в stdout, помилки й підказки — у stderr. За кодами: 0 — успіх, 2 — проблема з аргументами, 1 — помилка виконання». І ви вже виглядаєте людиною, яка пише софт, а не просто набір команд, які запускає вручну.
5. Демо HTTP: статуси й error envelope
Після CLI логічно перейти до HTTP: тут ви показуєте, що ті самі ідеї — контракти, передбачуваність, помилки — масштабуються на мережевий інтерфейс. В HTTP особливо важливо не розпорошуватися: якщо ви почнете розповідати про middleware, шари й DI, ви втратите 5–10 хвилин ще до першого запиту. Тому тримаємо фокус: запит → статус → тіло → контракт помилки.
У цьому курсі ми заздалегідь домовилися про єдиний формат помилок у JSON: error envelope. Це як форма для документів: хай нудна, зате будь‑який клієнт знає, куди дивитися.
Міні‑приклад: структури error envelope
package api
type ErrorResponse struct {
Error ErrorBody `json:"error"`
}
type ErrorBody struct {
Code string `json:"code"`
Message string `json:"message"`
Fields map[string]string `json:"fields,omitempty"`
}
Ця структура має бути незмінною: якщо ви змінюєте її щоразу за настроєм, тестувати й підтримувати API буде боляче.
Міні‑приклад: єдиний writeError без витоку внутрішніх деталей
Дуже важлива інженерна звичка: для помилок 500‑го класу назовні не віддавайте «внутрішню правду». У користувача має бути стале, безпечне формулювання. Внутрішні деталі — для логів. Це напряму пов’язано з тим, що в Go помилки — це значення, і ви часто додаєте контекст у міру просування вгору по стеку. Контексту може бути багато, і не все з нього потрібно показувати клієнту.
package api
import (
"encoding/json"
"net/http"
)
func writeError(w http.ResponseWriter, status int, code, msg string, fields map[string]string) {
if status >= 500 {
code = "internal"
msg = "internal error"
fields = nil
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(ErrorResponse{
Error: ErrorBody{Code: code, Message: msg, Fields: fields},
})
}
У демо варто прямо проговорити: «Внутрішню помилку я не показую назовні, бо це і безпека, і стабільність контракту». Це один із найсильніших сигналів якості, який можна показати за 10 секунд.
Як показувати HTTP наживо
Вам потрібно 3–4 запити. Наприклад:
- GET /health → 200 OK → текст ok або JSON, якщо саме так ви вирішили.
- POST /api/v1/tasks → 201 Created → JSON із завданням і id.
- GET /api/v1/tasks/999 → 404 Not Found → error envelope.
- POST /api/v1/tasks із поганим JSON або порожнім title → 400 Bad Request → error envelope + fields.
У демо не треба ідеально памʼятати curl. Можна тримати в README готові команди. Це не шахрайство, а турбота про себе: ви показуєте продукт, а не навички швидкого набору тексту.
6. Request ID як сигнал «продакшену»
Request ID — це мінімальна спостережуваність. Навіть якщо у вас немає красивого трейсингу й метрик, один request_id уже дає змогу пов’язати «помилка в клієнта» зі «рядком у логах». І найприємніше: показати це можна за 15 секунд, а виглядає воно дуже переконливо.
У демо зазвичай достатньо сказати: «Я підтримую заголовок X-Request-ID. Якщо його надіслали — використовую його. Якщо ні — генерую. І додаю в логи з ключем request_id». Вам навіть не обовʼязково показувати генерацію: вона може бути всередині middleware, але стандарти іменування варто зафіксувати.
Міні‑приклад: константи для заголовка й логів
package api
const RequestIDHeader = "X-Request-ID"
const LogFieldRequestID = "request_id"
Чому це важливо? Бо без констант в одному місці з’явиться X-Request-Id, в іншому — X-REQUEST-ID, а в логах хтось напише reqId. І це та сама «смерть від тисячі порізів», коли все ніби працює, але підтримувати неможливо.
7. Сигнали якості й підготовка до показу
Сигнали якості — це спостережувані ознаки, які не вимагають, щоб ви «повірили на слово». У демо їх треба не просто мати, а називати. Не «ну, тести там є», а «ось команда, ось результат». У Go це особливо природно: інструменти прості, а дисципліна читається по дрібницях.
Водночас важливо не скочуватися в хизування. Демо — не змагання «хто більше прапорців go test знає». Краще показати один‑два сильні сигнали.
Хороший набір на 5–10 хвилин виглядає так:
- go test ./... проходить — це можна показати наприкінці одним рядком.
- Вивід CLI/HTTP стабільний і не змінюється випадково.
- Помилки контрактні: у CLI є 0/1/2, у HTTP — статуси + envelope.
- Внутрішні помилки не виходять назовні: 5xx завжди internal error.
- Є request_id як мінімальна спостережуваність.
Якщо ви хочете додати ще один штрих, то лише один: покажіть один unit‑тест на парсинг id або на мапінг помилок у код виходу. Це швидко й по ділу.
Міні‑приклад: мапінг помилки в exit code
package main
import "errors"
var ErrUsage = errors.New("usage")
func exitCode(err error) int {
if err == nil {
return 0
}
if errors.Is(err, ErrUsage) {
return 2
}
return 1
}
Це хороший стиль: навіть якщо всередині ви повертаєте різні помилки, назовні ви мапите їх передбачувано. До речі, сама ідея «помилки як значення» — одна з базових ментальних моделей Go, і тут вона дуже наочно проявляється.
Техніка безпеки демо: як не потонути в проєкті
Перед фіналом я хочу проговорити один психологічний момент. Коли ви нервуєте, ви починаєте пришвидшуватися, а разом із цим — показувати зайве: «а ще в мене тут пакет internal», «а ось middleware», «а ось діаграма», «а зараз я відкрию папку adapters». Це виглядає як спроба сховати відсутність результату за активністю.
Найспокійніший спосіб втриматися — заздалегідь прийняти правило: у демо ми не відкриваємо редактор коду. Узагалі. Навіть «на секундочку». Бо щойно ви відкрили код, ви вже не показуєте продукт. Ви показуєте процес розробки.
Якщо вам дуже хочеться все-таки показати, що «код охайний», зробіть це опосередковано: тести проходять, формат помилок стабільний, внутрішні деталі не виходять назовні, request_id узгоджений. Це не гірше за перегортання файлів і при цьому не вимагає віри.
8. Типові помилки під час показу CLI/HTTP демо
Помилка № 1: демо перетворюється на розповідь про архітектуру, а не про поведінку.
Це трапляється, коли ви починаєте з «ось у мене є папка internal, а ось domain…». У підсумку слухач так і не зрозумів, що проєкт робить. Рішення просте: почніть із команди CLI або curl-запиту. Спочатку результат, потім, якщо спитають, — устрій.
Помилка № 2: ви не фіксуєте контракт stdout/stderr і exit codes, і все виглядає випадковим.
Якщо ви друкуєте частину помилок у stdout, частину — у stderr, а help повертає код 1, у слухача виникає відчуття, що «воно просто запускається». Значно краще один раз явно проговорити договір: stdout — результат, stderr — помилки/usage; 0/2/1 — як базовий контракт.
Помилка № 3: в HTTP помилки «плавають» за форматом, бо кожен обробник пише по-своєму.
Сьогодні у вас {"message":"bad"}, завтра {"error":"bad"}, післязавтра http.Error. Клієнту від цього не легше. У демо достатньо показати один error envelope і один writeError, щоб стало ясно: формат стабільний, тестований, підтримуваний.
Помилка № 4: на помилках 500‑го класу ви віддаєте клієнту err.Error().
Це виглядає чесно, але на практиці майже завжди погано: ви розкриваєте внутрішні деталі й робите контракт нестабільним. У Go помилки часто обгортаються й несуть багато контексту, і цей контекст корисний вам у логах, але не клієнту. У демо краще показати протилежне: для 5xx повідомлення фіксоване, деталі — у логах.
Помилка № 5: ви показуєте лише успішний сценарій, і створюється враження «працює, доки не чіпаєш».
Один контрольований негативний сценарій — CLI usage або HTTP validation error — різко підвищує довіру. Це як краш‑тест: не тому, що ви любите аварії, а тому, що ви заздалегідь подумали про реальність.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ