CLI приложения #16. Спрашивать или не спрашивать
Мы научили приложение принимать аргументы и красиво выводить результат. Но есть ситуация, которую мы обходили стороной: пользователь запустил программу и не указал то, что нужно для работы.
Вариантов поведения два, и оба варианта можно встретить в реальных инструментах.
Первый — молчаливое падение с ошибкой. Так ведёт себя git push origin без указания ветки. Второй — спросить недостающее. Так работает npm init: запускаешь без параметров и отвечаешь на вопросы.
Правильного поведения не существует, но есть правильный принцип: программа должна работать без вопросов, если ей всё передали. Спрашивать можно только то, чего не хватает, и только когда есть кому отвечать.
Вторая часть важнее первой. Представьте: ваше приложение запустили в скрипте на сервере сборки, оно не нашло нужный параметр и задало вопрос. Отвечать некому, программа ждёт ввода, сборка висит до таймаута. Отладить такое тяжело — в логе просто обрывается вывод.
Поэтому перед любым вопросом проверяем, есть ли по ту сторону человек:
if (Console.IsInputRedirected)
{
// ввод приходит не с клавиатуры — спрашивать некого
}
Свойство IsInputRedirected вернёт true, когда данные подаются из файла или из другой программы. Есть и более общая проверка от Spectre.Console, она учитывает и другие не интерактивные случаи:
AnsiConsole.Profile.Capabilities.Interactive
Собираем логику целиком:
deployCommand.SetAction(parseResult =>
{
var environment = parseResult.GetValue(environmentOption);
if (string.IsNullOrEmpty(environment))
{
if (!AnsiConsole.Profile.Capabilities.Interactive)
{
Console.Error.WriteLine("Не указано окружение. Используйте --environment");
return ExitCode.InvalidArguments;
}
environment = AnsiConsole.Ask<string>("В какое окружение разворачиваем?");
}
Console.Error.WriteLine($"Разворачиваем в {environment}");
return ExitCode.Success;
});
Три ветки, и каждая осмысленна. Параметр передали — работаем. Не передали, но человек на месте — спрашиваем. Не передали и спросить некого — внятная ошибка и ненулевой код возврата.
Важно!
Сообщение об ошибке должно подсказывать решение. Не «не указано окружение», а «не указано окружение, используйте
--environment». Человек, который запустил вашу программу из скрипта, не полезет читать справку — он прочитает первую строку ошибки и захочет сразу починить.
Отдельного упоминания заслуживают опасные операции. Удаление, перезапись, развёртывание на проде — такое принято подтверждать:
if (!force && !AnsiConsole.Confirm("Удалить все данные?", defaultValue: false))
{
Console.Error.WriteLine("Отменено пользователем");
return ExitCode.Cancelled;
}
Обратите внимание на флаг force. Опция --force или --yes — обязательный спутник любого подтверждения, иначе вашу программу невозможно использовать в автоматизации. И значение по умолчанию у опасного вопроса всегда должно быть отрицательным: пользователь, который машинально жмёт Enter, не должен потерять данные.
В следующем посте разберём, как задавать вопросы по-человечески: меню выбора, проверка ответа на лету и скрытый ввод паролей.