编程规范

本文最后更新于 2025年2月19日 上午

本文主要分享了编程过程中积累的规范。

编程命名规范

  1. 匈牙利命名法(将变量类型写进变量名的命名方法)。

    其基本原则是,变量名=属性+类型+对象描述。通过在变量名前面加上相应的小写字母的符号标识作为前缀,标识出变量的作用域,类型等。

    这些符号可以多个同时使用,顺序是先m_(成员变量),再指针,再简单数据类型,再其他。例如:m_lpsStr,表示指向一个字符串的长指针成员变量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # 前缀类型
    a 数组(Array)
    b 布尔值(Boolean)
    by 字节(Byte)
    c 有符号字符(Char)
    cb 无符号字符(Char Byte,并没有神马人用的)
    cr 颜色参考值(Color Ref)
    cx,cy 坐标差(长度 Short Int)
    dw 双字(Double Word)
    fn 函数(Function)
    h Handle(句柄)
    i 整形(Int)
    l 长整型(Long Int)
    lp 长指针(Long Pointer)
    m_ 类成员(Class Member)
    n 短整型(Short Int)
    np 近程指针(Near Pointer)
    p 指针(Pointer)
    s 字符串(String)
    sz 以 Null 做结尾的字符串型(String with Zero End)
    w 字(Word)
  2. 驼峰式命名法,又叫小驼峰式命名法。常用于变量名,函数名。

    要求第一个单词首字母小写,后面其他单词首字母大写。

    1
    2
    3
    int myAge;
    char myName[10];
    float manHeight;
  3. 帕斯卡命名法,又叫大驼峰式命名法。常用于类名,属性,命名空间等。

    与小驼峰式命名法的最大区别在于,每个单词的第一个字母都要大写。

    1
    2
    3
    int MyAge;
    char MyName[10];
    float ManHeight;
  4. 下划线命名法。

    下划线命名法并不如大小驼峰式命名法那么备受推崇,但是也是浓墨重彩的一笔。尤其在宏定义和常量中使用比较多,通过下划线来分割全部都是大写的单词。还有变量名太长的变量

    该命名规范,也是很简单,要求单词与单词之间通过下划线连接即可。

    1
    2
    3
    int my_age;
    char my_name[10];
    float man_height;
  5. 在C++编程中,变量名后加_的命名方式通常用于表示类的私有数据成员。这是一种命名约定,用于区分类的数据成员和局部变量,以提高代码的可读性。例如,fftwInput_fftwOutput_plan_都是类的私有数据成员,它们的名字都以_结尾。

    一般来说,类的数据成员应该被定义为私有(private),这是面向对象编程中的封装原则。通过将数据成员设为私有,可以防止外部代码直接访问或修改这些数据,从而保护类的内部状态的完整性。

    然而,有时候,你可能会选择将某些数据成员设为公有(public)。这通常在数据成员是类的公开接口的一部分,或者类本身就是一个简单的数据结构时发生。

    至于是否在公有数据成员的名称后加_,这完全取决于你的命名约定。在某些命名约定中,可能会在所有数据成员的名称后加_,无论它们是公有的还是私有的。在其他命名约定中,可能只在私有数据成员的名称后加_

    总的来说,关键是选择一种命名约定,并在整个代码库中一致地遵循它,以提高代码的可读性和一致性。

    变量名称前加下划线:这通常用于表示私有成员变量或者类的内部变量。然而,根据C++标准,名称以一个下划线开头的变量可能被保留给编译器的实现,所以一般不推荐这种做法。

  6. 在编程中,ijk通常用作循环变量,特别是在嵌套循环中。idxjdx是对这些传统变量名的扩展,其中idx可能表示"index",jdx可能表示第二个索引。

    然而,更具描述性的变量名可能会使代码更易于理解。例如,如果idx遍历的是当前帧的目标检测框,那么你可能会选择名为curBoxIdx的变量名。同样,如果jdx遍历的是前一帧的目标检测框,那么你可能会选择名为prevBoxIdx的变量名。

  7. 在我们的系统中,所有环境变量都使用大写字母命名。所以当我们声明局部变量时,应该使用小写字母来声明,以避免环境和局部变量名发生冲突。

  8. 在命名变量时,使用后缀dirpath有助于区分变量的用途:

    • dir:通常用于表示一个目录(文件夹)。例如,dataset_dir表示数据集所在的目录。
    • path:通常用于表示一个具体的文件路径或目录路径。它可以是一个文件的完整路径,也可以是一个目录的路径。例如,dataset_path表示数据集的完整路径。
  9. 使用动词来命名函数是一种常见的做法。例如,我们不命名我们的函数:dateFormatting,我们将其命名为:formatDate

  10. 变量命名:分类_属性,例如:value_max

  11. 等等。

代码注释

一般注释

C++

1
2
3
4
5
6
7
// 一般注释

/**
* @brief 多行注释
* @param
* @return
*/

python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 单行注释

