"Что-то пошло не так" - как правильно информировать пользователя об ошибках

Постоянно сталкиваюсь с приложениями, в том числе от Microsoft, разработчики которых не утруждают себя сообщать пользователям, что же на самом деле произошло, и почему приложение поломалось.

Наример:

  • "Что-то пошло не так"
  • "Неизвестная/неопознанная ошибка"
  • "Установка прервана"
  • "Дальнейшая работа приложения невозможна"

И это не выдранные из контекста тексты, а полноценные сообщения, которые вы хотя-бы раз в жизни видели, ну разве что в конец еще "...попробуйте позднее" еще дописать можно. И больше ничего.

Так же здесь встречаются вопросы, в которых всё очень плохо с обработкой ошибок. Не технически, а именно логически. Ссылку на этот пост можно будет давать в помощь новичкам.

К примеру, возьмем для простоты консольное приложение, которое записывает файл.

// плохой код
static void Main(string[] args)
{
    Console.Write("Введите имя файла для записи: ");
    string path = Console.ReadLine();
    try
    {
        File.WriteAllText(path, "Hello World!");
        Console.WriteLine("Файл записан!");
    }
    catch
    {
        Console.WriteLine("Что-то пошло не так!");
    }
}

Ответом можно описать, на ваше усмотрение:

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

В целом, нужен ответ на вопрос: Что делать с исключениями новичку-разработчику десктопного приложения, чтобы пользователь не растерялся при его возникновении?

(самоответа не будет, у меня тоже с этим плохо)



Ответы (2 шт):

Автор решения: Blackmeser

Для простых программ - можно сделать самоочищающееся окно лога, которое находится где-нибудь в настройках. Оптимизировать можно StringBuilder'ом и выводить только по открытию.

Для сложных коммерческих программ (или программ для "шарящих" пользователей) окно лога должно быть под рукой, возможно на какой-нибудь вкладке или по кнопке в отдельном окне. Да и MessageBox'ы для серьёзных ошибок никто не отменял.

Приличная доля пользователей не являются безмозглыми юзверями и в случае какой-нибудь проблемы они способны увидеть последнюю строчку в окне лога, да и техподдержка и администраторы, сопровождающие вашу программу будут вам петь оды, ибо не раз такое бывало, что какая-нибудь важная программа имеет бедный интерфейс и почти не ведёт лог-файлы, а потом из-за того, что часть пользователей не может к ней подключиться - пол-организации в простое, и главный админ не может разобраться что не так. Сотни/тысячи человеко-часов в день улетает в трубу, т.к. у программы нет тупого Multiline-MessageBox'а, который бы тупо выводил лог подключений.
Это пример из жизни.

Чем больше сообщений в окне лога - тем лучше (опять-же не конечному юзверю, а тому, кто потом будет разбираться с проблемами, и они точно будут, те или иные), если будет слишком много - уровни логирования.
Что касается stack-trace, если приложение в debug-режиме - то его можно и в лог интерфейса выводить.
В обещем в лог интерфейса всё, даже успешные действия.

Касательно лог файлов - туда только проблемы и процедуры запуска (чутка телеметрии не повредит), причём проблемы те, которые которые не являются внутренними проблемами, а касаются взаимодействия с внешними интерфейсами: tcp/udp подключения, запреты доступа к файлам, некорректный ввод данных, итд...

Там, где вы явно ловите ожидаемую ошибку в try-catch (например сервер недоступен) - выводите ошибку пользователю, с возможными вариантами решения, где предполагаете что может быть ошибка но по туче неизвестных причин - дополнительно stack-trace в лог-файл.

К другим способам - syslog сервер.
Ещё как вариант - дочерний процесс, который получает подробный лог от родителя (пайпы), и в случае краша родителя - выводит и логгирует в файл журнал последних действий/ошибок. Сейчас у этого подхода может есть более продвинутые альтернативы.

