不拉磨小说网 > 网络其他电子书 > 软件工程思想 >

第14章

软件工程思想-第14章

小说: 软件工程思想 字数: 每页4000字

按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!




6。2  良好的编程风格

  内功深厚的武林高手出招往往平淡无奇。同理,编程高手也不会用奇门怪招写程序。良好的编程风格是产生高质量程序的前提。

6。2。1 命名约定
  有不少人编程时用拼音给函数或变量命名,这样做并不能说明你很爱国,却会让用此程序的人迷糊(很多南方人不懂拼音,我就不懂)。程序中的英文一般不会太复杂,用词要力求准确。
  匈牙利命名法是Microsoft公司倡导的 'Maguire 1993',虽然很烦琐,但用习惯了也就成了自然。没有人强迫你采用何种命名法,但有一点应该做到:自己的程序命名必须一致。
  以下是我编程时采用的命名约定:
(1)宏定义用大写字母加下划线表示,如MAX_LENGTH;
(2)函数用大写字母开头的单词组合而成,如SetName; GetName ;
(3)指针变量加前缀p,如 *pNode ;
(4)BOOL 变量加前缀b,如 bFlag ;
(5)int 变量加前缀i,如 iWidth ;
(6)float 变量加前缀f,如 fWidth ;
(7)double变量加前缀d,如 dWidth ;
(8)字符串变量加前缀str,如 strName ;
(9)枚举变量加前缀e,如 eDrawMode ;
(10)类的成员变量加前缀m_,如 m_strName; m_iWidth ;
对于 int; float; double 型的变量,如果变量名的含义十分明显,则不加前缀,避免烦琐。如用于循环的int型变量 i;j;k ;float 型的三维坐标(x;y;z)等。(奇。书。网…整。理。提。供)

6。2。2 使用断言
  程序一般分为Debug版本和Release版本,Debug版本用于内部调试,Release版本发行给用户使用。
  断言assert是仅在Debug版本起作用的宏,它用于检查“不应该”发生的情况。以下是一个内存复制程序,在运行过程中,如果assert的参数为假,那么程序就会中止(一般地还会出现提示对话,说明在什么地方引发了assert)。
//复制不重叠的内存块
void  memcpy(void *pvTo; void *pvFrom; size_t size)
  {
void *pbTo = (byte *) pvTo;
void *pbFrom = (byte *) pvFrom;
assert( pvTo != NULL  &&  pvFrom != NULL );
while(size … … 》 0 )
*pbTo + + = *pbFrom + + ;
return (pvTo);
  }
  assert不是一个仓促拼凑起来的宏,为了不在程序的Debug版本和Release版本引起差别,assert不应该产生任何副作用。所以assert不是函数,而是宏。程序员可以把assert看成一个在任何系统状态下都可以安全使用的无害测试手段。
  很少有比跟踪到程序的断言,却不知道该断言的作用更让人沮丧的事了。你化了很多时间,不是为了排除错误,而只是为了弄清楚这个错误到底是什么。有的时候,程序员偶尔还会设计出有错误的断言。所以如果搞不清楚断言检查的是什么,就很难判断错误是出现在程序中,还是出现在断言中。幸运的是这个问题很好解决,只要加上清晰的注释即可。这本是显而易见的事情,可是很少有程序员这样做。这好比一个人在森林里,看到树上钉着一块“危险”的大牌子。但危险到底是什么?树要倒?有废井?有野兽?除非告诉人们“危险”是什么,否则这个警告牌难以起到积极有效的作用。难以理解的断言常常被程序员忽略,甚至被删除。'Maguire 1993'
  以下是使用断言的几个原则:
(1)使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,后者是必然存在的并且是一定要作出处理的。
(2)使用断言对函数的参数进行确认。
(3)在编写函数时,要进行反复的考查,并且自问:“我打算做哪些假定?”一旦确定了的假定,就要使用断言对假定进行检查。
(4)一般教科书都鼓励程序员们进行防错性的程序设计,但要记住这种编程风格会隐瞒错误。当进行防错性编程时,如果“不可能发生”的事情的确发生了,则要使用断言进行报警。

