JavaRush /Курсы /Go SELF /Демо‑сценарий: как показать CLI/HTTP функциональность за ...

Демо‑сценарий: как показать CLI/HTTP функциональность за 5–10 минут

Go SELF
72 уровень , 0 лекция
Открыта

1. Зачем нужен демо‑сценарий на 5–10 минут

Если вам когда‑нибудь показывали проект «сейчас я вам всё объясню» и через 20 минут вы всё ещё смотрели на структуру папок — вы понимаете боль. Демо‑сценарий — это способ не превращать показ результата в экскурсию по коду (код мы и так любим, но не все обязаны разделять это чувство). Формат «5–10 минут» дисциплинирует: вы показываете только то, что даёт ценность и доверие.

Важная мысль: хорошее демо — это не «что у нас есть», а «что пользователь может сделать и на что он может рассчитывать». Поэтому демо почти всегда строится в порядке поведение → контракты → сигналы качества. Поведение — это наблюдаемое действие (команда CLI или HTTP-запрос). Контракты — это стабильные правила (stdout/stderr, exit codes, статусы HTTP, error envelope). Сигналы качества — это «почему этому можно доверять» (стабильность вывода, тесты, неутечка внутренних ошибок, request_id и т.п.).

Чтобы не быть голословными, в этой лекции будем считать, что у нас есть учебное приложение Tasker: менеджер задач с CLI и HTTP API. Мы не будем «дописывать функциональность» (курс уже финализируется), мы будем учиться показывать то, что вы уже построили.

Мини‑схема: что именно мы показываем

flowchart TD
    A[Демо начинается с поведения] --> B[CLI: команды и exit codes]
    B --> C[HTTP: статусы и JSON-контракт]
    C --> D[Сигналы качества: тесты, стабильность, request_id]
    D --> E[Вопросы / финальная проверка ожиданий]

2. Собираем демо как одну историю

Самая частая проблема начинающих — желание показать весь проект сразу, потому что «я же старался». Это нормально (мы все хотим, чтобы нас погладили по коду), но демо работает иначе: это короткая история с началом, действием и концом. Как в хорошем анекдоте: если надо объяснять 10 минут — это уже не анекдот, а лекция по истории юмора.

Сценарий удобно держать в голове как мини‑протокол:

  1. у вас есть исходное состояние (пустой список задач или несколько задач),
  2. вы выполняете 2–4 действия,
  3. каждое действие даёт наблюдаемый результат,
  4. вы один раз показываете, как система ведёт себя при ошибке,
  5. вы заканчиваете на «успешном» финале (задача создана/выполнена/получена по API).

Чтобы это было проще повторять, удобно сделать демо в виде «таймлайна». Не списком в воздухе, а таблицей — она дисциплинирует и вас, и рассказ.

Время Шаг демо Что запускаем Что проверяем глазами
0:00–0:30 Pitch речь (3–5 предложений) ясно, что за проект и зачем
0:30–2:30 CLI happy path
tasker add
tasker list
tasker done
stdout = результат, exit code = 0
2:30–3:30 CLI ошибка неправильная команда/аргумент stderr = ошибка/usage, exit code = 2
3:30–6:30 HTTP happy path
GET/POST
curl
статусы 200/201, JSON корректен
6:30–8:00 HTTP ошибка
400/404
+ error envelope
единый формат ошибок, fields для validation
8:00–10:00 Сигналы качества
go test ./...
request_id
проект «взрослый», не только «работает»

В этой лекции мы разберём, как быстро подготовить каждую часть, и какие маленькие кусочки кода помогают удерживать контракт (чтобы ваш проект не превращался в «оно сегодня так себя ведёт, а завтра — посмотрим»).

3. Pitch: короткое вступление

Смешно, но факт: люди чаще всего «проваливают» демо не на коде и даже не на баге, а на первом объяснении. Когда вы начинаете с архитектуры и папок, слушатель не понимает, что вообще происходит, и мозг уходит «в энергосбережение». Pitch нужен, чтобы мозг слушателя понял: «а, я понял задачу и критерии качества».

Pitch должен быть коротким и скучным (в хорошем смысле). Пример для 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

Вместо десяти команд покажите три:

  1. «Создать задачу» (happy path).
  2. «Показать список» (стабильный вывод, сортировка/порядок — если есть).
  3. «Сломать ввод» (ошибка, 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 запроса. Например:

  1. GET /health200 OK → текст ok или JSON (что выбрали).
  2. POST /api/v1/tasks201 Created → JSON с задачей и id.
  3. GET /api/v1/tasks/999404 Not Found → error envelope.
  4. 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 это особенно естественно: инструменты простые, а дисциплина читается по мелочам.

При этом важно не уходить в «show off». Демо — не соревнование «кто больше флагов go test знает». Лучше показать один‑два сильных сигнала.

Хороший набор на 5–10 минут выглядит так:

  • go test ./... проходит (можно показать в конце одной строкой).
  • Вывод CLI/HTTP стабилен (без случайного порядка).
  • Ошибки контрактные: в CLI есть 0/1/2, в HTTP — статусы + envelope.
  • Внутренние ошибки наружу не сливаются (5xx всегда internal error).
  • Есть request_id как минимальная наблюдаемость.

Если вы хотите добавить ещё один штрих (только один!), покажите один unit‑тест на парсинг id или на маппинг ошибок в exit code. Это быстро и «по делу».

Мини‑пример: маппинг ошибки в 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 ошибки «плавают» по формату, потому что каждый handler пишет по‑своему.
Сегодня у вас {"message":"bad"}, завтра {"error":"bad"}, послезавтра http.Error. Клиенту от этого не легче. В демо достаточно показать один error envelope и один writeError, чтобы стало ясно: формат стабилен, тестируем, поддерживаем.

Ошибка №4: на 500‑класс вы отдаёте клиенту err.Error().
Это выглядит честно, но на практике почти всегда плохо: вы раскрываете внутренние детали и делаете контракт нестабильным. В Go ошибки часто оборачиваются и несут много контекста, и этот контекст полезен вам в логах, но не клиенту. В демо лучше показать обратное: для 5xx сообщение фиксированное, детали — в логах.

Ошибка №5: вы показываете только happy path, и создаётся ощущение «оно работает, пока не трогать».
Один контролируемый негативный сценарий (CLI usage или HTTP validation error) резко повышает доверие. Это как краш‑тест: не потому что вы любите аварии, а потому что вы заранее подумали о реальности.

1
Задача
Go SELF, 72 уровень, 0 лекция
Недоступна
Демо питч
Демо питч
1
Задача
Go SELF, 72 уровень, 0 лекция
Недоступна
Командный каркас
Командный каркас
1
Задача
Go SELF, 72 уровень, 0 лекция
Недоступна
Контракт ошибок
Контракт ошибок
1
Задача
Go SELF, 72 уровень, 0 лекция
Недоступна
Трекер запросов
Трекер запросов
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