跳转至

软件开发统一规范

规范目的1

作为一名合格的开发者,最基本的素质就是要做到编码规范,从小我们就接受教导 “字如其人”,而写代码亦是如此,良好的代码风格,彰显了个人的工作素养。而良好的代码规范,能够帮助我们进行更好的团队协作,它能方便代码的交流和维护;不会影响编码的效率,不与大众习惯冲突;使代码更美观、阅读更方便;使代码的逻辑更清晰、更易于理解。

一个软件的生命周期中,80%的花费在于维护;几乎没有任何一个软件,在其整个生命周期中,均由最初的开发人员来维护。

好的编码约定可使源代码严谨、可读性强且意义清楚,与其它语言约定相一致,并且尽可能的直观。

高质量的代码往往具有如下特质:

  • 易懂性 —— 代码必须易读且简单明确的。它们必须能展示出重点所在,且代码应该做到易于重用,不可包含多余代码,它们必须带有相应文档说明。

  • 正确性 —— 代码必须正确展示出其要告知使用者的重点。代码必须经过测试,且可以按照文档描述进行编译和正确运行。

  • 一致性 —— 代码应该按照一致的编程风格和设计来保证代码易读。 同样的,不同代码之间也应当保持一致的风格和设计,让使用者能够很轻松的结合使用它们。一致性能将我们代码库优良的品质形象传递给使用者,展示出我们对于细节的追求。

  • 流行性 —— 代码应当展示现行的编程实践,例如使用 Unicode,错误处理,防御式编程以及可移植性。代码应当使用当下推荐的运行时库和 API 函数,以及推荐的项目和生成设置。

  • 可靠性 —— 代码必须符合当地法律,隐私和政策标准和规范。不允许展示入侵性或低质的编程实践,不允许永久改变机器状态。所有的安装和执行过程必须是可以被撤销 的。

  • 安全性 —— 代码应该展示如何使用安全的编程实践 :例如最低权限原则,使用运行时库函数的安全版本,以及 SDL 推荐的项目设置。

命名规范

基本命名原则

  • 一定要为各种类型,函数,变量,特性和数据结构选取有意义的命名。其命名应该能直接反映其作用。

所谓 自注释 的代码就是好代码。避免容易被主观解释的难懂的名称,如 AnalyzeThis(),或者属性名 Temp。这样的名称会导致多义性。

在类属性的名称中包含类名是多余的,如 User.UserName。而是应该使用 User.Name, “.”即中文的“的”的意思。

  • 名称应该 说明“什么”而不是“如何”

通过避免使用公开基础实现(它们会发生改变)的名称,可以保留简化复杂性的抽象层。例如,可以使用 GetNextStudent(),而不是 GetNextArrayElement()

  • 不应该在标识符名中使用不常见的或有歧义的缩短或缩略形式的词。

比如,使用 GetTemperature 而不是 GetTempTemp 到底是 Temperature 的缩写还是 Temporary 的缩写呢。对于公共类型或大家都知道的缩写,则可以使用缩略词,如:线程过程,窗口过程,和对话框过程函数,为 ThreadProc, DialogProc, WndProc 等使用公共后缀。

  • 不要使用计算机领域中未被普遍接受的缩写。

在适当的时候,使用众所周知的缩写替换冗长的词组名称。例如,用 UI 作为 User Interface 缩 写,用 OLAP 作为 On-line Analytical Processing 的缩写。

  • 命名严禁使用拼音与英文混合的方式,更不允许直接使用中文。

正确的英语拼写和语法可以让阅读者易于理解,避免歧义。注意:即使纯拼音命名的方式也要避免采用。正例:name / order/ baidu / alibaba 等国际通用的名称可视为英文。 反例:zhekou(折扣)/Shuliang(数量)/int 变量=1

  • 单词力求语义表达要完整,不要嫌名字长。

