编程格式规范大全

1 代码总体原则

1.1 清晰第一

清晰性是易于维护、易于重构的程序必需具备的特征。代码首先是给人读的,好的代码应当可以像文章一样发声朗诵出来。

1.2 简洁为美

简洁就是易于理解并且易于实现。代码越长越难以看懂,也就越容易在修改时引入错误。写的代码越多,意味着出错的地方越多,也就意味着代码的可靠性越低。因此,我们提倡大家通过编写简洁明了的代码来提升代码可靠性。

2 C语言编程规范

2.1 头文件

  • 头文件中适合放置接口的声明,不适合放置实现
  • 每一个.c文件应有一个同名.h文件,用于声明需要对外公开的接口
  • 禁止头文件循环依赖
    • 头文件循环依赖,指a.h包含b.hb.h包含c.hc.h包含a.h之类导致任何一个头文件修改,都导致所有包含了a.h/b.h/c.h的代码全部重新编译一遍。而如果是单向依赖,如a.h包含b.hb.h包含c.h,而c.h不包含任何头文件,则修改a.h不会导致包含了b.h/c.h的源代码重新编译。
  • 建议每一个模块提供一个.h
    • 一个模块通常包含多个.c文件,建议放在同一个目录下,目录名即为模块名。为方便外部使用者,建议每一个模块提供一个.h,文件名为目录名需要注意的是,这个.h并不是简单的包含所有内部的.h,它是为了模块使用者的方便,对外整体提供的模块接口。
    • 以Google test(简称GTest)为例,GTest作为一个整体对外提供C++单元测试框架,其1.5版本的gtest工程下有6个源文件和12个头文件。但是它对外只提供一个gtest.h,只要包含gtest.h即可使用GTest提供的所有对外提供的功能,使用者不必关系GTest内部各个文件的关系,即使以后GTest的内部实现改变了,比如把一个源文件c拆成两个源文件,使用者也不必关心,甚至如果对外功能不变,连重新编译都不需要。

2.2 函数

  • 一个函数仅完成一件功能
  • 重复代码应该尽可能提炼成函数,降低维护成本
  • 避免函数过长,新增函数不超过 50 行 (非空非注释行)
  • 函数的参数个数不超过5个
    • 函数的参数过多,会使得该函数易于受外部(其他部分的代码)变化的影响,从而影响维护工作。函数的参数过多同时也会增大测试的工作量。函数的参数个数不要超过5个,如果超过了建议拆分为不同函数。
  • 除打印类函数外,不要使用可变长参函数。
    • 可变长参函数的处理过程比较复杂容易引入错误,而且性能也比较低,使用过多的可变长参函数将导致函数的维护难度大大增加。
  • 在源文件范围内声明和定义的所有函数,除非外部可见,否则应该增加static
    • 如果一个函数只是在同一文件中的其他地方调用,那么就用static声明。使用static确保只是在声明它的文件中是可见的,并且避免了和其他文件或库中的相同标识符发生混淆的可能性。

2.3 标识符命名与定义

2.3.1 概述

标识符的命名规则历来是一个敏感话题,典型的命名风格如unix风格、windows风格等,从来无法达成共识。实际上,各种风格都有其优势也有其劣势,而且往往和个人的审美观有关。

标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理解的缩写,避免使人产生误解尽可能给出描述性名称,不要节约空间,让别人很快理解你的代码更重要。

// 正确示例:
int error_number;
int number_of_completed_connection;

// 错误示例:
int n;
int nerr;
int n_comp_conns;

函数命名,变量命名,文件命名要有描述性,少用缩写。

序号 类型 描述
1 小驼峰命名法 首字母小写,每个逻辑断点都用大写字母来标记,一般用于全局变量、函数、结构体变量、对象名。示例:myData
2 帕斯卡(Pascal)命名法 与小驼峰命名法类似,又被称为“大驼峰命名”,只是首字母大写,一般用于结构名、类名。示例:MyData
3 下划线命名法 函数名中的每个逻辑断点都用下划线_分割,C程序和UNIX这样的环境中,它的使用非常普遍,一般用于函数、变量。示例:my_data
4 匈牙利命名法 变量名前面加上相应的小写字母的符号标识作为前缀,标识出变量的作用域、类型等。示例:i_MyData

2.3.2 代码文件命名

  • 文件命名统一采用小写字符
    • 因为不同系统对文件名大小写处理会不同(如MS的DOS、Windows系统不区分大小写,但是Linux系统则区分),所以代码文件命名建议统一采用全小写字母命名。
    • 尽量使用下划线_连接
    • 举例:my_useful_class.c

2.3.3 函数

