Python的非程序员教程3
什么是调试?
“当我们开始编程时,我们惊讶地发现,程序不像我们想的那样容易编写正确。调试必须被发现。我记得清楚地记得那一刻,当时我意识到,从那时起,我的大部分时间将花在寻找自己程序中的错误上。” — 莫里斯·威尔克斯(Maurice Wilkes)发现调试,1949年
到现在为止,如果你一直在玩程序,你可能已经发现,有时候程序做的事情并不是你想要的。这是很常见的。调试是找出计算机在做什么,然后让它做你想要它做的事情的过程。这可能很棘手。我曾经花了将近一周的时间来追踪并修复一个错误,那个错误是因为某人把“x”放在了应该是“y”的位置。
本章将比以前的章节更为抽象。
程序应该做什么?
做的第一件事(这听起来很显而易见)是弄清楚程序如果运行正确应该做什么。想出一些测试用例,看看会发生什么。例如,假设我有一个程序用于计算矩形的周长(所有边长的总和)。我有以下测试用例:
height | width | perimeter |
---|---|---|
3 | 4 | 14 |
2 | 3 | 10 |
4 | 4 | 16 |
2 | 2 | 8 |
5 | 1 | 12 |
现在我对所有的测试用例运行程序,看看程序是否按照我预期的方式工作。如果没有,那么我需要找出计算机到底在做什么。
更常见的情况是,有些测试用例能通过,有些不能通过。如果是这种情况,你应该尝试找出通过的用例有什么共同点。例如,下面是一个计算周长的程序的输出(你稍后会看到代码):
Height: 3
Width: 4
perimeter = 15
Height: 2
Width: 3
perimeter = 11
Height: 4
Width: 4
perimeter = 16
Height: 2
Width: 2
perimeter = 8
Height: 5
Width: 1
perimeter = 8
注意,前两个输入不正确,接下来的两个是正确的,最后一个不正确。试着找出哪些是正确的,哪些不正确。找出工作正常的测试用例有什么共同点。一旦你有了一些想法,找出问题的根源就会变得更容易。对于你自己的程序,如果需要,你应该尝试更多的测试用例。
程序在做什么?
接下来需要做的事是查看源代码。编程过程中最重要的一件事就是阅读源代码。主要的方式就是代码走查(Code Walkthrough)。
代码走查从程序的第一行开始,逐行检查直到程序结束。while
循环和 if
语句意味着某些行可能永远不会运行,而某些行可能会运行多次。每一行,你都要弄明白 Python 到底在做什么。
让我们从一个简单的计算周长的程序开始。不要直接输入代码,你要读它,而不是运行它。源代码是:
height = int(input("Height: "))
width = int(input("Width: "))
print("perimeter =", width + height + width + width)
问题: Python运行的第一行是什么? 回答: 第一行始终是首先运行的。这里是:height = int(input("Height: "))
这一行做什么? 它打印 Height:
,等待用户输入一个字符串,然后将该字符串转换为一个整数并赋值给变量 height
。
接下来运行的是哪一行? 通常情况下,接下来运行的是下面的一行:width =
int(input("Width: "))
这一行做什么? 它打印 Width:
,等待用户输入一个数字,并将用户输入的数字赋值给变量 width
。
接下来运行的是哪一行? 因为下一行没有缩进的变化,所以接下来运行的是:print("perimeter
=", width + height + width + width)
这一行做什么? 它首先打印 perimeter =
,然后打印 width +
height + width + width
的总和。
width + height + width + width
计算周长正确吗? 让我们来看,矩形的周长是底边(width)加上左边(height)加上上边(width)再加上右边(huh?)。最后一项应该是右边的长度,也就是 height。
你理解为什么某些时候周长被计算“正确”了吗? 当 width 和 height 相等时,周长计算是正确的。
接下来我们将对一个程序进行代码走查,这个程序本该在屏幕上打印出5个点。然而,程序输出的是:
. . . .
这里是程序代码:
number = 5
while number > 1:
print(".",end=" ")
number = number - 1
print()
这个程序的走查会更复杂,因为它有缩进部分(控制结构)。让我们开始吧。
问题: 第一行运行的是什么? 回答: 文件的第一行:number =
5
它做什么? 将数字 5 放入变量 number
。
接下来的行是什么? 接下来的行是:while number > 1:
它做什么? while
语句通常检查其表达式,如果为真,它会执行接下来的缩进代码块;如果为假,它会跳过接下来的缩进代码块。
那现在它做什么? 如果 number > 1
为真,接下来的两行代码将被执行。
number > 1
是否为真? 最后赋给 number
的值是5,而 5 > 1
所以是的。
接下来运行的行是什么? 由于 while
为真,接下来运行的是:print(".",end=" ")
这一行做什么? 打印一个点,并且因为 end=" "
这个额外参数的存在,下一次打印的文本不会换行。
接下来运行的行是什么? number = number - 1
因为它是下一行并且没有缩进变化。
它做什么? 它计算 number - 1
,即当前的 number
(或者是5)减去1,赋值给 number
。所以它把 number
的值从5变为4。
接下来运行的行是什么? 由于缩进级别下降,所以我们需要回到 while
循环,检查:while number > 1:
它做什么? 它检查 number
的值,这里是4,判断 4 >
1
,为真,继续执行。
接下来运行的行是什么? 由于 while
循环为真,接下来运行的是:print(".",end=" ")
它做什么? 打印第二个点,并且继续在同一行。
接下来运行的行是什么? number = number - 1
它做什么? 它计算 number - 1
,即 4 -
1
,然后把新值3赋给 number
。
接下来运行的行是什么? 返回到 while
循环,检查 while number
> 1:
它做什么? 它检查 number
的值,这里是3,判断 3 >
1
,为真,继续执行。
接下来运行的行是什么? 由于 while
循环为真,接下来运行的是:print(".",end=" ")
它做什么? 打印第三个点。
接下来运行的行是什么? number = number - 1
它做什么? 它计算 number - 1
,即 3 -
1
,然后把新值2赋给 number
。
接下来运行的行是什么? 返回到 while
循环,检查 while number
> 1:
它做什么? 它检查 number
的值,这里是2,判断 2 >
1
,为真,继续执行。
接下来运行的行是什么? 由于 while
循环为真,接下来运行的是:print(".",end=" ")
它做什么? 打印第四个点。
接下来运行的行是什么? number = number - 1
它做什么? 它计算 number - 1
,即 2 -
1
,然后把新值1赋给 number
。
接下来运行的行是什么? 返回到 while
循环,检查 while number
> 1:
它做什么? 它检查 number
的值,这里是1,判断 1 >
1
为假,所以跳
出 while
循环。
接下来运行的行是什么? 跳出 while
循环后,执行接下来的行:print()
它做什么? 让屏幕换行。
为什么程序没有打印5个点? 循环多执行了1次,少打印了1个点。
如何修复? 让循环少执行1次。可以通过多种方式来实现。可以将 while
循环修改为:while number > 0:
,或者修改条件为:number >=
1
,还有其他几种方式。
如何修复我的程序?
你需要弄明白程序在做什么。你需要弄明白程序应该做什么。找出两者之间的区别。调试是一项需要练习才能掌握的技能。如果你一个小时后还没弄明白,休息一下,和别人讨论问题,或者凝视着肚脐沉思。过一会儿再回来,你很可能会对问题有新的思路。祝你好运。