Кто должен читать Exception.Message, если вообще?

27

При разработке исключений я должен писать сообщения, которые должен понимать пользователь или разработчик? Кто на самом деле должен быть читателем сообщений об исключениях?

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

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


Меня спросили, дают ли какой-либо из этих вопросов ответ на мой вопрос:

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

Я даже думаю, что известное сообщение вообще не имеет никакой реальной ценности, поскольку оно просто повторяет имя типа исключения в разных словах, так зачем вообще их писать? Я могу сгенерировать их автоматически.

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

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

t3chb0t
источник
4
Здесь на ум приходят еще два вопроса о программистах. Я не уверен, являются ли они дубликатами или нет, но я думаю, что вы должны прочитать их, и, возможно, они решат некоторые или даже все ваши проблемы: как написать хорошее сообщение об исключении и почему многие сообщения об исключениях не содержат полезных деталей ?
Томас Оуэнс
2
@ThomasOwens Я прочитал их обоих, но они не адресованы конкретному получателю сообщения об исключении. Они говорят о пользователях в целом, но кто он? Это пользователь программного обеспечения, который теперь имеет представление о программировании или разработчиках, должен проанализировать, что пошло не так? Я никогда не уверен, к кому мне обращаться при написании сообщений.
t3chb0t
2
Я думаю, что хороший ответ здесь может относиться к одному или обоим этим вопросам, но они далеко не дублируют друг друга.
Легкость гонки с Моникой
1
С какой стати это было закрыто как дубликат? Даже если бы он был дубликатом этого другого вопроса (но это не так), у этого ответа явно есть лучший, он должен быть отмечен как дубликат.
Бен Ааронсон
2
@MichaelT Я до сих пор не вижу особого совпадения, если только вы не предполагаете, что сообщения об исключениях предназначены для пользователей
Бен Ааронсон,

Ответы:

46

Эти сообщения для других разработчиков

Ожидается, что эти сообщения будут прочитаны разработчиками, чтобы помочь им отладить приложение. Это может принимать две формы:

  • Активная отладка. Вы на самом деле запускаете отладчик, когда пишете код и пытаетесь выяснить, что происходит. В этом контексте полезное исключение поможет вам, упростив понимание того, что происходит не так, или в конечном итоге предложив обходной путь (хотя это необязательно).

  • Пассивная отладка. Код запускается в производство и дает сбой. Исключение регистрируется, но вы получаете только сообщение и трассировку стека. В этом контексте полезное сообщение об исключении поможет вам быстро локализовать ошибку.

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

Например, IOSecurityExceptionтип исключения, сгенерированного при записи файла, не очень ясно описывает проблему: это потому, что у нас нет прав доступа к файлу? Или, может быть, мы можем прочитать это, но не написать? Или, может быть, файл не существует, и у нас нет прав для создания файлов там? Или, может быть, он заблокирован (надеюсь, тип исключения в этом случае будет другим, но на практике типы могут иногда быть загадочными). Или, возможно, Code Access Security не позволяет нам выполнять какие-либо операции ввода-вывода?

Вместо:

IOSecurityException: файл был найден, но в разрешении на чтение его содержимого было отказано.

гораздо более явный Здесь мы сразу знаем, что права доступа к каталогу установлены правильно (иначе мы не сможем узнать, существует ли файл), но разрешения на уровне файла проблематичны.

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

Обратите внимание, что по техническим (часто из-за производительности) причинам некоторые сообщения оказываются гораздо более загадочными, чем должны быть. .NET's NullReferenceException:

В экземпляре объекта не задана ссылка на объект.

отличный пример сообщения, которое не помогает. Полезное сообщение будет:

Ссылка на объект productне установлена ​​на экземпляр объекта при вызове product.Price.

Эти сообщения не для конечных пользователей!

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

В экземпляре объекта не задана ссылка на объект.