6。2。3  new、delete与指针
在C++中,操作符new用于申请内存,操作符delete用于释放内存。在C语言中,函数malloc用于申请内存,函数free用于释放内存。由于C++兼容C语言,所以new、delete、malloc、free都有可能一起使用。new能比malloc干更多的事,它可以申请对象的内存,而malloc不能。
C++和C语言中的指针威猛无比,用错了会带来灾难。对于一个指针p,如果是用new申请的内存,则必须用delete而不能用free来释放。如果是用malloc申请的内存,则必须用free而不能用delete来释放。
在用delete或用free释放p所指的内存后,应该马上显式地将p置为NULL,以防下次使用p时发生错误。示例程序如下:
void Test(void)
  {
float *p;
p = new float'100';
if(pNULL) return;
…// do something
delete p;
p=NULL;   // 良好的编程风格

// 可以继续使用p
p = new float'500';
if(pNULL) return;
…// do something else
delete p;
p=NULL; 
  }

我们还要预防“野指针”,“野指针”是指向“垃圾”内存的指针,主要成因有两种:
(1)指针没有初始化。
(2)指针指向已经释放的内存,这种情况最让人防不胜防,示例程序如下:
class A 
  {
  public:
  void Func(void){…}
  };
void Test(void)

A  *p;

A  a;
p = &a;// 注意 a 的生命期
  }
p…》Func();// p是“野指针”,程序出错
  }

6。2。4 使用const
在定义一个常量时,const比 #define更加灵活。用const定义的常量含有数据类型,该常量可以参与逻辑运算。例如:
constint LENGTH = 100;// LENGTH是int类型
constfloatMAX=100;// MAX是float类型
#defineLENGTH  100// LENGTH 无类型
#defineMAX 100// MAX 无类型

除了能定义常量外,const还有两个“保护”功能:
  一、强制保护函数的参数值不发生变化
以下程序中,函数f不会改变输入参数name的值,但是函数g和h都有可能改变name的值。
void f(String  s);// pass by value
void g(String &s);// pass by referance
void h(String *s);// pass by pointer
main()
  {
String  name=“Dog”;
f(name);// name的值不会改变
g(name);// name的值可能改变
h(name);// name的值可能改变
  }

对于一个函数而言,如果其‘&’或‘*’类型的参数只作输入用,不作输出用,那么应当在该参数前加上const,以确保函数的代码不会改变该参数的值(如果改变了该参数的值,编译器会出现错误警告)。因此上述程序中的函数g和h应该定义成:
void  g(const String &s);
void  h(const String *s);

  二、强制保护类的成员函数不改变任何数据成员的值
以下程序中,类stack的成员函数Count仅用于计数,为了确保Count不改变类中的任何数据成员的值,应将函数Count定义成const类型。
class Stack
  {
  public:
void push(int elem);
void pop(void);
intCount(void)  const;// const类型的函数
  private:
intnum;
intdata'100';
  };

