调试的秘密艺术

精通调试的技巧很少。我从多年从事企业系统工作的经验中知道这一点。如果这很简单,那么将会有更多的人这样做,并且每个人都可以找到错误。现实情况是,大多数商店都有一个被称为“灭虫者”的“亲密接触者”,被召唤来扫除那些虫子,而其他人都无法找到。我曾在产品公司和咨询公司任职,在这两个职位上,我们业务的很大一部分都围绕着查找讨厌的错误?并进行修复。

我相信故障排除是一种可以教授,学习和掌握的技能。不幸的是,太多的人专注于工具和语言功能,而不是一个不管语言,平台或工具如何都会起作用的思维框架。有兴趣了解动态解决代码的秘密吗?继续阅读

我从事错误修复已有数十年了。我发表的第一篇专题文章之一是一篇名为“ The Exterminator’s Code”的故障排除文章,该文章在1999年的《 News / 400》杂志上刊登。我发现的一件事是,有效的Bug搜寻涉及多种技能。仅仅了解技术还不够。有一种疯狂的方法。可以学习某些步骤,而且随着您在职业生涯中遇到更多系统,经验只会增加。

总是令我惊讶的是,善于发现缺陷的人与未发现缺陷的人之间的差距。您可能认为这将是一连串的技能,但是我发现人们要么得到要么就没有-做事的人迅速而一致地做到这一点。那么秘密是什么?

训练你的眼睛?

帮我一个忙,并做一个快速的流行测验。阅读下面的报价,快速计算段落中F的数量。

完成的文件就是结果
多年的科学研究
结合经验
年。

一会儿,我会回到答案。如果我把它放在那太容易了。只需记下您的想法,然后再进行一些讨论即可。这是另一套说明,请相信我,这一切都可以解决。您准备好参加另一场比赛了吗?

我希望您观看一个很短的视频。该视频有一些说明。只要让它播放一次即可。不要试图暂停或快进。这是链接,或者您可以按下面的视频播放。继续观看,然后写下分数(您会明白的)。

现在,我希望您开始明白我的观点,这是掌握调试技术的第一步。以我的经验,大多数开发人员都不以正确的方式调试代码。当他们按下F5键并开始逐步执​​行程序时,他们并没有看到正在发生的事情。

什么?我在开玩笑吗?他们设置了断点。有监视窗口。他们尽职尽责地敲击F10和F11以进入和退出子程序。我什么意思这是问题所在:

他们正在等待程序执行他们期望的操作。很难不,尤其是当您是编写程序的人时因此,当您单步执行该代码块时,“是的,我只是在这里初始化一些变量”并快速按F10键,您只是想念它,因为一个字符串文字拼写错误或引用了错误的字符串不变。

“ F”测验的答案是6。大多数人算3,是因为他们在脑海中念出单词并聆听“ f”音,而不仅仅是看字母。这就是人们在调试时所做的事情-他们感觉到程序,而不是观察程序的真正作用。

认真地:训练你的眼睛

你看到大猩猩了吗?大多数人不会第一次。这是因为他们正在按照说明进行操作。他们正在计算通过次数,这正是练习的目的。但是,您能相信看到并知道您要查找的内容有多明显吗?你怎么会错过这样的东西?

希望到现在为止,我们已经确定您的思想有一个很好的过滤器,并将尝试为您提供所需的东西。因此,当您满怀期待地浏览代码时,您会猜到什么?您将看到调试器按照您的期望进行操作,而错过了可能导致该错误的实际情况。

但是如何? ¯(ツ)/¯

您可以采取多种措施来提高您的调试技能,我鼓励您尝试所有这些。

让其他人调试您的代码,并提出对其进行调试。了解如何查看代码并查看其功能的最佳方法是逐步浏览您不熟悉的代码。乍一看似乎很乏味,但是它是一种学科和技能,可以帮助您学习如何以正确的方式遍历代码,而不做任何假设。

尽量不要将代码作为块。换句话说,当您有一个初始化变量的例程时,不要将其作为“初始化工作块”来执行。逐步研究每个语句。不要将语句视为句子,而要回到您的编程根源,然后在等号的左侧看到一组符号,在等号的右侧看到一组符号。您会惊讶于这如何帮助您快速完成错误或重复的作业。例如,在MVVM中很常见,开发人员剪切并粘贴并最终得到如下代码:

private string _lastName; 
private string _firstName; 

public string FirstName 
{
   get { return _firstName; }
   set { _firstName = value; RaisePropertyChanged(()=>FirstName); }
}

public string LastName 
{
   get { return _firstName; }
   set { _lastName = value; RaisePropertyChanged(()=>LastName); }
}

您发现错误了吗?如果没有,请花一些时间,您会的。当您编写代码时,这要困难得多,因为期望可以“正常工作”。

回到基础?

有了所有告诉我们如何重构代码和为我们扫描类的精美工具,有时我们忘记了必须进行故障排除的基本工具。
我正在与一个客户端一起对内存泄漏问题进行故障排除,发现自己开始于庞大的依赖关系,句柄和实例图。我可以看到某些对象被创建了太多次,但是看一下代码,它看起来就正确了。其他东西从哪里来?

