对于每个程序员来说,不犯错误的可能性是没有的,但是通过不断的经验积累,就能有意识主动避免了很多错误,慢慢的错误也就少了,错误一般分为三种:
1.编译出错
这种错误是编译时,编译器报告出来的,刚进入编程是最容易犯。这只是语法错误,等到有了一些经验之后,还是会犯这样的错误,不过会少得多,而且你能更快地发现错误原因。等到经验更丰富之后你就会觉得,语法错误是最简单最低级的错误,编译器的错误提示也就那么几种,即使错误提示是有误导的也能够立刻找出真正的错误原因是什么。相比下面两种错误,语法错误解决起来要容易得多。
2.运行出错
编译器检查不出这类错误,仍然可以生成可执行文件,但在运行时会出错而导致程序崩溃。一般这种错误容易重现,有丰富调试经验的人也容易找出问题所在。所以真正的程序员必须具有丰富的软件调试经验。
3.逻辑出错
这类错误是比较难发现的,编译和运行都会很顺利,看上去也不产生任何错误信息,但是程序没有干它该干的事情,而是干了别的事情。特别是一些边界或者临界错误,他们不会随便发生,想要重现来寻找问题是有一定难度的,为了找这种问题,有时不得不审视一行行源码漏洞可能。
要避免臭虫,这自然就和之初软件设计有莫大的关系,一个简单软件功能,也可以有千奇百怪的不同实现方式,但一种比较简洁的方式去实现,就能避免很多的臭虫。简洁并不是简单。
例如有一个双向链表
typedef struct node *link;
struct node { // 链表成员结构体
link prev, next;
unsigned char item;
};
link head = NULL; // 链表头
void insert(link p) // 添加成员
{
p->next = head;
if (head)
head->prev = p;
head = p;
p->prev = NULL;
}
link delete(link p) // 删除成员
{
if (p->prev)
p->prev->next = p->next;
else
head = p->next;
if (p->next)
p->next->prev = p->prev;
return p;
}
现加入一个新的功能,要求每个链表成员具有10分钟生存期,每分钟将调用checkOverTime函数将表中超过时间成员剔除。
初眼看到这个问题,可能很多程序员觉得很EASY,没多想就开始直接编写代码。有兴趣的可以自行先做,在看看下面本人的实现,比较一下有何差别。
.
.
.
.
.
.
.
.
.
.
.
.
首先思考,链表成员现在具有时间属性,故成员结构体需要增加一个时间属性。
struct node { // 链表成员结构体
link prev, next;
unsigned char item;
unsigned long time;
};
但如何来使用这个变量呢?
也许有人就开始这么简单折腾了,在insert函数加入p->time = 10;编写下面函数
void checkOverTime(void)
{
link p = head;
while(p != NULL) {
p->time--;
if(p->time == 0) {
delete(p);
}
p = p->next;
}
}
这么做确实很简单,也很好,看不出有任何错误问题。但是否这就是最好的做法呢?
其实再深入思考一下,这么遍历整个列表,如果列表成员数少,确实是一种不错做法,但是成员很多的情况下,checkOverTime函数每秒检查调用,是否可取就得很好斟酌了,它也许就成为一条臭虫。
我们仔细考虑到成员的加入,这不就是按照时间先后顺序进行插入吗?如果将time变量用来记录当前加入的时间,其实就只需检查最早插入成员是否超时,一旦没有超时,后面也就没有必要进行检查了。但是time记录什么,又会是一个很考究的问题,如果当前是linux系统,清楚jiffies值的含义,记录当时jiffies值将是一个最佳的选择。对双向链表作了些修改补充,那么最后的源码:
typedef struct node *link;
struct node { // 链表成员结构体
link prev, next;
unsigned char item;
unsigned long time;
//...
};
typedef struct {
link head, tail;
} tag_list;
tag_list list = {NULL,NULL}; // 链表
void insert(link p) // 添加成员
{
p->recv = list.tail;
p->next = NULL;
if (list.tail == NULL) {
list.head = p;
list.tail = p;
} else {
list.tail->next = p;
list.tail = p;
}
p->time = jiffies;
}
link delete(link p) // 删除成员
{
if (p->prev == NULL) {
list.head = p->next;
list.head->prev = NULL;
} else {
p->prev->next = p->next;
}
if (p->next == NULL) {
list.tail = p->prev;
list.tail->next = NULL;
} else {
p->next->prev = p->prev;
}
return p;
}
void checkOverTime(void)
{
link p;
p = list.head;
while(p != NULL) {
if(jiffies - p->time > (10 * 60 * HZ)) {
delete(p);
free(p);
} else break;
p = p->next;
}
}
上面只是范例,实际应用还需要考虑很多其它因素,且只能在单线程上调用操作,多线程间使用还需要作重入保护问题的考虑。
xucun915_925777961 2011-11-7 14:36
用户1185343 2011-11-7 11:50
不否认可以把案例看成是一个优化问题,只想表达一个想法,链表的操作设计,预先想到要避免链表大小影响,说明对链表应用已有一个很好的本质认识,设计的出发点就比较高,自然会主动避免设计缺陷问题。这自然要求林冲替身说的扎实的基本功,没能深入看到问题,是谈不上去优化的,不一定是没责任心和不追求质量。
用户1515844 2011-11-6 14:58
用户1335696 2011-11-4 20:01
用户1147991 2011-11-4 16:20
用户1185343 2011-11-4 13:01
用户1625031 2011-11-3 18:10
用户1629079 2011-11-3 10:40
用户1602177 2011-11-1 18:13
用户1185343 2011-11-1 18:05