Найти в Дзене
Цифровая Переплавка

⚡ Зачем писать валидацию для CLI, если можно парсить правильно сразу?

Оглавление
Сравнение традиционной CLI-валидации и подхода с парсер-комбинаторами, где код на TypeScript задаёт строгие зависимости и исключения между опциями.
Сравнение традиционной CLI-валидации и подхода с парсер-комбинаторами, где код на TypeScript задаёт строгие зависимости и исключения между опциями.

Любой разработчик, который хоть раз писал консольные утилиты, сталкивался с этой болью: куча однотипного кода, проверяющего корректность аргументов. Одни опции зависят от других, некоторые взаимоисключающие, третьи работают только в определённой среде. В итоге код валидации занимает порой до трети программы и дублируется из проекта в проект.

Хонг Минхи в статье Stop writing CLI validation. Parse it right the first time предложил радикальное решение: не валидировать, а сразу парсить данные в корректный тип.

🔍 Проблема

Большинство CLI-парсеров работает так:

🧳 Сначала аргументы складываются в «мешок свойств» (например, opts.server, opts.port, opts.json).
🛠 Затем десятками if-ов проверяется:

  • указал ли пользователь --port без --server;
  • не выбрал ли он одновременно --json и --yaml;
  • не забыл ли передать --auth в проде.

Такой подход похож на то, как если бы мы читали JSON «как есть», а потом вручную проверяли каждое поле. Но ведь для JSON давно есть библиотеки вроде Zod, которые сразу преобразуют данные в нужную форму и отсекают всё лишнее.

🧩 Решение — парсер-комбинаторы

Минхи написал библиотеку Optique, вдохновившись Haskell-решением optparse-applicative. Суть проста:

  • 📦 object() — описывает набор опций;
  • 🔀 or() — гарантирует выбор одного из вариантов;
  • ⚙️ withDefault() — задаёт значения по умолчанию;
  • 🔗 Все парсеры можно комбинировать и вкладывать друг в друга.

Главное: результатом становится строго типизированная структура в TypeScript. Недопустимые комбинации невозможны уже на этапе компиляции.

🧪 Примеры

  • 🌐 Зависимые опции: если server = false, то port просто не существует в типе.
  • 📄 Взаимоисключающие форматы: json | yaml | xml вместо трёх булевых флагов.
  • 🏭 Разные окружения: для prod обязательно --auth, для dev доступен --debug, и перепутать невозможно.

💡 Мой взгляд

Мне это очень близко: в продакшн-CLI часто не баги в логике, а именно ошибки валидации. Человек запускает утилиту, у него «конфликт параметров», и ты тратишь часы на отладку условий.

С Optique идея другая: если программа скомпилировалась, значит её CLI-контракты уже корректны. Это напоминает подход «type-driven development», где типовая система становится защитой от целого класса ошибок.

Кроме того, это открывает простор для экспериментов:

🚀 проще добавлять новые режимы и опции;
🧹 код становится короче и чище (валидацию просто удаляют!);
🔄 рефакторинг перестаёт быть страшным — TypeScript подсветит все места, где нужно поправить код.

📚 Ссылки