'''
使用 3 个单引号分别作为注释的开头和结尾
可以一次性注释多行内容
这里面的内容全部是注释内容
'''

"""
使用 3 个双引号分别作为注释的开头和结尾
可以一次性注释多行内容
这里面的内容全部是注释内容
"""

函数注释

C/C++注释规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* @brief 打开文件 \n
* 文件打开成功后,必须使用::CloseFile函数关闭
* @param[in] fileName 文件名
* @param[in] fileMode 文件模式,可以由以下几个模块组合而成:
* -r读取
* -w 可写
* -a 添加
* -t 文本模式(不能与b联用)
* -b 二进制模式(不能与t联用)
* @return 返回文件编号
* --1表示打开文件失败(生成时:.-1)
* @note文件打开成功后,必须使用::CloseFile函数关闭
* @par 示例:
* @code
* //用文本只读方式打开文件
* int ret = OpenFile("test.txt", "a");
* @endcode
* @see 指定参考信息。函数::ReadFile::CloseFile (“::”是指定有连接功能,可以看文档里的CloseFile变成绿,点击它可以跳转到CloseFile.)
* @deprecated 由于特殊的原因,这个函数可能会在将来的版本中取消
* @bug 调试Bug说明
* @warning 警告说明 (warning) 定义一些关于这个函数必须知道的事情
* @remarks 备注说明 (remarks) 定义一些关于这个函数的备注信息
* @todo 将要完成的工作 (todo) 说明哪些事情将在不久以后完成
* @example 使用例子说明 (example) 例子说明
*/
int OpenFile(const char* fileName, const char* fileMode);

/**
* @brief 关闭文件
* @param [in] file 文件
*
* @retval 返回值 0 成功
* @retval -1 失败
* @pre file 必须使用OpenFile的返回值
*/
int CloseFile(int file);

特殊注释

这些是注释中的标签(tag),有时也被称作“代码标签(codetag)”或“标记(token)”。

标识:

  • TODO:标记代码中需要实现的功能或任务。
  • FIXME:标记代码中需要修复的问题或缺陷。
  • XXX:如果代码中有该标识,说明标识处代码虽然实现了功能,但是实现的方法有待商榷,代码有问题或具误导性,需引起警惕。希望将来能改进,要改进的地方会在说明中简略说明。
  • HACK/BODGE/KLUDGE:标记临时性修复或不优雅的解决方案。英语翻译为砍。如果代码中有该标识,说明标识处代码我们需要根据自己的需求去调整程序代码。
  • BUG/DEBUG:标记已知的Bug或错误。
  • UNDONE:对之前代码改动的撤销。
  • NOTE:提供额外的注释或提示信息,帮助理解代码意图或设计决策。

格式:

1
2
3
4
5
6
7
/*
* 1. 使用大写字母
* 2. 只用双正斜杠//,而不是三个正斜杠 ///
* 3. 在标签后使用半角冒号 :
*/
//TODO: Need implementation.
//FIXME: We need to avoid the problem of duplicating windows when clicking multiple times on this menu item.

编写更简洁的代码

10 Practices I Try to Follow for Cleaner Code

Become a Better Coder: 10 Tips

