敏捷的调试

敏捷调试

记录解决问题的日志

面对问题并解决它们所开发人员的一种生活方式。当问题发生时,我们希望赶紧把它解决掉。如果一个熟悉的问题再次发生,我们会希望记起第一次时如何解决的,而且希望下次能够更快地把它搞定。然而,有时一个问题看起来跟以前遇到的完全一样,但是我们却不记得说如何修复的了。这种状况时常发生。

从网上寻找答案当然胜过仅靠个人努力解决问题,可这是非常耗费时间的过程。有时可以找到需要的答案,有时出了找到一大堆意见和建议之外,发现不了实质性的解决方案。

Don't get burned twice
要想得到更好的效果,不妨维护一个保存曾遇到的问题以及对应解决方案的日志,可以快速搜索以前用过的方法,它叫每日日志(daylog)

可以选择符合需求的任何格式。比如,

  • 问题发生日期

  • 问题简述

  • 解决方案详细描述

  • 引用文章或网址,以提供更多细节或相关信息。

  • 任何代码片段、设置或对话框的截屏,只要它们说解决方案的一部分,或者可以帮助更深入地理解相关细节。

  • 要把日志保存为可供计算机搜索的格式,就可以进行关键字搜索以快速查找细节。

  • 如果面临的问题无法在日志中找到解决方案,在问题解决之后,要记得马上将新的细节记录到日志中去。

  • 要共享日志给他人,而不仅仅是靠一个人维护。把它放到共享的网络驱动器中,这样其他人也可以使用。或者创建一个wiki,并鼓励其他开发人员使用和更新其内容。

具体技巧

  • 记录问题的时间不能超过解决问题上花费的时间。要保持轻量级和简单,不必达到对外发布时的质量。
  • 找到以前的解决方法非常关键,使用足够的关键字,可以帮助你在需要的时候发现需要的条目。
  • 如果通过搜索Web,发现没人曾经遇到同样的问题,也许搜索的方式有问题。
  • 要记录发生问题时应用程序、应用框架或平台的特定版本。同样的问题在不同的平台或版本上可能表现得不同。
  • 要记录团队做出一个重要决策的原因。否则,在6~9个月之后,想再重新回顾决策过程的时候,这些细节就很难再记得了,很容易发生互相指责的情形。

警告就是错误

当程序中出现一个编译错误时,编译器或是构建工具会拒绝产生可执行文件。我们别无选择——必须要先修正错误,再继续前行。

然而警告却是另外一种状况。即使代码编译时产生了警告,我们还是可以运行程序。有些警告说过于挑剔的编译器的良性副产品,有些则不是。

可能有人会说优秀的单元测试可以发现这些问题。说的,可如果编译器可以发现这种问题,那为什么不利用它呢?者可以节省大量的时间和麻烦。要找到一种方式让编译器将警告作为错误提示出来。如果编译器允许调整警告的报告级别,那就把级别调到最高,让任何警告不能被忽略。

在修改设置的时候,要记得在构建服务器上使用的持续集成工具中,修改同样的设置选项。这个小小的设置,可以大大提升团队签入到源码控制系统中的代码质量。

具体技巧

  • 虽然这里探讨的主要是编译语言,解释型语言通常也有标志,允许运行时警告。使用相关标志,然后捕获输出,以识别并最终消除警告。
  • 由于编译器的bug或是第三方工具或代码的原因,有些警告无法消除。如果确实没有应对之策的话,就不要再浪费更多时间了。但是类似的状况很少发生。
  • 应该经常指示编译器:要特别注意别将无法避免的警告作为错误进行提示,这样就不用费力去查看所有的提示,以找到真正的错误和警告。
  • 弃用的方法被弃用是有原因的。不要再使用它们了。至少,安排一个迭代来将它们以及它们引起的警告信息安全地移除掉。
  • 如果将过去开发完成的方法标记为弃用方法,要记录当前用户应该采取何种变通之策,以及被弃用的方法将会在何时一起移除。

对问题各个击破

单元测试带来的积极效应之一,是它会强迫形成代码的分层。要保证代码可测你,就必须把它从周边代码中解脱出来。如果代码依赖其他模块,就应该使用mock对象,来把它从其他模块中分离开。这样做不但让代码更加健壮,且在发生问题时,也更容易定位来源。

否则发生问题时有可能无从下手。也许可以先使用调试器,逐行执行代码,并试图隔离问题。也许在进入到感兴趣的部分之前,要运行多个表单或对话框,这会导致更难发现问题的根源。你会发现自己陷入整个系统之中,徒然增加了压力,而且降低了工作效率。

大型系统非常复杂——在执行过程中会有很多因素起作用。从整个系统的角度来解决问题,就很难区分开,哪些细节对要定位的特定问题产生影响,而哪些细节没有。

