有用的错误消息会给开发人员带来什么问题?[关闭]


29

至今令我震惊的是,在如今的今天,由专业团队开发的至今仍在使用多年的产品一直未能提供给用户有用的错误消息。在某些情况下,仅添加少量额外信息就可以节省用户数小时的麻烦。

产生错误的程序,是有原因的。它拥有一切可用的信息,可以尽一切可能地通知用户,为什么会失败。但是,提供信息以帮助用户的优先级较低。我认为这是一个巨大的失败。

一个示例来自SQL Server。当您尝试还原正在使用的数据库时,它绝对不会让您这样做。SQL Server 知道哪些进程和应用程序正在访问它。为什么它不能包含有关正在使用数据库的进程的信息?我知道并不是每个人都Applicatio_Name在其连接字符串上传递属性,但是即使是有关所涉及机器的提示也可能会有所帮助。

另一个选择,也是SQL Server(和mySQL)是可爱的string or binary data would be truncated错误消息和等效项。很多时候,只需仔细阅读生成的SQL语句,表格就会显示出哪一列是罪魁祸首。情况并非总是如此,如果数据库引擎发现了错误,为什么它不能节省我们的时间,而只是告诉我们那是该死的列呢?在此示例中,您可能认为检查它可能会对性能造成影响,并且这会妨碍编写者。好我买 怎么样,一旦数据库引擎知道存在错误,它就会在事后快速比较要存储的值和列的长度。然后将其显示给用户。

ASP.NET的可怕表适配器也很内gui。可以执行查询,并且可以显示一条错误消息,指出某处的约束已被违反。感谢那。是时候将我的数据模型与数据库进行比较了,因为开发人员太懒了,甚至无法提供行号或示例数据。(根据记录,我从来没有使用这种数据访问方法的选择,它只是我继承了一个项目!)。

每当我从C#或C ++代码中引发异常时,我都会将手边的一切提供给用户。已经决定扔掉它,所以我可以提供的信息越多越好。为什么我的函数抛出异常?传递了什么,期望什么?我只需要一点时间就可以在异常消息的正文中添加有意义的内容。地狱,它只是帮助,而我发展,因为我知道我的代码抛出的事情,是有意义的。

有人可能会辩称,不应将复杂的异常消息显示给用户。虽然我不同意这一点,但是根据您的构建,通过使用不同级别的冗长程度可以轻松地解决这个问题。即使这样,ASP.NET和SQL Server的用户也不是您的典型用户,他们宁愿使用充满冗长和美味信息的内容,因为他们可以更快地查找问题。

在当今时代,为什么开发人员认为可以在出现错误时提供最少量的信息呢?

这是2011人,前来


11
哇,这真是个咆哮。
乔治·马里安

3
@George,您能告诉我,我花了很长时间跟踪某些问题,如果遇到适当的错误,这些问题本来可以更快地解决?:)
Moo-Juice

4
我建议深呼吸,也许做一些瑜伽和冥想。:)(也就是说,我完全知道您的感受。)
George Marian

2
+1-“字符串或二进制数据将被截断”总是让我发疯!
2011年

Answers:


22

尽管有很多错误消息的例子是不应该的,但您必须记住,并非总是能够提供您希望看到的信息。

公开过多信息也令人担忧,这可能会导致开发人员在谨慎的情况下出错。(我向你保证,双关语不是故意的,但由于注意到了,所以我不会将其删除。)

有时,还有一个复杂的错误消息对最终用户没有用的事实。信息超载不一定是处理错误消息的好方法。(最好将其保存以进行日志记录。)


+1我什至没有想到信息过载方面。
瑞安·海斯

我在帖子中解释说,我可以理解为什么有些人认为对最终用户的冗长的使用可能对他们没有用。但是,您是在说API(例如ASP.NET的适配器)或数据库引擎(SQL-Server)的最终用户不希望出现冗长的错误吗?是否可以为我们节省数小时的损失时间?顺便说一句,我喜欢双关语:)
Moo-Juice

1
@George,也涉及信息超载及其对最终用户的有用性,我再次看到不向字处理器的用户抛出完整的堆栈跟踪的情况,以免他们有棕色裤子的那一刻并认为他们已经删除了互联网。但是,您已经在日志文件中提供了复杂信息的出色替代方案。现在所有需要做的消息就是添加For further information, see log20111701.txt。这将是朝正确方向迈出的一步。
Moo-Juice

