调试同构 Go web 应用包括以下项目:
- 识别编译器/传输程序错误
- 检查紧急堆栈跟踪
- 跟踪代码以查明问题的根源
我们可以把编程看作是你(程序员)和机器(编译器/transpiler)之间的对话。因为 Go 是一种类型化语言,我们可以在编译/传输时发现许多错误。这是编写普通 JavaScript 的一个明显优势,在这种情况下,问题(由于缺乏类型检查而导致)可能会隐藏起来,并在最不合适的时候暴露出来。编译器错误是机器向我们传达程序存在根本性错误的手段,无论是语法问题还是类型使用不当。
kick
对于显示编译器错误非常方便,因为它将显示来自 Go 编译器和 GopherJS transpiler 的错误。当您引入一个错误(编译器/transpiler 可以识别)并保存源文件时,您将在运行kick
的终端窗口中看到该错误。
例如,让我们打开client/client.go
源文件。在run
函数中,让我们注释掉将ts
变量设置为TemplateSet
对象的行,我们通过templateSetChannel
接收该对象:
//ts := <-templateSetChannel
我们知道ts
变量稍后将用于填充env
对象的TemplateSet
字段。让我们通过引入以下代码将ts
变量设置为false
的布尔值:
ts := false
当我们保存client.go
源文件时,kick
会给我们一脚(有意双关语),关于我们刚才引入的错误,如图 A1所示:
图 A1:kick 命令在保存 Go 源文件时立即向我们显示 transpiler 错误
收到的编译器错误向我们显示了问题发生的确切位置,我们可以从中诊断和纠正问题。从这个例子中可以学到的教训是,在开发同构的 Go web 应用时,在后台运行kick
终端窗口非常方便。通过这样做,您将能够在出现编译器/transpiler 错误时立即看到它们。
对于传输程序在传输时间无法发现的运行时错误,通常会有一个有用的紧急堆栈跟踪,显示在 web 浏览器控制台中,为我们诊断问题提供有价值的信息。GopherJS 生成的 JavaScript 源映射文件有助于 web 浏览器将 JavaScript 指令映射到 Go 源文件中各自的行。
让我们引入一个运行时错误,根据该错误,客户端程序在语法上是正确的(它将通过 transpiler 检查);但是,代码在运行时会出现问题。
回到client/client.go
源文件中的run
函数,注意我们对ts
变量所做的以下代码更改:
func run() {
println("IGWEB Client Application")
// Fetch the template set
templateSetChannel := make(chan *isokit.TemplateSet)
funcMap := template.FuncMap{"rubyformat": templatefuncs.RubyDate, "unixformat": templatefuncs.UnixTime, "productionmode": templatefuncs.IsProduction}
go isokit.FetchTemplateBundleWithSuppliedFunctionMap(templateSetChannel, funcMap)
// ts := <-templateSetChannel
env := common.Env{}
// env.TemplateSet = ts
env.TemplateSet = nil
env.Window = dom.GetWindow()
env.Document = dom.GetWindow().Document()
env.PrimaryContent = env.Document.GetElementByID("primaryContent")
env.Location = env.Window.Location()
registerRoutes(&env)
initializePage(&env)
}
我们已经注释掉了ts
变量的声明和初始化,也注释掉了ts
变量对env
对象的TemplateSet
字段的赋值。我们引入了一行代码,将nil
值分配给env
对象的TemplateSet
字段。通过执行此操作,我们基本上禁用了客户端模板集,这将阻止我们在客户端渲染任何模板。这还可以防止渲染任何 cog,因为 cog 依赖于模板集以正常工作。
加载 IGWEB 主页后,将生成一个紧急堆栈跟踪,并在 web 浏览器控制台中可见,如图 A2 所示:
图 A2:web 浏览器控制台中显示的紧急堆栈跟踪
在前端调试过程中,您经常会遇到以下错误消息:
client.js:1412 Uncaught Error: runtime error: invalid memory address or nil pointer dereference
runtime error: invalid memory address or nil pointer dereference
通常意味着我们试图对一个等于 JavaScriptnull
值的值执行操作(例如访问或修改属性)
检查生成的紧急堆栈跟踪有助于我们关注该问题:
Uncaught Error: runtime error: invalid memory address or nil pointer dereference
at $callDeferred (client.js:1412)
at $panic (client.js:1451)
at throw$1 (runtime.go:219)
at Object.$throwNilPointerError (client.js:29)
at Object.$packages.github.com/isomorphicgo/isokit.TemplateSet.ptr.Members (templateset.go:37)
at Object.$packages.github.com/isomorphicgo/isokit.TemplateSet.ptr.Render (templateset.go:115)
at Object.$packages.github.com/uxtoolkit/cog.UXCog.ptr.RenderCogTemplate (uxcog.go:143)
at Object.$packages.github.com/uxtoolkit/cog.UXCog.ptr.Render (uxcog.go:179)
at Object.$packages.github.com/EngineerKamesh/igb/igweb/shared/cogs/carousel.Carousel.ptr.Start (carousel.go:47)
at Object.InitializeIndexPage (index.go:31)
at initializePage (client.go:45)
at run (client.go:100)
at main (client.go:112)
at $init (client.js:127543)
at $goroutine (client.js:1471)
at $runScheduled (client.js:1511)
at $schedule (client.js:1527)
at $go (client.js:1503)
at client.js:127554
at client.js:127557
紧急堆栈跟踪中的相关区域以粗体显示。从紧急堆栈跟踪中,我们可以确定旋转木马 cog 未能渲染,因为似乎TemplateSet
有问题。通过进一步检查紧急堆栈跟踪,我们可以确定调用是在client.go
源文件的第 112 行对run
函数进行的。run
函数是我们通过将env
对象的TemplateSet
字段设置为nil
引入错误的地方。从这个调试练习中,我们可以看到,在这种情况下,紧急堆栈跟踪没有揭示问题的确切路线,但它为我们提供了足够的线索来纠正问题。
在客户端开发时要遵循的一个良好实践是始终打开 web 浏览器的控制台,以便在出现问题时能够看到问题。
另一个很好的客户端调试实践是跟踪,即打印出程序流程中的关键步骤的实践。在调试场景中,这将包括围绕可疑的问题代码区域战略性地调用println
(或fmt.Println
函数。您可以使用 web 浏览器的控制台验证是否达到这些打印语句,这将使您更好地了解客户端程序在运行时的工作方式。
例如,在调试上一节介绍的问题时,我们可以在 run 函数中放置以下println
调用:
func run() {
//println("IGWEB Client Application")
println("Reached the run function")
// Fetch the template set
templateSetChannel := make(chan *isokit.TemplateSet)
funcMap := template.FuncMap{"rubyformat": templatefuncs.RubyDate, "unixformat": templatefuncs.UnixTime, "productionmode": templatefuncs.IsProduction}
go isokit.FetchTemplateBundleWithSuppliedFunctionMap(templateSetChannel, funcMap)
// ts := <-templateSetChannel
println("Value of template set received over templateSetChannel: ", <-templateSetChannel)
env := common.Env{}
// env.TemplateSet = ts
env.TemplateSet = nil
env.Window = dom.GetWindow()
env.Document = dom.GetWindow().Document()
env.PrimaryContent = env.Document.GetElementByID("primaryContent")
env.Location = env.Window.Location()
println("Value of template set: ", env.TemplateSet)
registerRoutes(&env)
initializePage(&env)
}
我们通过打印程序流程中的关键步骤,通过进行策略性的println
函数调用来执行跟踪。第一个println
调用用于验证我们是否达到run
函数。第二个println
调用用于检查从模板集通道返回给我们的模板集的运行状况。第三个,也是最后一个println
调用,用于在我们通过填充env
对象的字段完成对其的预处理后,检查模板集的运行状况。
图 A3显示了 web 控制台,其中显示了打印语句,以及client.go
源文件中进行println
调用的相应行号:
图 A3:web 控制台中显示的打印语句
通过跟踪练习,我们可以首先验证我们已经成功地达到了run
功能。其次,我们可以通过注意对象的属性出现(例如members
、Funcs
和bundle
来验证通过templateSetChannel
接收到的TemplateSet
对象的健康状况。第三条,也是最后一条 print 语句,在env
对象已准备好之后,还将验证TemplateSet
对象的运行状况。这个 print 语句通过向我们显示[T8]对象尚未初始化来揭示问题的根源,因为我们在 print 语句中没有看到对象的任何属性。