понедельник, марта 31, 2008

Console Application - дурное дело, не хитрое?

В больших проектах часто случается создавать консольные приложения для использования их в качестве утилит в процессе разработки, развертывания или эксплуатации основного приложения.
Обычно такие утилиты создаются в пожарном порядке, буквально «на коленке» в качестве заплаток, для того чтобы решить срочную конкретную горящую проблему. Но, как известно, нет ничего более постоянного, чем временное. Созданные таким образом утилиты постепенно становятся важной частью системы, либо процесса ее развертывания и настройки.
Постоянное статус консольных утилит подразумевает их использование в пакетах и сценариях автоматизации (bat и cmd файлы), а это накладывает ряд специфических требований. Вот несколько нехитрых правил, соблюдение которых поможет создавать надежные и удобные консольные утилиты:

  1. Измените сигнатуру метода Main. Вместо static void Main(string[] args) используйте static Int32 Main(string[] args). Значение Int32, возвращаемое методом будет использовано, как Exit code приложения. Это позволит вашему приложению просигнализировать вызывающему коду о характере возникших проблем. Обычно в качестве Exit code, сигнализирующего об успешном выполнении используют значение 0. Значения отличные от нуля сигнализируют о наличии ошибок.

  2. Поместите все тело метода Main() в блок try – catch. Необработанные исключения делают невозможным использование вашего консольного приложения в пакетах и сценариях. В блоках catch вы можете возвращать различные значения (отличные от нуля), которые расскажут вызывающему коду о характере произошедших ошибок. Для обработки ошибок вы можете использовать событие AppDomain.CurrentDomain.UnhandledException, но, на мой взгляд, try – catch в Main() и проще и нагляднее.

  3. Не используйте чтение с консоли (Console.Read() Console.ReadKey() Console.ReadLine()) потому, что это может оказаться неприятным сюрпризом для тех, кто использует ваше консольное приложение в пакете для автоматизации каких либо действий. Для ввода значений используйте либо аргументы командной строки, либо (если значений очень много) файлы.

  4. Предусмотрите выдачу help-a по параметру /? Это существенно облегчит жизнь тем, кто будет использовать утилиту, а от вас не потребует много труда на реализацию.

  5. Не скупитесь на Console.WriteLine() в коде своей утилиты. Пусть она подробно рассказывает о том, что делает, особенно при выполнении длительных операций. При обработке исключений используйте Console.WriteLine(exception.ToString()). Таким образом, вы выведите максимальное количество информации об ошибке, в т.ч. и stack trace. Для консольной утилиты это самое то, что надо.

2 комментария:

blacklion комментирует...

Хмм. Странные советы.

1. Неужели этого кто-то не знает?!
2. Да, разумно.
3. А почему не упомянуты stdin/stdout? Это удобнее файлов на два порядка, так как позволяет использовать КАК именованные файлы (через перенаправление ввода-вывода), так и пайпы. Которые в Win есть и отлично работают.
4. Да.
5. Во-первых, не упомнуто, что весь этот мусор должен идти на stderr. См. пункт 3. Но даже это мелочи по сравнению с тем, что правильный подход ровно противоположный: правильно отработавшая утилита не должна продуцировать ничего кроме непосредственного результата. Вся эта отладочная печть о работе -- ТОЛЬКО при задании ключей -v или --verbose, (извините, /v), причём кумулятивно нарастающим. В случае запуска без таких ключей правильная консольная утилита должна что-то выводить на экран (читай: в stderr, в stdout должны быть или выходные ДАННЫЕ или молчание, если вывод идёт в файл) ТОЛЬКО если что-то сломалось. Сделал -- промолчи, не мусори.

Вот так.

Sergey Rozovik комментирует...

to blacklion
>Хмм. Странные советы.
>1. Неужели этого кто-то не знает?!

Многие не знают, к сожалению. Либо относятся к написанию подобных утилит спустя рукава: обернули какую то готовую функцию системы в exe-шник, и - готово.

А дополнения хорошие, спасибо. Правда это уже несколько иной уровень погружения в проблему.