PPSSPP代码规范:编码标准和最佳实践
1. 项目背景与规范重要性
PPSSPP作为跨平台的PSP模拟器(支持Android、Windows、macOS和Linux),其代码库采用C++开发,包含超过400个源代码文件,涉及模拟器核心、图形渲染、输入处理等复杂模块。统一的编码规范对确保代码可维护性、降低协作成本至关重要。本文档基于项目现有实践(如.editorconfig配置、头文件宏定义)和行业最佳实践,系统化整理编码标准。
2. 基础编码规范
2.1 文件格式与编码
| 规范项 | 要求 | 适用范围 |
|---|---|---|
| 字符编码 | UTF-8(无BOM,除非特殊需求如PSPOskDialog.cpp使用UTF-8-BOM) | 所有.cpp/.h文件 |
| 行尾符 | LF(Unix风格) | 所有文本文件 |
| 缩进 | Tab(宽度8空格) | 除特殊文件外(见2.2) |
| 文件结尾 | 单个空行 | 所有源代码文件 |
2.2 特殊文件类型例外
# .editorconfig核心配置示例
[*]
indent_style = tab
charset = utf-8
insert_final_newline = true
[*.{py,yml}]
indent_style = space
indent_size = 2
[libretro/**.{cpp,h}]
indent_style = space
indent_size = 3
3. 命名约定
3.1 标识符命名规则
| 类型 | 风格 | 示例 |
|---|---|---|
| 类/结构体 | PascalCase | GraphicsContext, CoreState |
| 函数/方法 | PascalCase | Core_Resume(), BreakReasonToString() |
| 变量/参数 | camelCase | frameSkipCount, relatedAddress |
| 常量 | UPPER_SNAKE_CASE | MAX_PATH, DEFAULT_FRAME_RATE |
| 枚举值 | PascalCase | CORE_RUNNING_CPU, BreakReason::DebugBreak |
| 宏定义 | UPPER_SNAKE_CASE | DISALLOW_COPY_AND_ASSIGN, ARRAY_SIZE |
3.2 命名示例对比
// 正确:类名PascalCase,成员变量camelCase
class FrameTiming {
private:
int frameCount;
double lastFrameTime;
};
// 正确:枚举值PascalCase
enum class DisplayFramerateMode {
Auto,
Fixed60,
Fixed30
};
// 错误:函数名使用下划线
void frame_timing_update(); // 应改为FrameTimingUpdate()
4. 代码格式
4.1 括号与换行
采用K&R风格(左括号不换行):
// 正确
if (coreState == CORE_RUNNING_CPU) {
ProcessFrame();
} else {
HandlePause();
}
// 错误:左括号换行
if (coreState == CORE_RUNNING_CPU)
{
ProcessFrame();
}
4.2 空格使用规范
- 关键字后加空格:
if (condition),for (int i=0;...) - 运算符两侧加空格:
a = b + c,x == y - 逗号后加空格:
function(a, b, c) - 指针/引用符号靠近类型:
int* ptr(而非int *ptr)
4.3 注释规范
// 单行注释:句首大写,后接空格(用于临时说明)
/*
* 多行块注释:用于函数/类文档
* 描述:初始化图形上下文
* 参数:ctx - 指向GraphicsContext的指针
*/
void Core_SetGraphicsContext(GraphicsContext *ctx);
// TODO: 待办事项需注明责任人
// TODO(hrydgard): 优化纹理加载性能
5. 类型与宏定义
5.1 基础类型使用
优先使用C++标准类型及项目自定义类型:
#include "CommonTypes.h" // 项目基础类型定义
// 正确:使用明确宽度类型
u8 byteValue; // 无符号8位
s32 intValue; // 有符号32位
f32 floatValue; // 单精度浮点
// 错误:使用模糊原生类型
int count; // 应改为s32或u32
5.2 禁用复制与赋值宏
使用DISALLOW_COPY_AND_ASSIGN防止对象复制:
class EmulatorInstance {
private:
DISALLOW_COPY_AND_ASSIGN(EmulatorInstance); // 禁止复制构造与赋值
public:
EmulatorInstance() = default;
// ...
};
5.3 枚举类位运算支持
使用ENUM_CLASS_BITOPS启用枚举类位运算:
enum class LogOutput {
Console = 1 << 0,
File = 1 << 1,
Network = 1 << 2
};
ENUM_CLASS_BITOPS(LogOutput); // 启用|=, &=等运算符
// 使用示例
LogOutput output = LogOutput::Console | LogOutput::File;
6. 类与函数设计
6.1 类成员顺序
按访问权限排序:public → protected → private,同类成员按声明顺序排序:
class Core {
public:
void Run();
void Pause();
protected:
virtual void UpdateState();
private:
CoreState currentState;
u32 frameCounter;
};
6.2 函数设计原则
- 单一职责:函数应完成一个明确任务(不超过200行)
- 参数数量:优先使用结构体封装多参数(超过4个时)
- 返回类型:避免返回复杂对象的原始指针(优先使用智能指针)
// 推荐:使用结构体封装多参数
struct FrameParams {
u32 width;
u32 height;
bool vsync;
};
void RenderFrame(const FrameParams& params);
7. 错误处理与调试
7.1 断言使用场景
- 开发阶段:使用
DEBUG_ASSERT验证内部状态 - 运行时检查:对用户输入使用显式错误返回
#include "Common/Common.h"
void LoadTexture(const std::string& path) {
DEBUG_ASSERT(!path.empty()); // 开发时检查前置条件
if (path.empty()) {
ERROR_LOG(Load, "Invalid texture path");
return;
}
// ...加载逻辑
}
7.2 日志记录规范
使用项目日志宏按模块分类:
ERROR_LOG(GPU, "Failed to compile shader: %s", shaderName.c_str());
WARN_LOG(Audio, "Low latency mode not supported");
INFO_LOG(UI, "Theme loaded: %s", themePath.c_str());
8. 性能优化准则
8.1 内存管理
- 优先使用栈内存(
std::array替代new[]) - 大内存块使用
MemArena(项目内存池)
// 示例:使用内存池分配临时缓冲区
MemArena arena;
u8* buffer = arena.Alloc(4096); // 自动释放,无需手动delete
8.2 循环优化
- 减少循环内计算(提取不变式)
- 使用
const和constexpr优化编译期计算
// 优化前
for (int i=0; i<textures.size(); i++) {
if (textures[i].width > screenWidth / 2) { // 重复计算screenWidth/2
ResizeTexture(&textures[i]);
}
}
// 优化后
const int maxWidth = screenWidth / 2;
for (const auto& tex : textures) { // 使用范围for循环
if (tex.width > maxWidth) {
ResizeTexture(&tex);
}
}
9. 跨平台兼容性
9.1 条件编译规范
使用项目预定义宏隔离平台代码:
#if PPSSPP_PLATFORM(WINDOWS)
#include <windows.h>
#elif PPSSPP_PLATFORM(LINUX)
#include <unistd.h>
#elif PPSSPP_PLATFORM(ANDROID)
#include <android/log.h>
#endif
9.2 路径处理
使用Path类(Common/File/Path.h)处理跨平台路径:
Path romPath = Path::FromUTF8(gamePath);
if (romPath.Exists()) {
LoadROM(romPath);
}
10. 代码审查与质量保障
10.1 审查重点清单
| 审查维度 | 检查项 |
|---|---|
| 功能正确性 | 边界条件处理、错误码检查 |
| 性能影响 | 算法复杂度、内存分配频率 |
| 可读性 | 命名清晰度、注释充分性 |
| 兼容性 | 跨平台API使用、C++17特性合规性 |
10.2 持续集成要求
- 提交前通过本地编译(
cmake && make) - 单元测试覆盖率≥70%(
unittest/目录) - 静态分析无警告(
cppcheck/clang-tidy)
11. 总结与最佳实践清单
11.1 核心规范速查表
- 命名:PascalCase(类/枚举)、camelCase(变量/函数)、UPPER_SNAKE_CASE(宏)
- 格式:Tab缩进、UTF-8编码、K&R括号风格
- 安全:禁用
new/delete(使用内存池)、检查所有指针有效性 - 性能:避免循环内字符串拼接、优先使用栈分配
11.2 推荐学习资源
- 项目源码:
Common/目录(基础组件)、Core/HLE/(HLE实现) - 开发文档:PPSSPP开发者 Wiki(需替换为国内镜像链接)
- 工具链:CMake 3.16+、Clang 10+、Android NDK 21+
通过遵循以上规范,PPSSPP代码将保持一致性、可维护性和高性能,同时降低协作冲突。所有贡献者应在提交代码前运行clang-format(项目根目录执行./format.sh)确保格式统一。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