Если в кратце - ошибки, мешающие работе, выводить нужно всегда.
Т.к. когда у пользователя (к коим относятся и техподдержка с админами) проблемы, он действует по следующему алгоритму:

  1. Что-то делать с программой
  2. Ой, что-то не так
  3. Какая-то ошибка
  4. В чём проблема? Какова причина?
  5. Как сделать так чтоб заработало?
  6. Исправить и вернуться к пункту 0
  7. Если не получается - искать информацию на стороне: инете/у техподдержки/админа
  8. Если найдена информация в инете - к пункту 5, если обращение к ответственному лицу (условно техподдержка), способному решить проблему.
  9. Техподдержка повторяет все предыдущие действия, но вместе с тем, чтобы лезть в инет он может заглянуть в собственные инструкции (любимые всеми скрипты техподдержки с финальной фразой: "перестанавите шиндовс, перезагрузите роутер")
  10. Если пользователь обычный смертный человек - на него кладётся болт, и он либо лезет дальше штрудировать инет в поисках решения (если навыки позволяют) и скорее в конечном итоге забивает на вашу программу. Если же пользователь корпорат и техподдержка обязана решить проблему - заводится тикет и проблему уже решают более квалифицированные люди.
  11. Далее снова воссоздаётся порядок действий, люди пытаются решить проблему, лезут в инет и в последствии связываются с теми, кто продал/написал программу тем или иным способом.

На пунктах 0-3 - всё просто в интерфейсе.
3-5 - достаточно messagebox.
4-9 - смотрят активный лог.
6-9 - если пользователь не юзверь, а вполне себе энтузиаст с навыками он тут уже читает лог файл и выискивает что на каком этапе неправильно работает, и тут уже нужны какие-то подробные инструкции с потенциальным списком проблем с возможными путями решений. Может доходить до банального, например, если ваше приложение сервер и к нему нет коннекта - в инструкциях должен быть вариант: "Не забудьте пробросить порты в роутере, добавьте в исключения файрвола/антивируса".
9-10 - тут уже лог файл, который будут слать вам. (про дампы ничего не скажу, не мой уровень)
И вишенка на торте №11 - если с ходу не разберётесь, то придётся выкатывать обновления исключительно для того, чтобы программа писала дополнительную инфу (с стектрейсами и свойствами классов) в лог файл. Более того, придётся это делать для старой версии, которая официально более не поддерживается, но т.к. её купили и ей пользуются...

Более подробно можно будет сказать только основываясь на данных о назначении программы и на кого она будет ориентирована.

Если просят вылизанную сложную программу, где бы пользователь не видел информации об ошибках и ему не требовалось что-либо самому решать и всё это оффлайн без регистрации и СМС, то нужно одно из двух: армия техподдержки или плевок этому недоделанному постановщику задачи. Чаще всего это манагер, который кроме айфона ничем не пользовался, и при первой-же попытке неправильно введённого пароля бежит требовать в техподдержку (чтобы у него всё "заработало"), рыдать к админам, и поносить программеров...

Хорошая программа - не та, что прячет ошибки под вылизанным интерфейсом, а та что даёт больше возможностей для их решения (чем удобнее - тем лучше). Проблемы были, есть и будут всегда и везде. Главное чтобы была возможность их решения. В конечном итоге выбираются те программы, где проблему можно решить самостоятельно, чем те, где ждёшь неделю в ожидании пока тикет дойдёт в очереди до нужного человека, чтобы он сказал переставить винду.

И в ответ на вопрос:
Что делать с исключениями новичку-разработчику десктопного приложения, чтобы пользователь не растерялся при его возникновении?
Я бы сказал следующее: если хотите чтобы пользователь мог решить проблему сам (если это не баг в коде конечно, а проблемы с внешними ресурсами), больше проверок - лучше, например: "сервер недоступен, порт занят, файл повреждён, итд...", должными не выпрыгивать стектрейсами, а проверяться до их возникновения, с более подробной информацией в окне лога, даже когда всё ок, например "подключение успешно, файл (url) скачан с сервера за T секунд, формат .***, дескриптор открыт/закрыт, файл (C:\**1) чтение, файл (C:\**2) чтение, файл (C:\**3) чтение".