2
@moo最终,我要说的是很容易批评。(我知道,我经常这样做。)但是,我要记住,要告诉您错误消息中应包含多少信息并不一定很容易。很多时候,我不得不返回并输出比调试时最初预期的更多的详细信息。另外,在很多情况下,错误消息的作用不如我在创建错误消息时所预期的那样有用。
乔治·玛丽安

1
@moo问题的症结在于,多少信息有用并不总是很明显。您提供的示例似乎很明显。但是,将其扩展到一般情况并不容易。
乔治·玛丽安

21

开发人员不是编写错误消息的合适人选。

他们从错误的角度看待应用程序。他们非常清楚代码内部出了什么问题,但是从尝试完成某件事的角度来看,他们通常不会接近该软件。结果,对于开发者重要的信息不一定对于用户重要。


+1对于许多Framework / API类型的错误,部分问题是库的开发人员无法了解上下文。也就是说,OP似乎主要关注“某些问题,但是即使我知道,我也不会告诉您”这类错误消息。我想这是安全的,对吗?
MIA

8

慈善观点:

  • 开发人员与代码紧密合作,并努力理解没有理解的人。结果,他们认为错误消息很好,只是他们没有很好的参考标准来判断错误消息。

  • 智能错误消息实际上很难做到。不仅要编写它们,而且您还必须找出可能出问题的所有内容,评估可能性,并向阅读消息的人提供有用的信息(几乎可以肯定会根据情况而有所不同),以使他们进行修复。

  • 对于大多数应用程序来说,很难像现在这样开发这种事情,而下一个开发人员真的很想为这种事情做好准备,因为这种错误很少发生。此外,如果您有多余的时间,是否不应该花时间阻止错误而不是报告错误?

不可救药的观点:

  • 错误消息和错误处理很乏味,还有很多有趣的事情要处理,而我的老板则对下一件事表示支持。我理解此代码,我会很好的,下一个家伙会最终解决它,到那时我会离开这里,所以这不是我的问题。

现实可能在中间,尽管我的经验实际上告诉我,它比前者要重要得多-基本上,这实际上很困难,并且需要付出相当大的努力来处理可能不会发生的事情。