int Stack::Count(void)  const
  {
++ num;// 编译错误,num值发生变化
  pop();// 编译错误,pop将改变成员变量的值
  return num;


6。2。5 其它建议
(1)不要编写一条过分复杂的语句,紧凑的C++/C代码并不见到能得到高效率的机器代码,却会降低程序的可理解性,程序出错误的几率也会提高。
(2)不要编写集多种功能于一身的函数,在函数的返回值中,不要将正常值和错误标志混在一起。
(3)不要将BOOL值TRUE和FALSE对应于1和0进行编程。大多数编程语言将FALSE定义为0,任何非0值都是TRUE。Visual C++将TRUE定义为1,而Visual Basic则将TRUE定义为…1。示例程序如下:
  BOOLflag;

if(flag){ // do something }// 正确的用法
if(flagTRUE){ // do something }// 危险的用法
if(flag1){ // do something }// 危险的用法
if(!flag){ // do something }// 正确的用法
if(flagFALSE) { // do something }// 不合理的用法
if(flag0){ // do something }// 不合理的用法

(4)小心不要将“= =”写成“=”,编译器不会自动发现这种错误。
(5)不要将123写成0123,后者是八进制的数值。
(6)将自己经常犯的编程错误记录下来,制成表格贴在计算机旁边。

6。3  小 结

C++/C程序设计如同少林寺的武功一样博大精深,我练了8年,大概只学到二三成。所以无论什么时候,都不要觉得自己的编程水平天下第一,看到别人好的技术和风格,要虚心学习。
本章的内容少得可怜,就象口渴时只给你一颗杨梅吃,你一定不过瘾。我借花献佛,推荐一本好书:Marshall P。 Cline著的《C++ FAQs》'Cline 1995'。你看了后一定会赞不绝口。
  会编写C++/C程序,不要因此得意洋洋,这只是程序员基本的技能要求而已。如果把系统分析和系统设计比作“战略决策”,那么编程充其量只是“战术”。如果指挥官是个大笨蛋,士兵再勇敢也会吃败仗。所以我们程序员不要只把眼光盯在程序上,要让自己博学多才。我们应该向北京胡同里的小孩们学习,他们小小年纪就能指点江山,评论世界大事。
奇*書网收集整理
第七章  测试与改错

  编程大师说:“任何一个程序,无论它多么小,总存在着错误。”
   初学者不相信大师的话,他问:“如果一个程序小得只执行一个简单的功能,那会怎样?”
   “这样的一个程序没有意义,”大师说,“但如果这样的程序存在的话,操作系统最后将失效,产生一个错误。”
   但初学者不满足,他问:“如果操作系统不失效,那么会怎样?”
   “没有不失效的操作系统,”大师说,“但如果这样的操作系统存在的话,硬件最后将失效,产生一个错误。”
   初学者仍不满足,再问:“如果硬件不失效,那么会怎样?”
  大师长叹一声道:“没有不失效的硬件。但如果这样的硬件存在的话,用户就会想让那个程序做一件不同的事,这件事也是一个错误。”
   没有错误的程序世间难求。'James 1999'
   
   错误是一种严重的程序缺陷。测试的目的是为了发现尽可能多的缺陷,并期望通过改错来把缺陷统统消灭,以期提高软件的质量。但关于测试与改错实在没有什么高明的方法值得大书特书,也不能表现出程序员的聪明才智。相反地,它们带来了更多的牢骚与痛苦。因此在教学和开发实践中,测试与改错总是被当作万般无奈的工作踢到角落里。
   医生可以把他的错误埋葬在地下了事,但程序员不能。我们必须要学会测试与改错,并且把测试与改错工作做好。
   
7。1 对测试的理解

   测试的道理并不深奥,计算机专业人员都应该明白。但就是这么简单的事,计算机专业的博士们也未必都已经理解。
   有一天,一位比我聪明,编程比我快,学习能力比我强的计算机专业博士生恭恭敬敬地请我坐好,并且史无前例地削了苹果请我吃,为的是向我请教“软件工程”问题。你必定以为这位仁兄好学之极。非也,我和他同事三年来从未探讨过“软件工程”问题。只因为他明天要去应聘,参加面试,生怕被人问倒,就央我当晚为他恶补一把“软件工程”。他还特地问我“什么是白盒测试和黑盒测试?应该由谁来执行?”(有公司曾经这样面试应聘者)当我解释完测试的道理时,他叹了一口气说:“这些玩意儿我读大学十年来都没搞过,怎么能讲得出道理来。唉,就去碰碰运气吧。”我有“兔死狐悲”的感觉。我们这一群博士生三年来尽干些自欺欺人的事,到毕业时学问既不深也不博。个个意志消沉,老气横秋。长此以往,总有一天招聘会的大门前将贴出标语“博士与狗不得入内”。
   以下是关于测试的几个重要观念。

7。1。1 测试的目的
   测试的目的是为了发现尽可能多的缺陷。
   这里缺陷是一种泛称,它可以指功能的错误,也可以指性能低下,易用性差等等。测试总是先假设程序中存在缺陷,再通过执行程序来发现并最终改正缺陷。理解测试的目的是个很重要的意识问题。如果说测试的目的是为了说明程序

返回目录 上一页 下一页 回到顶部 2 3

你可能喜欢的