调试器适合失败者

没有 计划代码缺陷; 它们是由于无法理解不同输入条件下的执行流程而导致的事故 。 但是就像安全气囊是汽车的最后一道防线一样, 调试器应该是程序员的最后一道防线。 防御性驾驶减少或消除了对安全气囊的依赖。 防御性驾驶是积极主动的 ,它利用所有可用数据来预测避免问题。

防御性驾驶另一种方法是你是在一个驱动漫不经心和享受驾驶
拆除德比。 存在硬件和软件调试器必不可少且无可替代的情况。 但是大多数时候,调试器并不是发现和消除缺陷的最佳工具。

调试器不会为您修复该缺陷。

调试器与洗衣机不同 ,您可以在其中放入代码和一些肥皂,去喝咖啡和看电视,然后在30分钟后清除缺陷只有了解了问题的根本原因,您才能修复缺陷。 记住, 消除缺陷不会推进您的项目; 您的项目将暂停,直到解决问题。 确保缺陷永远不会进入您的代码,而不是学会变得有效地消除它们,这将为您提供更好的服务。 最糟糕的程序员在调试器中花费的时间是最好的程序员的25倍。链接

灵感而非汗水

缺陷是对程序行为的观察 ,该行为偏离了特定要求(无要求=无缺陷)。 熟悉代码途径的开发人员可以通过简单地推理观察结果如何发现缺陷。


McGill ,我是必须进行大型机编程的最后一年。 大型机上不存在调试器,您只需将代码提交以在批处理系统中执行,如果有缺陷,则进行代码转储,然后将清单和转储发送给打印机。 在大学大型机的本质是,你会在与其他10-15人们都在等待他们的打印输出的高速打印机排队


我没有其他事情要做,我会考虑我的程序可能会如何失败 –在大多数情况下,我能够在列印清单之前弄清楚缺陷的来源。 吸取的教训是思考的能力对发现缺陷至关重要。 不能替代通过缺陷进行推理的能力。 但是,如果您尽可能简单地放置代码路径,则将加快发现缺陷的过程。 此外,如果有复杂的代码段,则可以通过重写它们而受益,特别是如果它们是由其他人编写的。

当你想要他们时不在

即使您认为调试器是最好的发明,因为切片面包,我们大多数人都不能在客户的生产 环境中使用调试器。

在具有最少O / S基础结构的计算机上, 防火墙掩盖了许多生产环境。 大多数客户不会让您混乱他们的生产环境,因此在这些计算机上获取调试器的符号信息将是天作之举 。 在那些环境中,典型的解决方案是使用良好的日志记录系统 (即Log4J等)。 日志记录调用的放置位置与引发异常的位置相同 –但是由于这是调试器的讨论而非调试,我们将在另一时间讨论日志记录。

调试器和幻影问题


我为一个客户端工作,在其中启用编译器调试信息实际上导致间歇性错误弹出。 该程序将随机崩溃,没有明显的原因,我们都对问题的根源感到困惑。 有趣的是,问题仅发生在我们的开发环境中,而不是生产环境中。 我们的开发性能相当缓慢 ,我怀疑带有符号信息的代码文件可能是问题的根源。 我开始在没有符号信息的情况下编译程序,以查看是否可以提高性能。 令我惊讶的是 ,我们不仅获得了性能提升,而且随机崩溃也消失了。 经过进一步的调查,我们发现调试器的符号信息占用了太多的RAM,从而导致内存溢出

调试器和生产力


对开发人员生产力的 研究表明,生产力最高的开发人员至少比其他所有人快10倍 。 如果您与一个生产效率高的人一起工作,然后问他们在调试器中花费了多少时间,很可能他们没有……


你知道查尔斯·普罗特斯·斯坦梅茨是谁吗? 他是电气工程的先驱,并于1902年从GE退休。退休后,GE打电话给他,是因为他建造的非常复杂的系统之一被破坏了,GE的最佳技术人员无法修复它。 查理回来当顾问。 他在系统上走了几分钟,然后拿出粉笔,在零件上打了个X (他正确地识别出故障零件)。 查理随后向通用电气提交了10,000美元的帐单,但通用电气却被帐单的大小吓了一跳(今天大约30万美元)。 GE要求提供一份详细的咨询发票,Charles寄回了以下发票:

使粉笔标记1美元
知道在哪里放置它$ 9999

调试员分神

正如Brian W. Kernighan和Rob Pike所说的那样,他们在其出色的著作《 编程实践 》中