我了解您来自许多最终用户的情况。但是,在针对数据库编写代码时,我在原始帖子中概述的情况可能经常发生。对于诸如文字处理器或计算机游戏之类的最终用户应用程序,除非发生了不良情况,否则通常不应该发生错误,即使这样,该错误对于最终用户也可能没有任何意义(Process Handle Raped By Spawned Injector, Quitting SYstem....。用户:“我做了wtf吗?`)。但是对于SQL Server / ASP.NET,我认为应该付出更多的努力:)
Moo-Juice

@ Moo-Juice-我认为这归结为整个“错误消息很难”的事情。在后台,优化器完成后实际扔给数据库的东西与您向数据库扔的东西只是短暂的相似之处。识别错误是一回事,追溯通过的错误是另一回事,而且绝对不容易。
乔恩·霍普金斯

在某些情况下可能并非无关紧要。我在某处简单的代码捕获了string/binary data错误,该错误从所述表中提取所有列信息,检查过去的值并显示哪些列将被违反。这是微不足道的。也许我太讨厌了,因为我的案例很琐碎,但我完全理解情况并非总是如此。我们如何阻止强奸喷油器强奸程序可能很困难:)
Moo-Juice

6

不幸的是,更有用的错误消息会花费开发人员时间,这等于公司的钱。产品足够昂贵,无法按原样构建。是的,拥有更多有用的错误消息真棒,我同意应该包括更多有用的错误消息。但是,生活中的一个事实就是,当您在截止日期之内并且资金短缺时,您必须削减一些开支。经理可能会看到“更漂亮的错误消息”,这可能是第一件事。


啊,是的,很好。一直存在成本问题。
乔治·玛丽安

我可以承认成本观点(该死的,这并不意味着我必须喜欢它)。我无法从SQL Server或ASP.NET的开发者的角度来看说话,但我知道它并不需要多少额外的努力来提供一个列名。我承认,就我的简单示例而言,它可能会在一小时内解决。然后可能会产生其他1000个错误。但它们至少可以从普通的开始:)
Moo-Juice

当然,我不了解SQL Server的内部知识-但总的来说,在检测到错误情况时,您经常没有这些信息。像“字符串或二进制数据”这样的表述很清楚地向您表明,在错误位置,甚至不清楚被截断的值是字符串还是数字...
Ichthyo 2011年

在某种程度上,这可能是有效的,但同样的问题甚至出现在简单的perl脚本中,只需添加$!就可以了。错误消息将非常有帮助。(相对于开发人员的时间而言)写'die“ $ path:$!”比写完全没用的'die“无法打开$ path”要
便宜

6

我同意错误消息应尽可能具体。

产生一般错误消息的原因之一可能是使用错误代码。使用异常时,您可以随意在错误消息中传递所有需要的信息。使用返回码没有空间来编码列名。也许错误是由一个不知道上下文的通用函数处理的,只是将错误代码转换为字符串。


更糟糕的是:虽然您可以在非常特定的点触发错误,但是为了处理错误,您不得不将几种可能性归为一类情况,并通过一次恢复来处理它们。这种概括行为经常迫使您丢弃更多稀缺的附加上下文信息。
Ichthyo 2011年

顺便说一句...这种机制会导致一整类有趣的错误消息 -类似于“模块dwarf678中的严重错误-567:未检测到错误”
Ichthyo 2011年

4
  • 有时,过多的信息可能会带来安全风险;您不知道谁在阅读错误消息
  • 有时引发异常的操作是如此复杂,以至于在不负面影响代码的速度/复杂性的情况下,不可能获得有意义的消息

我回答了您的第一点,允许您更改代码中引发的异常的详细程度。鉴于此时已发生错误并且速度不再是决定因素(imho),您的第二点没有意义。如果生成冗长的错误消息需要在抛出异常之前对性能产生影响,那么我同意。引发异常后,将该代码移至之后。我认为这些观点都不是反对正确错误消息的有效论据:)
Moo-Juice

是的,我想您可以在发生问题后搜索信息,而不用跟踪您可能需要的信息。虽然,如果您处于嵌套循环中,则捕获索引/索引可能会出现问题。
StuperUser 2011年

我不同意的第一点是-1,第二点是+1。
乔恩·霍普金斯

1
@jon将一般错误消息与登录错误进行对比,而更具体的错误消息则实际告诉您出了什么问题。例如:“我们找不到用户名/密码组合”与“您输入的密码不正确。” /“该用户名不存在。” 我似乎还记得ASP.NET中的一个漏洞,该错误消息实际上对于破解加密密钥很有用。
乔治·马里安

@乔治-我同意这个例子,但我认为它并不广泛。一旦授予您访问应用程序的权限,您就可以假设该人员已(a)被授权并且(b)可以从实际应用程序中获得大部分所需的风险,因此一定程度的风险就会消失。
乔恩·霍普金斯

3

作为一般通知,您应该考虑到任何错误或例外情况-恰恰是:例外。它贯穿了计划和设计的过程。它与计划的工作方式不兼容。如果不是这种情况,那么就无需因错误中止而纾困。

我们对程序员进行了培训,以保护这个计划好的程序的自我保护。因此,我们通常会进行一些检查,以验证我们的假设。当这些健全性检查失败时,我们只知道该计划以某种方式失败了。

如果您考虑这样一个系统的实际工作,那么您的需求-从用户的角度看来很可能是常识-无法实现。您要求提供有条理的声明,并组织良好且适当的信息。此外,您甚至要求演示文稿以适合常规应用程序/处理设计的方式进行。显然,此信息存在于系统中的某个位置。显然,它甚至以有序和组织良好的方式存在(希望如此)。但是在发生错误的那一刻,没有人计划提供此信息。一个错误总是部分失控。这种失控可能发生在各个级别。这可能是系统在运行时的行为与计划不同。但这也可能是实际的执行结果比计划的要困难得多,细节也有所不同,并且没有多余的条款可以令人满意地处理这种情况。这也是部分失控。

将此与您给出的具体示例相关联:如果在错误发生时知道表列的名称和类型,以及所需空间的长度,那么就没有任何理由引发错误,不是吗?我们可以解决问题并按计划进行。我们被迫提出错误的事实向我们表明,事情已误入歧途。这些信息不在手的事实是例外

我在这里写的内容似乎是不言而喻的,只是常识。但是实际上很难掌握。我们不断尝试通过应用道德范畴来理解它-就像必须有一个罪魁祸首,必须有一个懒惰的人不关心可怜的用户,必须有一个贪婪的商人做了廉价的计算方法。所有这些完全超出了重点

失去控制的可能性源于我们以计划有序的方式做事的事实


我不同意。它知道出了点问题(无法执行该操作)。知道某件事是错误的,这表明了对它为什么错误的认识。对于该列(或该表中的列),此字符串太大。它可以告诉我,无需太多工作。这是一个该死的企业数据库引擎。它知道。但是它不会中继该信息。假设程序员可以编写一个查询以在事实发现之后运行,以表明数据库引擎可以执行相同的操作。这些不是未公开的秘密方面。只是糟糕的错误消息:)
Moo-Juice

呵呵...您的论点应该基于逻辑,而不是基于狂妄的思考。从什么时候起计算机什么都不知道?这不是好莱坞电影院。程序是严格的,预先安排好的设置,当您的假设被打破时,您将失去控制。这真的很难理解吗?计算机不了解该程序,因此他不了解该程序何时出错。几乎曾经,当一些一致性检查触发时,执行已经执行了成千上万条语句,而且没人知道什么数据已经损坏。您所能做的就是限制伤害
Ichthyo 2011年

2

我是一家软件公司的支持工程师,经常看到新的错误消息。通常,当我将这些信息反馈给我们的开发团队时,他们必须在应用程序源中搜索消息。

在我看来,即使没有提供给用户,如果开发团队在每次添加新注释时都会在错误消息索引文档或数据库中添加注释,这对我们来说将是很安全的。对于我们来说,更快地找到引起客户问题的原因并节省他们的时间...


1
抱歉,这是不切实际的。谁负责管理该索引文档,以确保其准确性。由于任何书面文档都与代码分开,因此它将变得过时并在编写代码时就开始获取新错误的来源。
Ichthyo 2011年

相反,如果每个错误都有一个唯一的数字,那么脚本可以从源代码中提取这种东西,并且每个错误的描述也可以以(特殊格式)注释的形式或以串。因此,该文档将在每次构建时自动生成。这样,提取出来的消息可能比您希望最终用户看到的任何内容都要详细得多。不错的主意!
珍妮·平达

您不需要这样做的脚本-许多运行时系统都可以记录行号或一些类似的信息。不幸的是,这并不能解决原始问题,因为这样,我们只能知道在哪里检测到异常,但是我们不知道出了什么问题。找出后者称为调试 -在实践中,这或多或少是由人类完成的繁琐且工作量大的任务。
Ichthyo 2011年

我与@Jeanne在一起-非常简单,可以将其合并到代码中或在代码中包含错误代码及其解释的中央索引。一旦知道在哪里检测到异常,这可能足以告诉您环境存在问题,而不是代码中需要解决的问题。
glenatron 2011年

2

您看到的问题实际上是正确封装和信息隐藏的副作用之一。

现代系统极其复杂。一般的开发人员在编写任何特定任务时,几乎不可能拥有从UI到原始二进制文件的整个系统的思维模型。

因此,当开发人员坐下来进行编码(例如,还原数据库文件的部分)时,他的思维模型完全被围绕还原数据库文件的行为所填充。他需要(而且我想我还没有写这段代码)读取文件,检查属性,读取版本,备份现有数据,选择合并/覆盖策略等。很可能,即使有通过考虑到UI,它在他在异常处理程序中编写的错误字符串中。就像是:

catch (IOException error)
{
//File is in use
throw new GenericExceptionParsedByTheUIThread("File is in use.");
}

在此示例中,您正在就可能正在使用该文件甚至数据库中其他线程的进程询问的有用信息完全超出了范围(以编程方式)。处理数据库文件的类可能有数百种;使用文件将所有其他进程的信息或访问文件系统并查看正在运行的其他进程的功能导入这些类的信息(假定这些进程甚至在此THIS计算机上),将导致所谓的泄漏抽象;生产力有直接的成本。

因此,这些错误会在当今持续存在。显然,它们都可以被修复;但是在许多情况下,所涉及的开销比人们想象的要大得多(例如,在这种情况下,您可能必须让此功能遍历网络连接和共享的枚举,以查看远程计算机是否正在将此数据库文件用于某些用途)。原因),而付出的代价却微不足道。


@GWLlosa,这是一个很好的答案,我没有考虑过。就是说(我不能在此处提供完整的代码示例),当我得到IOException时,在抛出Io之前GenericException,我可以:1)向ProcessesSubsystem索要进程列表。2)询问每个进程他们正在使用什么数据库。3)将进程与分配给我要覆盖的数据库的数据库进行匹配,4)引发DatabaseInUse(dbName, processName, applicationName异常。:)
Moo-Juice

1
当然可以;但是将数据库与进程匹配(特别是通过网络)的代码本身可能很复杂/缓慢/容易出错,这可能会导致一些附加的挫败感(“我只是在尝试恢复本地(*#%^! %file !!!!为什么我要
担心网络共享

1
@ Moo-Juice:您的建议听起来很幼稚。在像数据库这样的大型多线程环境中,您不能仅通过某些全局服务来获取“所有xyz”的列表。即使与另一个线程中的某些其他服务进行通信,您也需要一个锁,这可能导致整个系统的死锁,甚至可能破坏其他数据,或者至少会导致其他错误,您也需要处理该错误。 ....
Ichthyo 2011年

1
该示例确实很有启发性:通常,在这样的catch处理程序中,您甚至无法确定try块中的哪个部分失败(通常有多个部分可能导致IOException)。除此之外,很有可能在那个位置您不能说出文件名,以免您知道哪个逻辑表与此文件相对应。
Ichthyo 2011年

@Ichthyo,没有冒犯,但我不认为这是幼稚的。我不是在要求数据库引擎锁定系统来告诉我哪个进程仍具有数据库句柄。我并不是要它使整个企业停顿下来。如果需要给我一个简单的答案,那首先表明了数据库服务器的一个基本设计缺陷。请记住,它应该设计为以最快的方式提供相关信息。要问这个问题“谁?” 确实不应该像您所说的那样天真:)
Moo-Juice

2

我最喜欢的是(70年代末期)Univac Fortran IV编译器,它在读取非数字字符时如实地宣布

试图解释无意义的数据


+1,绝对棒!这种错误使您只想放弃并去斐济养鸡。
Moo-Juice

的确,此错误消息是100%准确的-如果您想使用更多的combettxt发出更友好的消息,则需要进行一些猜测和启发。这样,你添加的可能性误导性的错误消息..
Ichthyo

0

我相信大多数错误消息实际上都是面向程序员(自我)的,经过赞美的调试代码/ printf语句。

编写面向用户的错误消息意味着了解您的用户,并预期他们将要知道的内容。在编写代码的过程中,开发人员更倾向于编写对自己有用的错误消息,以便调试他们当前正在编写的代码,或者对已知或占主导地位的用例有用。

从编写代码到设计用户界面的文本部分(或用户体验)的切换是上下文切换。在大型应用程序(例如多语言应用程序)中,这可能意味着它会移交给其他人或团队(UI,翻译),而不是由开发人员完全处理,因此,除非开发人员仔细解释消息的上下文,否则可能会产生重要的细节容易丢失。

当然,检查和验证错误处理通常被认为是低优先级的,只要能够处理(发现)错误和异常,就不会对其进行过多强调。对于开发人员或QA而言,这是代码库在精神上毫无用处的地方,因为错误条件通常具有与之相关的负面含义(即错误或失败)。


0

我认为原因是错误报告/处理总是事后才想到的。即使他们不完全被考虑,他们还是总体设计中的二等公民。

现代软件通常由多层组成。当您的错误报告逻辑匆匆忙忙地放下时,通常很难收集呈现有用的错误消息所需的所有信息。

例如,错误被捕获在后端,但是它需要来自高层的一些信息来组成全面的错误消息。大多数良好的错误消息都需要几乎所有层的信息(以使我们全面了解系统中发生的情况)。理想的错误消息看起来像是:“好,我们首先收到一个字符串,然后它通过了解析器,它给出了一些有趣的内容,您可能想看一下那里。无论如何,该内容进一步传递了下来……”

如果错误报告机制在设计阶段不是一流的公民,那么这样做通常需要不必要的耦合。我想大多数人会发现,对于看起来不太令人印象深刻的事情(他们喜欢说“看?我的程序崩溃了,错误消息很有用!”),有太多的工作要做。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.