0%

电控组代码规范

电控组代码维护手册

为什么需要规范的代码?

代码规范的重要性

在一个小型团队中(人数不超过三人)进行代码维护时,经常出现版本混乱、代码重复以及团队成员之间沟通不畅的问题。特别是在不同版本的项目打包压缩时,可能会出现以下问题:

  1. 业务代码与测试代码混杂:模块功能代码和测试代码未分离,容易产生代码覆盖问题,导致修改和调试时难以确认哪些内容被改动过。
  2. 版本命名不规范:版本号不一致,导致无法清楚地知道各个版本包含哪些功能或修复了哪些问题。
  3. 代码风格不统一:不同开发人员的编程风格和规范不同,可能会导致理解上的歧义,进而产生功能错误。

官方代码风格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @brief 底盘无力的行为状态机下,底盘模式是raw,故而设定值会直接发送到can总线上故而将设定值都设置为0
* @author RM
* @param[in] vx_set 前进的速度,设定值将直接发送到can总线上
* @param[in] vy_set 左右的速度,设定值将直接发送到can总线上
* @param[in] wz_set 旋转的速度,设定值将直接发送到can总线上
* @param[in] chassis_move_rc_to_vector 底盘数据
* @retval 返回空
*/

static void chassis_zero_force_control(fp32 *vx_can_set, fp32 *vy_can_set, fp32 *wz_can_set, chassis_move_t *chassis_move_rc_to_vector)
{
if (vx_can_set == NULL || vy_can_set == NULL || wz_can_set == NULL || chassis_move_rc_to_vector == NULL)
{
return;
}
*vx_can_set = 0.0f;
*vy_can_set = 0.0f;
*wz_can_set = 0.0f;
}

抽象代码风格

  1. 状态机不使用状态枚举,只使用数字,降低了代码的可读性。
  2. 标志位无法判断是局部变量还是全局变量,生命周期混乱,难以维护。
  3. 函数命名混乱,有时使用大小写字母、下划线等不同风格,增加了理解难度。
  4. 函数缺乏详细注释,无法清晰知道函数的功能和参数,导致在协作时容易产生误解。
  5. 抽象层次不合理,如延时环节和业务功能代码混杂,导致代码执行流程混乱。
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
38
39
40
41
//自瞄模式控制
int fire_mode = 0;
static void gimbal_autoangel_control(fp32 *yaw, fp32 *pitch, gimbal_control_t *gimbal_control_set)
{
static int step;
static int delay_time;

if (yaw == NULL || pitch == NULL || gimbal_control_set == NULL){
return;
}
switch(step){
case 0:{
//自瞄检测状态
status_auto = 0;
if(Find_IS_NOT() == 1){
step = 1;
}
}break;
case 1:{
//开启自瞄
Auto_Track(yaw,pitch,gimbal_control_set);
if(Find_IS_NOT() == 0){
step = 2;
}
}break;
case 2:{
delay_time++;
Auto_Track(yaw,pitch,gimbal_control_set);
if(delay_time < 500){
if(Find_IS_NOT() == 1){
step = 1;
delay_time = 0;
}
}else{
step = 0;
delay_time = 0;
}
}break;
}
return;
}

技术债的定义和表现

技术债是指在软件开发过程中,由于快速开发或未按照最佳实践编写代码而引入的可维护性问题或质量问题。类似于“金融债”,技术债需要通过“利息”来偿还,这表现为开发效率和维护成本的增加。

存在的问题

  1. 变量命名抽象,宏定义中使用“魔法数字”,变量的依赖关系不清晰。
  2. 函数功能不明确,函数过于复杂、实现繁琐,导致单一文件内功能代码过多,难以理解和维护。
  3. 标志位滥用,指针时而检查时而不检查,条件判断有盲区。
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/************** shoot.c ***************/

// >> 函数声明

// 初始化串口与DMA,设置数据长度
void AUTO_init(void);
// 自动射击任务,处理自动射击过程
void auto_task(void const * argument);
// 自动追踪函数,控制云台的yaw和pitch角度
void Auto_Track(fp32 *yaw, fp32 *pitch, gimbal_control_t *gimbal_control_set);
// 串口中断处理函数
void USART1_IRQHandler(void);
// 串口接收函数
void UART1_receive_IDE(void);
// 检查是否未找到目标
int Find_IS_NOT(void);
// 自瞄拟合函数,用于计算目标距离与pitch角度的关系
float Fitting_function(float distance);
// 获取自动射击数据结构指针
auto_data *get_AUTOshoot_point(void);

// >> 变量