для конечного пользователя абсолютно ничего не значит, и его следует избегать любой ценой.

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

  • Обрабатывает исключения в первую очередь. Большинство из них могут быть обработаны, не мешая пользователю. Сеть не работает? Почему бы не подождать несколько секунд и повторить попытку?

  • Запрещает пользователю привести приложение к исключительному случаю. Если вы попросите пользователя ввести два числа и разделить первое на второе, почему вы позволите пользователю ввести ноль во втором случае, чтобы обвинить его несколько секунд спустя? Как насчет выделения текстового поля красным цветом (с полезной подсказкой о том, что число должно отличаться от нуля) и отключения кнопки проверки, пока поле не останется красным?

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

  • Если больше ничего не работает, показывает полезную, дружескую ошибку, которая специально написана, чтобы уменьшить разочарование пользователя, помочь пользователю понять, что пошло не так и в конечном итоге решить проблему, а также помочь ему предотвратить ошибку в будущем (когда это применимо).

    В вашем вопросе вы предложили иметь два сообщения в виде исключения: техническое для разработчиков и одно для конечных пользователей. Несмотря на то, что это допустимое предложение в некоторых незначительных случаях, большинство исключений создается на уровне, когда невозможно создать значимое сообщение для пользователей. Возьмите DivisionByZeroExceptionи представьте, что мы не можем предотвратить возникновение исключения и не можем справиться с этим сами. Когда происходит разделение, знает ли фреймворк (поскольку это фреймворк, а не бизнес-код, который выдает исключение), что будет полезным для пользователя сообщением? Точно нет:

    Деление на ноль произошло. [ХОРОШО]

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

    Поле D13 не может иметь значение, идентичное значению в поле E6, поскольку вычитание этих значений используется в качестве делителя. [ХОРОШО]

    или, может быть:

    Значения, сообщаемые службой ATP, не соответствуют локальным данным. Это может быть вызвано несинхронизацией локальных данных. Хотите синхронизировать информацию о доставке и повторить попытку? [Да нет]

Эти сообщения не для анализа

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

Представьте себе сообщение об исключении:

Время ожидания подключения к серверу кэширования истекло после ожидания 500 мс. Либо увеличьте время ожидания, либо проверьте мониторинг производительности, чтобы определить падение производительности сервера. Среднее время ожидания кеширующего сервера составило 6 мс. за последний месяц 4 мс за последнюю неделю и 377 мс. за последний час.

Вы хотите извлечь значения «500», «6», «4» и «377». Подумайте немного о подходе, который вы будете использовать для анализа, и только потом продолжайте чтение.

У тебя есть идея? Отлично.

Теперь оригинальный разработчик обнаружил опечатку:

Connecting to the caching sever timed out after waiting [...]

должно быть:

                            ↓
Connecting to the caching server timed out after waiting [...]

Более того, разработчик считает, что месяц / неделя / один час не особенно актуальны, поэтому он также вносит дополнительные изменения:

Среднее время ожидания кеширующего сервера составило 6 мс. за последний месяц 5 мс за последние 24 часа 377 мс за последний час.

Что происходит с вашим разбором?

Вместо синтаксического анализа вы можете использовать свойства исключений (которые могут содержать все, что угодно, как только данные могут быть сериализованы):

{
    message: "Connecting to the caching [...]",
    properties: {
        "timeout": 500,
        "statistics": [
            { "timespan": 1, "unit": "month", "average-timeout": 6 },
            { "timespan": 7, "unit": "day", "average-timeout": 4 },
            { "timespan": 1, "unit": "hour", "average-timeout": 377 },
        ]
    }
}

Насколько легко использовать эти данные сейчас?

Иногда (например, в .NET) сообщение может даже быть переведено на язык пользователя (IMHO, перевод этих сообщений абсолютно неверен, поскольку любой разработчик, как ожидается, сможет читать на английском языке). Разбор таких сообщений практически невозможен.

