调试的本质与调试的基本流程

调试的本质与调试的基本流程

调试的本质与调试的基本流程

一、为什么需要调试

在我们开始编写我们的程序的时候,我们一般意义上会出现各种各样的问题,可能是语法上的问题,这并不严重,现代化的IDE会帮助我们纠正一切。比较难解决地是逻辑上的问题,就比如说下面这样一个简单的例子:

#include

int mian() {

printf("Hello,World\n");

return 0;

}

如果你刚入门C语言的话,你大概很难看出来这个程序为什么不输出,IDE也并没有爆出错误,这实际上是因为我们把main写成了mian,但他是符合语法规范的。

这只是一个小小的例子,但是在大型的项目中,这种错误比比皆是,所以说我们需要具有一种能够快速找到bug原因的能力,也就是我们所说的调试。

二、调试的定义

通过上文,我们就可以知道,调试是一个很重要的东西,他的具体定义如下:

调试 调试是一种查找bug的根源并将其解决的方案。

看到这里,很多人就会想到,调试其实就是程序界的故障检修,实则不然。在面对一个问题时,如果我们能够清楚地知道故障的症状,然后就可以去一本厚厚的册子上找寻可能的解决方案,并将其修复,这就是故障检修。

而当我们需要进行调试的时候,我们面临的问题往往是未知的,我们要从零开始发现问题的根源并将其解决,甚至是发现不合理的地方,将其修改,之后将这种情况写在册子上供检修人员使用,这才是调试。

调试是在产品发行前尝试解决理论上的问题,故障检修是在产品售后时解决实际应用上的问题(个人理解,不喜勿喷)。

PS:《调试九法》有一个简明的例子:医生在给病人治疗的时候就是故障检修,而不是调试,因为他已经不能让上帝对病人的身体构造进行修改了。

三、调试的例子

在对我们的程序进行调试之前,不妨先了解以下调试的具体思路,下文中将用一个例子介绍以下原则:

制造失败不要想,而要看一次只改一个地方

让我们先来看以下程序:

int climbStairs(int n) {

std::vector dp(n + 1, 0);

dp[0] = 1;

dp[1] = 1;

for (int i = 2; i <= n; i++) {

dp[i] = dp[i - 1] + dp[i - 2];

}

return dp[n];

}

emm,熟悉他的人很快就能看出来,这是70. 爬楼梯 - 力扣(LeetCode)的代码,他非常的简单,也能完美的通过leetcode的判题,但是如果把这个代码改成输入输出的格式,在放到洛谷的P1255里,你就会发现他不能过了,这是为什么呢?

我们发现错误的第一时间,先不要重读代码,而是尝试复现这个错误(制造失败),只有在看到错误是怎么发生了之后的,才能知道怎么修改,而不是空想(不要想,而要看),我们尝试着输入了一些数据,发现当输入数据到了

50

50

50的时候,我们的输出变成了:

-1109825406

我们成功的制造出了一个失败,而失败的原因显而易见,是因为溢出导致的,于是我们把数组的类型和函数的返回值改为了long long(一次只改一个地方),对的比之前多,但还是出现问题了,所以我们开始尝试更大的数据是否有问题,果然,输入为

2000

2000

2000时,我们的输出就变成了:

-820905900187520670

而洛谷的数据范围是到

5000

5000

5000的,于是我们知道了,想要解决这个问题,我们需要写出高精度算法来(这里不做代码描述),至此,调试完成。

四、三个调试原则的详细介绍

1.制造失败

当发现bug的出现时,我们首先就要尝试复现这个错误,一是因为我们并不能空口无凭的说这里有bug,我们需要证据,二来就是只有当亲眼看到bug是怎么发生的时候,我们才能更好的去解决bug。

这里要说明一种比较特殊的情况,就是随机性出现的bug,这种bug往往是很难进行复现的,他是各种条件的综合,我们不知道他出现的必要条件,也不知道他下次出现的时间,但我们可以统计每次bug出现时的时间和各种信息,然后用控制变量法增大他出现的概率,以方便进行调试,同时观察这个条件为什么能对这个bug产生影响,这里举出《调试九法》中的一个例子(我也忘了这个例子是哪条原则里的了,不过放在这里也可以的样子):

Example 一个公司的中央系统每到下午三点就会崩溃,但是引发崩溃的程序是随机的,每天都不一样,调试工程师们对这种情况也没有太好的办法,只能出现什么bug解决什么bug,而不能从根源上解决问题,后来他们思考为什么会是下午三点,然后发现下午三点的时候是职员们休息的时间,这个时候自动售货机的工作量很大,导致中央系统供电不足,最后才引发了程序和中央系统的崩溃。

PS:复现错误和复现错误原理是两回事,复现错误原理是指我们猜测是这么错误的,然后脱离大的系统,去实现一个小型的demo,然后对demo进行调试,但是这样有可能会对原有bug的原因产生误导,所以我们最好是直接复现错误,而不是复现错误的原理。

2.不要想,而要看

人们往往会根据自己的经验来对问题进行判断,这是一件好事,因为这样可以帮助我们节省很多时间,但是如果判断错误了,他将会浪费我们更多的时间,甚至因为这种经验带来的“偏见”导致我们更难找到真正的问题所在。所以说面对一个新出现的bug,我们要做到的就是不要进行任何主观臆断,而是亲自去看bug的发生。

3.一次只改一个地方

在调试的过程中,我们可能会猜测出许多种错误的原理,并一一尝试去解决,这种尝试有三种可能:

让问题得到解决事情变得更加糟糕了并没有任何事情发生

如果发生了可能一或者可能二还好,因为一的修改是正确的,而二中事情变得更糟糕后我们会把修改移除,但是如果我们遇上三,请一定要记住,就算什么也没有发生,我们也要把进行的修改删除掉,因为这有可能会引发新的bug(即使目前没有发现),我在这里举出一个刷算法题的例子:

Example 在一道时间比较紧张的题目中,我的算法出现了一定的错误,我在代码里加入了大量的cout来查看算法中途发生了什么样的错误,最后错误得以解决,当我再次进行提交时,算法超时了,这个时候我意识到,是中间加入的那些cout占用了大量的时间,于是我把cout去掉,然后算法成功通过了。

自古以来都有控制变量这种实验方法,他在调试领域一如既往地起到了非凡的作用。

相关推荐

热血传奇现在最高等级多少级?
365完美体育

热血传奇现在最高等级多少级?

📅 07-12 👁️ 2353
DNF刺客异界套哪套最好全面对比解析
beat365在线登录app

DNF刺客异界套哪套最好全面对比解析

📅 07-03 👁️ 2142
2015年羊是什么命
beat365在线登录app

2015年羊是什么命

📅 06-30 👁️ 6340

友情链接