函数的命名可以使用三种方法:驼峰命名法myFunctio()、下划线命名法my_function()、帕斯卡(Pascal)命名法MyFunction()
个人建议采用小驼峰命名法:void calculateTotalPrice()
此外,建议:函数名应该以动词开头,因为函数是一组具有特定功能的语句块。比如一个函数,它用于取得外部输入的数值,则可以命名为int getInputNumber()

2.3.4 类型命名

所有类型命名:类,结构体,类型定义(typedef),枚举,类型模板参数,均使用相同约定,即以大写字母开始,每个单词首字母均大写,不包含下划线(帕斯卡命名法)。例如:

class BookCartoon {
	string 	title_;		
	string 	author_;		
};

struct BookCartoon {
	char 	title[40];		
	char 	author[40];		
};

typedef struct {
	char 	title[40];		
	char 	author[40];				
} BookCartoon;

enum ButtonState {
	SHORT_PRESS,	/* 短按 */
	LONG_PRESS,		/* 长按 */
};

typedef enum  {
	SHORT_PRESS,	/* 短按 */
	LONG_PRESS,		/* 长按 */
} ButtonState;

2.3.4 变量

变量(包括函数参数)和数据成员名一律小写,单词之间用下划线_连接。举例:stu_namefile_path

  • 尽量避免名字中出现数字编号,除非逻辑上的确需要编号
// 错误示例:如下命名,使人产生疑惑。
#define EXAMPLE_0_TEST_
#define EXAMPLE_1_TEST_

// 正确示例:应改为有意义的单词命名。

#define EXAMPLE_UNIT_TEST_
#define EXAMPLE_ASSERT_TEST_
  • 全局变量应增加g_前缀,静态变量应增加s_

  • 禁止使用单字节命名变量,但允许定义i 、j、k作为局部循环变量

  • 不建议使用匈牙利命名法

    • 匈牙利命名法是一种编程时的命名规范。基本原则是:变量名=属性+类型+对象描述。
    • 历来对匈牙利命名法的一大诟病,就是导致了变量名难以阅读,本规范特意强调,变量命名不应采用匈牙利命名法,而应该想法使变量名为一个有意义的词或词组,方便代码的阅读。

变量命名需要说明的是变量的含义,而不是变量的类型。在变量命名前增加类型说明,反而降低了变量的可读性;更麻烦的问题是,如果修改了变量的类型定义,那么所有使用该变量的地方都需要修改。

2.3.5 命名小结

项目 命名方法 示例
程序文件名 小写字母 + 下划线 my_useful_class.c
函数名 小驼峰命名 void calculateTotalPrice()int getInputNumber()
自定义类型名 大驼峰命名 class BookCartoonstruct BookCartoonenum ButtonState
变量命名 小写字母 + 下划线 stu_namefile_path

PS:根据后面Matlab和Python的命名规范,若完全统一的话,建议也采用:
程序文件名、函数名、变量名 --> 小写字母 + 下划线

2.4 注释

2.4.1 文件头部注释

/********************************************************************************
* @File name: biu.c
* @Author: fangqw
* @Version: 1.1
* @Date: 2021-3-19
* @Description: The function interface。
********************************************************************************/

2.4.2 函数注释

/**
 * @brief 
 * @param1
 * @param2
 * @return
 */
int getStuName();

参考链接1:C语言注释规范 - 知乎
参考链接2:C语言代码注释规范 - CSDN
参考链接3:C语言编程规范——注释
参考链接4:初学者必须掌握的编码规范 - 腾讯云
参考链接5:华为C语言编程规范(精华总结) - 程序员改变世界的文章 - 知乎

3 Matlab编程规范

3.1 命名规范

Matlab的文件名、函数、变量等命名规范与C/C++基本一致。但是有一个例外,即:Matlab中一般要求函数名和文件名一致,那么对于Matlab来说,命名规范就变成了:
程序文件名、函数名、变量名 --> 小写字母 + 下划线

3.2 注释

3.2.1 函数注释

function I = calc_circle_holefd(CircleHoleFD, theta)
%================================================================
% 功能: 求圆孔的夫琅禾费衍射光强分布
% 输入参数:    
%        CircleHoleFD为圆孔结构体,包含圆孔衍射相关信息;
%        theta为衍射场的次波方向,可以为向量,求取各方向的光强
% 返回值:   
%        I为衍射光强分布
% 主要思路:
%        使用夫琅禾费单缝衍射公式计算
% 备注:   
%        入射角只考虑一个维度的
% 调用方法:
%        calc_circle_holefd(输入参数1,输入参数2)
% 日期:  2011/7/12 20:37
%================================================================

...
end

4 Python语言编程规范

4.1 命名规范

Python的文件名、函数、变量等命名规范与Matlab完全一致。也即:
程序文件名、函数名、变量名 --> 小写字母 + 下划线

4.2 注释

4.2.1 函数注释

  • 为函数编写文档字符串非常重要,因为它能帮助其他开发人员快速理解函数的用途、参数和返回值。文档字符串通常包括以下部分:
    • 函数的简要描述
    • 参数说明
    • 返回值说明
    • 例子(如果需要)