// 外部结构体指针
extern UART_HandleTypeDef huart1; // 串口1的句柄
extern DMA_HandleTypeDef hdma_usart1_rx; // 串口1的DMA的句柄
extern ext_game_robot_state_t robot_state; // 当前机器人的状态
extern ext_power_heat_data_t power_heat_data_t; // 电源和热量数据
extern RC_ctrl_t rc_ctrl; // 遥控器控制数据
extern gimbal_control_t gimbal_control; // 云台控制数据
// 接收缓冲区和数据结构
uint8_t RX_buf[RX_BUF_NUM]; // 接收数据缓冲区
autorec recd; // 自动射击数据结构
pid_type_def auto_pid; // PID控制结构
int status_auto = 0; // 自动射击状态
int flag_received = 0; // 数据接收标志位
char date_length = 0; // 接收数据的数据长度
char auto_flag = 0; // 自动射击标志位
_feedBackFrame data_sent; // 数据返回帧
// 云台角度的偏差量
float yaw_angle_bias; // 云台yaw轴角度的偏差量
float pitch_angle_bias; // 云台pitch轴角度的偏差量
float yaw_angle, pitch_angle; // 自瞄计算之后的绝对目标角度
// 云台控制标志
uint8_t flag_shoot = 0; // 云台射击标志
// 自动射击所需的常量参数
extern fp32 INS_angle[3]; // 姿态角度
#define DATE_LENGTH 12 // 数据长度
#define RX_BUF_NUM 32 // 接收缓冲区大小
// 云台角度的限制
#define YAW_ANGLE 180.0f // yaw轴角度限制
#define PITCH_ANGLE_UP 45.0f // pitch轴上限
#define PITCH_ANGLE_DOWN -45.0f // pitch轴下限
// 拟合函数的系数
#define THREE_FACTOR 0.5f
#define TWO_FACTOR 0.3f
#define ONE_FACTOR 0.1f
#define ZERO_FACTOR 0.0f
#define NOT_FIND_GOAL -999.0f // 未找到目标的返回值

如何识别和解决技术债

识别技术债

  1. 代码审查:主要问题包括变量命名不清晰、代码块复用率低、复杂逻辑缺乏注释等。
  2. 性能瓶颈:如通信接口的数据性能瓶颈,任务执行间隔的时间片瓶颈等。

解决技术债

  1. 定期清理:定期回顾和整理已完成的功能代码,及时发现和解决存在的问题,减少后续开发中的重构和调试工作量。
  2. 重构代码:在对模块功能有深入理解后,对代码进行重构,优化代码结构,提升可维护性。

代码规范与标准

命名规范

  • 变量命名
    • 使用有意义、简洁的名称,避免使用单字母或循环字母命名,如:aaa、test1等。
    • 使用有意义的前缀来表明变量的作用域,如:g_data_length。
    • 局部变量可不使用前缀,除非函数特别长。
    • 框架变量要遵循

框架的命名规范,如:QueueHandle_t xQueue。

  • 函数命名

    • 使用小写字母和下划线分隔单词,如:shoot_feedback_update。
    • 函数名应采用动词 + 名词的组合形式,如:initialize_IMU。
    • 动词命名要统一,避免多义性。
      • initialize_*:用于初始化模块。
      • configure_*:用于配置模块。
      • set_*:用于设置参数或状态。
      • get_*:用于获取状态或值。
      • enable_ / disable_:用于启用或禁用功能。
    • 命名应简洁明了。
      • clear_buffer(清空缓冲区)
      • update_display(更新显示)
  • 常量命名

    • 宏定义和常量全大写,单词之间使用下划线分隔,如:MOTOR_RPM_TO_SPEED。
  • 结构体、枚举命名

    • 使用小写单词和下划线拼接,并在末尾加上“e”(表示枚举)或“t”(表示typedef struct),如:shoot_mode_e、gimbal_motor_t。

注释规范

  • 函数注释

    • 每个函数都应有注释,说明函数目的、输入输出参数及返回值(如果有),并备注需要注意的地方。
      1
      2
      3
      4
      5
      6
      /**
      * @brief 返回yaw电机数据指针
      * @param[in] none
      * @retval yaw电机指针
      */
      extern const gimbal_motor_t *get_yaw_motor_point(void);
  • 代码段注释

    • 对于复杂或不易理解的代码段,添加必要注释,简要介绍其功能或目的。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      HAL_UART_DMAStop(&huart1);  // 停止DMA传输
      date_length = RX_BUF_NUM - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); // 计算已接收数据
      if(date_length == DATE_LENGTH) // 数据接收长度符合设定
      {
      memcpy(recd.buf, RX_buf, DATE_LENGTH); // 复制数据至结构体
      flag_received = 1;
      }
      memset(RX_buf, 0, RX_BUF_NUM); // 清空接收缓冲区
      HAL_UART_Receive_DMA(&huart1,RX_buf, RX_BUF_NUM); // 继续接收数据
  • 条件编译指令

    • 对于灵活挂载的模块,使用条件编译,并在预编译指令旁写上对应的功能说明。
      1
      2
      3
      4
      5
      #if	(CAP_BOARD==1)  // 超级电容板使用的CAN
      // 逻辑代码
      #else // C板使用的CAN
      // 逻辑代码
      #endif

