调试程序

为什么要调试

我们开发的程序运行的时候,经常会发现运行的结果和我们预期的不符。

这就是程序运行的错误,我们通常叫做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 步

  1. 从输入框获取销量信息

  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次的每次关键变量输出。

您需要高效学习,找工作? 点击咨询 报名实战班

点击查看学员就业情况