def add(a, b):
    """
    功能:计算两个数的和。
    参数:
      a (int, float): 第一个数
      b (int, float): 第二个数
    返回值:
      int, float: 两个数的和
    调用示例:
      >>> add(2, 3)
      >>> add(2.5, 3.5)
    """
    return a + b

result = add(1, 2)
print(result)
print('--- 用 help() 内置函数查看 add() 的说明文档 ---')
help(add)
print('--- 用 __doc__ 属性查看 add() 的说明文档 ---')
print(add.__doc__)

4.2.2 类

  • 为类编写文档字符串同样重要,它能帮助其他开发人员理解类的用途、属性和方法。类的文档字符串通常包括以下部分:
    • 类的简要描述
    • 属性说明
    • 方法说明
    • 例子(如果需要)
class Dog:
    """
    一个表示狗的类。
    属性:
      name (str): 狗的名字
      age (int): 狗的年龄
    方法:
      bark(): 打印狗叫的声音
    """
    def __init__(self, name, age):
        """
        初始化狗的属性。
        参数:
          name (str): 狗的名字
          age (int): 狗的年龄
        """
        self.name = name
        self.age = age

    def bark(self):
        """
        打印狗叫的声音。
        """
        print("Woof!")

5 关于类和函数

在这一部分暂时插入一个话题:我们为什么要使用类?函数能完成的功能为什么要使用类?什么情况下要编写一个类?

首先,列一下自己的几个理解,然后粘贴一些相关的讨论文章、博客。

  • 理解1:从结构体和类的关系角度

  • 理解2:从多个函数功能和函数参数的角度

    • 若多个函数都是描述同一个抽象事物——这样描述可能有点难理解,换种说法:多个函数都有几个相同的参数输入,说明这几个函数有很大的关系是相关联的。
      • 比如,有一个函数需要使用运动目标状态(位置坐标属性、速度属性、加速度属性)计算目标运动了多长时间,另一个函数需要使用运动目标状态(位置坐标属性、速度属性、加速度属性)计算在另一个参考系中三个参数的值,可以观察到这两个函数都需要这三个参数,那我们何不把这三个参数组合到一起再加上两个函数构成一个类呢?
    • 此外从函数参数个数角度理解,比如以前我在编写Matlab函数的时候,会出现一个函数需要很多传入参数或者传出参数,有时候函数参数多的一行都写不下,然后我就会把这些参数全部弄成一个结构体,然后把结构体作为1个参数传入函数,其实想想这部也算是一种另类的面向对象思想嘛。
  • 理解3:从参数状态的角度

    • 其实函数处理完成之后除去传出的参数,其他都属于局部变量,函数结束,这些变量也就释放了,无法再使用了。但是很多时候,我们需要把函数中间处理的变量的内容/状态保存下来,说到这儿,我最想举的例子就是深度学习中的卷积类。
    • 知乎上的一个提问:为什么深度学习pytorch库里的torch.nn的一些函数就能实现的功能要用类实现,如卷积层?
    • 我说一下自己的理解:这是因为卷积核中的数值是要一直保存的,后面进行梯度反向传播的时候要时刻更新卷积核中的参数,如果你使用函数来实现的话,那么函数一般只会输出当前输入的图像经过卷积之后的输出结果,而不会输出当前卷积核中的数值。你可能会说:我让函数输出的参数中包含卷积核数值不就行了,那这样的话就需要在内存中开辟一块新的空间定义一个新的变量来保存,并且要能不断刷新其中的值,这样的话我们直接定义一个类不就得了,类中有一个卷积核的成员属性,还有一个用于计算图像经过卷积后的结果函数,不就比零散的单独定义函数和变量好多了。
    • 简单一句话:是有状态的;而函数是无状态的,有状态就是可以记住一些东西,在下一次调用的时候还可以继续用。无状态就是每次调用时都是一张白纸(函数运行完后局部变量都释放了)。

当你不确定要使用函数还是类的时候,目前有两种解决办法:

  1. 直接写类,一个类就算一个函数也写类;
  2. 直接写函数,如果函数字段比较多,考虑用函数加结构体。当你写了几个针对这个结构体的函数,发现这几个函数针对结构体的耦合比较强,那就要抽出一个类。定义好它的接口。

软件应当是重构过来的。。。而不一定是设计过来的。。。

相关的讨论链接:
C++利用面向对象思想编程时,如何确定何时要写一个类实现某一功能? - 知乎
Python读书笔记23(浅谈为什么要用类)- 腾讯云
【Python】一文说清楚类与函数的选择 - 刘早起的文章 - 知乎

posted @ 2025-04-02 10:21  博客侦探  阅读(123)  评论(0)    收藏  举报