正例:MaxStockCount 反例:Max_Count;反例 2:int a 的随意命名方式(除非极少数简单情况可以使用简单变量如int i作为索引。

  • 抽象类命名推荐使用 Base 结尾,异常类命名使用 Exception 结尾,测试类命名以它要测试的类的名称开始,以 Test 结尾。杜绝不规范的缩写,避免望文不知义。例:NotFoundException

命名样式

名称 使用范围 示例 备注
Pascal 命名法/ UpperCamelCase 类名,结构体名,namespace MyClass MyStruct 现代,主流流行。对应实例化的类对象使用 lowerCamelCase
驼峰命名法/ lowerCamelCase 函数名,局部变量名 getBall position 现代,主流流行
SCREAMING_SNAKE head file #include CICLE_H 使用不多。在 MSVC 中直接使用 #pragma once 即可。
匈牙利命名法 【禁用】 gPlay mPosX 使用 g 或者 m 等前缀标识变量的作用域,在现代智能感知编辑器中属于**极其累赘**的行为,不仅 m g 意义不明,同时输入累赘
lower_camel_case 【慎用】 get_ball 带有下划线的在输入过程中多打 _ 需要多按两个键,极度影响输入效率,完全就是累赘
lower_camel_case 的使用场景

lower_camel_case 只有少数情况下可以使用。

  1. 某些临时测试过程中的后缀。比如 ballPosition_1 test_3 getBall_test3
  2. 某些语义相差甚远的单词。比如 getBall_testtest 不属于 getBall 的一部分,临时用于自我标记。但是在对外接口命名设计上,要将过程测试的名称删除或者重新改回 getBall
  3. 以及某些 lowerCamelCase 不能标识的单词。比如 getBall_SRC

本项目所使用的命名样式对应表

标识符 规范 命名结构 示例
文件名 Pascal 或者 camel 建议对于大型的、封装的、接口的文件采用 Pascal 命名法;对于局部的、小型的、具体实现的文件采用 camel 命名法
类名、结构体 Pascal 命名法 名词 class Point{...};
namespace Pascal 命名法 名词
枚举 Pascal 命名法 名词
函数 camel 命名法 动词或动词短语
返回 bool 类型的函数应当含有 can has is 等单词
void getBall(){...}
bool canChipPassTo(){...}
变量/字段 camel 命名法 名词
其中 bool 类型的变量应当含有 is 或者 has
int ballPosition;
bool isBubbled;
常量 camel 命名法(同变量)
或者全大写
名词
属性 Pascal 命名法 名词 public: int Name;
方法 Pascal 命名法 动词或动词短语 public: void GetBall(){...}
注意

1.变量名命名补充规则

只要合适,在变量名的末尾或开头加计算限定符(AvgSumMinMaxIndex)。在变量名中使用互补对,如 min/maxbegin/endopen/close

布尔变量名通常应该包含 is或者has,这意味着 True/False 值,如 isFileFound,若单词的意义本身已经包含是非的情况,可省略 is,如 exist

在命名状态变量时,避免使用诸如 Flag 的抽象泛用名词。状态变量不同于布尔变量的地方是它可以具有两个以上的可能值。比如订单状态不应该是 OrderFlag,而是使用更具描述性的名称,OrderStatus

即使对于可能仅出现在几个代码行中的生存期很短的变量,仍然需要使用有意义的名称。仅对于短循环索引使用单字母变量名,如 ij

只有临时变量和仅供自己测试使用的变量名称末尾可以使用下划线_,加上诸如test v1之类的后缀

2.属性和方法分别是对字段/函数的进一步封装。在 C++ 语法中并无对应。但是在某些情况下认为是属性/方法。比如

class Student
{
private:
    int age;
public:
    const int& Age() const;
    int& Age();
}

分别使用 Age() 函数对 age 字段进行了封装。int& Age() 是可读可写的属性,const int& Age() const; 是只读属性,供 const 对象调用。

属性的实现方法还有很多,以下是另一种常见的属性实现:

class Student
{
private:
    int age;
public:
    const int& GetAge() const;
    void SetAge();
}

其中 GetAge()SetAge() 函数同样是对 age 这一私有字段的封装。同学们在编程实践中肯定遇到很多这类情况。命名推荐是使用 Pascal 命名法以同私有字段区别。

方法在 C++ 中的实现:

class Student()
{
private:
    void eat(int agrs[]);
public:
    void Eat();
}

其中 eat(int agrs[]) 是内部实现,涉及到很多传参,甚至有可能递归调用。Eat() 是供外部调用的接口。为以示区别,同样应当采用 Pascal 命名法

项目已有命名现象举例

名称 说明 应当修改成
goCmuRush Cmu 意义不明。放在 go 这样的 task 中毫无意义,任务本身不需要使用 Cmu 说明。 Rush 或者 rush
getball() 应当使用 camel 或者 Pascal 命名法。无论如何都是 getBall() 或者 GetBall() 单词之间由大写字母分隔。犯此类错误是对命名规则的不了解 getBall() 或者 GetBall()
bufcnt 意义不明,难以记忆。 complexSwitch isLongEnough hasLastedFrames(帧数) 等
posX 多处命名不统一并且混乱。在调用时容易和 ball.x() ball.X() ball.posx() ball.posX() ball.velX() ball.velPosX() 混淆 统一各处命名如 ball.x()。或者采用 ball.position().x() 等更近一层封装的方式解决。
position 没必要缩写成 pos。鉴于项目各处已经广泛使用 pos 则保持 pos 统一即可。
gPlay 混乱地采用的匈牙利命名法。 GlobalPlayStart 或者 GlobalPlayEntrance 或者 GlobalPlaySetting
MATCH.lua 混乱的命名 Match.lua
NormalKick_th 正式文件接口不建议使用意义不明的人名、拼音 NormalKick 或者 NormalKick_test1,把 TaoHan 写在文件开头的注释中

代码样式规范

注释规范

代码的可读性优于注释, 并不是注释越多越好

  1. 文件开头应当表明由谁开发(这样便于维护时找到负责人),开发时间[可选],本文件主要内容等
    /**
     * @file:  main.cpp
     * @author: TaoHan
     * @date:   2023-11-11
     * @brief:  This is the main file of the project.
     */
    
  2. 对外的接口函数应当写明函数内容,参数意义等。可以在 Visual Studio 编辑器中在函数前面敲入 /// 则会自动生成带 xml 样式的长注释
    // Incorrect
    // This function returns the power
    float power(float a, float b);
    
    // Correct
    /**
     * This function returns the power
     * @param a the base
     * @param b the exponent
     * @return a^b
     */
    float power(float a, float b);
    
  3. 通过代码无法表达的东西,如复杂的逻辑, 用法, TODO等,都可以通过注释的方式表达! asciiflow

杂项

  • 使用C++智能指针并避免使用原始指针。

  • 避免在同一行初始化多个变量。

    // Incorrect
    int x, y, z = 0;
    
    // Correct
    int x;
    int y;
    int z = 0;
    
    // However, the author may have intended the following
    // or a code reader may have assumed the following
    int x = 0;
    int y = 0;
    int z = 0;
    

  • 避免使用三元运算符。清晰性比行数更重要。

    // 不正确
    c = ((a == 0) || ((a + b) < 10)) ? a : a + b;
    
    // 正确
    if ((a == 0) || ((a + b) < 10))
    {
      c = a;
    }
    else
    {
      c = a + b;
    }
    

  • 总是在代码块周围使用花括号,即使花括号只包围一个语句。

    // Incorrect
    while (i < 10)
      i++;
      c[i] = i + 1;
    
    // Correct
    while (i < 10)
    {
      i++;
    }
    c[i] = i + 1;
    

  • 避免直接使用数字,除非它是数学或物理公式的一部分(例如 A=0.5(b*h))。

      // Incorrect
      float distance = catch_distance * 2.15;
    
      // Correct
      const float CATCH_DISTANCE_SCALE_FACTOR = 2.15;
      float distance = catch_distance * CATCH_DISTANCE_SCALE_FACTOR;
    

  • 复杂的if逻辑判断应该用函数封装起来,并使用有意义的函数名。

      // Incorrect
        if((projMe.dist(me.Pos()) < 5 || ball.Vel().mod() < 15) && (me.Pos().dist(ball.Pos()) < (25.5 * log(ball.Vel().mod()) - 120) || me.Pos().dist(ball.Pos()) < 23)&& modify(me2ball.dir() - me.Dir()) < 0.1)
        {
            // ...
        }
    
      // Correct
      if (isReadyGetBall(ball))
      {
        // ...
      }
    

Git提交规范

关键

  • 不同的事情尽量不要混在一次commit里面
  • 讲清楚改了什么
  • 不要commit一些与改动无关,自动改变的,如owl.ini

格式

包括三个字段:type(必需)、scope(可选)和subject(必需)。

type

type用于说明 commit 的类别,只允许使用下面7个标识。

  • feat:新功能(feature)
  • fix:修补bug
  • docs:文档(documentation)
  • style: 格式(不影响代码运行的变动)
  • refactor:重构(即不是新增功能,也不是修改bug的代码变动)
  • test:增加测试
  • chore:构建过程或辅助工具的变动

如果typefeatfix,则该 commit 将肯定出现在 Change log 之中。其他情况(docschorestylerefactortest)由你决定,要不要放入 Change log,建议是不要。

scope

scope用于说明 commit 影响的范围,比如数据层、控制层、视图层等等,视项目不同而不同。

subject

subject是 commit 目的的简短描述,不超过50个字符。

  • 以动词开头,使用第一人称现在时,比如change,而不是changedchanges
  • 第一个字母小写
  • 结尾不加句号(.

详情见git message编写指南


  1. 来自网络文件《C# 代码规范手册》