文档规范

  • 功能文档:提供系统的整体功能说明和功能模块的执行流程。

    参考开源项目介绍页面,或其他团队的开源框架。

  • 模块接口文档:详细描述每个模块的输入输出参数以及与其他模块的交互。

    参考接口文件(.c/.h)和接口文档的编写方式。

  • 硬件接口文档:描述硬件平台接口,包括GPIO配置、ADC、PWM设置等。

    参考相关开发手册和硬件教程。

版本控制与分支管理

基本操作

  • 创建分支

    • 开发新功能或者修复 Bug 时,应先创建一个新的分支,而不是直接在 main 分支上修改。
      1
      git checkout -b feature/your-feature-name
    • 例如,开发一个新的电控模块,可以创建 feature/control-module 分支。
  • 提交修改

    • 在分支上完成开发后,进行本地提交。每次提交时,确保提交信息简洁明了。
      1
      2
      git add .
      git commit -m "添加电控模块,完成基本功能"
  • 推送到远程仓库

    • 提交完代码后,将本地分支推送到远程仓库,便于其他人查看和协作。
      1
      git push origin feature/your-feature-name
  • 合并分支

    • 开发完成后,将分支合并回 main 分支,合并之前确保已经将 main 分支上的最新更新合并到当前分支。
      1
      2
      3
      4
      git checkout main
      git pull origin main # 拉取最新的 main 分支代码
      git checkout feature/your-feature-name
      git merge main # 合并 main 分支上的最新内容
  • 解决冲突

    • 合并分支时可能会出现代码冲突,Git 会标记出冲突的部分,开发者需要手动解决冲突。
      1
      2
      3
      # 在冲突文件中,手动解决冲突后
      git add <conflicted-file> # 标记冲突已解决
      git commit -m "解决分支合并冲突"
  • 删除分支

    • 合并完成后,删除已完成开发的分支,保持分支结构简洁。
      1
      2
      git branch -d feature/your-feature-name  # 删除本地分支
      git push origin --delete feature/your-feature-name # 删除远程分支

分支管理 GitHub Flow

  • 主要分支

    • main:主分支,始终保持可部署状态。
  • 功能分支

    • 开发者从 main 分支创建功能分支进行开发,开发完成后通过 pull request 进行代码审查和合并。
      1
      2
      git checkout main
      git checkout -b feature/feature-name
  • 合并到主分支

    • 提交代码后,通过 GitHub 的 Pull Request(PR)进行代码审查,确认无误后合并回 main 分支。
      1
      git push origin feature/feature-name

如何避免常见的 Git 问题

  • 避免在 main 分支上直接开发

    • 永远不要在 main 分支上直接进行功能开发或修复。开发工作应该始终在独立的功能分支上进行,确保 main 分支始终是稳定的。
  • 频繁拉取远程代码

    • 在进行任何开发之前,先从远程仓库拉取最新的代码,避免本地和远程代码差异过大。
      1
      git pull origin main
  • 编写清晰的提交信息

    • 每次提交时,都应写明提交的目的和内容。避免使用类似 “修复bug” 这样的模糊描述。
      1
      git commit -m "修复电控模块初始化时的内存问题"
  • 合并时解决冲突

    • 如果合并时遇到冲突,手动解决冲突并确保冲突解决后测试代码是否正常运行。然后再进行提交。

示例流程

假设你正在开发一个新的功能,以下是完整的 Git 操作流程:

  1. main 分支创建功能分支

    1
    2
    3
    git checkout main
    git pull origin main # 拉取最新的 main 分支
    git checkout -b feature/add-control-module
  2. 在功能分支上进行开发和提交

    • 开发功能时,频繁提交并推送到远程:
      1
      2
      3
      git add .
      git commit -m "添加电控模块基础功能"
      git push origin feature/add-control-module
  3. 合并功能分支到 main

    • 功能完成后,首先拉取 main 分支的最新代码:

      1
      2
      3
      4
      git checkout main
      git pull origin main
      git checkout feature/add-control-module
      git merge main
    • 解决合并冲突并提交:

      1
      2
      git add <resolved-file>
      git commit -m "解决合并冲突"
  4. 推送功能分支并创建 Pull Request

    • 将功能分支推送到远程并创建 Pull Request:
      1
      git push origin feature/add-control-module
  5. 代码审查和合并

    • 通过 GitHub 创建 Pull Request,请团队成员进行代码审查,审查通过后合并到 main
  6. 删除功能分支

    • 合并后,删除本地和远程的功能分支:
      1
      2
      git branch -d feature/add-control-module  # 删除本地分支
      git push origin --delete feature/add-control-module # 删除远程分支