答案很清晰:不要试图马上了解系统的所有细节。要想认真调试,就必须将有问题的组件或模块与其他代码库分离开来。如果有单元测试,这个目的就已经达到了。否则,你就得开动脑筋了。

Prototype to isolate.
识别复杂问题的第一步,是将它们分离出来。

可是,很多应用的代码在编写时没有注意到这一点,使得分离变得特别困难。应用到各个构成部分之间会彼此纠结:想把这个部分单独拿出来,其他的会紧随而至。在这些状况下,最好花一些时间把关注的代码提取出来,而且创建一个可以让其工作的测试环境。

隔离问题不应该只在交付软件之后才着手。在构建系统原型、调试和测试时,各个击破的战略都可以起到帮助作用。

具体技巧

  • 如果将代码从其运行环境中分离后,问题消失不见了,这有助于隔离问题。
  • 另一方面,如果将代码从其运行环境中分离后,问题还在,这也有助于隔离问题。
  • 以二分查找的方式来定位问题时很有用的。
  • 在向问题发起攻击之前,先查找你的问题解决日志。

报告所有异常

从事任何编程工作,都要考虑事物正常状况下是如何运作的。不过更应该想一想,当出现问题——也就是事情没有按计划进行时,会发生什么。

在调用别人的代码时,它也许会抛异常,这时我们可以试着对其处理,并从失败中恢复。当然,要是在用户没有意识到的情况下,可以恢复并继续正常处理流程最好不过了。要是不能恢复,应该让调用代码的用户知道,到底是哪里出现了问题。

具体技巧

  • 决定由谁来负责处理异常是设计工作的一部分。
  • 不是所有的问题都应该抛出异常。
  • 报告的异常应该在代码的上下文中有实际意义。
  • 如果代码中会记录运行时调试日志,当捕获或是抛出异常时,都要记录日志信息;这样做对以后的跟踪工作很有帮助。
  • 检查异常处理起来很麻烦。没人愿意调用抛出31种不同检查异常的方法。这是设计上的问题:要把它解决掉,而不是随便打个补丁就算了。
  • 要传播不能处理的异常。

提供有用的错误信息

当应用发布并且在真实世界中得到使用之后,仍然会发生这样那样的问题。当无法满足用户需求时,要以优雅的方式进行处理。类似的错误发生时,是不是只要弹出一条优雅且带有歉意的信息给用户就足够了?并不尽然。当然了,显示通用的信息,告诉用户发生了问题要好过由于系统崩溃造成应用执行错误的动作,或者直接关闭(用户会因此感到困惑,并希望知道问题所在)。然而,类似“出错了”这样的消息,无法帮助团队针对问题做出诊断。用户在给支持团队打电话报告问题时,我们希望他们提供足够多且好的信息,以帮助尽快识别问题所在。遗憾的是,用很通用的错误消息,是无法提供足够的数据的。

针对这个问题,常用的解决方案是记录日志:当发生问题时,让应用详细记录错误的相关数据。错误日志最起码应该以文本文件的形式维护。不过也许可以发布到一个系统级别的事件日志中。可以使用工具来浏览日志,产生所有日志信息的RSS Feed,以及诸如此类的辅助方式。

记录日志很有用,可是单单这样做是不够的。开发人员认真分析日志,可以得到需要的数据,但对于不幸的用户来说起不到任何帮助作用,他们还是一点头绪都没有,不知道自己到底做错了什么,应该怎么做可以绕过这个错误,或者在给技术支持打电话时应该报告什么。

如果你注意的话,在开发阶段就能发现这个问题的早期警告。作为开发人员,经常要将自己假定为用户来测试新功能,要是错误信息很难理解或者无助于定位错误的话,就可以想想真正的用户和支持团队,遇到这个问题时会有多么困难了。

一方面要提供给用户清晰、易于理解的问题描述和解释,使他们有可能寻求变通之法。另一方面还要提供具备关于错误的详细技术细节给用户,方便开发人员寻找代码中真正的问题所在。

错误报告对于开发人员的生产率,以及最终的支持活动消耗成本,都有很大的影响。在开发过程中,如果定位和修复问题让人倍受挫折,就考虑使用更加积极主动的错误报告方式吧,调试信息非常宝贵,而且不易获得。不要轻易将其丢弃。

具体技巧

  • 像“无法找到文件”这样的错误信息,就其本身而言无助于问题的解决。“无法打开...以供读取”这样的信息更有效。
  • 没有必要等待抛出异常来发现问题。在代码关键点使用断言以保证一切正常。当断言失败时,要提供与异常报告同样详细的信息。
  • 在提供更多信息的同时,不要泄露安全信息、个人隐私、商业机密或其他敏感信息。
  • 提供给用户的信息可以包含一个主键,以便于在日志文件或是审核记录中定位相关内容。
posted @ 2019-08-20 22:42  睿阳  阅读(199)  评论(0编辑  收藏  举报