电控组代码维护手册
为什么需要规范的代码?
代码规范的重要性
在一个小型团队中(人数不超过三人)进行代码维护时,经常出现版本混乱、代码重复以及团队成员之间沟通不畅的问题。特别是在不同版本的项目打包压缩时,可能会出现以下问题:
- 业务代码与测试代码混杂:模块功能代码和测试代码未分离,容易产生代码覆盖问题,导致修改和调试时难以确认哪些内容被改动过。
- 版本命名不规范:版本号不一致,导致无法清楚地知道各个版本包含哪些功能或修复了哪些问题。
- 代码风格不统一:不同开发人员的编程风格和规范不同,可能会导致理解上的歧义,进而产生功能错误。
官方代码风格
1 | /** |
抽象代码风格
- 状态机不使用状态枚举,只使用数字,降低了代码的可读性。
- 标志位无法判断是局部变量还是全局变量,生命周期混乱,难以维护。
- 函数命名混乱,有时使用大小写字母、下划线等不同风格,增加了理解难度。
- 函数缺乏详细注释,无法清晰知道函数的功能和参数,导致在协作时容易产生误解。
- 抽象层次不合理,如延时环节和业务功能代码混杂,导致代码执行流程混乱。
1 | //自瞄模式控制 |
技术债的定义和表现
技术债是指在软件开发过程中,由于快速开发或未按照最佳实践编写代码而引入的可维护性问题或质量问题。类似于“金融债”,技术债需要通过“利息”来偿还,这表现为开发效率和维护成本的增加。
存在的问题
- 变量命名抽象,宏定义中使用“魔法数字”,变量的依赖关系不清晰。
- 函数功能不明确,函数过于复杂、实现繁琐,导致单一文件内功能代码过多,难以理解和维护。
- 标志位滥用,指针时而检查时而不检查,条件判断有盲区。
1 | /************** shoot.c ***************/ |
如何识别和解决技术债
识别技术债
- 代码审查:主要问题包括变量命名不清晰、代码块复用率低、复杂逻辑缺乏注释等。
- 性能瓶颈:如通信接口的数据性能瓶颈,任务执行间隔的时间片瓶颈等。
解决技术债
- 定期清理:定期回顾和整理已完成的功能代码,及时发现和解决存在的问题,减少后续开发中的重构和调试工作量。
- 重构代码:在对模块功能有深入理解后,对代码进行重构,优化代码结构,提升可维护性。
代码规范与标准
命名规范
- 变量命名
- 使用有意义、简洁的名称,避免使用单字母或循环字母命名,如: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
9HAL_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
// 逻辑代码
// 逻辑代码
- 对于灵活挂载的模块,使用条件编译,并在预编译指令旁写上对应的功能说明。
文档规范
功能文档:提供系统的整体功能说明和功能模块的执行流程。
参考开源项目介绍页面,或其他团队的开源框架。
模块接口文档:详细描述每个模块的输入输出参数以及与其他模块的交互。
参考接口文件(.c/.h)和接口文档的编写方式。
硬件接口文档:描述硬件平台接口,包括GPIO配置、ADC、PWM设置等。
参考相关开发手册和硬件教程。
版本控制与分支管理
基本操作
创建分支:
- 开发新功能或者修复 Bug 时,应先创建一个新的分支,而不是直接在
main
分支上修改。1
git checkout -b feature/your-feature-name
- 例如,开发一个新的电控模块,可以创建
feature/control-module
分支。
- 开发新功能或者修复 Bug 时,应先创建一个新的分支,而不是直接在
提交修改:
- 在分支上完成开发后,进行本地提交。每次提交时,确保提交信息简洁明了。
1
2git add .
git commit -m "添加电控模块,完成基本功能"
- 在分支上完成开发后,进行本地提交。每次提交时,确保提交信息简洁明了。
推送到远程仓库:
- 提交完代码后,将本地分支推送到远程仓库,便于其他人查看和协作。
1
git push origin feature/your-feature-name
- 提交完代码后,将本地分支推送到远程仓库,便于其他人查看和协作。
合并分支:
- 开发完成后,将分支合并回
main
分支,合并之前确保已经将main
分支上的最新更新合并到当前分支。1
2
3
4git 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 "解决分支合并冲突"
- 合并分支时可能会出现代码冲突,Git 会标记出冲突的部分,开发者需要手动解决冲突。
删除分支:
- 合并完成后,删除已完成开发的分支,保持分支结构简洁。
1
2git branch -d feature/your-feature-name # 删除本地分支
git push origin --delete feature/your-feature-name # 删除远程分支
- 合并完成后,删除已完成开发的分支,保持分支结构简洁。
分支管理 GitHub Flow
主要分支:
main
:主分支,始终保持可部署状态。
功能分支:
- 开发者从
main
分支创建功能分支进行开发,开发完成后通过 pull request 进行代码审查和合并。1
2git checkout main
git checkout -b feature/feature-name
- 开发者从
合并到主分支:
- 提交代码后,通过 GitHub 的 Pull Request(PR)进行代码审查,确认无误后合并回
main
分支。1
git push origin feature/feature-name
- 提交代码后,通过 GitHub 的 Pull Request(PR)进行代码审查,确认无误后合并回
如何避免常见的 Git 问题
避免在
main
分支上直接开发:- 永远不要在
main
分支上直接进行功能开发或修复。开发工作应该始终在独立的功能分支上进行,确保main
分支始终是稳定的。
- 永远不要在
频繁拉取远程代码:
- 在进行任何开发之前,先从远程仓库拉取最新的代码,避免本地和远程代码差异过大。
1
git pull origin main
- 在进行任何开发之前,先从远程仓库拉取最新的代码,避免本地和远程代码差异过大。
编写清晰的提交信息:
- 每次提交时,都应写明提交的目的和内容。避免使用类似 “修复bug” 这样的模糊描述。
1
git commit -m "修复电控模块初始化时的内存问题"
- 每次提交时,都应写明提交的目的和内容。避免使用类似 “修复bug” 这样的模糊描述。
合并时解决冲突:
- 如果合并时遇到冲突,手动解决冲突并确保冲突解决后测试代码是否正常运行。然后再进行提交。
示例流程
假设你正在开发一个新的功能,以下是完整的 Git 操作流程:
从
main
分支创建功能分支:1
2
3git checkout main
git pull origin main # 拉取最新的 main 分支
git checkout -b feature/add-control-module在功能分支上进行开发和提交:
- 开发功能时,频繁提交并推送到远程:
1
2
3git add .
git commit -m "添加电控模块基础功能"
git push origin feature/add-control-module
- 开发功能时,频繁提交并推送到远程:
合并功能分支到
main
:功能完成后,首先拉取
main
分支的最新代码:1
2
3
4git checkout main
git pull origin main
git checkout feature/add-control-module
git merge main解决合并冲突并提交:
1
2git add <resolved-file>
git commit -m "解决合并冲突"
推送功能分支并创建 Pull Request:
- 将功能分支推送到远程并创建 Pull Request:
1
git push origin feature/add-control-module
- 将功能分支推送到远程并创建 Pull Request:
代码审查和合并:
- 通过 GitHub 创建 Pull Request,请团队成员进行代码审查,审查通过后合并到
main
。
- 通过 GitHub 创建 Pull Request,请团队成员进行代码审查,审查通过后合并到
删除功能分支:
- 合并后,删除本地和远程的功能分支:
1
2git branch -d feature/add-control-module # 删除本地分支
git push origin --delete feature/add-control-module # 删除远程分支
- 合并后,删除本地和远程的功能分支: