65.9K
CodeProject 正在变化。 阅读更多。
Home

聊聊错误消息

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.60/5 (11投票s)

2022 年 1 月 14 日

CPOL

10分钟阅读

viewsIcon

15194

错误消息及其重要性。

引言

现在我最大的烦恼之一就是那些毫无用处或缺乏任何有用信息的错误消息。我敢肯定你经常看到它们,而且作为一名开发者,你可能自己也会显示无用的错误消息。

坦白说,我也有这个毛病,但我每天都在努力改进。

我希望这篇文章能帮助你思考你正在显示的错误消息,也许改变你的错误消息,为用户(和开发者)提供更多信息,使其更有用。

错误消息应该告诉用户(以及开发者/支持人员)发生了什么错误、为什么会发生错误以及在哪里发生的错误。大多数开发者都能理解第一部分,但在第二和第三部分上却失败了。

我将使用 Visual Studio 和我自己的代码作为错误消息的示例,以及如何改进它们。

Visual Studio 有很多非常无用的消息,但它远非孤例。多年来,我一直在努力更新我应用程序中的所有错误消息,使它们更有用。你不必一定要回去重构你所有的错误消息,但今后,你应该考虑你正在返回/显示的内容。

为什么错误消息很重要?

当出现问题或用户做了不该做的事情时,错误消息会提供额外的信息。这很重要,这样我们作为用户可以纠正我们的输入,或者作为开发者可以找到并修复一个 bug。

清晰、信息丰富的错误消息有助于用户和支持软件的人员。诚然,很多用户不会阅读错误消息,当消息弹出时,他们只会简单地点击“确定”,但至少你尝试告诉了他们发生了什么。

含糊不清的错误消息对任何人都没有帮助,只会让用户感到沮丧,因为他们不知道是他们自己操作的问题还是软件中的 bug。

开始吧!

错误消息应该告诉你的主要有三件事。

发生了什么错误

出错了,是什么错了?大多数开发者大多数时候都能正确处理,但也有很多非常糟糕的错误消息的例子。有时,错误消息只有开发者才明白,或者错误消息过于笼统,以至于毫无用处。

为什么会出错

大多数开发者都不 bother 告诉用户为什么会发生错误。在某些情况下,你可能不知道为什么会发生错误,但如果你知道,请将其添加到错误消息中。无法保存文件?为什么不行?是权限问题?磁盘空间不足?文件名无效?

在哪里出错

在解决问题时,知道 **在哪里** 出错了非常重要。如果你不知道在哪里出了错,用户就很难纠正他们的错误,或者你就很难找到并修复你的 bug。

很多时候,这些信息很容易确定,因为错误消息离源头很近。我点击表单上的一个按钮,然后弹出错误消息。我的上下文很清楚,它与按钮的代码有关。然而,错误越深,上下文就越重要。如果按钮中的代码调用了另一个代码,再调用另一个代码,依此类推,上下文就变得越来越不清晰。

**如果可能的话**,如果可以确定如何纠正错误,请提供一些帮助。

错误消息应该尽可能清晰简洁。如果信息与 **发生什么**、**为什么** 或 **在哪里** 无关,那么它可能就不应该包含在错误消息中。

糟糕的错误消息示例及如何改进

示例 1

这个错误消息只告诉我 **发生了什么**,而没有告诉我 **为什么** 发生或 **在哪里** 发生。

虽然它告诉我操作无法完成是好事,但它未能提供一些非常重要的信息。“操作”,好吧,我们说的是哪个操作无法完成?这是我无法控制的后台操作,所以我甚至不知道从哪里开始查找问题。

然后它说无法完成。好吧,既然我不知道它说的是哪个操作,我也不知道它为什么失败。

  • 是缺少信息吗?
  • 它正在使用的信息是否不正确?
  • 它是否耗尽了资源?
  • 该操作是否与 Visual Studio 或第三方相关?

该错误消息引起了太多的疑问,以至于对我来说它基本上是无用的,因为我无法弄清楚发生了什么或如何修复它。

至少,错误消息应该告诉我哪个操作失败了。如果它能回答我上面提出的任何一个问题,那么对我来说,跟踪问题就会更容易、更快。

示例 2

这是我最喜欢的之一,因为我当时并没有进行任何需要连接到任何东西的操作。这显然是 Visual Studio 中的一个后台任务,我几乎可以肯定它与 Xamarin 以及连接到我的 MacBook 进行 iOS 开发有关(即使当时我不在 Xamarin 项目中)。

像大多数错误一样,这个错误消息只告诉我 **发生了什么**,而没有告诉我 **为什么** 发生或 **在哪里** 发生。

这个错误应该回答一些问题

  • 它连接到哪个服务器?
  • 什么东西试图连接到服务器?
  • 是否有原因导致连接丢失?

如果你不提供足够的信息让我采取行动,那么为什么还要显示这个错误?除了说“发生了问题”,它毫无用处。

示例 3

在这个例子中,我碰巧知道错误的上下文,因为我正在从源代码管理中获取最新的文件。所以至少有了这个错误消息,我知道 **在哪里** 发生的。它仍然给我留下一些额外的问题,这些问题可能容易也可能不容易回答。