Write Clean Functions - I Will Show You How

  1. 使用有意义的名字。像transactionHistory这样的变量或者像这样的方法 validateSufficientBalanceForRefund 清楚地传达他们的目的。

  2. 遵循单一职责原则(SRP)。通过将不同的职责委托给不同的类,每个类都有一个更改的理由,从而简化了维护并提高了可读性和可测试性。

  3. 缩短方法。函数应该只具有比其名称低一级的代码。将长方法分解为更小、更集中的方法可以使代码更不容易出错并且更易于维护。它还简化了调试并提高了清晰度。样式指南通常建议将方法保持在 20-30 行左右。如果某个方法超出了此范围,则通常表明该算法过于复杂或该方法试图执行的操作过多。

    1. 函数应该是可重用的。而且函数越大,可重用的可能性就越小。这也与为什么一个函数应该只做一件事相关。如果它只做一件事,那么它很可能会很小。即,将一个长函数分解为多个分管各个小功能的短函数组成。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public static double calculateTotalRevenue(double[] prices, int[] quantities) {
    if (prices.length != quantities.length) {
    throw new IllegalArgumentException("Prices and quantities arrays must have the same length");
    }

    double totalRevenue = 0.0;

    for (int i = 0; i < prices.length; i++) {
    double price = prices[i];
    int quantity = quantities[i];
    double productRevenue = price * quantity;

    if (quantity >= 100) {
    productRevenue *= 0.85;
    } else if (quantity >= 50) {
    productRevenue *= 0.90;
    } else if (quantity >= 10) {
    productRevenue *= 0.95;
    }

    totalRevenue += productRevenue;
    }

    return totalRevenue;
    }

    让我们以一种每个方法只有一个抽象级别的方式增强我们的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public static double calculateTotalRevenue(double[] prices, int[] quantities) {
    if (prices.length != quantities.length) {
    throw new IllegalArgumentException("Prices and quantities arrays must have the same length");
    }

    double totalRevenue = 0.0;

    for (int i = 0; i < prices.length; i++) {
    double productRevenue = calculateProductRevenue(prices[i], quantities[i]);
    totalRevenue += productRevenue;
    }

    return totalRevenue;
    }

    private static double calculateProductRevenue(double price, int quantity) {
    double productRevenue = price * quantity;
    return applyQuantityDiscount(productRevenue, quantity);
    }

    private static double applyQuantityDiscount(double revenue, int quantity) {
    if (quantity >= 100) {
    return revenue * 0.85;
    } else if (quantity >= 50) {
    return revenue * 0.90;
    } else if (quantity >= 10) {
    return revenue * 0.95;
    }
    return revenue;
    }
  4. 以有意义的方式使用注释。过度使用注释或添加只是重申代码正在执行的操作的注释是多余的,并且不会增加任何价值。此外,过时或不正确的注释可能会误导开发人员并造成混乱。另外,如果逻辑已修改,请不要忘记更新注释。

  5. 一致的格式。一致的格式极大地提高了可读性和团队合作。有许多工具可以帮助制定缩进和间距的编码标准。另外,在任何 IDE 中,这都只是一个快捷方式。

  6. 提供有意义的报错消息。通过捕获特定的异常并提供有意义的错误消息,调试和理解错误变得更加容易。此外,使用记录器记录异常,而不是打印堆栈跟踪,而是将错误集成到集中式日志记录系统中,使它们更易于管理和监控。

  7. 让你的代码保持在界限内。将代码保持在边缘线内可以轻松快速扫描。 IDE 通常会提供指导原则,通常为每行 80 或 100 个字符(数量可自定义),以帮助遵循此实践。例如,IntelliJ IDEA 甚至提供了边距的可视化表示。此外,将长行分成更小的部分还可以促进更好的编码实践,例如将逻辑封装到命名良好的方法和类中。这简化了代码审查和协作,因为团队成员可以快速掌握代码的结构和意图,而无需排长队。

  8. 编写有意义的测试用例。有效的测试清晰、简洁,并专注于验证代码的特定行为,包括正常条件、边界情况和潜在错误。它们应该易于其他开发人员理解,明确正在测试的内容和原因。

  9. 审查你的代码。定期的代码审查对于确保质量、一致性和可维护性至关重要。代码审查对于知识共享和预先识别潜在问题的重要性怎么强调都不为过。永远不要懒惰这样做。更重要的是,始终对那些花时间审查和评论您的代码的人做出回应。确认他们的反馈,以表明他们的声音被听到并且他们的意见受到赞赏。这可以培养团队文化并加强关系。

  10. 不断改进你的方法。了解何时优先考虑清晰性而非简洁性、简单性而非复杂性以及特殊性而非通用性对于编写有效的代码和成为专业的团队成员至关重要。确保您的代码像您希望其他人的代码一样易于理解。

  11. 开闭原则 (OCP) 规定类、方法或函数必须对扩展开放,但不能对修改开放。这意味着定义的任何类、方法或函数都可以轻松地重用或扩展用于多个实例,而无需更改其代码。举个例子,我们有一个名为地址的类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Address:
    def __init__(self, country):
    self.country = country

    def get_capital(self):
    if self.country == 'canada':
    return "ottawa"
    if self.country == 'america':
    return "Washington D.C"
    if self.country == 'united Kingdom':
    return "London"

    address = Address('united Kingdom')
    print(address.get_capital())

    这不符合 OCP,因为每当有一个新的国家时,我们就需要编写一个新的if语句来补充它。现在这可能看起来很简单,但想象一下我们有 100 个或更多的国家/地区需要考虑。那看起来怎么样?这就是 OCP 发挥作用的地方。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    capitals = {
    'canada': "Ottawa",
    'america': "Washington D.C",
    'united Kingdom': "London"
    }

    class Address:
    def __init__(self, country):
    self.country = country

    def get_capital(self):
    return capitals.get(self.country, "Capital not found")

    address = Address('united Kingdom')
    print(address.get_capital())
  12. 避免使用幻数(Magic Numbers);避免对文件路径或 URL 进行硬编码;请改用配置文件或环境变量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # good
    NUM_OF_ORDERS = 50
    SELECT TOP NUM_OF_ORDERS * FROM orders

    import os
    file_path = os.getenv("FILE_PATH")

    # bad
    SELECT TOP 50 * FROM orders

    file_path = "/path/to/file.txt"
  13. 避免深层嵌套。限制循环、条件或函数内的嵌套级别以提高可读性。

  14. 函数的理想参数数量为零。然后我们有一个和两个参数函数。应避免构造具有三个参数的函数。超过三个参数需要特殊的理由——即使这样,也不应该使用这样的函数。如果您有超过 3 个参数,将它们分组到一个对象中可能是一个解决方案。


编程规范
http://zeyulong.com/posts/6d4fe931/
作者
龙泽雨
发布于
2025年2月19日
许可协议