所以,我回到了基础。我在构造函数中放入了一条调试语句,然后再次运行它。突然,我意识到有些实例正在忠实地报告自己,而另一些则没有。怎么可能?啊…该类是从基类派生的。因此,我在基类中放置了另一个调试语句。果然,它也被实例化了。快速暴跌调用堆栈并解决了问题……不是通过K线走势图,重构工具,而是良好的旧侦探工作。

不要相信文档?

我从早期学到的一课是不要相信文档。我正在写一本关于正在开发的新框架的书。该文档稀疏,多变,并且经常是错误的。在一个特定的部分中,我正在写某些应用程序应该如何工作。该文档非常具体地说明了如何将工作划分为多个线程。我的导师说:“不信任它”,并鼓励我改为调试。在逐步检查代码并观察实际发生的情况而不是预期的发生时,我了解到体系结构是非常不同的。我能够帮助修复文档,并帮助开发人员避免不必要的代码开销。

我不断建立一些小型项目来学习语言和平台。例如,考虑以下JavaScript:

const doSomething = (payload, fn) => { fn(payload); };

doSomething('This should echo', console.log);

let text = 'Some text'; 
doSomething(text, text => text += ' appended to.');
console.log(`The text after the call: ${text}`);

let textPayload = { text }; 
doSomething(textPayload, payload => payload.text += ' appended to.');
console.log(`The text after the payload call: ${textPayload.text}`);  

您可以预测将写入控制台的内容吗?您可以通过运行此jsFiddle来验证您的答案。

这是一段简单的代码,但是帮助我超越了概念化的范围,在JavaScript中理解了原语和对象的不同之处,从而亲身体验了它们的实际作用。

实践使完美P​​erfect️‍♂️?️‍♀️

我经常调试正常的代码。我经常发现没有立即明显副作用的潜在问题。我可能会注意到初始化代码被多次调用(这可能会导致大规模的性能问题),或者事件是由实例注册的,而该实例超出了范围而没有注销。这可能导致内存泄漏

有时,当您脱离假设时,甚至“简单的代码”也可能会泄露秘密。我从事了一些用Angular.js编写的最大项目(是的,这是要求的旧版本 $scope 要么 controller as)。一些使用数据绑定的非常简单的页面在“幕后”上有很多工作。通过调试这些页面,我发现效率低下,例如运行多个“摘要循环”,这些随着时间的流逝会影响性能。

一个很棒的练习是下载一个开源项目,最好是一个实用程序或工具。花一些时间查看源代码,以确定您认为代码的行为方式。然后,启动调试器并逐步执行每个语句。千方百计您可能会对发现和学习到的东西感到惊讶。您执行的次数越多,您就越会做好准备。你会:

  1. 了解如何通过预览源代码来分析代码
  2. 发现开发人员用来解决各种问题的模式
  3. 潜在地发现作者尚未意识到的问题

此练习可能只是发现您可以做出的改进,并作为开源贡献提交给项目。

最好的调试工具就是您的想法?

最后,我将为您提供许多年前导师给我的同样建议,这是我多年前开始排除第一个企业问题时所给的。他告诉我,目标应该是永远不必启动调试器。每个调试会话都应该从逻辑上演练代码开始。您应该分析期望的结果,并进行虚拟遍历…如果我在这里通过了,我会明白的,然后再去那里,这样就会循环…这样的练习将比帮助您理解代码。在我浏览源代码的十个错误中,有九个是错误的,而不必使用F5。

当我确实按F5键时,现在我对代码应该执行的操作有所期望。当它做一些不同的事情时,通常更容易找出计划哪里出了问题以及执行代码如何脱离脚本。在许多不允许您完全运行调试器的生产环境中,此技能尤其重要。自那时以来,我一直受教并遵循这样的哲学:修复甚至最丑陋的错误,都需要结合源代码,适当放置的跟踪语句和深刻的思想。

启动调试器并在您认为可能存在缺陷的位置设置断点很容易。在紧要关头,这可能是最好的方法,如果您成功了,那顶帽子会提示您如果不是,请不要陷入相信您的假设的陷阱。相反,请退后一步,开始分析实际发生​​的情况,而不是您认为应该发生的情况。

我的流程通常如下所示:

  1. 尝试解决我认为的问题(如果我可以编写因缺陷而失败并在修复时通过的单元测试,则可加分)
  2. 如果不是,请退后一步并分析代码以确定其实际作用
  3. 打开调试器,但是不要在我认为可能是问题的地方设置断点,而是从头开始逐步进行操作,并注意发生了什么,而不是我认为应该发生的事情
  4. 添加调试语句以帮助留下痕迹
  5. 孤立地提取代码,以查看是否可以找到缺陷而没有完整解决方案的复杂性和开销

我已经做了足够长的时间,以至于我80%的时间都在第一步中找到了解决方案,最终在20%的时间中为最后几步花费了额外的时间。

这是一门可以学的艺术

调试是一门可以耐心,专注和有经验的艺术。我希望前面的练习可以帮助您理解有时会阻止您修复代码的筛选器,并且这些技巧将帮助您下次遇到问题时有所不同。请记住,没有无法解决的缺陷……而且调试器没有比您的耳朵更强大的调试器。

您最喜欢的调试技巧是什么?请使用以下评测分享。

资讯来源:由0x资讯编译自DEV,原文:https://dev.to/dotnet/the-secret-art-of-debugging-1lfi ,版权归作者所有,未经许可,不得转载
你可能还喜欢