在多线程代码的世界里,生成一个有用的错误消息可能很困难,因为你只是在外面等待。你等待一个进程完成,但你不知道它在哪里,或者为什么它还没有完成。

如果你能够设计你的长时间运行的进程来跟踪它在哪里以及它正在做什么,请这样做。

我想在这里得到答案的问题是

  • 它在做什么操作?
  • 什么文件受到影响?

示例 4

以我代码中的这个例子为例

e.ErrorText = "This type of file is not allowed.";

这是我做的一个网站的代码。上下文已经给出,因为那个页面上只有一个上传文件的位置,所以 **在哪里** 已经得到了解答。如果我在错误消息中包含这些信息,那将是多余的,并且会增加无用的噪音。 **为什么** 已经得到了解答,因为他们试图上传一个不允许的文件类型。

我可以做两件事来改进这个错误消息。第一是显示他们试图上传的文件名,或者至少是他们试图上传的文件类型。第二件事是可以改进错误消息的是提供他们允许上传的文件类型。

如果有人收到这个错误消息,他们可能会问的第一个问题是“好吧,我允许上传什么?”通过向他们展示一个列表,他们就可以立即得到这个问题的反馈。

通过显示文件名,也可以帮助支持人员。很多用户会截取错误消息的屏幕截图并发给支持人员。如果我看到他们试图上传“windows.exe”,而我只接受*.PDF* 文件,那么我就可以告诉他们他们哪里做错了。如果我看到他们试图上传“untitled.pdf”,而我接受*.PDF* 文件,但它却告诉他们不允许,那么我就知道我的代码中有 bug 需要修复。

所以,更好的错误消息应该是

e.ErrorText = $"This type of file \"{fileExtension}\" is not allowed. 
                You are only allowed to upload .pdf files";

示例 5

一个常见的验证是检查像这样的内容的长度

这是一个虚构的例子,因为在这种情况下,我总是在错误消息中包含所有需要的信息。然而,我经常看到这类错误消息,并想在这里展示它。

if (lastName.Length > 25)
{
   e.ErrorText = "Invalid Length";
   return;
}

这个错误有几个问题。

第一个问题是上下文。什么东西长度无效?我们在代码中知道它是什么,因为我们在测试它。为了给它更多的上下文,我们可以将错误消息改为“姓氏太长”。

这仍然留下一个问题:多长算太长?我们应该在错误消息中也提供这些信息。所以现在错误消息应该是:“姓氏不能超过 25 个字符”。

现在,如果你想真正地帮助他们,你可以这样做

ErrorText = $"Last name cannot exceed 25 characters. 
              The last name you entered was {lastName.Length} characters long.";

如果某项内容的长度必须在 2 个值之间(例如 5 到 10 个字符),那么你应该在错误消息中也包含这些信息。

示例 6

一个常见的验证是检查像这样的值的范围

这是另一个虚构的例子,用于说明观点。

if (volumeLevel < 0 || volumeLevel > 100)
{
    ErrorText = "Value out of range";
}

与上面的长度示例一样,没有关于错误消息与什么相关的上下文。也没有指出值是多少或它期望落在哪个范围内。

一个更好的错误消息会是

if (volumeLevel < 0 || volumeLevel > 100)
{
    ErrorText = $"The volume level {volumeLevel} must be between 0 and 100";
}

这将告诉用户(和支持人员)该值是什么以及它的预期值。如果出于某种原因音量级别为 -5,这可能被认为是程序中需要修复的一个 bug。

如果你无法确定该错误消息的来源上下文,你可以随时向错误消息添加额外的上下文。也许这个检查是在尝试设置当前音量级别时完成的,在这种情况下,你可以这样做

if (volumeLevel < 0 || volumeLevel > 100) 
{ 
   ErrorText = $"Error while trying to set the volume. 
                 The volume level {volumeLevel} must be between 0 and 100"; 
}

最终想法

错误消息的好坏取决于它们提供的信息。在某些情况下,如果“什么”、“为什么”和“在哪里”很容易确定,则不需要提供非常详细的信息。

当我创建一个错误消息时,我会问自己:“我需要什么信息来解决这个错误?”

如果我捕获了一个我没有预料到的异常,对我来说最重要的信息将是调用堆栈(如果可用)。至少,我想要知道异常的实际错误消息。

所以我的错误消息可能是这样的

catch (Exception ex)
{
   return $"Unable to save the connections settings file.  Error was '{ex.Message}'";
}

如果我正在处理很多对象,并且出现问题,我需要知道错误发生时正在处理哪个对象,所以我试图包含任何可能标识该对象的信息(数据库 ID、客户姓名、其他...)。

当我创建 API 之类的东西时,错误消息可能相似(例如“登录失败”)。我可能会对用户保持错误消息的模糊性,因为他们可能不需要知道登录失败的所有细节。我所做的是在错误消息中添加一个唯一的数字,这样我就可以找到失败的原因。所以错误消息可能是“登录失败 (-100)”。然后我就可以进入我的代码,找到 -100 并知道它为什么失败。

如果你的错误消息与传递的参数有关,请尝试显示这些参数的值(如果可能)。

随机无用的 Visual Studio 错误消息

历史

  • 2022 年 1 月 14 日:初始版本
© . All rights reserved.