万能的排查错误方法:代码删减法
对于一个成熟的程序员来说,不管任何代码错误,没有找不到的错误,只是时间问题罢了。
一些初学者在面对代码错误的时候,全凭肉眼看。尤其是代码比较长的时候,更是对 bug 无从下手。这里推荐初学者一个排查错误的方法:删减法。
所谓删减法,就是逐步删除项目里的无关代码,确保每一步删减之后,故障都存在。删到最后无法再删的时候,通常你就能发现问题在哪了。
下面举个例子,有个同学在写一个画图板项目,执行后画圆时有错误,会产生这样的效果(画的圆有明显的残影):
完整的有 Bug 的源码如下(代码不太成熟,请忽略):
#include <graphics.h>
#include <conio.h>
#include <math.h>
void InitGUI(int color[])
{
setbkcolor(WHITE);
cleardevice();
rectangle(140, 410, 500, 470);
for (int i = 0; i < 6; i++)
{
setfillcolor(color[i]);
solidrectangle(140 + 60 * i, 410, 140 + 60 * (i + 1), 470);
}
for (int i = 0; i < 3; i++)
{
setlinecolor(BLACK);
rectangle(570, 150 + 60 * i, 630, 210 + 60 * i);
}
circle(600, 180, 20);
rectangle(580, 230, 620, 250);
line(580, 280, 620, 320);
rectangle(10, 20, 70, 80);
rectangle(70, 20, 130, 80);
outtextxy(20, 45, L"SAVE");
outtextxy(80, 45, L"LOAD");
}
int Judge_color(ExMessage m, int &color, int col[])
{
int i = 0;
if (m.x < 200 && m.x > 140)
{
i = 0;
}
else if (m.x < 260 && m.x > 200)
{
i = 1;
}
else if (m.x < 320 && m.x > 260)
{
i = 2;
}
else if (m.x < 380 && m.x > 320)
{
i = 3;
}
else if (m.x < 440 && m.x > 380)
{
i = 4;
}
else if (m.x < 500 && m.x > 440)
{
i = 5;
}
color = col[i];
setlinecolor(WHITE);
rectangle(145 + 60 * i, 415, 195 + 60 * i, 465);
return i;
}
int Judge_shape(ExMessage m, bool &CIR_flag, bool &REC_flag, bool &LINE_flag)
{
int j = 0;
if (m.y < 210 && m.y > 150)
{
CIR_flag = 1; REC_flag = 0; LINE_flag = 0;
rectangle(575, 155, 625, 205);
j = 0;
}
else if (m.y < 270 && m.y > 210)
{
REC_flag = 1; CIR_flag = 0; LINE_flag = 0;
rectangle(575, 215, 625, 265);
j = 1;
}
else if (m.y < 330 && m.y > 270)
{
LINE_flag = 1; CIR_flag = 0; REC_flag = 0;
rectangle(575, 275, 625, 325);
j = 2;
}
return j;
}
void Draw(bool &CIR_flag, bool &REC_flag, bool &LINE_flag, int flag, int a, int b, int c, int d)
{
if (REC_flag == 1)
{
if (flag == 1)
rectangle(a, b, c, d);
else
clearrectangle(a, b, c, d);
}
else if (LINE_flag == 1)
{
if (flag == 1)
line(a, b, c, d);
else
{
clearpie(a, b, c, d, -360, 360);
}
}
else if (CIR_flag == 1)
{
if (flag == 1)
circle((a + c) / 2, (b + d) / 2, sqrt(((a - c) * (a - c) + (b - d) * (b - d)) / 2.0));
else
clearcircle((a + c) / 2, (b + d) / 2, sqrt(((a - c) * (a - c) + (b - d) * (b - d)) / 2.0));
}
}
int main()
{
initgraph(640, 480,EW_DBLCLKS);
ExMessage m;
int col[6] = { RED, BLUE, BLACK, YELLOW, GREEN, BROWN };
InitGUI(col);
int a = 0, b = 0, c = 0, d = 0;
int c1 = 0, d1 = 0;
int old_c, old_d;
int color = BLACK;
int i = 0, j = 0;
bool REC_flag = 0,LINE_flag = 0,CIR_flag = 0;
int flag = 0;
while (1)
{
m = getmessage(EM_MOUSE | EM_KEY);
// 双击左键设置颜色,双击右键设置形状,单击左键绘制图像,按住CTRL可保存和加载图片;
switch (m.message)
{
case WM_LBUTTONDOWN:
if (m.y > 410 && m.y < 470 && m.x > 140 && m.x < 500)
{
clearrectangle(145 + 60 * i, 415, 195 + 60 * i, 465);
setfillcolor(col[i]);
solidrectangle(140 + 60 * i, 410, 140 + 60 * (i + 1), 470);
i = Judge_color(m, color, col);
}
else if (m.y > 150 && m.y < 330 && m.x > 570 && m.x < 630)
{
clearrectangle(575, 155 + 60 * j, 625, 205 + 60 * j);
setlinecolor(BLACK);
if (j == 0)
{
circle(600, 180, 20);
}
else if (j == 1)
{
rectangle(580, 230, 620, 250);
}
else if (j == 2)
{
line(580, 280, 620, 320);
}
j = Judge_shape(m, CIR_flag, REC_flag, LINE_flag);
}
a = m.x; b = m.y; flag = 0;
c1 = m.x; d1 = m.y;
break;
case WM_MOUSEMOVE:
if (flag != 2)
{
setlinecolor(color);
old_c = c1; old_d = d1;
c1 = m.x; d1 = m.y;
flag = 0;
Draw(CIR_flag, REC_flag, LINE_flag, flag, a, b, old_c, old_d);
flag = 1;
Draw(CIR_flag, REC_flag, LINE_flag, flag, a, b, c1, d1);
}
break;
case WM_LBUTTONUP:
c = m.x; d = m.y; flag = 1;
setlinecolor(color);
Draw(CIR_flag, REC_flag, LINE_flag, flag, a, b, c, d);
flag = 2;
break;
}
if (m.ctrl)
{
if (m.x < 70 && m.x > 10)
{
saveimage(_T("D:\\test.jpg"));
}
else if (m.x < 130 && m.x > 70)
{
loadimage(NULL, _T("D:\\test.jpg"));
}
}
}
_getch();
closegraph();
return 0;
}
然后找了很久没找到问题。
我以这个代码为例,讲一下代码删减法。首先,删掉无关的功能,比如画圆有故障,那么画矩形、画线、保存读取、选择颜色,都是无关的代码,都可以删掉。比如,删掉颜色选择,那就默认设置为黑色。删掉形状选择,那就默认形状是画圆。删减后的代码如下(Bug 仍在):
#include <graphics.h>
#include <conio.h>
#include <math.h>
void Draw(bool &CIR_flag, bool &REC_flag, bool &LINE_flag, int flag, int a, int b, int c, int d)
{
if (REC_flag == 1)
{
if (flag == 1)
rectangle(a, b, c, d);
else
clearrectangle(a, b, c, d);
}
else if (LINE_flag == 1)
{
if (flag == 1)
line(a, b, c, d);
else
{
clearpie(a, b, c, d, -360, 360);
}
}
else if (CIR_flag == 1)
{
if (flag == 1)
circle((a + c) / 2, (b + d) / 2, sqrt(((a - c) * (a - c) + (b - d) * (b - d)) / 2.0));
else
clearcircle((a + c) / 2, (b + d) / 2, sqrt(((a - c) * (a - c) + (b - d) * (b - d)) / 2.0));
}
}
int main()
{
initgraph(640, 480,EW_DBLCLKS);
setbkcolor(WHITE);
cleardevice();
ExMessage m;
int a = 0, b = 0, c = 0, d = 0;
int c1 = 0, d1 = 0;
int old_c, old_d;
int color = BLACK;
int i = 0, j = 0;
bool REC_flag = 0,LINE_flag = 0,CIR_flag = 1;
int flag = 2;
while (1)
{
m = getmessage(EM_MOUSE | EM_KEY);
// 双击左键设置颜色,双击右键设置形状,单击左键绘制图像,按住CTRL可保存和加载图片;
switch (m.message)
{
case WM_LBUTTONDOWN:
a = m.x; b = m.y; flag = 0;
c1 = m.x; d1 = m.y;
break;
case WM_MOUSEMOVE:
if (flag != 2)
{
setlinecolor(color);
old_c = c1; old_d = d1;
c1 = m.x; d1 = m.y;
flag = 0;
Draw(CIR_flag, REC_flag, LINE_flag, flag, a, b, old_c, old_d);
flag = 1;
Draw(CIR_flag, REC_flag, LINE_flag, flag, a, b, c1, d1);
}
break;
case WM_LBUTTONUP:
c = m.x; d = m.y; flag = 1;
setlinecolor(color);
Draw(CIR_flag, REC_flag, LINE_flag, flag, a, b, c, d);
flag = 2;
break;
}
}
_getch();
closegraph();
return 0;
}
然后,继续删掉鼠标操作部分。Bug 是由鼠标操作后产生的,那么就假设鼠标在某坐标位置点击、再移动、再松开,那么应该可以直接重现 bug。删减鼠标操作后的代码如下(Bug 仍在):
#include <graphics.h>
#include <conio.h>
#include <math.h>
void Draw(bool &CIR_flag, bool &REC_flag, bool &LINE_flag, int flag, int a, int b, int c, int d)
{
if (REC_flag == 1)
{
if (flag == 1)
rectangle(a, b, c, d);
else
clearrectangle(a, b, c, d);
}
else if (LINE_flag == 1)
{
if (flag == 1)
line(a, b, c, d);
else
{
clearpie(a, b, c, d, -360, 360);
}
}
else if (CIR_flag == 1)
{
if (flag == 1)
circle((a + c) / 2, (b + d) / 2, sqrt(((a - c) * (a - c) + (b - d) * (b - d)) / 2.0));
else
clearcircle((a + c) / 2, (b + d) / 2, sqrt(((a - c) * (a - c) + (b - d) * (b - d)) / 2.0));
}
}
int main()
{
initgraph(640, 480,EW_DBLCLKS);
setbkcolor(WHITE);
cleardevice();
int a = 0, b = 0, c = 0, d = 0;
int c1 = 0, d1 = 0;
int old_c, old_d;
int color = BLACK;
int i = 0, j = 0;
bool REC_flag = 0,LINE_flag = 0,CIR_flag = 1;
int flag = 2;
setlinecolor(BLACK);
// left button down
a = 100; b = 100; flag = 0;
c1 = 100; d1 = 100;
// move1
old_c = c1; old_d = d1;
c1 = 300; d1 = 300;
flag = 0;
Draw(CIR_flag, REC_flag, LINE_flag, flag, a, b, old_c, old_d);
flag = 1;
Draw(CIR_flag, REC_flag, LINE_flag, flag, a, b, c1, d1);
// move2
old_c = c1; old_d = d1;
c1 = 280; d1 = 280;
flag = 0;
Draw(CIR_flag, REC_flag, LINE_flag, flag, a, b, old_c, old_d);
flag = 1;
Draw(CIR_flag, REC_flag, LINE_flag, flag, a, b, c1, d1);
// left button up
c = 280; d = 280; flag = 1;
Draw(CIR_flag, REC_flag, LINE_flag, flag, a, b, c, d);
flag = 2;
_getch();
closegraph();
return 0;
}
因为 Bug 仍在,所以 Bug 应该不是鼠标操作引起的。剩下的代码里面,有些啰嗦。经过反复测试,再删掉一些无关的调用,同时清理一些用不到的变量(Bug 仍在):
#include <graphics.h>
#include <conio.h>
#include <math.h>
void Draw(bool &CIR_flag, int flag, int a, int b, int c, int d)
{
if (CIR_flag == 1)
{
if (flag == 1)
circle((a + c) / 2, (b + d) / 2, sqrt(((a - c) * (a - c) + (b - d) * (b - d)) / 2.0));
else
clearcircle((a + c) / 2, (b + d) / 2, sqrt(((a - c) * (a - c) + (b - d) * (b - d)) / 2.0));
}
}
int main()
{
initgraph(640, 480,EW_DBLCLKS);
setbkcolor(WHITE);
cleardevice();
bool CIR_flag = 1;
setlinecolor(BLACK);
Draw(CIR_flag, 1, 100, 100, 300, 300);
Draw(CIR_flag, 0, 100, 100, 300, 300);
_getch();
closegraph();
return 0;
}
然后,把 Draw 函数展开,把函数体直接写到 main 里面,同时清理重复代码,最后就剩下这么几行:
#include <graphics.h>
#include <conio.h>
#include <math.h>
int main()
{
initgraph(640, 480,EW_DBLCLKS);
setbkcolor(WHITE);
cleardevice();
setlinecolor(BLACK);
circle(200, 200, 200);
clearcircle(200, 200, 200);
_getch();
closegraph();
return 0;
}
然后通过这几行有限的代码可以清楚的看到,clearcircle 不能完全清空 circle 画的圆,这才是造成有残影的根本原因。
找到问题之后就容易解决了吧。如果仍然无法解决,此时,再把精简后仍然含有问题的代码发给别人请教,或者发到 https://qa.codebus.cn,这样精简的代码可以极大的提高你被帮助的概率。
添加评论
取消回复