$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогCLI как инструмент команды: UX текста, exit codes и обработка конфигов

CLI как инструмент команды: UX текста, exit codes и обработка конфигов

$ sudo teach IT
·25 июля 2026 г.·10 мин·75
CLI как инструмент команды: UX текста, exit codes и обработка конфигов

Сделаем CLI “дружелюбным”: читабельные сообщения, help-текст, стабильные exit codes и корректная загрузка конфигов. Разберём сценарии ошибок, которые чаще всего забывают в небольших утилитах.

Содержание
UX текста: сообщения, которые экономят времяДружелюбность начинается с формулировокРазделяйте stderr и stdoutHelp-текст: больше, чем “описание команды”Три уровня сообщений: info/warn/errorExit codes: контракт, который важен даже для маленькой утилитыПочему exit codes — часть UXБазовая схема: Unix-подходExit codes и исключения: не теряйте семантикуПрактический пример: Python и ясные exit codesСтратегия для “неожиданного” — без сюрпризовОбработка конфигов: от загрузки до валидацииМодель приоритетов: где правда живётГде искать конфигЧёткие ошибки конфигурацииВалидация структуры: лучше “раньше”, чем “позже”Слияние конфигурации: как избежать “магии”Конфиг-диагностика: “что именно было загружено”Типичные ошибки, которые встречаются почти всегда1) Нечёткие или отсутствующие категории ошибок2) Ошибки валидации, которые проявляются слишком поздно3) Конфиг, который “тихо игнорируется”4) Смешивание диагностики и пользовательского вывода5) Help, который не соответствует реальному синтаксису6) Логи/trace по умолчаниюСборка требований: мини-спецификация “дружелюбного CLI”Про подход к реализации:

Хороший CLI — это не просто “утилита, которая работает”. Это интерфейс между пользователем (и автоматизацией) и вашим инструментом. От качества CLI зависит, будет ли команда доверять вам в проде, как быстро операторы поймут причину сбоя и насколько предсказуемо поведёт себя скрипт в пайплайне.

В этой статье разберём три слоя “дружелюбия” CLI:

  1. UX текста: как писать сообщения об ошибках и help-текст так, чтобы их читали.
  2. Exit codes: как сделать поведение стабильным и пригодным для автоматизации.
  3. Конфиги: как корректно загружать настройки и обрабатывать ошибки конфигурации — в том числе в небольших утилитах, где этому обычно уделяют меньше внимания.

Параллельно будем собирать “чеклист” и типовые схемы, которые часто забывают.


UX текста: сообщения, которые экономят время

Дружелюбность начинается с формулировок

Утилиты обычно пишут так:

  • “Error”
  • “Something went wrong”
  • “Invalid input”

Это почти всегда плохой UX: пользователь не знает, что именно сломалось, где смотреть, что сделать дальше.

Минимальная дисциплина для CLI:

  1. Сообщение должно быть конкретным. Не “ошибка”, а “не удалось открыть файл”, “невалидное значение для параметра X”.
  2. Сообщение должно указывать на действие. “Проверьте путь”, “укажите флаг…”, “значение должно быть в диапазоне…”.
  3. Сообщение должно иметь контекст. Включайте имя команды, параметр, значение (аккуратно, если есть секреты), конкретное исключение или причину.

Пример плохого сообщения:

Error

Пример хорошего:

Не удалось прочитать конфиг: /etc/mytool/config.toml (permission denied). Проверьте права доступа или укажите другой путь через --config.

Обратите внимание: здесь три уровня полезности одновременно: что сломалось, где и что делать.

Разделяйте stderr и stdout

Правило на практике:

  • stdout — вывод полезных данных (результаты команды).
  • stderr — диагностические сообщения (ошибки, предупреждения, логи уровня debug/trace — по договорённости).

Тогда пайплайны, обработчики и пользователи смогут отделять данные от диагностики:

code
result="$(mytool export --format json 2> /tmp/err.txt)"
if [ $? -ne 0 ]; then
  cat /tmp/err.txt >&2
  exit 1
fi

Если диагностика смешивается с stdout, парсинг становится сложным и ломается.

Help-текст: больше, чем “описание команды”

Help — это основной документ CLI. Его читают в двух ситуациях:

  1. Когда пользователь впервые сталкивается с утилитой.
  2. Когда пользователь “примерно помнит” синтаксис, но ошибается.

