Project in C++ Coding Practice
在利用C++编程的实践中,通常会存在一些影响程序性能、效率、本地化等的缺陷,下面将列出其中的一部分缺陷供大家参考,并且给出一些修正缺陷的意见,以及根据缺陷所造成的影响程度来给出缺陷查找优先级,1★优先级为最低。
1、程序中的提示信息
程序中的提示信息和程序的代码混合。
缺陷查找优先级:★
说明:将程序中的提示信息直接写在代码中,将会不利于程序的本地化。比如程序的提示信息为英文,并且写在了CPP文件中,编译完成后就分不清哪些是代码,哪些是提示信息了,这样汉化时就非常不方便。解决的方法是:如果是用VC++编程时,可以将程序中所有用到的字符串常量都放到资源文件的String Table中。当用到String Table时就可以用Cstring::LoadString(nID) 从资源文件中load出来。那么当需要汉化的时候,只要把String Table中的提示信息都翻译成中文就可以了。 如果不是用VC++来开发程序,则可以把这些字符串常量都放到一个文本文件中,汉化时只要把此文本文件中的内容汉化即可。
此外,将程序中的提示信息和代码混合还会增加程序中的错误,不利于程序的修改。把程序代码和提示性信息分开,可以在编写大型的软件时减少出错的次数。
2.输入信息的获取
当获取输入信息时,往往需要指定接受输入的缓冲区。直接使用缓冲区来存储输入的信息,这是一个缺陷。
缺陷查找优先级: ★ ★
说明:如果不对输入的长度进行判断或限制,可能会出现缓冲区溢出的问题,这将可能导致系统的崩溃。
3.I/O操作
进行I/O操作时不对操作结果进行检查。
缺陷查找优先级: ★ ★
说明:如果不对I/O操作结果进行检查,I/O产生的异常会导致程序的崩溃,引起用户的不满。比如要打开一个文件,但不对该文件进行一下判断,如果此文件不存在,或者没有权限来使用这个文件,那么程序就会出现异常。
4.程序中使用的常量
直接在程序中使用常量的值。
缺陷查找优先级: ★ ★
说明:嵌入到程序中的大量常量数值会使得程序难以维护,同时也会给程序的调试带来了困难。最好方法是将常量定义成常型变量const。
5.cout中的效率
在利用cout输出时,大量的使用endl来换行。
缺陷查找优先级: ★
说明:cout向标准设备输出时,对应着一个内存缓冲区,endl的使用会清除该缓冲区,频繁的使用endl会降低程序的效率,应该写成“\n”。
6.操作可能失败的函数
对于可能操作失败的函数,不进行操作结果的检查。
缺陷查找优先级: ★ ★
说明:比如分配一段内存,就有可能分配不到所要求的内存空间。对于这种潜在的操作失败不进行任何检查,而让程序继续进行,会导致程序行为的异常甚至崩溃。一定要对所有潜在的操作失败都进行一下判断。
7.Include和namespace的滥用
重复的包含某些文件或者使用命名空间,或者包含不必要的文件。
缺陷查找优先级: ★
说明:反复的使用Include和namespace,这是一个缺陷,即多次的使用或不该用的地方瞎用。比如,一个类已经在其头文件中include "iostring.h",又在.cpp文件中include "iostring.h",这样在程序中Include一个.h文件好几遍,就可能在程序中出现类的重复定义错误。即使不出错,也会导致命名污染,并且增加了编译的代价。所谓污染是指来回交叉Include的时候,可能会出现一个地方修改了,而另外一个地方忘了修改,这样程序就会出问题。其类似于数据库中用范式来消除数据的冗余,程序也一定要避免数据冗余。
8.不恰当的继承
把对于别的类功能的使用视为一种继承。
缺陷查找优先级: ★ ★ ★
说明:使用继承关系并不一定能很好的体现类与类之间的关系,而且有时还可能会导致程序结构的混乱,只有is-a的时候才能使用继承关系。如果只是使用另外一个类的功能,那么将类与类的关系定义成继承就不恰当了。这个时候是has-a的关系,或是整体与部分的关系,乱用继承只会导致类的关系混乱。此外,继承关系的乱用还会导致另一个问题,比如:
Class X{
Y y;
}
这种结构通常会出现在X.h文件中,而且必须在此文件的开头写上 #include "Y.h",这样就使得类X与类Y紧耦合在一起。程序做完以后,如果把Y改了,即Y.h变了,必然导致X.h也发生变化,这样就导致凡是include "X.h"的文件也都发生了变化,引起了一系列的连锁反应。现在的编译器通常都是采用增量编译的方式,即只编译做了修改的那部分文件。因此,在此程序中即使只是修改了Y.h的很小一部分内容,也会引起大量文件的变化,而导致编译器将这些大量变化的文件都进行了编译,这将会耗费大量的时间。
要想在X.h中避免include "Y.h"的解决方式如下:
Class Y;
Class X{
Y *y; //y是Y的指针,占4个字节
X(){
y = new Y;
}
}
这里作一说明,必须使用Y *y的形式,否则直接使用Y y就会出现问题。因为每次生成一个X的实例x,编译器都要为x分配内存空间,同时还要了解实例y所占的空间有多大。而要想知道y有多大,就必须知道Y的定义,因此又必须得include "Y.h"了。而此程序中使用了指针形式,指针在Windows系统中始终只占4个字节,编译器也就始终知道应该为x分配多大的内存,所以此处只需知道Y是个class足已,而不需知道Y的具体定义。
再回到上面那个问题,为什么定义继承的时候容易产生问题,是因为继承实际上是在派生类的实例的顶部放了一个基类的实例,比如:
Class X:public Y{ //放一个基类的实例,一定要给基类分配空间
( Y y; )
……
}
既然要放一个基类的实例,那么只有知道这一实例有多大,才能给派生类的实例分配内存,道理等同于以上所述。所以使用继承这种关系,必导致在其头部include "Y.h",而这必然会导致上述的问题,这也正是面向对象开发方法中被人所诟病的一个缺点。面向构件的软件开发方法和面向对象的软件开发方法的一个很大的区别,就在于面向构件的软件开发方法摒弃了面向对象的软件开发方法中的一个问题,就是复杂的继承关系使得程序紧耦合在一起。面向构件的开发方法为每一个构件都定义一个明确的接口,构件之间利用接口进行交互,这样耦合度就很低。
9. 错误信息的丢失
在程序中对于出现错误的原因没有进行详细的记录,或者简单地使用数值来表示错误信息,导致了错误信息的丢失。
缺陷查找优先级: ★
说明:保留错误的详细信息可以更好的找到出现错误的原因,而且当用户使用出现错误时,也能很方便地根据详细信息进行错误的重现。因此当每个程序可能出错时,应当返回一个精确的错误编码,有利于调试与维护。
10.类型转换
使用C风格的强制类型转换。
缺陷查找优先级: ★ ★
说明:C风格的强制类型转换,比如(int)变量名,这种形式的类型转换很难定位,不利于在代码中查找强制类型转换,从而降低了程序的可维护性。应该使用C++所提供的强制类型转换操作符static_cast、dynamic_cast、const_cast、reinterpret_cast,这样就可以通过关键字cast很容易的找到程序中出现类型转换的地方了。
11.对于类型的选择
不根据数据真正的取值范围来进行类型定义,使用不恰当的数据类型。
缺陷查找优先级: ★ ★
说明:比如用int型来定义年龄,而年龄是不会出现负数的,这样就扩大了数据的取值范围,浪费了存储空间。同时,也增加了对于数据有效性判断的复杂性。
12.冗余的定义
对于数据类型、常量进行了冗余的定义
缺陷查找优先级: ★
说明:对数据类型、常量进行冗余的定义会导致程序难以维护。比如模板类数据类型一般定义形式为 type<sub_type> i,这样就应该将其定义成type def type<sub_type> x,以后每次用到时都用x来定义即可。
13.冗余的代码
完成同一功能的同样代码多次重复出现。
缺陷查找优先级: ★ ★
说明:同样的操作不要在程序中写两遍,这样会增大程序出错的可能性,不利于程序的维护。
14.内存的分配和回收
大量采用引起内存分配和回收的工作,如new 、delete、对象的拷贝。
缺陷查找优先级: ★ ★ ★
说明:内存的分配是很费时的事情,大量的使用会造成程序的效率很低。比如程序中不要每次都去动态申请(new)一块小内存。如果是知道可能需要使用的内存空间较大,就可以一次性申请一块大内存。
15. 程序中使用的数值
在程序中,当需要使用一些数值来控制程序的流程时,直接使用了数字。
缺陷查找优先级: ★ ★
说明:数字的直接使用会导致程序难以理解与维护。比如,for(i=1;i<10; )这样的语句就是不太好的,应该写成如下的形式:
const maxnum=10;
for(i = 1;i<maxnum; )
因为程序中如果到处都是这样直接使用的数字,那么当需要修改的时候就非常的费劲了。
16.数据结构的选择
选择了不恰当的数据结构。
缺陷查找优先级: ★
说明:选择不恰当的数据结构会导致程序的效率受到影响,延长程序的运行时间。
17.前缀还是后缀?
在前缀和后缀自增式都可以完成要求的情况下,使用后缀表达式。
缺陷查找优先级: ★
说明:使用后缀表达式效率不高,因为后缀表达式操作中会生成一个临时变量,而这一临时变量是会占用额外的时间和开销的。