千家信息网

现代软件工程讲义 3 代码规范与代码复审

发表于:2024-11-22 作者:千家信息网编辑
千家信息网最后更新 2024年11月22日,在第9章中,同学们完成了WC程序,经过评比,九条的程序获得了第一名。这时,阿超说,现代软件产业经过几十年的发展,已经不可能出现一个人单枪匹马完成一个软件的事情了,软件都是在相互合作中完成的。阿超建议大
千家信息网最后更新 2024年11月22日现代软件工程讲义 3 代码规范与代码复审



在第9章中,同学们完成了WC程序,经过评比,九条的程序获得了第一名。这时,阿超说,现代软件产业经过几十年的发展,已经不可能出现一个人单枪匹马完成一个软件的事情了,软件都是在相互合作中完成的。阿超建议大家互相看看别人的代码,在TFS中每个人都把各自项目的权限放宽,允许别人访问,交流一下意见。

两个小时后,小飞来抱怨说,九条的代码都是一行到底,随意缩进,跟他提了意见,他还说"编译通过就行了"。

他们找到了九条。

九条:我打麻将的时候牌都是乱摆,赢的也不少呀。

阿超:为什么要乱摆?

九条:因为怕围观的人看清我的牌路,我自己清楚就行了。

上课了。阿超问大家,我们写的代码是给人看的,还是给机器看的?

杂曰:人也看,机器也看。

阿超:对,但是最终是人在看。而且和打麻将不同,我们的代码要让"旁观者"看得清清楚楚。请看下一段代码,如代码清单10-1所示,如果你接手这样的代码,有什么感想?

代码清单10-1 badly formatted code - big C[1]

 #include "stdafx.h"            #include             "stdio.h"          void test();                   int _tmain                   (int argc,                      _TCHAR*           argv[])                          { test(); return                                    0; }                                  char C[25][40];void d(int x,int y)               {C[x][y]=C[x][y+1]=32;}int f(int x){return (int)x*x*.08;}               void test(){int i,j;                                    char s[5]="TEST";          for(i=0;i<25;i++)         for(j=0;j<40;j++)        C[i][j]=s[(i+j)%4];       for(i=1;i<=7;i++)       {d(18-i,12);       C[20-f(i)][i+19]=        C[20-f(i)][20-i]=32;         }d(10,13);d(9,13);          d(8,14);d(7,15);              d(6,16);d(5,18);d(5,20);                         d(5,22);d(5,26);                  d(6,23);d(6,25);d(7,25);for(i=0;i<25;i++,printf("\n"))                                  for(j=0;j<40;printf("%c",C[i][j++]));}

同学们纷纷发言,基本上有如下的反应:

1Faint!!

2)重写程序!!

3)找到原作者,暴打一顿!!!

4)让此人从公司辞职!!!!

计算机只关心编译后的机器码,你的程序是什么样的缩进风格,以及变量名是否有统一的规范等和机器码的执行无关。但是,做一个有商业价值的项目,或者在团队里工作,代码规范相当重要。

我们讲的"代码规范"可以分成两个部分。

1)代码风格规范。主要是文字上的规定,看似表面文章,实际上非常重要。

2)代码设计规范。牵涉到程序设计、模块之间的关系、设计模式等方方面面,这里有不少与具体程序设计语言息息相关的内容(如C/C++/Java/C#),但是也有通用的原则,这里主要讨论通用的原则。

10.1 代码风格规范

代码风格的原则是:简明,易读,无二义性。

提示:这是移山公司的一家之言,如果碰到争执,关键是要本着"保持简明,让代码更容易读"的原则,看看如下争执中的代码规范是否能够让程序员们更好地理解和维护程序。

10.1.1 缩进

是用Tab键好,还是248个空格?

结论:4个空格,在VS2005和其他的一些编辑工具中都可以定义Tab键扩展成为几个空格键。不用 Tab键的理由是Tab键在不同的情况下会显示不同的长度。4个空格的距离从可读性来说正好。

10.1.2 行宽

行宽必须限制,但是以前有些文档规定的80字符行宽太小了(以前的计算机/打字机显示行宽为80字符),现在时代不同了,可为100字符。

10.1.3 括号

在复杂的条件表达式中,用括号清楚地表示逻辑优先级。

10.1.4 断行与空白的{ }

程序的结构是什么风格?下面有几种格式,我们一一讨论。

最精简的格式A

if (condition) DoSomething();

else DoSomethingElse();

有人喜欢这样,因为可以节省几行,但是不同的语句(Statement)放在一行中,会使程序调试(DeBug)非常不方便,如果要一步一步观察conditioncondition有可能是包含函数调用的复杂表达式)中各个变量的变化情况,单步执行就很难了。

因此,我们还是要有断行,这样可以得到如下的结构--格式B

if (condition)

DoSomething();

else

DoSomethingElse();

这样的结构,由于没有明确的"{"和"}"来判断程序的结构,在有多层控制嵌套的时候,就不容易看清结构和对应关系。下面的改进(格式C)虽好,但是阿超认为还是不够清晰:

if ( condition) {

DoSomething();

} else {

DoSomethingElse();

}

是我们最后做了这个选择,每个"{"和"}"都独占一行。就是格式D

if ( condition)

{

DoSomething();

}

else

{

DoSomethingElse();

}

10.1.5 分行

不要把多行语句放在一行上。

a = 1; b = 2; // bogus

if (fFoo) Bar(); // bogus

更严格地说,不要把不同的变量定义在一行上。

Foo foo1, foo2; // bogus

10.1.6 命名

阿超:我在某个同学的程序中看到有些变量叫"lili","yunyun",不知道这些变量在现实生活中有没有什么意义。

下面哄笑起来。

果冻:(红着脸问)那有些变量的确想不出名字,简单的变量像ijk都用完了,怎么办?

阿超:当我们的程序比"Hello World"复杂10倍以上的时候,像给变量命名这样简单的事看起来也不那么简单了。我们就来谈谈如何起名字这个问题。程序中的实体、变量是程序员昼思夜想的对象,要起一个好的名字才行。大家都知道用单个字母给有复杂语义的实体命名是不好的,目前最通用的,也是经过了实践检验的方法叫"匈牙利命名法"。例如:

fFileExist,表明是一个bool值,表示文件是否存在;

szPath,表明是一个以0结束的字符串,表示一个路径。

如此命名的目的是让程序员一眼就能看出变量的类型,避免在使用中出错。早期的计算机语言(如BCPL)不作类型检查,在C语言中,intbytecharbool大概都是一回事。下面这一句话:

if (i)

从语义来说,i可以是表示真/假的一个值,也可以表示长度是否为零,

还可以表示是否到了字符串的结束位置,或者可以表示两个字符串比较的结果不相等(strcmp()返回-101)。从程序的文字上,很难看出确切的语义。

同样是字符串类型,char *BSTR的有些行为是很不一样的。

HRESULT的值也可以用来表示真假,但是HR_TRUE == 0HR_FALSE ==1,这和通常的true/false刚好相反。

大部分的程序,错就错在这些地方!在变量面前加上有意义的前缀,就可以让程序员一眼看出变量的类型及相应的语义。这就是"匈牙利命名法"的用处。

匈牙利命名法的一些通用规定,见本书附录B(第337页)。

还有一些地方不适合用"匈牙利命名法",比如,在一些强类型的语言(如C#)中,不同类型的值是不能做运算的,对类型有严格的要求,例如C# 中,if()语句只能接受bool值的表达式,这样就大大地防止了以上问题的发生。在这样的语言中,前缀就不是很必要的,匈牙利命名法则不适用了。Microsoft .Net Framework就不主张用这样的法则。

10.1.7 下划线问题

下划线用来分隔变量名字中的作用域标注和变量的语义,如:一个类型的成员变量通常用m_来表示。移山公司规定下划线一般不用在其他方面。


0