Арсений Мурзенко
источник
2
Хороший вопрос о конечном пользователе. Я бы добавил, что конечный пользователь также не должен видеть сообщения об исключениях из соображений безопасности. Знание точной природы ошибки для пользователя, не являющегося непрофессионалом, может представлять собой эксплойт. Конечный пользователь должен знать об ошибке только в контексте того, что он или она делает.
Нейл
1
@Neil: это слишком теоретически. На практике выгода от сокрытия журналов от пользователя перевешивается сложностью ведения онлайн-журналов. Не считая удобного для пользователя аспекта. Представьте, что вы устанавливаете Visual Studio на новую виртуальную машину Windows. Внезапно установка не удалась. Вы бы предпочли просто зайти в журналы и самостоятельно диагностировать проблему (с помощью Stack Exchange и блогов) или подождать, пока Microsoft не решит проблему и не выпустит исправление?
Арсений Мурзенко
4
Я думаю, что все знают, что сообщение "ссылка на объект ..." бесполезно. Однако уместный вопрос: какой машинный код вы хотите, чтобы генерировался джиттер, генерирующий нужное вам сообщение? Если вы выполните это упражнение, вы быстро обнаружите, что сообщение не может быть легко сгенерировано без огромных затрат производительности, наложенных на все неисключительные случаи . Выгода от легкого облегчения работы небрежного разработчика не оплачивается за счет снижения производительности для конечного пользователя.
Эрик Липперт
7
Что касается исключений безопасности: чем меньше информации в исключении, тем лучше. Мой любимый анекдот в том, что в предварительной бета-версии .NET 1.0 вы могли получить сообщение об исключении, например «У вас нет разрешения определять имя файла C: \ foo.txt». Отлично, спасибо за подробное сообщение об исключении!
Эрик Липперт
2
Я не согласен с тем, что никогда не показываю конечному пользователю подробное сообщение об ошибке. Много раз со мной случалось, что я получал загадочное сообщение об ошибке, препятствующее запуску моей игры / программного обеспечения, но когда я в Google, я обнаружил, что есть простой обходной путь. Без этого сообщения об ошибке не было бы способа найти обходной путь. Может быть, лучше просто скрыть детали под кнопкой «подробнее»?
BlueRaja - Дэнни Пфлюгофт
2

Ответ на это полностью зависит от исключения.

Самый важный вопрос, который нужно задать, это "кто может решить проблему?" Если исключение вызвано ошибкой файловой системы при открытии файла, возможно, имеет смысл передать это сообщение конечному пользователю. Сообщение может содержать больше информации о том, как исправить ошибку. Я могу вспомнить один конкретный случай в моей работе, где у меня была система плагинов, которая загружала DLL. Если пользователь произвел опечатку в командной строке, чтобы загрузить неправильный плагин, основное сообщение об ошибке на самом деле содержало полезную информацию, позволяющую устранить проблему.

Однако в большинстве случаев необработанное исключение не может быть исправлено пользователем. Большинство из них связаны с внесением изменений в код. В этих случаях потребителем сообщения явно является разработчик.

Случай с обнаруженными исключениями сложнее, потому что у вас есть возможность правильно обработать исключение и создать более дружественное. Один язык сценариев, который я написал, вызвал исключения, чье сообщение было очень четко предназначено для разработчика. Однако, когда стек разворачивался, я добавил читаемые пользователем трассировки стека из языка сценариев. Мои пользователи очень редко могли получить сообщение, но они могли посмотреть на трассировку стека в своем скрипте и выяснить, что происходило в 95% случаев. В оставшиеся 5%, когда мне позвонили, у нас была не только трассировка стека, но и сообщение для разработчиков, которое помогло мне разобраться в чем дело.

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

Корт Аммон - Восстановить Монику
источник
1
«Если исключение вызвано ошибкой файловой системы при открытии файла, возможно, имеет смысл передать это сообщение конечному пользователю»: но когда вы выдает ошибку файловой системы, вы не знаете контекст: это может быть действие пользователя или что-то совершенно не связанное (например, ваше приложение делает резервную копию какой-то конфигурации, даже если пользователь даже не заметил). Это означает, что у вас будет блок try / catch, который показывает сообщение об ошибке , которое сильно отличается от непосредственного отображения сообщения об исключении .
Арсений Мурзенко
@MainMa Вы можете не знать контекст явно, но есть множество подсказок. Как, например, если они находятся в процессе открытия файла, а исключение - «IOError: разрешение отклонено», есть разумный шанс, что пользователь может сложить 2 и 2 вместе и выяснить нужный файл. Мой любимый редактор делает это - он просто выводит текст исключения в диалоговом окне, без пуха. С другой стороны, если я загружал свое приложение и получал «отказано в разрешении» во время загрузки группы изображений, гораздо менее разумно предположить, что пользователь может что-то делать с информацией.
Корт Аммон - Восстановить Монику
В качестве аргумента для вашей точки зрения, я никогда не видел никакого значения в показе пользователю исключения NullReferenceException, кроме того, что простое действие отображения этого сообщения показывает, что ваше программное обеспечение не имеет ни малейшего понятия, как его восстановить! Это сообщение об исключении никогда не будет полезным для конечного пользователя.
Корт Аммон - Восстановить Монику