作为开发者,您偶尔会在代码中遇到问题,包括逻辑错误、API 使用不当或其他各种问题。本指南将教您如何调试代码、检测一些常见问题并找到解决方案。
糟糕!出现严重错误。我的程序崩溃了,或者输出了乱码,或者似乎永远运行下去。现在该怎么办?
初学者倾向于责怪编译器、库或除自身代码以外的任何东西。经验丰富的程序员也希望这样做,但他们知道,实际上,大多数问题都是他们自己的错。
幸运的是,大多数错误都很简单,可以通过简单的技术找到。检查错误输出中的证据,并尝试推断它是如何产生的。查看崩溃前的任何调试输出;如果可能,从调试器获取堆栈跟踪。现在您了解了一些发生的事情以及发生的地点。停下来反思一下。怎么会发生这种情况?从崩溃程序的状态倒推,以确定可能导致这种情况的原因。
调试涉及逆向推理,就像解决谋杀之谜一样。发生了一些不可能发生的事情,唯一可靠的信息是它确实发生了。因此,我们必须从结果倒推,找出原因。一旦我们有了完整的解释,我们就会知道要修复什么,并且在此过程中可能会发现一些我们没有预料到的事情。
这段摘自《程序设计实践》的内容强调了在进行调试任务之前收集足够数据和事实的重要性。有时,这意味着消除可能的原因以找到真正的问题。其他时候,这是一种间接解决问题的方法。
Solar2D 编辑器 是一个
安装 Sublime Text 和 Solar2D 编辑器后,它可以帮助您调试代码。例如,您可以在某些点停止模拟器并检查代码的状态,或者您可以单步执行代码并检查变量值。此外,您可以跟踪应用程序的**堆栈跟踪**,以进行更全面的调试。
使用交互式调试器的一个关键概念是能够在某些点停止程序,以便您可以观察当前状态。这包括查看作用域内变量的值、一次单步执行一行代码或一个函数,以及检查堆栈跟踪。这称为**使用断点**,它都围绕着在您想要检查的点停止程序。
您可以通过以下两种方式之一启用或禁用(切换)断点
单击代码行,然后从主菜单中选择 **Solar2D 编辑器** → **调试器** → **切换断点**。
右键单击代码行,然后从弹出上下文菜单中选择 **切换断点**。
设置断点后,该行上将出现一个小图标。然后,当您通过 **Solar2D 编辑器** → **调试器** → **运行** 从 Sublime Text 运行应用程序时,它将在到达此行代码时停止并返回 Sublime Text。此时,您有几个选项
**运行** — 运行代码直到下一个断点或直到完成。
**单步跳过** — 这将向前单步执行一行代码,如果它是一个函数,它将完成该函数并停止在下一行代码上。
**单步进入** — 这将向前单步执行一行代码,如果它是一个函数,它将进入该函数并停止在该函数的第一行。
**切换断点** — 关闭断点或设置新的断点。
**检查变量** — 请参阅下面的检查变量。
**停止** — 停止程序。
从主菜单中,选择 **调试器** → **检查变量**。Sublime Text 底部将打开一个搜索栏,您可以输入任何有效的变量。然后,您将看到该变量的详细信息,如果它是一个表,则与通常可用的内容相比,这可以提供更好的可视化表示。
有时,了解错误的来源很有帮助。当您调用一个函数并且它行为异常时,一个常见的原因是调用函数传递了无效数据。通过使用**堆栈跟踪**,您可以快速识别哪一行代码调用了您当前正在执行的函数。更具体地说,您可以在该函数中设置一个断点,继续运行您的应用程序,当该函数退出并到达下一个断点时,您可以检查用于调用该函数的变量。
有关堆栈跟踪的更多信息,请参阅下面的查看运行时错误。
如果您在模拟器首选项中启用了**显示运行时错误**,则运行时错误(导致应用程序崩溃的错误)将中止程序并显示一个消息对话框,其中包含有关错误的更多信息,包括
第 218 行
尝试对 nil 值执行算术运算。
stack traceback: main.lua:218: in function 'start' main.lua:222: in main chunk
这些集体信息通常可以精确定位代码中问题所在的确切位置。在本例中,您可以打开 `main.lua` 文件,找到第 `218` 行,并查找其中一个值为 `nil` 的数学运算 - 特别是尝试对未定义的变量执行数学运算的位置。此外,堆栈跟踪显示错误位于名为 `start` 的函数内,并且该函数是从第 `222` 行调用的。
有时,堆栈跟踪不太具体,必须仔细跟踪。例如
?: in function <?:221> main.lua:218: in function 'start' main.lua:222: in main chunk
由此,您知道错误位于第 `221` 行,但您必须向后跟踪以获取更多详细信息。在这种情况下,问题发生在第 `221` 行,并且该行是从 `main.lua` 的第 `218` 行名为 `start` 的函数调用的。因此,即使堆栈跟踪的第一行没有精确定位问题发生的位置,它通常也可以暴露错误的根源。
在为 iOS 构建时,您可以选择**开发**构建或**分发**构建。在为 Android 构建时,您可以使用**调试**密钥库或**发布**密钥库进行构建。通常,您的选择会影响堆栈跟踪中可用的信息,如下所示
如果您使用开发配置文件或调试密钥库进行构建,Solar2D 将在 Lua 代码中保留调试符号。这会导致最小的性能影响和更大的应用程序包。
如果您使用分发配置文件或发布密钥库进行构建,Solar2D 将从 Lua 代码中去除调试符号。
如果您希望保留这些符号,无论构建类型如何,只需覆盖默认行为,如此处所示。这将使跟踪代码更容易,但如果错误发生在 Solar2D 核心代码内部,调试符号仍然不可用。在这种情况下,您需要向后跟踪并确定您传递给 Solar2D 库函数的信息导致错误的原因。
诊断输出也可以在**模拟器控制台**中查看。这里将显示有用的调试消息,以及代码中 `print()` 语句的值。虽然这种做法对某些开发人员来说可能已经过时,但跟踪 `print()` 命令的输出通常可以揭示错误的原因或条件逻辑的问题。
上述方法可以帮助诊断大多数编码问题,但可能需要 **Xcode 模拟器**(适用于 macOS)来预览应用程序在实际 iOS 设备上的运行情况。这有助于模拟模拟器无法模拟的一些设备功能,但它仍然不如真实设备准确。
要在 Xcode 模拟器中测试应用程序
在 Solar2D 模拟器中选择**文件** → **构建** → **构建 iOS…**。
在对话框窗口中,在**构建目标:**
继续构建过程,应用程序将在模拟设备窗口中打开。
从此应用程序中,选择**调试** →
有些错误只有在将应用程序安装到设备上后才会出现。幸运的是,每个设备都有自己的控制台日志,您可以访问它,对于 Apple,您可以在 Xcode 中查看此控制台日志,如下所示
如果 Xcode 尚未运行,请打开它。
打开**设备和模拟器**窗口(**窗口** → **设备和模拟器**)。
将设备连接到您的计算机,并等待它出现在窗口的左列中。出现后,选择它。
单击**打开控制台**按钮以打开设备的控制台日志。
在 Android 上进行调试更像是“面向命令行”的,但是一旦您弄清楚了命令,安装应用程序、在应用程序运行时查看日志以及查找错误消息就相当快了。
首先,您必须从 Google 安装一些名为**Android 调试桥**的免费工具。只需按照以下步骤操作即可
访问下载页面。不要单击大的下载按钮,而是单击**下载选项**链接。然后,在
安装工具。在 Windows 上,运行安装程序;在 macOS 上,解压缩文件并将文件夹移动到合适的位置。
按照添加 SDK 包的说明进行操作。至少安装Android SDK 工具和
安装工具后,您可以使用命令行工具。连接您的 Android 设备后,只需在终端 (macOS) 或命令提示符 (Windows) 中键入以下命令,即可查看显示的消息。此命令将过滤掉除 Solar2D 生成的消息之外的所有消息。
adb logcat Corona:v *:s
有时,Solar2D 以外的其他因素也会产生错误,例如 Google Play 与 Google
adb logcat
这将阻止过滤print()
语句可能会有所帮助。例如
print( "MYAPP - Purchase: *********************************************" ) store.purchase( "com.coronalabs.NewExampleInAppPurchase.NonConsumableTier1" )
如果您更喜欢可视化工具,adb 还提供了一个 GUI 版本的adb logcat
如果您正在开发 Win32 桌面应用程序,可以在此处找到有关调试的一些详细信息。
如果您正在开发 macOS 桌面应用程序,可以在此处找到有关调试的一些详细信息。
有一些常见问题,一旦您了解它们,通常很容易解决。
代码可读性最重要的实践是正确的缩进(在行前添加制表符或空格以区分代码块)。您应该使用能够自动处理缩进的文本编辑器或集成开发环境 (IDE)。考虑以下代码
local function doSomething( params ) if params.someValue == 10 then if params.someOtherValue == "test" then params.yetAnotherValue = params.someValue end end
如果不缩进代码行以显示不同的代码块,则很难阅读。实际上,如果代码缩进正确,则会发现一个明显的错误。比较一下区别
local function doSomething( params ) if params.someValue == 10 then if params.someOtherValue == "test" then params.yetAnotherValue = params.someValue end end
如您所见,doSomething
函数缺少 end
关键字,并且在代码正确缩进后,这一点就清晰地显现出来了。
许多代码编辑器可以通过自动缩进代码来帮助您,并且当您关闭一个代码块时,它会退回一级并将 end
放在正确的位置。每个操作系统都有一系列代码编辑器可供选择,而更好的编辑器则具有 Lua 语法高亮显示功能。有关推荐编辑器的列表,请参阅我们针对 macOS 或 Windows 的安装指南。
另一个重要方面是命名变量和函数,以便您和其他人知道它们的含义。考虑以下两个代码块
local function a( b ) local c = b.target if c.x < 100 then c.alpha = 0 end return true end
local function touchEventHandler( event ) local target = event.target if target.x < 100 then target.alpha = 0 end return true end
这些函数在行为上是相同的,但是通过使用更清晰的变量和函数名称,大多数开发人员可以很容易地理解您想要完成的任务。
一些开发人员沮丧地发现他们的项目可以在模拟器中运行,但在实际设备上却无法运行。通常,以下因素之一是罪魁祸首
在某些情况下,当您尝试删除侦听器时,事件侦听器函数可能仍在运行,或者当您尝试调用某个操作时,Solar2D 库将完成一个内部进程。例如,您可能尝试在 GPS 事件仍在处理时删除GPS侦听器,或者您可能尝试在物理实体与另一个对象碰撞的那一刻停用它。这两种情况都可能导致错误/警告。
解决这类问题的办法是在短暂的计时器延迟后执行必要的操作
local function handleGPS( event ) timer.performWithDelay( 10, function() Runtime:removeEventListener( "location", handleGPS ); end ) end
如果您已经尝试了所有上述调试方法,请前往论坛。在那里,您可以搜索描述您遇到的相同问题的主题。通常,其他开发人员会报告相同的问题,并且在许多情况下,帖子中会包含解决方案——因此,请不要在没有先在现有帖子中搜索答案的情况下发布新主题。
请注意,新贡献者的第一篇帖子将受到审核——这些帖子将被录入系统,但在版主批准之前,其他用户将无法看到它们。
如果您在之前的任何论坛帖子中都找不到解决方案,请发布新的请求并遵循以下准则
不要多次发布相同的主题。此外,请选择最合适的
中发布相同的主题。 请提供足够的信息,以便社区能够提供帮助。您应提供以下信息
在论坛帖子中包含代码时,请使用 [lua] [/lua]
标签将其括起来,以提高清晰度和可读性
[lua] ... [/lua]