Что-то меня понесло, т.к. наболело что как прогера, когда хочется сказать "ну вот смотри последнюю строчку в лог-окне, спецом же для тебя вынес", как и админа, когда программа никак не информирует, а чинить надо т.к. люди работают, и как юзера, когда и сам для себя хочу решить проблему, а приложение - чёрный ящик. Надеюсь что не напорол чуши.

→ Ссылка
Автор решения: MSDN.WhiteKnight

Постоянно сталкиваюсь с приложениями, в том числе от Microsoft, разработчики которых не утруждают себя сообщать пользователям, что же на самом деле произошло, и почему приложение поломалось.

На мой взгляд, это неправильно, но тем не менее, я могу понять, почему это делается. Если сообщение об ошибке выводится пользователю, следует озаботиться тем, чтобы оно было ему понятным: переведено на тот язык, на котором он говорит, объясняло все в терминах, понятных простому пользователю и т.д. При типичном MessageBox.Show(ex.Message) это все обычно не соблюдается. Поэтому логичное решение - просто наплевать на это, заменив сообщение на бессмысленное, и рекомендовать опытным пользователям копаться в логах, если их очень интересует настоящее сообщение об ошибке.

Другой причиной может быть бизнес-модель. Если программа бесплатная или дешевая, а основной доход разработчик получает от платной поддержки, разработчику выгоднее не предоставлять пользователю информацию для самостоятельного решения проблемы, а вывести безликий код ошибки и предложить обратиться в поддержку.

Если вы не Microsoft, рекомендую все же не следовать этому подходу, мой поход см. ниже.

catch
{
    Console.WriteLine("Что-то пошло не так!");
}

Это почти всегда анти-паттерн. Игнорировать исключения можно, но в этом случае ничего и на экран выводить не надо, если оно ожидаемое. Когда же что-то по настоящему "пошло не так", надо хотя бы логировать.

каким способом сообщить пользователю об ошибках: интерфейс, логи, другие способы.

  • В интерфейсе вывести сообщение вида "Произошла ошибка: "+ex.GetType().ToString()+ex.Message.
  • В логи записать полный вывод ex.ToString() (т.е, со стеком).
  • Через телеметрию, если она есть, также отправить на сервер полный вывод ex.ToString().

При таком подходе есть некий риск, что пользователь не поймет информацию, которая выведена на экран, но по мне лучше принять это, чем вставлять палки в колеса пользователю, скрывая информацию. В отличие от веб-приложений, в настольных нет риска случайной выдачи секретных данных, так как программа работает только с теми данными, которые пользователю и так доступны.

Если пользователь - программист, можно и на экран выводить стек вызовов, только не в стандартный MessageBox, а сделать свой, где стек вынесен в отдельное поле с прокруткой - как-то так:

image

логические типы ошибок: фатальные и т.д.

Не вижу смысла делить на фатальные и не фатальные. По настоящему фатальные, вроде Access Violation, все равно и обработать стандартными средствами не получиться. Все остальные обрабатывать нужно, даже те, которые являются следствием бага. Доводить до падения процесса и стандартного диалога "Отправьте в Microsoft..." из-за необработанного NRE строго не рекомендуется. В этом случае все несохраненные данные пользователя будут потеряны, что сильно перевешивает теоретический риск повреждения данных при продолжении работы с багом. А от отправки в Microsoft все равно толку ноль, ибо им до лампочки баги в наших поделиях.

Внимание! Раз на вопросе метка desktop, все рекомендации только для настольных приложений. Для веб приложений подход должен быть абсолютно другим.

→ Ссылка