调试程序
为什么要调试
我们开发的程序运行的时候,经常会发现运行的结果和我们预期的不符。
这就是程序运行的错误,我们通常叫做bug。
有两种类型的bug: 运行错误
和 逻辑错误
。
所谓 运行错误
,就是执行代码时,js引擎 可以直接发现的代码错误,它没法继续执行,就会报错。
比如,请大家运行如下代码
let result = un * 5
console.log('result is ' + result)
就会发现 js引擎 显示如下的错误提示
Uncaught ReferenceError: un is not defined
出现这种问题,比较好办,去看报错提示,确定我们错误是什么。
比如上面,js引擎明确告诉我们,错误是: un 没有定义
有了这样的错误提示,我们就知道去检查对应代码,很快就可以发现:un 确实还没有定义就写到表达式中了
然后再改正一下,比如把代码变成这样
let un = 3
let result = un * 5
console.log('result is ' + result)
当运行错误都没有了, 是不是程序就没有问题了呢?
完全不是!!
我们经常会发现,运行程序 js引擎 并不报错,只是运行的结果和我们预期的不一样。
这种就是 逻辑错误
。
我们来看一个例子:
一个网页程序让手机店主输入当天各型号手机销量,格式如下所示。
三星S8, 110,50,5200
三星Note8, 123,30,6800
华为Mate10, 170,170,4100
华为P10, 167,57,3300
小米 6, 133,81,2200
小米 mix, 173,61,3200
其中逗号分隔的列,其含义依次是 : 手机型号,库存,当天销量,价格
网页程序的任务很简单:要计算得到 销量最多手机型号。
我们来看下面的网页和内嵌的一段代码
<!DOCTYPE html>
<html>
<body>
<h1>调试示例</h1>
<textarea rows="15" cols="35">
三星S8, 110,50,5200
三星Note8, 123,30,6800
华为Mate10, 170,170,4100
华为P10, 167,57,3300
小米 6, 133,81,2200
小米 mix, 173,61,3200
</textarea>
<script>
function getSaleInfo(){
let value = document.querySelector('textarea').value
return value
}
// 从输入框中获取销量记录文本
let content = getSaleInfo()
// 下面两个变量记录当前找到的销量最多的手机和卖出数量
// 初始值都是 null
let mostsoldphone = null
let mostsoldcount = null
for (let info of content.split('\n')){
info = info.trim()
// 去掉空行
if (info === '')
continue
let items = info.split(',')
// 销量在倒数第3列,获取销量信息
let soldcount = items[2]
// 型号在 第1列, 获取型号信息
let phonetype = items[0]
// 如果前面已经有销量最多的手机记录,和当前这款手机销量比较
if (mostsoldphone != null){
// 如果当前这款手机销量更高,把它置为最热卖手机
if (mostsoldcount < soldcount){
mostsoldcount = soldcount
mostsoldphone = phonetype
}
}
// 如果前面没有有销量最多的手机记录,说明这是第一条记录
// 暂时先把它置为最热卖手机
else{
mostsoldcount = soldcount
mostsoldphone = phonetype
}
}
alert(`最热卖手机是 ${mostsoldphone}, 销量是 ${mostsoldcount}`)
</script>
</body>
</html>
大家根据注释理解一下代码的逻辑。
咋一看应该没有问题吧?
好的,按F12看看控制台输出的运行结果
发现结果如下
最热卖手机是 小米 6, 销量是 81
怎么回事?我们眼睛一看都知道,最热卖的是 华为Mate10 ,卖出了 170 部。
怎么算成小米6了?
这时候,js引擎并没有报错,说明没有任何 js 语法语义上的错误,而是代码处理逻辑有问题。
怎么办?
很多初学者,往往这时候第一反应就是有点懵,不知道该怎么办。
然后,就是反复的看代码。 有时也不能发现代码到底错在哪里。
其实这时候,最有效的方法的就是去 调试程序
所谓调试程序就是:检查程序运行过程中的 一些关键步骤里面变量 ,看看是否正确。从而判断出是哪里代码的问题。
调试程序的方法
DevTools 断点调试
最常用的方法就是 使用 浏览器 DevTools
的调试功能, 在关键代码处 设置断点, 查看关键变量的值。
什么是设置断点?
设置断点就是 设置某些代码行位置,当 程序运行 到这些位置,就会暂停执行。
我们在程序运行过程中, 查看某个变量的值,必须要让运行的程序能停在相应的位置。
下面我们看一下具体做法。
首先看决定程序运行结果的关键变量和关键代码有哪些
上面这个程序大体分为2 步
-
从输入框获取销量信息
-
一行行分析销量数据,获得最热销手机型号
首先第一步必须正确,也就是获取回来的销量信息是否正确
所以我们可以在如下代码处设置断点
DevTools 要设置断点调试 非常简单,
-
点击打开
Source
标签页 (中文叫源代码
标签页) -
点击左侧代码目录里面包含代码的网页
会显示网页html内容
-
要设置断点的代码左边边框上,也就是上图的箭头指向的地方,点击一下鼠标就可以了。
设置好了后,就会出现上图所示的一个蓝色标记。
-
接下来刷新网页,让网页重新运行
就会发现,网页内置js代码运行了,并且会停留在刚才设置的断点处 。 如下所示
注意,高亮的一行,表示程序执行 暂停在此处,而且此处高亮行 代码 尚未执行
这时,我们想查看变量content的值(因为里面是获取的具体手机销售信息)。
由于当前暂停的一行还没有执行,所以content变量没有得到值。
需要我们执行一行代码,把当前行执行结束,content变量才会被赋值。
那么怎样去执行一行代码呢?
IDE 通常有2种方式。
一种是 step over
, 对应下图标记为 1 的 按钮图标
点击该图标,就会让当前的程序执行完当前行的代码。
如果该代码里面有函数调用,执行 不会暂停在函数里面
而是直接运行完所有的函数里面的代码, 暂停在 下一行代码。
另一种是 step into
, 对应上图标记为 2 的 按钮图标
点击该图标,就会让当前的程序执行一步当前行代码
如果该代码里面有函数调用,执行 就会暂停在函数里面 。
这里我们可以点击 step over
的图标,全部执行完当前行,光标就会停在下一行,
同时下方的会有一个变量列表(在),里面会显示所有当前变量的值,
当然也包括变量content的值,如图所示
由于这个字符串比较长,我们可以双击内容,显示出完整的变量内容
可以看出读出的内容没有错。
那么下一个要检查的关键的变量是在 随后 获取每款手机销量,并进行判断的代码中。
所以,可以设置新的断点,如下图所示
然后点击箭头处按钮,表示继续调试程序,程序运行就会停在下一个断点处
这时候,如果我们仔细观察,要比较的两个变量 mostsoldcount 和 soldcount
细心的读者,就会发现这个两个变量的类型都是字符串。
对字符串进行大小比较
?? !!!
这就是问题所在:我们忘了把字符串转为整数类型了,导致比较销量大小的时候出现了问题。
这时,就可以结束调试。修改代码,加上转化为整数的操作,如下
let soldcount = parseInt(items[2])
重新运行一下,发现结果如下
最热卖手机是 华为Mate10, 销量是 170
这下就正确了。
打印出关键变量的值
除了断点调试,我们还可以直接用代码打印出关键变量的值。
检查是否符合我们的预期就可以了。
比如可以在上面的程序加上一些 console.log
语句
function getSaleInfo(){
let value = document.querySelector('textarea').value
return value
}
// 从输入框中获取销量记录文本
let content = getSaleInfo()
console.log(`得到的字符串内容为:${content}`)
// 下面两个变量记录当前找到的销量最多的手机和卖出数量
// 初始值都是 null
let mostsoldphone = null
let mostsoldcount = null
for (let info of content.split('\n')){
info = info.trim()
// 去掉空行
if (!info)
continue
let items = info.split(',')
// 销量在倒数第3列,获取销量信息
let soldcount = items[2]
// 型号在 第1列, 获取型号信息
let phonetype = items[0]
console.log(`mostsoldcount为:${mostsoldcount}`)
console.log(`soldcount为:${soldcount}`)
// 如果前面已经有销量最多的手机记录,和当前这款手机销量比较
if (mostsoldphone){
// 如果当前这款手机销量更高,把它置为最热卖手机
if (mostsoldcount < soldcount){
console.log(`程序判断 ${mostsoldcount} < ${soldcount}`)
mostsoldcount = soldcount
mostsoldphone = phonetype
}
else{
console.log(`程序判断 ${mostsoldcount} > ${soldcount}`)
}
}
// 如果前面没有有销量最多的手机记录,说明这是第一条记录
// 暂时先把它置为最热卖手机
else{
mostsoldcount = soldcount
mostsoldphone = phonetype
}
}
console.log(`最热卖手机是 ${mostsoldphone}, 销量是 ${mostsoldcount}`)
运行结果如下
得到的字符串内容为:三星S8, 110,50,5200
三星Note8, 123,30,6800
华为Mate10, 170,170,4100
华为P10, 167,57,3300
小米 6, 133,81,2200
小米 mix, 173,61,3200
mostsoldcount为:50
soldcount为:30
程序判断 50 > 30
mostsoldcount为:50
soldcount为:170
程序判断 50 > 170
mostsoldcount为:50
soldcount为:57
程序判断 50 < 57
mostsoldcount为:57
soldcount为:81
程序判断 57 < 81
mostsoldcount为:81
soldcount为:61
程序判断 81 > 61
最热卖手机是 小米 6, 销量是 81
分析输出可以发现, 如下地方有明显异常
mostsoldcount为:50
soldcount为:170
程序判断 50 > 170
这时候可以,有些经验的程序员就会怀疑两个变量的类型是否是整数了,检查一下代码就可以发现原来是字符串,而不是整数。
使用哪种方法?
用 设置断点 和 添加打印语句 都可以调试程序。
那么我们该使用哪种方法呢?
应该结合使用
大部分时候,我们使用 设置断点的方法, 因为毕竟比 添加打印语句 方便, 可以方便的查看任意变量 的值, 可以随意添加 断点。
但是有的时候,要检查的地方在循环中,而且要循环很多次,比如100次,才是我们要看的内容。
这时,断点在循环里面就比较麻烦(当然有时可以使用条件断点),这时可以加一些打印语句,直接观看运行100次的每次关键变量输出。