Поэтому help должен отвечать на вопросы:

  • Какие команды/подкоманды доступны?
  • Какие флаги обязательны?
  • Какие значения допустимы?
  • Как ведёт себя инструмент по умолчанию?
  • Где лежат файлы конфигурации и переменные окружения?

Хороший help обычно включает:

  • Короткую сводку: 1–2 предложения.
  • Список параметров: для каждого — тип, значение по умолчанию, ограничения.
  • Примеры: 2–5 коротких примеров команд (не больше, иначе help превращается в справочник по всему продукту).
  • Ссылку на конфиг: где искать конфиг, как его переопределить.

Пример структуры:

  • mytool [OPTIONS] COMMAND
  • Commands: init, run, status
  • Options: --config PATH Путь к конфигу (по умолчанию: ~/.config/mytool/config.toml) --verbose Увеличить логирование --version Показать версию

Три уровня сообщений: info/warn/error

Если ваш CLI содержит сложные сценарии, вводите предсказуемую “иерархию” сообщений:

  • info: “делаем X”, “загружено Y записей” — только когда это полезно;
  • warn: “параметр игнорируется”, “найден старый формат конфигурации”;
  • error: прекращаем выполнение из-за ошибки.

Это может быть как цветной вывод (по желанию), так и простой текст. Главное — единый стиль и смысл.


Exit codes: контракт, который важен даже для маленькой утилиты

Почему exit codes — часть UX

Команды CLI часто вызывают другие программы: cron, CI, bash-скрипты, операторы руками. Для автоматизации exit code — главный контракт.

Если ваш инструмент возвращает “всё подряд одинаково”, то в автоматике нельзя корректно различать “не найден файл” и “внутренняя ошибка”.

Минимальная цель: стабильная семантика exit codes и корректное завершение.

Базовая схема: Unix-подход

Классическая договорённость в Unix-like системах:

  • 0 — успех
  • 1 — общая ошибка
  • 2 — ошибки использования (невалидные аргументы/команда/флаги)
  • 3+ — специфические категории

Примените это так, чтобы в будущем было куда расширяться.

Пример:

  • 0 — ок
  • 2 — неверные аргументы пользователя (валидация флагов/схемы CLI)
  • 3 — конфиг не загружен (файл не найден, не парсится, отсутствуют обязательные поля)
  • 4 — недоступны внешние ресурсы (сеть, токены, permission)
  • 5 — логическая ошибка/состояние (например, “команда не применима в текущем режиме”)
  • 125 — “скрипт/команда не поддерживается” (можно использовать как “internal usage error”, но лучше придерживаться единого подхода)
  • 126/127 — часто зарезервированы оболочкой; в своих утилитах обычно не нужны
  • 255 — нераспознанная внутренняя ошибка (если вы используете верхний уровень обработчика)

Exit codes и исключения: не теряйте семантику

Частая ошибка: ловят исключение и делают sys.exit(1) везде. Итог — пользователи видят одинаковый код, а разработчикам нечем различать сценарии.

Лучше создать слой “кодирования ошибки”:

  1. Ошибки валидации аргументов → 2
  2. Ошибки конфигурации → 3
  3. Ошибки внешних систем → 4
  4. Внутренняя ошибка → 1 или 255 (в зависимости от вашей философии)

Практический пример: Python и ясные exit codes

Ниже пример “скелета” с аккуратной категоризацией. Это не привязано к конкретной библиотеке CLI, но показывает принцип.

code
import sys
from dataclasses import dataclass

@dataclass
class CliError(Exception):
    message: str
    code: int = 1

def main(argv: list[str]) -> int:
    try:
        # 1) Пример: парсинг аргументов
        args = parse_args(argv)  # может бросить ValueError

        # 2) Пример: загрузка конфигов
        config = load_config(args.config_path)  # может бросить ConfigError

        # 3) Пример: выполнение
        run_with_config(config, args)

        return 0
    except ValueError as e:
        raise CliError(str(e), code=2)
    except ConfigError as e:
        raise CliError(f"Не удалось загрузить конфиг: {e}", code=3)
    except ExternalResourceError as e:
        raise CliError(f"Ошибка доступа к ресурсу: {e}", code=4)
    except CliError as e:
        print(e.message, file=sys.stderr)
        return e.code
    except Exception as e:
        # Логируйте подробности (traceback) по желанию в verbose/debug
        print(f"Внутренняя ошибка: {e.__class__.__name__}", file=sys.stderr)
        return 255

# Заглушки, чтобы код был читаемым
class ConfigError(Exception): pass
class ExternalResourceError(Exception): pass

