欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

《程序设计实践》第一章编码风格的不完全总结

程序员文章站 2022-07-24 14:59:10
名字 全局变量使用描述性的名字,局部变量则使用简洁的名字。根据定义,全局变量可以出现在整个程序中的任何地方,因此他们需要一个足够长并且详细描述的名字让读者想起它们的意义。给每个全局变量声明附加一个简短注释也非常有帮助: 全局函数、类和结构应该也要有描述性的名字,以表明它们在程序里扮演的角色。对于局部 ......

名字

全局变量使用描述性的名字,局部变量则使用简洁的名字。根据定义,全局变量可以出现在整个程序中的任何地方,因此他们需要一个足够长并且详细描述的名字让读者想起它们的意义。给每个全局变量声明附加一个简短注释也非常有帮助:

    int npending = 0; // current length of input queue

全局函数、类和结构应该也要有描述性的名字,以表明它们在程序里扮演的角色。
对于局部变量使用简短的名字就够了。在函数里,n 可能就足够了,npoints 也还可以,用 numberOfPoints 就太过分了。
按常规方式使用的局部变量可以采用极短的名字。例如用 i、j 作为循环变量,p、q 作为指针,s、t 表示字符串等。比较:

    for (theElementIndex = 0; theElementIndex < numberOfElements; theElementIndex++)
        elementArray[theElementIndex] = theElementIndex;

to

    for (i = 0; i < nelems; i++)
        elem[i] = i;

对返回布尔值的函数命名,应该清楚地反映其返回值情况。因此

    if (checkoctal(c)) ...

没有表明哪一个返回值是真,哪一个是假,而:

     if (isoctal(c)) ...

说清楚了如果参数是八进制数字则该函数返回真,否则为假。

表达式和语句

使用自然的表达式。含有否定运算的条件表达式比较难以理解:

    if (!(block_id < actblks) || !(block_id >= unblocks))

在两个测试中都用到了否定运算,有点多余。应该改变关系运算符的方向:

    if ((block_id >= actblks) || !(block_id < unblocks))

现在代码读起来就自然多了。

用括号解决歧义

    leap_year = y % 4 == 0 && y % 100 != 0 || y % 400 == 0;
    leap_year = ((y%4 == 0) && (y%100 != O)) || (y%400 == 0);

去掉了一些空格:将高优先级运算符的操作数聚合在一起,帮助读者更快地看清表达式的结构。

要清晰。程序员有时把自己无尽的创造力用到了尽可能地写最简短的代码,或者寻求巧妙方法去得到一个结果。有时这种技能被误用了,因为我们的目标是写出清晰的代码,而不是巧妙的代码。

运算符 ?: 适用于简短的表达式,这时它可以把4行的 if-else 程序变成1行。例如这样:

    max = (a > b) ? a : b;

当心副作用。 I/O 操作有时会带来难以发现的问题。下面的例子希望从标准输入读入两个相关联的数:

    scanf("%d %d", &yr, &profit[yr]);

你可能认为答案依赖于参数的求值顺序,但是实际上传入 scanf 的所有参数在函数被调用前就已经计算好了,所以 &profit[yr] 实际使用的是旧的 yr 。解决办法是把语句分解为两个:

    scanf("%d", &yr);
    scanf("%d", &profit[yr]);

一致性和习惯用法

如果你在一个之前不是你写的程序上开发,应该保留程序原有的风格。即使你更喜欢你自己的风格,当你需要做修改时,也不要使用。程序的一致性比你个人的习惯更重要,对于后续跟进的人来说更方便。

函数宏

函数宏最常见的一个严重问题是:如果一个参数在定义中出现多次,它就可能被多次求值。如果调用时的实际参数带有副作用,结果就会产生一个难以捉摸的错误。
下面的代码段来自<ctype.h>,其意图是实现对一个字符的测试:

    #define isupper(c) ((c) >= 'A' && (c) <= 'Z')

如果 isupper 被这样子中调用:

    while (isupper(c = getchar()))

getchar()函数会被调用两次,那么,每当遇到一个大于等于 A 的字符,程序就会将它丢掉,而下一个字符将被读入并去与 Z 做比较,从而导致难以预料的后果。

既然宏的缺点在很大程度上盖过了它的优点,使用过程一不小心还会引入问题,那么我们该在何时使用宏呢?答案如下:

    //计算出数组的元素个数
    #define NELEMS(array) sizeof(array)/sizeof(array[0])

    double dbuf[100];
    for (i = 0; i < NELEMS(dbuf); i++)
       ...

在这里,数组大小只在一个地方设置。如果数组的大小改变,其余代码都不必改动。对函数参数的多次求值在这里也不会出问题,因为它不会出现任何副作用.事实上,这个计算在程序编译时就已经做完了。这是宏的一个恰当使用,因为它做了某种函数无法完成的工作,从数组声明计算出它的大小。

魔术数字

给魔术数字命名。魔术数字包括各种常数、数组的大小、字符位置等等。除了 0 和 1 之外,程序里出现的任何数大概都可以算是魔术数字,它们应该有自己的名字。

把数字定义为常数,不要定义为宏。C 程序员的传统方式是用 #define 行来对付神秘的数值。使用宏进行编程是一种危险的方式,因为宏会在背地里改变程序的词法结构,我们应该让语言去做正确的工作 。在 C 和 C++ 里,整数常数可以用枚举语句定义。在 C++ 里任何类型都可使用 const 声明的常数:

    const int MACROW = 24, MAXCOL = 80;

在 Java 中可以用 final 声明:

    static final int MACROW = 24, MAXCOL = 80;

C 语言里也有 const 值,但它们不能用作数组的边界。所以 enum 语句仍然是在 C 里面可选用的方法。

与此类似的还有另一个问题,那就是程序里许多上下文中经常出现的 0。虽然编译系统会把它转换为适当类型,但是,如果我们把每个 0 的类型写得更明确更清楚,对读程序的人理解其作用是很有帮助的。例如,用 (v o i d \*) 0 或 NULL 表示 C 里的空指针值,用 ‘\0’ 而不是 0 表示字符串结尾的空字节。也就是说不要写成:

    str = 0;
    name[i] = 0;
    x = 0;

应该写成:

    str = NULL;
    name[i] = "\n";
    x = 0;

注释

当你改变代码的时候,一定要注意保证对应的注释是准确的。

 



以上就是我在阅读完《程序设计实践》第一章后做的一些笔记和总结,也算是个人博客的开篇,欢迎交流。