Любой разработчик, который хоть раз писал консольные утилиты, сталкивался с этой болью: куча однотипного кода, проверяющего корректность аргументов. Одни опции зависят от других, некоторые взаимоисключающие, третьи работают только в определённой среде. В итоге код валидации занимает порой до трети программы и дублируется из проекта в проект.
Хонг Минхи в статье 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 подсветит все места, где нужно поправить код.
📚 Ссылки
- Оригинальная статья: Stop writing CLI validation. Parse it right the first time