def parse_args(argv):
    # В реальном коде: валидация и синтаксис
    if "--unknown" in argv:
        raise ValueError("Неизвестный параметр --unknown")
    return type("Args", (), {"config_path": "~/.config/mytool/config.toml", "verbose": False})()

def load_config(path):
    # В реальном коде: чтение и парсинг
    if "broken" in path:
        raise ConfigError("парсер не смог прочитать формат")
    return {"mode": "fast"}

def run_with_config(config, args):
    return

if __name__ == "__main__":
    raise SystemExit(main(sys.argv[1:]))

Ключевые моменты:

  • Исключения классифицируются в одном месте.
  • Пользователь получает понятное сообщение (в stderr).
  • Exit code соответствует категории.

Стратегия для “неожиданного” — без сюрпризов

Если в вашей утилите есть общий catch-all, убедитесь, что он не “прикрывает” ошибки конфигурации и валидации. Иначе вы снова получите код 1 на всё. Нормальная практика: сначала ловить “известные” классы проблем, затем — общий fallback.


Обработка конфигов: от загрузки до валидации

Конфиг — источник большинства пользовательских проблем. Часто это:

  • неверный путь,
  • неверный формат,
  • устаревшая версия схемы,
  • конфиг загружен частично,
  • конфиг конфликтует с параметрами CLI и переменными окружения.

В небольших утилитах конфигурацию обычно делают “как получится”: прочитал файл, распарсил, использовал. И забывают про ошибки. Мы исправим.

Модель приоритетов: где правда живёт

Сначала зафиксируйте приоритет:

  1. CLI параметры (флаги команды)
  2. Переменные окружения
  3. Конфиг-файл
  4. Значения по умолчанию

Это самая распространённая и предсказуемая модель. Дальше — документируйте её в help и/или в отдельном разделе конфигурации.

Если этого не сделать, пользователи будут удивляться: “я поставил в конфиге одно, а работает другое”.

Где искать конфиг

Подумайте о том, что должно происходить в типовых случаях:

  • Пользователь не указал --config.
  • Пользователь указал относительный путь.
  • Путь содержит ~.
  • Утилита должна поддерживать “конфиг по умолчанию”, чтобы не ломать новичков.

Обычно удобно:

  • По умолчанию искать в ~/.config/mytool/config.toml (или платформенно корректный XDG).
  • Разрешить переопределение --config PATH.
  • Если конфиг не найден — либо ошибка (если без него нельзя), либо fallback на defaults (если утилита может работать “в базовом режиме”).

Чёткие ошибки конфигурации

Ошибка загрузки конфигурации должна:

  1. Указывать, какой файл пытались читать.
  2. Давать причину (not found, permission, parse error).
  3. Подсказывать дальнейшие действия (“используйте --config”, “проверьте формат TOML”).

Не выдавайте стек-трейс пользователю по умолчанию. Для debug — пусть есть флаг --verbose или --debug, который выводит детали.

Пример сообщений:

  • “Конфиг не найден: /home/user/.config/mytool/config.toml. Укажите путь через --config или создайте файл по шаблону.”
  • “Не удалось распарсить TOML в /etc/mytool/config.toml: строка 12, колонка 5 — ожидается ‘=’.”

Если вы используете библиотеку парсинга, постарайтесь сохранить позицию ошибки.

Валидация структуры: лучше “раньше”, чем “позже”

Даже если парсинг прошёл, конфиг может быть семантически неверным:

  • отсутствует обязательное поле,
  • тип не тот,
  • значения вне допустимого диапазона,
  • “режим” не соответствует схеме.

Хорошая практика: валидация конфигурации выполняется сразу после загрузки, до выполнения логики.

Технически это означает:

  • создать схему (валидатор),
  • валидировать,
  • при ошибке сформировать понятное сообщение для пользователя.

Пример подхода с dataclass и валидацией (идея, не привязка к библиотеке):

code
from dataclasses import dataclass

@dataclass
class Config:
    mode: str
    timeout_seconds: int

def validate_config(raw: dict) -> Config:
    allowed_modes = {"fast", "safe"}

    mode = raw.get("mode")
    if mode not in allowed_modes:
        raise ConfigError(f"Поле mode должно быть одним из {sorted(allowed_modes)}, получено: {mode!r}")

    timeout = raw.get("timeout_seconds")
    if not isinstance(timeout, int) or timeout <= 0:
        raise ConfigError(f"Поле timeout_seconds должно быть положительным целым, получено: {timeout!r}")

    return Config(mode=mode, timeout_seconds=timeout)

