Содержание

Терминальный интерфейс в Go

Терминальный интерфейс - это тоже пользовательский интерфейс. У него нет кнопок и карточек, но есть ввод, вывод, ошибки, цвета, прогресс, таблицы и ожидания пользователя. Хороший CLI можно использовать в скриптах, читать глазами и запускать повторно без сюрпризов.

Новички часто думают: "это же терминал, достаточно fmt.Println". Для маленькой программы этого правда хватает. Но как только появляется файл, сеть, несколько режимов, ошибки или длительная операция, качество вывода начинает сильно влиять на удобство.

stdout и stderr

У процесса есть стандартные потоки:

  • stdin - ввод;
  • stdout - обычный результат;
  • stderr - ошибки и диагностические сообщения.

Результат пишите в stdout, ошибки - в stderr:

fmt.Println(result) fmt.Fprintln(os.Stderr, "ошибка: файл не найден")

Так программу можно использовать в пайпах:

mytool input.txt > output.txt

Если ошибка тоже попадет в stdout, она окажется в output.txt и может сломать следующий шаг скрипта.

Exit codes

Exit code сообщает операционной системе, успешно ли завершилась программа.

  • 0 - успех;
  • любое ненулевое значение - ошибка.

Обычно в main делают так:

func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, "error:", err) os.Exit(1) } }

Это удобно для тестов, CI и shell-скриптов:

if mytool ./data; then echo "ok" else echo "failed" fi

Сообщения об ошибках

Плохая ошибка:

failed

Лучше:

error: open config "config.yaml": no such file or directory

Еще лучше, если пользователь понимает следующий шаг:

error: config "config.yaml" not found hint: run `mytool init` or pass --config path/to/config.yaml

Не нужно превращать каждую ошибку в длинную инструкцию, но для частых проблем подсказка экономит много времени.

ANSI-цвета

Цвет в терминале задается escape-последовательностями:

const red = "\033[31m" const reset = "\033[0m" fmt.Println(red + "Ошибка" + reset)

Вручную писать ANSI-коды неудобно. Для учебных проектов можно использовать библиотеки вроде fatih/color, но важно понимать принцип: программа печатает специальные символы, а терминал интерпретирует их как цвет или стиль.

Хороший стиль:

ERROR config not found

А цвет может только усилить сигнал.

Таблицы

Если нужно вывести несколько колонок, не выравнивайте их вручную пробелами. Используйте text/tabwriter:

writer := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) fmt.Fprintln(writer, "NAME\tSTATUS\tSIZE") fmt.Fprintln(writer, "app.log\tok\t12 KB") fmt.Fprintln(writer, "data.csv\terror\t1 MB") if err := writer.Flush(); err != nil { return err }

\t разделяет колонки, а tabwriter красиво выравнивает их при выводе.

Таблицы хороши для людей. Для машинного вывода лучше JSON:

mytool list --format json

Так программу можно использовать в других скриптах.

Progress bar

Прогресс полезен, если операция занимает больше пары секунд:

bar := progressbar.Default(int64(len(files))) for _, file := range files { if err := process(file); err != nil { return err } bar.Add(1) }

Если операция быстрая, progress bar только создает шум. Также подумайте о CI: анимированный прогресс может засорять логи. Часто добавляют флаг --quiet или автоматически отключают анимацию, если вывод не является терминалом.

Quiet и verbose

У CLI часто есть режимы вывода:

  • обычный режим: только полезная информация;
  • --quiet: минимум вывода;
  • --verbose: подробные диагностические сообщения.

Пример:

mytool import data.csv mytool import data.csv --quiet mytool import data.csv --verbose

Не заставляйте пользователя читать десятки строк логов при успешном запуске. Но дайте возможность получить детали, когда что-то идет не так.

Интерактивные TUI

TUI - terminal user interface. Это интерфейс внутри терминала: меню, списки, формы, выделение выбранного элемента, обработка клавиш.

Для простых игр и интерактивных программ часто используют:

  • низкоуровневые библиотеки для управления экраном и клавишами;
  • Bubble Tea для архитектуры Model -> Update -> View;
  • ANSI и box-drawing символы для рамок.

У Bubble Tea приложение обычно устроено так:

type Model struct { selected int items []string } func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { case "q": return m, tea.Quit case "down": m.selected++ case "up": m.selected-- } } return m, nil } func (m Model) View() string { return fmt.Sprintf("selected: %d\n", m.selected) }

Model хранит состояние. Update меняет состояние по событиям. View превращает состояние в строку для терминала. Это похоже на frontend-подход, только результатом является текст.

Горячие клавиши и выход

Интерактивная программа должна иметь очевидный выход:

  • q;
  • Esc;
  • Ctrl+C.

Если пользователь не понимает, как выйти, интерфейс ощущается сломанным. Внизу экрана часто показывают короткую строку:

↑/↓ move • enter select • q quit

Это не учебный текст внутри приложения, а рабочая подсказка интерфейса.

Хороший CLI UX

  1. --help показывает назначение, примеры и флаги.
  2. Ошибка говорит, что случилось и как исправить.
  3. Долгая операция показывает прогресс или статус.
  4. Есть --quiet или --verbose, если вывода много.
  5. Exit code отражает результат.
  6. Машинный вывод доступен через JSON или другой стабильный формат.
  7. Интерактивный режим не мешает неинтерактивному использованию.

Мини-чеклист

  1. Ошибки идут в stderr.
  2. Результат идет в stdout.
  3. При ошибке программа возвращает ненулевой exit code.
  4. Цвета не ломают читаемость.
  5. Таблицы не собираются пробелами вручную.
  6. У долгих операций есть понятный статус.
  7. Интерактивный режим можно выйти через q, Esc или Ctrl+C.
  8. --help содержит реальные примеры запуска.

Следующий шаг после статьи

Закрепите тему в реальном проекте и продолжайте обучение на курсах Praxis.

Продолжить изучение

Выбери следующую статью по маршруту или углубись в смежную тему.

Похожие статьи