慢羊羊的空间

无为,无我,无欲,居下,清虚,自然

万能的排查错误方法:代码删减法

对于一个成熟的程序员来说,不管任何代码错误,没有找不到的错误,只是时间问题罢了。

一些初学者在面对代码错误的时候,全凭肉眼看。尤其是代码比较长的时候,更是对 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,这样精简的代码可以极大的提高你被帮助的概率。

添加评论