Когда валидация добавлена, вы перестаёте ловить “неочевидные падения” в середине выполнения.

Слияние конфигурации: как избежать “магии”

Если вы реализуете слияние конфигурации (config + overrides), важно сделать поведение предсказуемым.

Возможные варианты:

  • “CLI override” перезаписывает значения из конфига.
  • “config override” перезаписывает defaults.
  • частичное слияние словарей (merge) — если схема это позволяет.

Подводный камень: если вы делаете глубинный merge без правил, пользователь может думать, что полностью переопределил блок, а на деле значения остались смешанными.

Решение:

  • Сначала определите, где допустим merge, а где нет.
  • Для сложных структур используйте явные правила: например, массивы всегда заменяются целиком, а словари мерджатся по ключам.

Конфиг-диагностика: “что именно было загружено”

Когда что-то пошло не так, пользователю полезно понять:

  • какой конфиг использован;
  • какие значения были применены (хотя бы ключевые);
  • откуда взялись переопределения.

Даже если вы не показываете весь конфиг, добавьте в --verbose вывод “профиля”:

  • используемый путь,
  • версия схемы,
  • применённый режим,
  • включенные/выключенные ключи.

Это снижает время диагностики в реальной эксплуатации.


Типичные ошибки, которые встречаются почти всегда

1) Нечёткие или отсутствующие категории ошибок

Если ваш CLI всегда возвращает 1, вы лишаете пользователя и автоматизацию возможности правильно реагировать. Минимум нужно различать “usage/arguments” и “runtime/config”.

2) Ошибки валидации, которые проявляются слишком поздно

Если вы проверяете допустимость значений только в момент выполнения, вы получаете:

  • непредсказуемые падения,
  • длинные “время до ошибки”,
  • непонятные сообщения.

Валидация должна быть “в начале”.

3) Конфиг, который “тихо игнорируется”

Классический кейс: пользователь указал --config, но код по ошибке использует дефолтный путь. Или конфиг не загружился, но инструмент продолжил с defaults, “потому что так проще”.

Так делать нельзя: пользователь должен понимать, что конфиг не применился. Если fallback допустим — проговорите это в сообщениях (например, warn).

4) Смешивание диагностики и пользовательского вывода

Если вы печатаете ошибки в stdout, пользователи не смогут легко отделять данные результата от диагностики. Это особенно больно для команд, которые возвращают JSON/таблицы.

5) Help, который не соответствует реальному синтаксису

Help должен обновляться вместе с кодом. Если help врёт, пользователи начинают “догадываться”, что разрушает доверие.

6) Логи/trace по умолчанию

Если инструмент всегда печатает traceback при ошибке, вы даёте пользователю много технического шума и мало практической пользы. Лучше:

  • короткое сообщение всегда,
  • детали — только в --verbose/--debug.

Сборка требований: мини-спецификация “дружелюбного CLI”

Сформулируем практический список, который можно использовать как чеклист на ревью:

  1. Help
    • есть короткое описание,
    • параметры описаны с типами/значениями,
    • есть примеры,
    • описаны источники конфигурации и приоритеты.
  2. Ошибки
    • сообщения конкретные и actionable,
    • stderr используется для диагностики,
    • есть уровни info/warn/error (хотя бы логически).
  3. Exit codes
    • 0 успех,
    • 2 — usage/валидация аргументов,
    • 3 — конфиг,
    • 4 — внешние ресурсы,
    • 255 — неожиданные ошибки.
  4. Конфиги
    • фиксированный приоритет CLI/env/config/defaults,
    • корректные правила поиска и обработки путей,
    • валидация схемы после парсинга,
    • в --verbose показывается, что именно было загружено.
  5. Предсказуемость
    • отсутствие “тихих” fallback там, где это не согласовано,
    • единообразный формат сообщений по всем командам.

Про подход к реализации:

Войдите, чтобы поставить лайк и оставить комментарий.

Автор

$ sudo teach IT

Typer PRO

Курс по теме

Typer PRO

С нуля до уверенной работы с CLI-проектами на Python. Подходит для начинающих и тех, кто уже пишет на Python. Разберитесь с Typer раз и навсегда — и перестаньте копировать команды вслепую.

Открыть курс

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

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу
Solidity DLC - поймай, если сможешь

Solidity DLC - поймай, если сможешь

