魔数
程序里许多上下文中经常出现的0。虽然编译系统会
把它转换为适当类型,但是,如果我们把每个0的类型写得更明确更清楚,对读程序的人理解
其作用是很有帮助的。例如,用(void *) 0或NULL表示C里的空指针值,用‘\ 0’而不是0表示
字符串结尾的空字节。也就是说,不要写:
str = 0;
name[i]=0;
x = 0;
应该写成:
str = NULL;
name[il = ' \ 0 ' ;
x = 0.0;
我们赞成使用不同形式的显式常数,而把0仅留做整数常量。采用这些形式实际上指明了有关
值的用途,能起一点文档作用。可惜的是,在C++ 里人们都已接受了用0 (而不是N U L L)表示
空指针。J a v a为解决这个问题采用了一种更好的方法,它定义了一个关键字n u l l,用来表示
一个对象引用实际上并没有引用任何东西。
注释
注释是帮助程序读者的一种手段。但是,如果在注释中只说明代码本身已经讲明的事情,
或者与代码矛盾,或是以精心编排的形式干扰读者,那么它们就是帮了倒忙。最好的注释是
简洁地点明程序的突出特征,或是提供一种概观,帮助别人理解程序。
不要大谈明显的东西。注释不要去说明明白白的事,比如i + +能够将i值加1等等。下面是我
们认为最没有价值的一些注释:
/*
default
*/
default:
break;
/* return SUCCESS */
return SUCCESS;
zerocount++; /* Increment zero entry counter */
/* Initialize " total" to "number-received" */
node->total = node->number-received ;
所有这些都该删掉,它们不过是一些无谓的喧嚣。
注释应该提供那些不能一下子从代码中看到的东西,或者把那些散布在许多代码里的信
息收集到一起。当某些难以捉摸的事情出现时,注释可以帮助澄清情况。如果操作本身非常
明了,重复谈论它们就是画蛇添足了:
这些注释也都应该删除,因为仔细选择的名字已经携带着有关信息。
不要注释差的代码,重写它。应该注释所有不寻常的或者可能迷惑人的内容。但是如果注释
的长度超过了代码本身,可能就说明这个代码应该修改了。下面的例子是一个长而混乱的注
释和一个条件编译的查错打印语句,它们都是为了解释一个语句:
/* If "result" i s 0 a match was found so return true
(non-zero).
Otherwise, "result" i s non-zero so return false (zero). */
#ifdef DEBUG
printf("w* isword returns ! result = %d\n" , ! result) ;
fflush(stdout);
#endif
return(! result) ;
否定性的东西很不好理解,应该尽量避免。在这里,部分问题来自一个毫无信息的变量名字
result。改用另一个更具说明性的名字matchfound之后,注释就再没有存在的必要,打
印语句也变得清楚了:
# ifdef DEBUG
printf ("*** isword returns matchfound = %d\n" , matchfound) ;
fflush(stdout) ;
#endif
return matchfound;
不要与代码矛盾。许多注释在写的时候与代码是一致的。但是后来由于修正错误,程序改变
了,可是注释常常还保持着原来的样子,从而导致注释与代码的脱节。
无论产生脱节的原因何在,注释与代码矛盾总会使人感到困惑。由于误把错误注释当真,
常常使许多实际查错工作耽误了大量时间。所以,当你改变代码时,一定要注意保证其中的
注释是准确的。
注释不仅需要与代码保持一致,更应该能够支持它。下面的例子里的注释是正确的,它
正确地解释了后面两行的用途。但细看又会发现它与代码矛盾,注释中谈的是换行,而代码
中说的则是空格:
time(&now) ;
strcpy(date, ctime(&now)) ;
/* get rid of trailing newline character copied from ctime */
i = 0 ;
while(date[i] >= ' ') i++;
date[il = 0;
一个可能的改进是采用惯用法重写代码:
time(&now) ;
strcpy(date, ctime(&now)) ;
/* get rid of trailing newline character copied from ctime */
for (i = 0; date[i] != '\n' ; i++)
date[ i ] = ' \ 0' ;
现在注释和代码一致了。但是这两者都还可以进一步改进,应该写得更直截了当些。这里要
解决的问题就是删除函数ctime返回时放在字符串最后的换行字符。注释里应该说明这个情
况,代码也应该是做这件事:
time(&now) ;
strcpy(date, ctime(&now)) ;
/* ctime() puts newline at end of string ; delete it */
date[strlen(date)-l] = '\0' ;
最后这个表达式是在C语言里截去字符串最后字符的习惯写法。现在代码变短了,注释也支持
它,解释了为什么这个语句需要做。
澄清情况,不要添乱。注释应该在困难的地方尽量帮助读者,而不是给他们设置障碍。下面的
例子中遵循了我们为函数写注释的建议,解释函数不寻常的特征。但是这里的函数是strcmp,
这是个标准的东西,具有人们熟悉的界面,它的不寻常特性对手头工作来说根本不重要:
int strcmp(char *sl, char *s2)
/* string comparison routine returns -1 if s1 is */
/* above s2 in an ascending order list , 0 if equal */
/* 1 if s1 below s2 */
{
while(*sl==*s2) {
if(*sl=='\0')
return(0);
sl++;
s2++;
}
if (*s1 > *s2)
return(1) ;
return(-1) ;
}
如果真的需要用许多话来解释出了什么事,这可能就说明了有关代码应该重写。上面的代码
可以改进,但其中真正的问题在注释,它几乎有代码本身那么长,而且也非常含糊(例如,什
么是这里的above?)。我们很难说这段代码难以理解,但是,由于它实现的是一个标准函数,
它的注释就应该在这方面有所帮助,应该概述其行为,告诉我们哪里有它原来的定义。下面
的注释是合适的:
/* strcmp: return < 0 if sl<s2, > 0 if s1 > s2, 0 if
equal */
/* ANSI C, section 4.11.4.2 */
int strcmp(const char *s1, const char *s2)
{
. . .
}
学生常被告之应该注释所有的内容。职业程序员也常被要求注释他们的所有代码。但是,
应该看到,盲目遵守这些规则的结果却可能是丢掉了注释的真谛。注释是一种工具,它的作
用就是帮助读者理解程序中的某些部分,而这些部分的意义不容易通过代码本身直接看到。
我们应该尽可能地把代码写得容易理解。在这方面你做得越好,需要写的注释就越少。好的
代码需要的注释远远少于差的代码。
学生常被告之应该注释所有的内容。职业程序员也常被要求注释他们的所有代码。但是,
应该看到,盲目遵守这些规则的结果却可能是丢掉了注释的真谛。注释是一种工具,它的作
用就是帮助读者理解程序中的某些部分,而这些部分的意义不容易通过代码本身直接看到。
我们应该尽可能地把代码写得容易理解。在这方面你做得越好,需要写的注释就越少。好的
代码需要的注释远远少于差的代码。
我们谈论的主要问题是程序设计的风格:具有说明性的名字、清晰的表达
式、直截了当的控制流、可读的代码和注释,以及在追求这些内容时一致地使用某些规则和
惯用法的重要性。没人会争辩说这些是不好的。
但是,为什么要为风格而煞费苦心?只要程序能运行,谁管它看起来是什么样子?把它
弄得漂亮点是不是花费了太多时间?这些规则难道没有随意性吗?
我们的回答是:书写良好的代码更容易阅读和理解,几乎可以保证其中的错误更少。进
一步说,它们通常比那些马马虎虎地堆起来的、没有仔细推敲过的代码更短小。在这个拼命
要把代码送出门、去赶上最后期限的时代,人们很容易把风格丢在一旁,让将来去管它们吧。
但是,这很可能是一个代价非常昂贵的决定。如果对好风格问题重
视不够,程序中哪些方面可能出毛病。草率的代码是很坏的代码,它不仅难看、难读,而且
经常崩溃。
这里最关键的结论是:好风格应该成为一种习惯。如果你在开始写代码时就关心风格问
题,如果你花时间去审视和改进它,你将会逐渐养成一种好的编程习惯。一旦这种习惯变成
自动的东西,你的潜意识就会帮你照料许多细节问题,甚至你在工作压力下写出的代码也会
更好。