你有没有想过为什么有时修复错误似乎比它应该花费更长的时间? 当你终于找到问题时, 事实证明你所需要的只是一个小小的改变. 然而, 花了很多时间才能找到正在发生的事情. 这种情况比我想象的更频繁.
另一方面, 当您编写代码并进行测试并且无法正常工作时, 修复错误非常快. 你跳回编辑器, 掀起一行代码, 问题就解决了.
为什么即使问题很简单, 有时修复错误也需要很多工作, 有时候, 修复问题的速度很快 - 甚至可能很难解决问题? 我们可以从易于修复的错误中学到一些东西, 这样我们可以花更少的时间来修复 bug 吗?
让我们来谈谈这个问题, 看看我们可以用什么方法来解决这个问题, 并且因为很难找到错误而停止拔头发.
典型的错误修复过程
为了确定在修复错误时花了这么长时间, 我们先来看看修复错误所涉及的步骤.
首先, 我们需要了解这个问题. 这意味着我们需要知道出了什么问题, 在哪里, 以及应该发生什么.
接下来, 我们需要重现这个 bug. 一个典型的案例可能是我们需要进入我们正在处理的应用程序并点击一些事情来看看会发生什么.
然后, 我们需要弄清楚代码的哪个部分导致了问题. 我们通常可以通过使用调试工具来启动它 - 例如使用 Chrome 的调试器在我们重现问题的页面上逐步执行代码.
一旦我们找到导致问题的代码片段, 我们就需要确定根本原因. 根据问题的复杂性, 这种困难可能会有很大的不同.
在确定了根本原因后, 我们终于可以修复该错误了.
最后, 我们需要确保错误实际修复. 这通常是通过尝试再次重现它来完成的.
嗯, 我想我们已经开始在这里看到一个问题了. 修复 bug 本身只有六分之一!
但在我们得出结论之前, 让我们更详细地看一下这些步骤. 然后, 我们可以看到每个步骤中的内容需要花费时间, 并找到使它们更快的方法.
第 1 步: 了解问题
错误修复过程的第一步是理解问题. 我们需要收集足够的信息, 以便我们知道发生了什么, 以及应该发生什么.
这个步骤花费很长时间的最大贡献者是可怕的, 糟糕的错误报告.
用户从不提供好的错误报告. 这是生活中无可否认的事实.
我可能会夸大一点, 但我确信你已经比你想要的更频繁地听到 "它不起作用" 的字样.
"X 不起作用, 它需要在昨天修复!"
然后你继续问一些问题, 希望你能从记者那里收集一些比 "它不起作用" 更有用的信息.
当然, 有时当行星和恒星正确对齐时, 你会得到一个好的错误报告. 您可以清楚地了解出现了什么问题, 重现它的精确步骤, 甚至可以获得有关用户使用的浏览器和操作系统的信息! 那时候你可以直接进入第 2 步并开始修复 bug.
但在最糟糕的情况下, 你只会对发生的事情有一个模糊的概念, 这意味着在第 2 步中需要付出更多的努力.
第 2 步: 重现错误
如果您在步骤 1 中获得了良好的错误报告, 则此部分可以很容易. 您可以按照错误报告中的步骤操作, 然后立即重现错误. 太棒了! 现在, 您可以继续查找损坏的代码.
可悲的是, 这一步往往不是那么顺利.
由于模糊的错误报告, 这一步往往涉及很多猜测.
也许用户使用的是 Firefox, 或者他们使用的是 Chrome. 在点击此按钮之前, 他们不确定他们做了什么. 我想知道我是否应该随意按下按钮并希望最好?
有时, 在尝试重现问题后, 您必须反复执行步骤 1 和步骤 2, 而不会产生任何结果. 希望您可以从用户那里获得更多信息, 然后再试一次.
在这一点上很清楚, 为了加快步骤 1 和 2, 我们需要收集尽可能多的信息. 我们掌握的信息越多, 理解和重现问题就越容易.
第 3 步: 找到有问题的代码片段
一旦我们再现了这个 bug, 我们就需要找到导致问题的代码的特定部分.
此步骤的难度各不相同, 主要取决于两个因素:
您拥有的代码量
您熟悉代码库
(在较小程度上, 您对调试工具的了解)
代码量会影响这一点, 因为每行代码都会增加可能的错误数量. 值得庆幸的是, 熟悉代码库可以显着缩小范围.
找到问题通常从采取有根据的猜测开始.
"好吧, 这就是问题, 这就是我可以重现它的方式, 所以我认为问题出现在代码的 Y 部分"
您对代码库的熟悉程度越高, 您的猜测就越好. 这使您可以缩小需要查看的代码量, 可能需要大量调整.
"好吧, 我最近在处理函数 Z, 它有与此相关的代码. 我最好先检查一下."
根据问题的类型, 您还可以使用调试工具来帮助您更轻松地找到有问题的代码.
"是的, 当我点击它时会出现错误. 我将在事件处理程序中设置一个断点并从那里开始."
在此过程的这一步, 最大的时间汇是找到问题发生的确切位置. 它可能是行为不当的功能, 用户的不良价值或任何数量的东西, 您需要在继续之前找到问题的根源.
第 4 步: 确定根本原因
这可能是这个过程中最重要的一步, 但它经常被完全跳过!
由于感知时间限制, 或者仅仅因为经验不足的开发人员可能不知道他们应该这样做, 它可能会被跳过. 无论哪种方式, 跳过此步骤通常意味着您的代码慢慢开始填充 hacks 和 kludges.
注意, 我说感觉时间限制. 通常你可能会感到有压力要快速修复.
"只需快速修复, 客户就在等待. 你以后可以做好工作."
因此, 您可以使用一些代码来修复损坏的代码并跳过根本原因. 当然, 你很可能永远无法妥善修复它, 因为总有一些东西需要完成.
但快速修复快速修复的结果与使用胶带修复漏水管道相同. 即使在芬兰, 我们称胶带为 "耶稣胶带", 因为它具有修复任何东西的神奇能力, 在某些时候胶带修复开始泄漏, 你需要再使用一些胶带. 不久之后, 你手上就会有一个巨大的混乱, 你必须把它全部撕下来.
最后, 您需要花费更多时间来修复快速修复, 而不是花费更多时间来完成正确的工作.
但我离题了.
确定根本原因意味着您需要找到错误的真正来源. 让我给你举个例子.
假设您网站上的某些价值显示不正确. 您可以通过更改显示代码来解决此问题, 但更多时候, 显示症状的代码不是根本原因.
如果您更深入地研究问题, 您可能会发现数据库中的数据也是错误的. 进一步深入研究, 您会发现保存该值的代码已被破坏. 这是问题的根本原因. 您找到的原始代码只是在其他地方显示问题的症状.
如果您只是修复了症状, 那么真正的问题就会存在. 它将继续在将来引起问题, 同时你不断修复更多的症状.
与查找症状不同, 此步骤不需要太多猜测. 你有一个起点, 从那里你可以追溯到根本原因, 所以你不需要猜测.
尽管如此, 这一步可能非常耗时, 因为您经常需要在几个级别上深入研究代码. 多长时间在很大程度上取决于根本原因与症状相比的位置 - 有时它们甚至可能是相同的, 但如示例所示, 它可能会降低几个等级.
第 5 步: 修复错误
最后我们可以解决这个问题. 我们已经复制了这个错误, 找到了症状发生的地方并找到了根本原因.
在完成所有工作之后, 这一步通常是相当微不足道的. 我们有关于出了什么问题, 应该发生什么以及出现什么症状的信息. 错误通常不需要大的修改来修复, 因此实现部分往往很快.
第 6 步: 确保错误得到修复
作为这个过程的最后一步, 我们需要确保错误被彻底掩盖.
这可以通过重复您之前重现问题的步骤来完成.
偶尔这个 bug 仍然会重现. 在这种情况下, 您通常需要返回步骤 4 或 5 并从那里继续.
如果对软件测试, 接口测试, 自动化测试, 性能测试, LR 脚本开发, 面试经验交流. 感兴趣可以 175317069, 群内会有不定期的发放免费的资料链接, 这些资料都是从各个技术网站搜集, 整理出来的, 如果你有好的学习资料可以私聊发我, 我会注明出处之后分享给大家.
这个过程有什么问题?
现在我们已经查看了错误修复过程中的每个步骤, 我们可以确定这些关键难点:
缺乏有关该问题的信息: 错误报告通常缺少重要信息, 这使得很难理解问题并重现问题. 我们拥有的信息越少, 花费的时间就越多.
猜测: 我们经常需要进行一些猜测. 我们没有解决问题所需的所有信息, 也没有办法在代码中查明问题. 因此我们需要猜测, 这本质上是容易出错的
时间限制的压力: 我们经常需要快速修复错误, 因为用户和客户以及业务依赖于软件的工作. 反过来, 这可能会使得不能正确分析问题, 这会导致更多的错误和问题.
所有这些都有助于减缓错误修复并使其成为一个繁琐的过程. 我们编写代码来修复 bug 的部分很少是最大的时间下沉!
但是, 尽管如此, 有时我们可以非常快速地修复错误. 这通常发生在我们添加新功能和处理全新代码时.
为什么是这样?
如果我们在考虑使用全新代码时的典型情况, 我们通常会非常熟悉我们刚才写的内容. 在这种情况下发现错误的人通常与编写代码的人相同.
这几乎完全消除了猜测!
我们知道我们刚写的代码
我们经常以小的渐进步骤测试我们的代码, 因此可能导致错误的代码更少
因此, 修复错误是一件轻而易举的事. 通常情况下, 您只需将 alt-tab 返回到编辑器, 立即发现问题并进行更快的修复, 而不是说 "它不起作用".
收集更好的信息
我们可以有把握地说, 缺乏信息和由此产生的猜测是导致修复 bug 变慢的最大因素.
我们可以做些什么来改善这种情况?
让我们首先看一下我们可以做些什么来从用户那里获取更多信息并进入错误报告. 迈出这一步的第一步是向用户询问一些具体问题. 这有助于指导用户为我们提供更有效地解决问题所需的信息.
以下是您可以使用的一些问题. 我不记得我第一次看到这些, 但我倾向于自己使用这种格式并且效果很好.
问题的简短描述
发生了什么?
应该发生什么呢?
如何重现这个问题?
第一个并不是绝对必要的, 但是如果您使用像 JIRA 这样的工具, 因为您需要该问题的名称, 这将非常有用. 第二个是相当明显的, 第三个对于理解用户期望发生的事情非常有用. 虽然你可以自己解决这个问题, 但最好事先了解一下 - 特别是有时它可能不是技术问题, 而只是混乱的结果.
第四点可能是最重要的, 但用户并不总是知道如何填补这一点. 如果可能的话, 最好给他们一个小样本, 如你想要它填充, 如 "1. 我在第 X 页 2. 我点击按钮 Y 3. 我输入值 Z".
特别是对于 web 应用程序有用的其他信息是用户的浏览器和操作系统. 根据用户的不同, 他们可能并不总是知道这一点, 因此能够自动收集这些信息可能很有价值. 为此, 您可以考虑集成 Usersnap https://usersnap.com/ 等服务, 这有助于将更多数据收集到错误报告中.
在脚本错误的情况下, 具有堆栈跟踪通常也是有用的, 尽管具有良好的再现步骤, 您也应该能够自己获得它. 像 Loggly http://www.loggly.com/ 这样的工具可用于自动收集有关错误的信息 (即使在客户端 JavaScript 代码中), 这样您就可以更好地了解发生的情况.
这些步骤是改进流程的良好起点. 但是他们并没有真正解决所有问题. 例如, 无论您尝试多少, 用户都可以并且将继续发送令人困惑的错误报告.
我知道. 我反复告诉用户他们需要包含重现问题的步骤, 或者我们不能做任何事情, 而且我仍然不断得到可怕的 "它不起作用" 的错误报告.
"Jani,X 破了修复它"
啊.
那么还有什么我们可以做到这一点并不依赖于那些挑剔的用户呢?
记录运行时信息
记录经常被忽视作为一种工具. 也许这样做缺乏好的库, 或缺乏使用日志输出的好工具 - 因为让我们面对它, 谁想通过手工查找特定事件来浏览一个巨大的日志文件? 但是正确完成并使用好的工具, 日志可以提供有价值的信息.
大多数开发人员仅将日志记录用作临时措施. 很容易将一堆 `console.log'打入我们的代码只是为了看看发生了什么 - 我做了很多.
但是当我说伐木时, 我不只是谈论调试日志或错误日志. 我正在谈论一般的登录 - 关于代码中发生了什么, 正在发送什么输入等的信息.
以更系统的方式登录需要一些工作. 我们需要注意包括登录我们的代码, 我们需要确保记录可能有用的信息. 那么这有助于加快 bug 的修复速度呢?
有时, 如果没有用户告诉我们, 可能会出现问题. 自动进程在没有记录的情况下基本上是不透明的, 你永远不会知道出了什么问题.
如果您有良好的日志, 他们可以帮助您更快地找到问题. 根据您记录的内容和方式, 他们可以指出问题的一般方向, 甚至提供更具体的信息, 例如向您显示哪些值是错误的.
特别是在自动化流程的情况下, 日志记录很重要. 除非您有日志, 否则通常无法跟踪此类进程中发生的情况. 日志应该足够详细, 以便让我们对发生的事情有一个合理的了解.
使用这样的日志有助于通过向我们提供更多有用信息来减少猜测工作量.
尽管我喜欢将 Java 变得糟糕, 但日志记录是他们做得很好的一件事. 它有许多库和已建立的日志实践. 如果您遇到过 Java 应用程序的问题, 您可能会查看日志甚至增加日志详细程度. 他们中的许多人输出了大量的日志.
记录有用的是什么? 对于每个应用程序来说, 拥有日志或记录所有内容并不是绝对必要的, 但这里有一些例子:
记录个人请求. 例如, 如果您使用过 Apache 或 Nginx, 它们都有一个日志文件, 该文件为服务器执行的每个请求获取一行, 以及有关它的信息. 仅此信息不一定有用, 但如果您为每个请求记录多条信息, 这可能是首先记录的好信息.
记录流程或事务中的步骤. 我们假设您的用户可以上传图片, 但您可以调整图片大小. 您可以记录流程中的每个步骤, 例如 "上传的图像","调整图像大小" 等.
记录与外部工具 / 服务的交互. 访问数据库, API 或在我们的图像大小调整示例中, 如果您调用 ImageMagick 等工具来执行某些操作.
根据您查找日志的有用程度, 实现启用 / 禁用某些类型的日志或基于每个用户启用 / 禁用日志的方法也是一个好主意.
更快地发现错误
到目前为止我们所看到的所有这些措施都没有解决我们的关键问题. 错误报告和日志虽然有用, 但只会在事后提供更多信息.
还记得我们如何找到快速修复的错误之间的重要区别, 以及需要很长时间才能修复的错误是我们检测问题的时间.
每当我们积极处理一段代码并在开发过程中发现错误时, 它们的修复速度要快得多. 我们对代码有了全新的记忆, 我们已经掌握了所有信息, 因此我们不需要进行其他必要的考古.
到目前为止, 这些都没有任何帮助, 即使这是花费多少时间的最大贡献者之一.
我们怎样才能更快地发现更多的错误, 甚至在我们的开发过程中?
显然, 我们可以聘请二十多位 QA 专家来用显微镜进行更改. 然而, 对于大多数团队而言, 这并不是很实用, 即使使用质量保证流程, 也可能需要一些时间来发现问题, 此时我们已经转向了其他方面, 所以它无济于事.
在开发过程中可以帮助我们发现错误的是测试自动化.
我们可以在开发机器上运行自动化测试
即使在小代码更改后, 单元测试套件也可以快速自动运行
精心设计的测试可为我们提供有关问题的准确信息
测试自动化是一个更加现实的目标. 它不需要大量的前期投资: 您可以逐步开始使用它, 并且每一步都可以获得越来越多的好处.
单元测试如何加速 bug 修复?
每当我们更改代码时, 我们都会冒险引入错误. 但是, 如果我们新添加的代码存在错误, 我们通常会轻松地发现并修复它们.
新添加或更改的代码很容易修复, 因为我们已经掌握了它 - 我们只是花时间研究它! 这意味着可以轻松找到并修复其中的任何问题, 因为我们不需要开始挖掘代码来找到它. 我们仍然可以记住事情的发展方向.
因此, 我们可以同意, 我们越早发现错误, 就越容易修复.
但单元测试如何帮助我们做到这一点?
首先, 单元测试通常包含以下部分:
正在测试的内容的描述性名称, 例如 "它应该验证用户的名称"
用于初始化和运行测试的简短代码段
验证. 每个测试都有一个断言来验证代码片段产生了正确的结果.
然后, 如果这样的测试失败, 我们会得到以下信息:
失败测试的名称
关于断言如何失败的具体信息, 经常给我们一个特定的比较, 例如 "用户名与预期格式不匹配"
堆栈跟踪, 它为我们提供了导致失败的特定代码行
让我们将这些与我们想要的错误报告进行比较:
名称
发生了什么的描述
应该发生什么的描述
重现问题的步骤列表
你能看到我们从测试中得到的结果吗? 我们从一个好的错误报告中获得了我们想要的信息! 不仅如此, 测试在开发过程中为我们提供了这些信息!
当您仍在处理代码时, 单元测试可以为您提供所需信息的即时反馈, 因此您的想法一切都很新鲜.
所有这些都有助于确保我们有足够的信息来快速修复错误. 我们知道发生了什么, 应该发生什么, 我们可以通过再次运行测试来重现错误... 我们甚至可以通过再次运行测试来验证我们的错误修复代码是否正常工作.
作为额外的好处, 测试也会捕获代码中其他地方的更多错误. 通常由于我们的更改, 我们会在其他地方意外地导致错误. 这些很容易被忽视, 最终难以修复, 但如果你有单元测试, 没问题 - 一旦你编写测试, 你可以保持它, 它不断捕捉错误和有用.
结论
通过修复错误, 最大的时间接收器不是编写修复程序. 在我们开始编写任何代码来修复 bug 之前, 我们需要做的所有工作. 其中大部分是由于缺乏信息 - 错误的错误报告, 大量的代码, 甚至糟糕的代码都可能导致错误.
我们可以通过尝试在错误报告中获取更多信息来改善这种情况, 但更快地修复错误的最佳方法是更早地发现错误.
在开发过程中遇到的错误是最快修复的, 因为我们正在积极处理有问题的代码, 并且我们在头脑中拥有所需的信息. 这意味着我们不需要开始挖掘错误报告或代码来弄清楚发生了什么.
在测试期间捕获更多问题的最佳方法是单元测试. 他们解决了所有三个问题:
我们没有足够的信息. 解决了 - 失败的测试告诉我们出了什么问题以及应该发生什么, 指出我们确切的功能. 测试本身就是 bug 的再现.
除非我们对整个代码库非常熟悉, 否则我们需要做一些工作来找到问题的根源. 已解决 - 失败的测试告诉我们哪个功能失败, 因此不会涉及任何猜测. 更好的是, 测试通常在开发过程中失败, 因此所有代码在我们的记忆中仍然是新鲜的.
由于时间限制, 有时会跳过查找根本原因, 导致代码错误. 解决 - 测试将失败的根本原因, 而不是症状, 给我们确切的位置, 我们需要解决.
您也不需要花费大量精力进行测试. 您可以逐个开始添加测试, 例如修复错误时. 随着您添加的每项测试, 您将获得越来越多的好处.
测试的主要问题是它可能很难入门. 然而, 一旦你学会了这些概念, 它们就不会过时了 - 不像当下流行的图书馆, 测试已经存在了很长时间, 无论你使用什么库或语言, 都可以使用完全相同的原理.
单元测试还有更多的好处, 而不仅仅是更快地修复 bug.
来源: http://www.jianshu.com/p/013d315c77b3