Аудит смарт-контрактов на реальных взломах. The DAO, Parity, Wormhole, Ronin, Euler и ещё 7 разборов: что пошло не так, сколько потеряли и что бы спасло. Атаки и защиты на Foundry, план входа в профессию аудитора.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Solidity pro - всё или ничего

Solidity pro - всё или ничего

Как пишут контракты, которые работают в основной сети. Наследование и OpenZeppelin, паттерны продакшена, оптимизация газа, ABI, delegatecall и подписи. 7 модулей, 33 урока, а в финале собственное dApp: мини-DAO с токеном, голосованием и казной.

Перейти к курсу

Другие статьи

Пишем CLI для разработчика на Python: команды, конфиги и корректные exit codes по стандарту проекта
пишем

Пишем CLI для разработчика на Python: команды, конфиги и корректные exit codes по стандарту проекта

Построим удобный интерфейс командной утилиты: структура подкоманд, чтение конфигов, валидация входных параметров и единая логика ошибок с понятными exit code. Сфокусируемся на UX для тех, кто запускает CLI в CI и в скриптах.

15 сентября 2026 г.
1380
Как написать свой первый CLI-инструмент на Python с Typer: от идеи до публикации в PyPI
написать

Как написать свой первый CLI-инструмент на Python с Typer: от идеи до публикации в PyPI

Создаём полноценный CLI-инструмент с командами, флагами, автодополнением и красивым выводом через Rich. Разбираем структуру проекта и публикуем пакет в PyPI шаг за шагом.

18 июля 2026 г.
880
Как выучить программирование самому и бесплатно: с чего начать и в каком порядке
выучить

Как выучить программирование самому и бесплатно: с чего начать и в каком порядке

Разберём понятный маршрут для новичка: как выбрать первый язык, что учить в первую очередь, какие мини-проекты делать и как не забросить занятия. Дам бесплатные ориентиры и план, чтобы знания складывались в портфолио.

21 сентября 2026 г.
1960
Как начать с HTML и сделать первую страницу: пошагово и без сложных слов
html

Как начать с HTML и сделать первую страницу: пошагово и без сложных слов

Разберём, что нужно новичку, чтобы за 1–2 вечера собрать свою первую веб-страницу: структура HTML, базовые теги, ссылки и картинки, плюс простой чек-лист, как понять, что страница работает правильно.

2 ноября 2026 г.
20
Cargo для продакшена: workspaces, фичи и стратегия версий зависимостей
cargo

Cargo для продакшена: workspaces, фичи и стратегия версий зависимостей

Разберём, как организовать Rust workspace, управлять optional features, фиксировать версии библиотек и уменьшать “дрейф” зависимостей при командной разработке и CI.

18 сентября 2026 г.
1960
Что такое HTML и чем он отличается от CSS — объясню простыми словами и на примерах
такое

Что такое HTML и чем он отличается от CSS — объясню простыми словами и на примерах

Разберём, из чего состоит веб-страница: что делает HTML (смысл и структура) и что делает CSS (оформление). В конце соберёте мини-страницу и поймёте, что нужно учить в каком порядке.

30 октября 2026 г.
130

Комментарии

Станьте первым в обсуждении

Поделитесь мнением о статье или задайте вопрос автору

Содержание

UX текста: сообщения, которые экономят времяДружелюбность начинается с формулировокРазделяйте stderr и stdoutHelp-текст: больше, чем “описание команды”Три уровня сообщений: info/warn/errorExit codes: контракт, который важен даже для маленькой утилитыПочему exit codes — часть UXБазовая схема: Unix-подходExit codes и исключения: не теряйте семантикуПрактический пример: Python и ясные exit codesСтратегия для “неожиданного” — без сюрпризовОбработка конфигов: от загрузки до валидацииМодель приоритетов: где правда живётГде искать конфигЧёткие ошибки конфигурацииВалидация структуры: лучше “раньше”, чем “позже”Слияние конфигурации: как избежать “магии”Конфиг-диагностика: “что именно было загружено”Типичные ошибки, которые встречаются почти всегда1) Нечёткие или отсутствующие категории ошибок2) Ошибки валидации, которые проявляются слишком поздно3) Конфиг, который “тихо игнорируется”4) Смешивание диагностики и пользовательского вывода5) Help, который не соответствует реальному синтаксису6) Логи/trace по умолчаниюСборка требований: мини-спецификация “дружелюбного CLI”Про подход к реализации: