如何编写高质量程序--学习总结篇
0 各种鸟的pk
新手,又名菜鸟,能尝试写出一些满足要求的程序。
编程老手,老鸟,能长期稳定地编写出高质量程序的程序员。
编程高手,大佬,能长期稳定地编写出高难度、高质量程序的程序员。
备注:CMMI是软件能力成熟度集成模型,分为5级。
进来阅读林锐博士的《高质量C编程指南》,从代码规范上获益良多,特总结一下。
1 项目规划
采用项目管理的相关要求,明确项目的时间节点、项目的主要目标和非目标,目标细分成各种小任务,项目团队零时搭建和分工,项目的资源等分析,时间进度规划等等。
2 文件结构
C和C++一般由头文件(以.h为后缀)、程序文件(以.c,.cpp,.cc,.cxx为后缀)。
如果一个软件的头文件数目比较多(如超过十个),通常应将头文件和定义文件分别保存于不同的目录,以便于维护。可将头文件保存于include目录,将定义文件保存于source目录(可以是多级目录)。
2.1 头文件
头文件是用于保持程序的声明。通过头文件来调用库功能和加强类型安全检查。
包括版权信息,文件名称摘要,当前版本信息,历史版本信息,预处理块,函数和类结构等声明。
其中:
为了防止头文件被重复引用,应当用ifndef/define/endif结构产生预处理块。
用 #include <filename.h> 格式来引用标准库的头文件(编译器将从标准库目录开始搜索)。
用 #include “filename.h” 格式来引用非标准库的头文件(编译器将从用户的工作目录开始搜索)。
头文件中只存放“声明”而不存放“定义”。
在头文件中最好不用全局变量,不要出现extern int value这类声明。
/*
* Copyright (c) 2019,XX有限公司XX部门
* All rights reserved.
*
* 文件名称:filename.h
* 文件标识:见配置管理计划书
* 摘 要:简要描述本文件的内容
*
* 当前版本:V1.1
* 作 者:输入作者(或修改者)名字 ,邮箱
* 完成日期:2019年05月19日
*
* 取代版本:V1.0
* 原作者 :输入原作者(或修改者)名字,邮箱
* 完成日期:2019年5月10日
*/
#ifndef GRAPHICS_H // 防止graphics.h被重复引用
#define GRAPHICS_H
#include <math.h> // 引用标准库的头文件
…
#include “myheader.h” // 引用非标准库的头文件
…
void Function1(…); // 全局函数声明
…
class Box // 类结构声明
{
…
};
#endif
2.2 源文件
定义文件为,开头处的版权和版本声明,对一些头文件的引用,程序的实现体(包括数据和代码)。
/*
* Copyright (c) 2019,XX有限公司XX部门
* All rights reserved.
*
* 文件名称:filename.C
* 文件标识:见配置管理计划书
* 摘 要:简要描述本文件的内容
*
* 当前版本:V1.1
* 作 者:输入作者(或修改者)名字 ,邮箱
* 完成日期:2019年05月19日
*
* 取代版本:V1.0
* 原作者 :输入原作者(或修改者)名字,邮箱
* 完成日期:2019年5月10日
*/
#include “graphics.h” // 引用头文件
…
// 全局函数的实现体
void Function1(…)
{
…
}
// 类成员函数的实现体
void Box::Draw(…)
{
…
}
3 具体程序
不要吝啬空行和空格,他们不会增加代码的存储大小。
一行代码只做一件事情,定义变量或者是一条语句,在定义变量的同时最好将其初始化。
if、for、while、do等语句占一行,不论执行语句多长,都记得加‘{’、‘}’。
3.1 空行
程序好的布局需要空行,在每个类声明之后、每个函数定义结束之后都要加空行;在一个函数体内,逻辑上密切相关的语句之间不加空行,其它地方应加空行分隔。
3.2 空格
关键字之后要留空格。象const、virtual、inline、case 等关键字之后至少要留一个空格,否则无法辨析关键字。象if、for、while等关键字之后应留一个空格再跟左括号‘(’,以突出关键字。
函数名之后不要留空格,紧跟左括号‘(’,以与关键字区别。void Func1(int x, int y, int z);
向前紧跟的“,”、“(”、“)”、“;”都不留空格;‘,’之后要留空格,如Function(x, y, z);’;’不是一行的结束符号,其后要留空格,如for (initialization; condition; update)。
赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符,如“=”、“+=” “>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元操作符的前后应当加空格。
一元操作符如“!”、“~”、“++”、“–”、“&”(地址运算符)等前后不加空格。
象“[]”、“.”、“->”这类操作符前后不加空格。
对较长if或for表达式可适当去掉一些空格。
3.3 对齐
程序的分界符‘{’和‘}’应独占一行并且位于同一列,同时与引用它们的语句左对齐。
{ }之内的代码块在‘{’右边数格处左对齐。
利用“Tab”进行对齐,注意不要随便更改软件Tab的大小,一般设定为4个空格。
3.4 拆分
代码行最大长度宜控制在70至80个字符以内。代码行不要过长,否则眼睛看不过来,也不便于打印。
长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要进行适当的缩进,使排版整齐,语句可读。
if ((very_longer_variable1 >= very_longer_variable12)
&& (very_longer_variable3 <= very_longer_variable14)
&& (very_longer_variable5 <= very_longer_variable16))
{
dosomething();
}
3.5 修饰符
将修饰符 * 和 & 紧靠变量名。
char *name;
int *x, y; // 此处y不会被误解为指针,最好拆分为int *x; int y;
3.6 注释
C语言的注释符为“/…/”。C++语言中,程序块的注释常采用“/…/”,行注释一般采用“//…”。
注释通常用于:
(1)版本、版权声明;
(2)函数接口说明;
(3)重要的代码行或段落提示。
注释是对代码的“提示”,便于理解,要少而精,不可过多和过于花哨。
简单明了地方不需要注释。
边写代码边注释,不应该产生歧义和使用缩写,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。
代码较长时,或者有多重嵌套时,应当在段落的结束处加注释,便于阅读。
3.7 类
类可以将数据和函数封装在一起,其中函数表示了类的行为(或称服务)。
类提供关键字 public、protected 和 private,分别用于声明哪些数据和函数是公有的、受保护的或者是私有的。
将 public 类型的函数写在前面,而将 private 类型的数据写在后面,以行为为中心,重点关注
的是类应该提供什么样的接口(或服务)。
class A
{
public:
void Func1(void);
void Func2(void);
…
private:
int i, j;
float x, y;
…
}
4 命名
常用的命名规范有“匈牙利”法,驼峰法,帕斯卡命名法。
常用命名方法推荐为:
-
标识符应当直观且可以拼读,可望文知意,不必进行“解码”。
-
Windows 应用程序的标识符通常采用“大小写”混排的方式,如AddChild。 而 Unix 应用程序的标识符通常采用“小写加下划线”的方式,如add_child。别把这两类风格混在一起用。
-
程序中不要出现仅靠大小写区分的相似的标识符。例如fo和F0。
-
程序中不要出现标识符完全相同的局部变量和全局变量,尽管两者的作用域不同而不会发生语法错误,但会使人误解。
-
变量的名字应当使用“名词”或者“形容词+名词”。
float Value; float oldValue;
-
全局函数的名字应当使用“动词”或者“动词+名词”(动宾词组)。类的成员函数应当只使用“动词”,被省略掉的名词就是对象本身。
DrawBox(); // 全局函数 box->Draw(); // 类的成员函数
-
用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。
Unix linux系统采用的命名方法为:
-
变量和函数都采用小写加下划线分割单词。
-
大写字母留给宏和枚举常量,以及根据统一惯例的常量。
-
函数名瞎用下划线分割小写字母的方式命名,例如设备名_操作名() ——
dev0_open();而操作名一般采用谓语动词或者谓语+宾语/表语,例如tic_init();abc_is_busy();uart_tx_char(); 中断函数命名直接使用设备名_isr()。 -
变量命名长度应该合适,准确不产生歧义;单字符作为函数的内局部变量;tmp为零时变量名;局部静态变量s_;全局变量加g_。
-
不建议大小写混用,临时变量可以用较短命名。
-
某应用程序下的函数遵循——属于某一模块的函数,加上模块简写缩写做前缀;函数表明函数意义格式为“前缀_名词_动词”。
5 表达式
5.1 运算符优先级
优先级 | 运算符 | 运算符 |
---|---|---|
1 | 后缀运算符:[] () · -> ++ --(类型名称){列表} | 从左到右 |
2 | 一元运算符:++ – ! ~ + - * & sizeof_Alignof | 从右到左 |
3 | 类型转换运算符:(类型名称) | 从右到左 |
4 | 乘除法运算符:* / % | 从左到右 |
5 | 加减法运算符:+ - | 从左到右 |
6 | 移位运算符:<< >> | 从左到右 |
7 | 关系运算符:<<= >>= | 从左到右 |
8 | 相等运算符:== != | 从左到右 |
9 | 位运算符 AND:& | 从左到右 |
10 | 位运算符 XOR:^ | 从左到右 |
11 | 位运算符 OR:1 | 从左到右 |
12 | 逻辑运算符 AND:&& | 从左到右 |
13 | 逻辑运算符 OR:11 | 从左到右 |
14 | 条件运算符:?: | 从右到左 |
15 | 赋值运算符: = , += , -= , *=, /= , %= , &= , ^= , 1 =, <<= , >>= | 从右到左 |
16 | 逗号运算符:, | 从左到右 |
注意:因编辑器原因,其中或等运算符|,采用1代替。
5.2 判断语句
1)不可将布尔变量直接与 TRUE、 FALSE 或者 1、 0 进行比较
根据布尔类型的语义,零值为“假”(记为 FALSE),任何非零值都是“真”(记为TRUE)。 TRUE 的值究竟是什么并没有统一的标准。
所以标准判断为:
if (flag) // 表示 flag 为真
if (!flag) // 表示 flag 为假
下面的用法属于不规范
if (flag == TRUE)
if (flag == 1 )
if (flag == FALSE)
if (flag == 0)
2)整型变量与零值比较
应当将整型变量用“ ==”或“! =”直接与 0 比较。不可模仿布尔变量的风格而写成
if (value) // 会让人误解 value 是布尔变量
浮点变量与零值比较
不可将浮点变量用“ ==”或“! =”与任何数字比较,应该设法转化成“ >=”或“ <=”形式。
// 假设浮点变量的名字为 x,应当将
if (x == 0.0) // 隐含错误的比较
// 转化为
if ((x>=-EPSINON) && (x<=EPSINON))
// 其中 EPSINON 是允许的误差(即精度)
指针变量与零值比较