作为个人选择,除了获取堆栈跟踪或一两个变量的值外,我们倾向于不使用调试器。 原因之一是很容易在复杂的数据结构和控制流的细节上迷失方向; 我们发现单步执行程序的效率不如认真思考,并在关键位置添加输出语句和自检代码。 单击结束语句所花费的时间比扫描明智放置的显示器的输出所花费的时间更长。 即使单步进入代码的关键部分,也可以花更少的时间来决定将打印语句放置在何处,即使假设我们知道该位置在哪里。 更重要的是,调试语句保留在程序中。 调试会话是暂时的。 调试器就像希腊神话中的警报器一样,很容易被调试器显示的所有数据迷住分散注意力

尽管现代调试器的显示在执行的点上下文数量惊人的能力,它只是代码的一个很小的部分的快照 。 这就像试图获取一幅绘画的图像,而您一次只能看到一个小窗口。 上面我们显示了一张图片的6张快照,很难看到大图片。

上面的快照是蒙娜丽莎上的小窗户,但是很难从较小的快照中看到更大的画面 。 为了有效地调试问题,您需要对图片有更高层次的了解,以评估正在发生的事情(有效性)。 如果必须在未编写的代码中发现缺陷,那么与简单地单步调试调试器中的代码以求幸运一样分析代码的整体结构(模块,对象等)仍然会更好。

当我们没有考虑相对于变量的所有可能数据组合,通过代码采取的所有行为途径时,就会发生缺陷。

一些常见的缺陷类别包括:

  • 未初始化的数据或缺少资源(即找不到文件)
  • 代码路径设计不当
  • 计算误差
  • 持续性数据调整不当(即丢失数据)

关键是缺陷的行为实际上只有几种。 如果您了解这些常见行为,则可以构造代码以最大程度地减少发生这些行为的机会。

防御性编程


防御性编程的概念与我们防御性驾驶的方式相同,即“最好的防御是好的进攻”。 我的防御性编程的定义是使用技术来构建代码,以便途径很简单 易于理解。 尽力编写无缺陷的代码,而不是调试有缺陷的代码。 另外,如果在出现问题时我可以使程序知道其上下文,则它应该停止 提示错误消息,以便我确切地知道发生了什么。 防御性编程技术太多,无法在此处进行介绍,因此,仅举几篇我喜欢的内容(向我发送您最喜欢的技术电子邮件 ):

  • 正确使用例外
  • 决策表
  • 合同设计
  • 自动化测试

正确使用例外

下面是一条设计代码途径的糟糕方法://程序不应到达此处。 任何写这样评论的开发人员都应该被枪杀 。毕竟,如果您错了并且程序确实到达那里该怎么办? 大多数程序中都存在这种情况,尤其是对于case / switch语句的默认情况。 您的评论不会停止该程序,它可能会继续在其他地方造成缺陷副作用 。 您可能需要花费很长时间才能弄清楚最终缺陷的根本原因。 尤其是因为您可能会被自己的注释所吸引,而不考虑代码到达该点的可能性。 当然,您至少应该记录该事件,但是本文是关于调试器而不是调试的-因此,让我们将有关日志记录的讨论留到本文结尾。 至少应使用以下内容替换注释:throw new Exception(“程序不应到达这里!”); 假设您有权访问异常 。 但是,这还远远不够。 毕竟,您掌握了到达该位置所必须满足的条件的信息。 该异常应反映这些条件,以便您立即了解看到的情况。 例如,如果到达代码中该位置的唯一方法是因为Customer对象不一致,那么以下方法会更好:throw new InconsistentCustomerException (“新事件日期先于Customer的创建日期” + Customer.getIdentity ()); 如果曾经抛出InconsistentCustomerException异常,那么您一看到该缺陷,便可能拥有足够的信息来修复缺陷。

决策表

代码路径的不正确设计还有其他的体现,例如没有在复杂的逻辑中规划足够的代码路径。 即,您使用7条代码路径构建代码,但实际上需要8条路径来处理所有输入。 任何需要缺少 8条路径的数据都将变成计算错误或不正确的持久数据调整,并在代码中的其他地方引起问题。

当您的代码具有多个途径的复杂部分时,请创建一个决策表来帮助您计划代码。 决策表自1960年代以来就已经存在,它们是主动设计基于复杂决策的代码段的最佳方法之一。 但是,除非计划将其更新,否则不要在决策中包括决策表(请参阅针对失败者的注释 )。

合同设计

合同设计 (DbC)的概念是1986年由Eiffel编程语言引入的。 我意识到很少有人会在Eiffel中编程,但是这个概念可以应用于任何语言。 基本思想是每个类函数都应具有在每次执行时都经过测试的前置条件后置条件

在功能入口处检查前置条件,在功能出口处检查后置条件。 如果违反任何条件,则将引发异常

