阅读其他语言:English Português 中文
有许多调试器教程可以教您如何设置行断点、记录值或计算表达式。虽然这些知识本身就为您提供了许多工具来调试应用程序,但实际场景可能会更复杂一些,并且需要更高级的方法。
在本文中,我们将学习如何在没有太多项目先验知识的情况下找到导致 UI 崩溃的代码,并即时修复损坏的代码。
如果您想遵循该示例,请首先克隆此存储库:https://github.com/flounder4130/debugger-example
假设您有一个复杂的应用程序,当您执行某些操作时该应用程序会崩溃。您知道如何重现错误,但困难在于您不知道代码的哪一部分负责此功能。
在我们的示例应用程序中,当您单击按钮 N 时会发生崩溃。然而,要找到负责此操作的代码并不容易:
让我们看看如何使用调试器来找到它。
方法断点相对于行断点的优点是它们可以在整个类层次结构中使用。这对我们的例子有什么用?
如果您查看示例项目,您将看到所有操作类均派生自 Action 接口,并具有单个方法:perform()。
在此接口方法上设置方法断点将在每次调用派生方法之一时挂起应用程序。要设置方法断点,请单击声明该方法的行。
启动调试会话并单击按钮 N。应用程序在 ActionImpl14 上暂停。现在我们知道这个按钮对应的代码在哪里了。
虽然在本文中我们的重点是查找错误,但当您想要了解某些内容在大型代码库中如何工作时,此技术也可以为您节省大量时间。
使用方法断点的方法效果很好,但它基于我们了解父接口的假设。如果这个假设是错误的,或者我们由于其他原因不能使用这种方法怎么办?
嗯,我们甚至可以在没有断点的情况下做到这一点。单击 按钮 N,当应用程序挂起时,转到 IntelliJ IDEA。从主菜单中,选择运行 | 调试操作 | 暂停程序。
应用程序将挂起,允许我们检查线程和变量选项卡中线程的当前状态。这让我们了解应用程序当时正在做什么。由于它挂起,我们可以识别导致阻塞的方法并将其追溯到调用站点。
这种方法比更传统的线程转储有一些优势,我们很快就会介绍。例如,它以方便的形式为您提供有关变量的信息,并允许您控制程序的进一步执行。
提示:有关暂停程序的更多提示和技巧,请参阅无断点调试和Debugger.godMode()
最后,我们可以使用线程转储,这并不是严格意义上的调试器功能。无论您是否使用调试器,它都可用。
单击按钮N。当应用程序崩溃时,转到 IntelliJ IDEA。从主菜单中,选择运行 | 调试操作 | 获取线程转储。
探索左侧的可用线程,在AWT-EventQueue中,您将看到导致问题的原因。
线程转储的缺点是它们仅提供程序在创建时的状态快照。您不能使用线程转储来探索变量或控制程序执行。
在我们的示例中,我们不需要诉诸线程转储。但是,我仍然想提一下这种技术,因为它在其他情况下也很有用,例如当您尝试调试在没有调试代理的情况下启动的应用程序时。
无论调试技术如何,我们都会到达 ActionImpl14。在此类中,有人打算在单独的线程中完成工作,但将 Thread.start() 与 Thread.run() 混淆了,后者在与调用代码相同的线程中运行代码。
IntelliJ IDEA 的静态分析器甚至在设计时警告我们:
在 UI 线程上调用执行繁重任务(或在本例中休眠很多时间)的方法,并阻止该方法直到该方法完成。这就是为什么我们在点击按钮N后一段时间内无法在UI中执行任何操作。
现在我们已经找到了错误的原因,让我们纠正问题。
我们可以停止程序,重新编译代码,然后再次运行它。然而,仅仅因为做了一个小更改就重新部署整个应用程序并不总是明智的。
让我们用聪明的方式来做吧。首先,使用建议的快速修复修复代码:
代码准备好后,点击运行 | 调试操作 | 重新加载更改的类。出现一个气球,确认新代码已到达虚拟机。
让我们返回应用程序并检查一下。单击按钮 N 不再使应用程序崩溃。
提示:请记住,HotSwap 有其局限性。如果您对扩展的 HotSwap 功能感兴趣,那么看看 DCEVM 或 JRebel 等高级工具可能是个好主意
使用我们的推理和一些调试器功能,我们能够找到导致项目中 UI 崩溃的代码。然后,我们继续修复代码,而不会浪费时间重新编译和重新分发,这在实际项目中可能会很长。
我希望您发现所描述的技术很有用。让我知道你的想法!
如果您对更多与调试和分析相关的文章感兴趣,请查看我的其他一些文章:
更多内容敬请期待!
以上是调试无响应的应用程序的详细内容。更多信息请关注PHP中文网其他相关文章!