|
也许是我才疏学浅,没见过什么市面吧。但是当我第一次读到libpcap的代码时,真的有种莫名的感动:优美的格式,清晰的结构,没有冗余,没有累赘,像一个窈窕淑女,徐徐向我走来……
记得曾经读过一篇文章,具体内容我记不清了,但是其中一句话却始终铭刻在我的头脑中:"天哪!你让一个函数做了三个函数的事情!"
仔细想想:人类之所以有如今的成就,就是因为人类能够从大量繁杂信息中提取有用部分,进行高度的抽象、概括,并应用在类似场合中……这就是所谓的举一反三吧。
举个例子:在我们刚刚学说话的时候,大人们会教我们叫“妈妈”,他们会用夸张的口形冲着我们清楚的说出“妈妈”两个字。此时,我们的大脑进行着复杂的活动:排除外界的干扰——也许此时邻居家正在装修,那么我们就要排除装修带来的噪音;即使是绝对理想的声音环境下(即,我们只听到了大人说的“妈妈”),依然有很多信息干扰着我们——比如我们看着大人夸张的嘴形,然而大人们的脸形却也同样夸张,因此我们就需要通过分析得出结论:需要关注的是口形,而非眼睛或者其他东西。接着,我们的大脑分析从眼睛获得的图像,排除各种背景和无用信息,而只关注有用的信息:口形。同时,大脑也许会试着操控面部肌肉和舌头、声带,去模仿大人……
这是一项多么伟大的工作啊!
首先,我们从外界获得了原始信息。然而,这份原始信息里面却存在着大量无关内容,我们要根据我们所关心的来将原始信息中的无关内容过滤掉,接着对有用的内容进行分析,将分析结果高度抽象后保存,然后试着将保存的内容应用到其他场合……
编程亦是如此。
首先,需要明确目的:做什么样的软件,软件需要有什么功能,软件的使用者是什么类型……
接着,将问题分解成不同的小问题,小问题继续向下分……
当决定写一个函数的时候,问问自己:这个函数是否需要?是不是还能继续向下划分成更小的函数?是不是拥有很好的可移植性?函数是不是足够小?……
没错,函数需要向外部提供简便的入口(参数),明了的出口(返回值),函数内部的实现,要对外部透明,这样,调用函数的外部所关心的仅仅是参数和返回值,而无需关心更多。
初次读到libpcap的代码,看到了这么一个函数:
[code:1]
char *
pcap_strerror(int errnum)
{
#ifdef HAVE_STRERROR
return (strerror(errnum));
#else
extern int sys_nerr;
extern const char *const sys_errlist[];
static char ebuf[20];
if ((unsigned int)errnum < sys_nerr)
return ((char *)sys_errlist[errnum]);
(void)sprintf(ebuf, "Unknown error: %d", errnum);
return(ebuf);
#endif
}
[/code:1]
这个不过是一个简单的输出错误信息的函数。在很多书上都有类似的实现。然而,又有多少人在乎它呢?至少我在网上读的到很多代码上,很多写的都是:
[code:1]
char *str;
str=(char *)malloc(1024);
if(str==NULL)
printf("Err:can not allocate enough memery space\n");
[/code:1]
…………这样实现看起来似乎简便易行,但是:
1 printf函数是向标准输出(stdout)输出内容,而非标准错误输出(stderr)——尽管一般情况下,结果都是显示在屏幕上。
2 如果想了解更多的信息,那么就没有办法了。而前面介绍的函数,则可以根据errno的值来获得错误信息的更多内容。
那么,让我们再次明确一下什么是函数:
1 函数是将具体的操作高度抽象的
2 函数是能够完成特定任务的
3 函数完成的任务要相对的简单——即:一个函数只作一件事
4 一个函数可以通过调用其他函数,来完成一个特定事
5 函数的内部实现对于外部要相对透明,调用函数的外部仅仅需要关心函数的入口和出口
以上便是对函数的一点点领悟。肯定有不全面的地方,还请指教。
下面再来看看libpcap中的一段代码:
[code:1]
/*
* Convert a port name to its port and protocol numbers.
* We assume only TCP or UDP.
* Return 0 upon failure.
*/
int
pcap_nametoport(const char *name, int *port, int *proto)
{
struct servent *sp;
char *other;
sp = getservbyname(name, (char *)0);
if (sp != NULL) {
NTOHS(sp->s_port);
*port = sp->s_port;
*proto = pcap_nametoproto(sp->s_proto);
/*
* We need to check /etc/services for ambiguous entries.
* If we find the ambiguous entry, and it has the
* same port number, change the proto to PROTO_UNDEF
* so both TCP and UDP will be checked.
*/
if (*proto == IPPROTO_TCP)
other = "udp";
else
other = "tcp";
sp = getservbyname(name, other);
if (sp != 0) {
NTOHS(sp->s_port);
#ifdef notdef
if (*port != sp->s_port)
/* Can't handle ambiguous names that refer
to different port numbers. */
warning("ambiguous port %s in /etc/services",
name);
#endif
*proto = PROTO_UNDEF;
}
return 1;
}
#if defined(ultrix) || defined(__osf__)
/* Special hack in case NFS isn't in /etc/services */
if (strcmp(name, "nfs") == 0) {
*port = 2049;
*proto = PROTO_UNDEF;
return 1;
}
#endif
return 0;
}
[/code:1]
这里还有一段:
[code:1]
/*
* Each packet in the dump file is prepended with this generic header.
* This gets around the problem of different headers for different
* packet interfaces.
*/
struct pcap_pkthdr {
struct timeval ts; /* time stamp */
bpf_u_int32 caplen; /* length of portion present */
bpf_u_int32 len; /* length this packet (off wire) */
};
[/code:1]
大可不必关心代码所要实现什么功能,我想说的,仅仅是代码中的注释。
没错!注释!
如今有多少人在写代码的时候就加入的注释?又有多少注释是清晰,易懂的?
尽管高级计算机语言已经接近自然语言,然而它依然无法替代自然语言。为了方便阅读代码的人,加入注释是极其有必要的。
注释要说明函数的参数,返回值,功能……结构体、共同体的功能,成员的意义、类中成员的意义等等。
如果一个人能够将自己的代码清晰的注释出来,那么不仅仅是自己在编码的时候轻松不少,更令读到代码的人有种很清爽的感觉。我觉得能够清晰注释自己的代码,可以说是对自己的负责,也同样是对他人的负责。说得严重些,这反映了一个人的做事态度,反映了一个人对他人的态度。
这仅仅是我初次读到libpcap后的一点感慨而已,有不对的地方还望多多指教。 |
|