DbC具有侵入性,并增加了程序中每个调用的开销 。 让我们看看有什么可以使这些开销值得的。 当我们编写程序时,调用栈可能会很深。

假设我们具有以下调用结构; 如您所见,可以通过不同的呼叫路径(即,通过任何呼叫FG的人 )到达H。 假设来自A的呼叫流有一个缺陷,需要在H中修复,很明显,这将影响来自B的呼叫流。 如果对H的修复将导致F中出现副作用,则很可能F (或B )的DbC后置条件将抓住问题并引发异常。 将其与解决没有制衡的问题相对照。 副作用可能表明与创建问题的距离很远,并且会导致很难跟踪的间歇性问题。

通过面向方面的编程的DbC

显然,我们中很少有人在埃菲尔编程。 如果可以访问面向方面的编程 (AOP),则可以通过AOP实现DbC。 今天,已经有AOP实现作为语言扩展或作为许多当前语言(Java,.NET,C ++,PHP,Perl,Python,Ruby等)的库。

自动化测试

正常的应用程序使用将在日常应用程序使用中行使很少的代码途径。 对于每个正常的代码途径,将有几种替代途径来处理异常处理,而这正是发现大多数缺陷的地方。 其中一些异常很少发生,因为它涉及的输入数据不尽人意-您是否真的希望这些异常在没有故障排除工具的远程客户系统首次发生? 如果您正在使用测试驱动开发 (TDD),那么您已经对所有代码进行了一系列单元测试 。 这个想法很好,但是我认为还远远不够。 自动化测试不仅应执行单元测试,还应在集成级别执行测试。 这不仅将在类级别以异常条件测试您的代码,而且还将在类和模块之间的集成级别测试代码。 最终,应该使用规范数据库来增强这些测试,在规范数据库中还可以测试数据副作用。

这个想法是建立自动测试,直到代码覆盖率达到80%以上 。 达到80%以上意味着您对系统途径的了解会非常好,并且修复缺陷所需的时间将大大减少。 为什么超过80%? (请参阅最小可接受代码覆盖率

如果您可以达到80%以上的水平,这也意味着您对代码路径有很好的了解 。 代码覆盖率测试的优点是它不会影响代码的运行时性能,并且可以增进您对代码的理解。 您所有的缺陷都隐藏在未经测试的代码中。

减少对调试器的依赖

如果您必须借助调试器来发现缺陷,请在解决问题后花几分钟,然后问:“ 有没有更快的方法? ”。 您从使用调试器中学到了什么? 您是否可以采取一些措施来防止您(或其他人)再次沿着同一路径使用调试器? 重构代码或放入更好的异常会有帮助吗? 解决缺陷的唯一方法是通过了解代码中正在发生的事情。 因此,下次您有一个缺陷需要解决时,请先考虑问题。 花几分钟时间,看看在加载调试器之前是否无法解决问题。

另一种技术是与另一位开发人员讨论缺陷的来源。 即使他们不熟悉您的代码,他们也会提示您一些问题,这些问题通常会帮助您找到缺陷的源头而无需借助调试器。

在几家公司中,我说​​服开发人员放弃使用调试器一个月。 实际上,我们在每个人的系统上都删除了调试器的代码。 这种激烈的行动迫使开发人员考虑缺陷的产生原因。 开发人员抱怨了大约2周,但在一个月内, 他们发现缺陷的平均时间下降了50%以上尝试在没有调试器的情况下工作一周,您要失去什么?

毕竟,下一张图显示了我们花费了多少精力来消除缺陷,平均每个项目最终花费了总时间的30%来消除缺陷。 想象一下,将效率提高50%对您的项目有什么影响? 黄色区域勾勒出大多数项目落在哪里。

功能点 平均努力
10 1个人月
100 7个人月
1000 84人月
10000 225 人年

结论

肯定会有很多时候, 调试器将是发现问题的最佳解决方案。 当您花时间在调试器中时,您的项目处于暂停状态 ,它将无法进行。 一些程序具有复杂复杂的部分,从而产生许多缺陷。 而不是使用调试器遍历这些部分,您应该对代码进行更高级别的分析并重写有问题的部分。 如果您发现自己在调试器中多次经历相同的代码路径,那么您可能想停下来问自己:“ 有没有更好的方法? 就像Master Card可能会说的那样:“ 不使用调试器……无价 ”。

没错,我是所有人中最大的“失败者”。 我相信我在书中至少犯了一次错误

参考: 加速开发博客上的JCG合作伙伴 Dalip Mahal的调试器是为失败者准备的

翻译自: https://www.javacodegeeks.com/2013/05/debuggers-are-for-losers.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值