OBS Studio 代码风格规范深度解读:clang-format 强制规则、各语言守则与架构设计准则
OBS Studio 的 CODESTYLE.md 是该项目所有贡献必须遵循的代码风格与架构守则:它不仅规定了 C、C++、Objective-C/C++、Swift、CMake、JSON/YAML 各语言的格式化工具与版本要求,还给出了一整套用于“减少潜在错误”的架构级编程准则。读完本文,你可以掌握 OBS Studio 代码规范的完整骨架——哪些规则由 clang-format/swift-format/gersemi 在 CI 中自动强制执行、哪些需要在人工层面自觉遵守,以及提交代码前如何本地验证格式合规。
总则:可自动执行的格式 + 不可自动执行的架构守则
文档开宗明义地指出,项目要求所有贡献的源代码都使用合适的格式化工具进行格式化,目的是把风格变更对结构化“diff”视图的潜在影响降到最低。在自动可执行规则之外,项目还偏好贡献者遵循一组架构准则——这些准则被刻意设计为减少未定义或意外行为,使代码在评审与维护时更容易推理。
也就是说,OBS Studio 的风格体系分为两层:
- 格式层:由
clang-format(C/C++/ObjC)、swift-format(Swift)、gersemi(CMake)自动检查并可自动应用,工具生成的格式优先于指南中的任何文字规则; - 语义层:命名习惯、架构原则、头文件包含顺序等,依赖贡献者与评审者共同遵守。
减少潜在错误:一组经过实践检验的架构守则
文档的核心章节“Reducing Potential For Errors”列出了为规避历史上常见错误而确立的良好实践。以下逐条说明并保留原文示例。
始终初始化变量
不同语言、语言标准、编译器与平台对变量何时、如何自动初始化的规则各不相同,因此某些变量在程序启动时可能带有随机值。天真的代码可能把“非 0”的值误判为“已正确初始化”,从而触发意外行为。由此派生出的硬性规则是:不要在同一行声明或初始化多个变量,不要混合声明与初始化。
int i, v = 0; // BAD - v is initialized to 0, i is uninitialized
int i = 0; // GOOD - i is explicitly declared and initialized
int v = 0; // GOOD - v is separately declared and initialized
不要默认用“0”作为有效的枚举值
很多枚举要求必须做出一个显式选择(一组“有效”值中只能设置一个),而“没有做选择”本身不应被视为有效状态。文档给出三种处理方式:
enum state { ACTIVE, // BAD - zero-initialized enum potentially
INACTIVE, // leads to implicit state changes.
DELETED
};
enum state { INVALID, // GOOD - zero-initialized enum produces
ACTIVE, // an invalid value by default, avoiding
INACTIVE, // an implicit state change.
DELETED
};
enum state { ACTIVE = 1, // GOOD, zero-initialization fails because
INACTIVE = 2, // it's not a valid enum value to begin with.
DELETED = 3
};
即在枚举开头放一个 INVALID 值,或干脆把所有有效值从 1 开始编号,让“零初始化”必然产生一个非法状态,从而避免隐式状态漂移。
用自然语言命名变量和类型
富于表现力的代码更容易被维护者与评审者推理,也能降低短期内离开后重新回到代码时的心智负担——代码“说明它做了什么”,变量“说明它代表什么”:
int c = 1; // BAD: What does "c" represent?
int count = 2; // BAD: Count of "what"?
int num_bytes = 3; // GOOD: "Num(ber) of bytes"
bool valid; // BAD: Meaning is ambiguous
bool has_valid_key; // GOOD: Describes state of element in an object
bool is_valid; // GOOD: Describes state of element itself
bool did_send_packet; // GOOD: Describes state of transaction.
float dur = 1.0; // BAD: Unit of duration unknown
float duration_ms = 5.0; // GOOD: Unit of duration encoded within name
// GOOD: Function signature communicates the unit of the delay explicitly.
start_transition_with_delay(transition_type *transition, float delay_ms);
命名中直接编码单位(如 duration_ms)或在函数签名中显式传达单位,是这类问题的推荐解法。
优先使用复合类型而非零散变量
对象的大小或尺寸、时间刻度、空间位置等信息,应当在逻辑上编码进复合类型,只有在真正消费时才“解包”:
// EVEN BETTER: Time values encoded as pieces of a fraction (1/1000th of a
// second representing a "microsecond") and explicitly passed to
// the function.
typedef struct {
int_64_t time_value;
int_32_t time_scale;
} time_unit;
start_transition_with_delay(transition_type *transition, time_unit delay);
// BAD: Behavior encoded in unrelated booleans, maybe even conflicting
start_transition(transition_type *transition, bool ignore, bool abort);
// GOOD: Function signature requires more meaningful enum rather than bool
enum transition_abort_mode {
TRANSITION_ABORT_INVALID, TRANSITION_ABORT_ALL,
TRANSITION_ABORT_NEW, TRANSITION_ABORT_NONE
};
start_transition(transition_type *transition, enum transition_abort_mode);
文档特别强调:用多个互不相关的布尔值(甚至可能相互冲突的布尔值)来编码行为,应替换为更具语义的枚举。
其余守则速览
- 不要使用不必要的缩写——如果某个类实现了“Advanced Output”,就命名它
AdvancedOutput而不是AdvOutput。代码被阅读的次数远多于被编写的次数(编写本身只是漫长推理过程的最终产物),为书写优化会给阅读背上债务。 - 按“做什么”而非“怎么做”给函数命名——函数签名就是它的“接口”,应该向调用者提示它提供什么功能;暴露实现细节反而有净负效应,因为调用者可能试图“比 API 更聪明”。注意例外:工厂方法这类实现细节就是 API 用户技术需求的场景,例如
MyData::loadFromUrl(const std::string &url)或MyData::loadFromFile(const std::filesystem::path &path),其中“what”与“how”本质上纠缠在一起。 - 偏好纯函数与不可变变量——副作用更难追踪,且容易与“避免全局作用域”的规则相互作用:全局(共享)变量倾向于鼓励偷偷改写共享状态的代码,使代码更难测试与推理。
- 不要在二进制协议之外使用特定整数类型——绝大多数情况下
int足够,有符号类型还能避免“小负数变成巨大正数”的意外溢出问题。只有与二进制协议、位域交互时(例如显式去掉最高位特殊含义)才使用定长/无符号类型;大数优先 64 位有符号整数而非 32 位无符号整数。 - 不要微优化、不要过度“聪明”——引用 Kernighan:“每个人都知道调试的难度是编写程序本身的两倍。所以如果你在写代码时已经聪明到了极限,你以后要怎么调试它?”
- 用性能分析工具找出真正的优化机会——编译器生成的代码行为可能与源码暗示的不同,甚至执行顺序也不同;过早优化还可能阻止编译器应用它自己的优化。同时警惕分析工具本身对运行时行为的影响。
- 测试时主动尝试搞坏代码——不要依赖“快乐路径”,不要假设数据永远不会“错”、或某份数据永远“在”。用 ISO C++ FAQ 的话说:“要写保证能工作的代码,而不是看起来不会坏的代码。”
- 写出 6、12、24 个月后仍然容易理解的代码——在编写时程序状态很容易推理,但其中很多“内部知识”几周后就会流失。要让任何项目新人都有相对轻松的上手路径,理解代码做什么、为什么这么做、依赖哪些数据。
- 不要依赖全局状态或单例——它们很容易被当作“捷径”滥用,以逃避实现更有表达力、更安全的设计。全局“application”实例这类单例是有正当用途的例外,而非规则。
- 把编程当作思维训练而非文本操作——既有代码是基于一组假设与理论设计出来的,并反过来塑造了架构选择。某些改动看似“又快又简单”,却可能违反那些架构假设,从而让代码处于危险状态(当其他代码依赖这些假设不被破坏时,甚至可能引发未定义行为)。
文档对该条做了加重论述:源代码与文档都无法完整表达代码背后的“理论”,它们充其量只是某种理论实现的不完整“快照”;不理解当前设计中的架构关切,就无法正确判断哪些改动“顺着”现有代码的工作方式。堆叠的“天真”代码(主要是 hack 和 workaround)越多,代码库越难使用,直到连发布“简单”的新功能都成为难题。文档还给出一个形象类比:图像编辑程序有其设计约束,仅仅加上“能解码视频文件”不等于它就能编辑视频——那是一个需求与前提可能相互对立的独立学科,此后任何新功能都要额外应付“可能面对的是视频文件”这一程序地基在概念上并未为之准备的情况。
语言特定准则:五种语言各守其规
文档明确列出 OBS Studio 当前包含 C、C++、Objective-C/C++、Swift、CMake 五种语言代码,持续集成代码主要基于 PowerShell、Zsh/Bash 脚本和 Python 3。
一个关键立场:虽然 C++ 和 Objective-C/C++ 是 C 的超集,项目仍把它们当作独立语言对待,各有自己的规则、约定与语言标准——适用于“C”的规则未必适用于它们,部分规则在这些语言中会被替换。
C:Linux Kernel 风格 + C17 标准
对“纯” C 代码,项目遵循 Linux Kernel Coding Style(其中与 Emacs、内核级分配器或仅限 Linux 内核源码可用的宏相关的部分不适用)。
项目当前的 C 语言标准为 C17,禁用 GNU 扩展。
补充说明:
- 格式由
clang-format检查并可由它应用,其生成格式优先于指南中的规则; - 行宽 120 字符,制表符缩进,tab 宽度 8 字符。
这一点与仓库根目录的 .editorconfig 完全吻合:root = true,全局默认 indent_style = tab、indent_size = 8、insert_final_newline = true、trim_trailing_whitespace = true、charset = utf-8,并针对 plugins/obs-outputs/librtmp/* 等第三方代码(4 空格)、CMakeLists.txt 与 cmake/**/*.cmake(2 空格)等做了例外配置。
C++:Google 风格为基,项目做了大量裁剪
项目把 C++ 当作独立语言而非“带类的 C”。其风格指南基于 Google C++ Code Style Guide,并按 Google 文档中出现各主题的先后顺序列出增改:
项目当前的 C++ 语言标准为 C++17,禁用 GNU 扩展,向 C++20 的迁移正在评估中。
头文件(Header Files)
- C++ 源文件用
cpp后缀(而非cc),C++ 专用头文件用hpp; - 头文件保护宏允许但非必须,
#pragma once即可; - 允许对类或结构体类型使用前向声明,但须留意 Style Guide 中提到的注意事项,避免“为启用前向声明而扭曲代码结构”;
- 头文件包含顺序遵循文档单独一节的规定(见下文)。
类(Classes)
- 若定义了任一构造函数或赋值运算符,五个特殊成员函数(拷贝构造、拷贝赋值、移动构造、移动赋值、析构)必须全部处于“定义、defaulted 或 deleted”状态(“Rule of Five”);
- 移动构造函数与移动赋值运算符必须标记
noexcept(并如此实现),避免使用自定义类型于std::vector等标准容器时可能被劣化(pessimization); - 多重继承只用于接口/协议模式(“implements an interface as defined by”关系),回避菱形模式与虚基类。
其他 C++ 特性(Other C++ Features)
- 项目不使用
cpplint; - 异常“可以(但不必须)”使用,尤其在没有好的“哨兵值”可用、且能避免在每次函数调用后散落正确性检查时;偏好把异常的“作用域”保持在同一模块(同一可执行文件或库)内;
- 必须意识到:C++ 中任何非析构函数、未显式标记
noexcept的函数,在正常错误处理中都可能抛出异常,而不一定意味着真正严重的故障; - 修改任何数据(哪怕是修改自身实例之外的数据)的成员方法不应标记为
const,以表明它在逻辑上改变了某些状态; - 不要在结构化绑定的类型声明中使用注释;
- C++20 特性的指南待项目切换到该标准后再评估;
- 项目不允许使用 Boost 库;但允许使用 Google 指南中所列的“被禁止的标准库特性”和某些“非标准扩展”。
命名(Naming)
- 接口头文件与实现文件的文件名使用类名(按类型命名规则);
- 实例方法、函数、变量使用
camelCase(这也与 Qt 的代码风格一致); - 既有常量允许保留
UPPERCASE命名,但新常量应遵循kCamelCase新约定; - 命名空间须遵循类型命名规则,目前项目仅在重构应用代码时使用
OBS命名空间; - 任何命名都不要使用下划线前缀(那是标准库实现保留的),私有成员变量使用尾随下划线。
注释与格式(Comments / Formatting)
- C++ 源码只允许 C++ 注释风格,禁止混用不同风格;不使用参数注释;
- 格式由
clang-format检查并可由它应用,生成格式优先于指南规则;行宽 120,制表符缩进、tab 宽 8; - 所有新 C++ 代码的所有循环与分支语句必须使用花括号,无例外;
- 偏好花括号初始化(brace initialization)以防范意外窄化转换,并优先于 C 风格赋值——除非行为差异确实需要(例如
std::vector); - 类的可见性标签(
public:等)不缩进。
补充建议
- 尽可能使用 C++ 标准模板库与 C++ 算法,但注意某些已知的性能问题(例如
std::regex的糟糕运行时性能); - 优先标准库而非自写循环;优先 C++ 集合与文件系统函数而非 C 变体(如
std::array优于 C 数组);优先范围基与迭代器基循环而非计数循环;优先 C++ 类型而非 C 类型;先用 C++ 代码包装 C 库代码以提供干净的 C++ 接口; - 枚举值使用
class enum而非enum,不要把 enum 用作“整数”别名; - 不要像 C 那样使用
inline或static。
文档对 static 关键字有一段专门的“避坑”说明:它是现代 C++ 中最令人困惑的关键字之一,含义随用途(函数、全局变量、函数级变量、类方法、类成员)而变。一般应把它的 C++ 用途限制在描述变量的“存储期”或用于类方法(如工厂方法)。须知自 C++11 起函数局部 static 变量的初始化是非并发安全的,但这不代表后续访问也是线程安全的,同时警惕“Static Initialization Order Fiasco”。constexpr 定义不需要 static(constexpr 隐含 const,const 默认隐含静态存储期)。混用 static 与 inline 要格外小心:inline 允许同一定义存在于多个翻译单元(可违反 ODR,由链接器去重),与 static 要求的局部可见性(每个翻译单元一份独立拷贝)恰好相反。
例外:Qt 准则
Qt 的许多核心概念诞生于 C++ 支持类似想法之前,因此大量与 Qt 库函数和 QObject 实例交互的代码必须违反上述某些核心语言原则:
- Qt 的所有权模型早于现代智能指针,因此要求传递裸指针:
- 任何
QWidget派生类必须用裸new实例化; - 父 QObject 拥有子对象并负责适当析构;
- 因此:不要
delete由父控件拥有的控件;不要创建不挂接父控件的控件。
- 任何
- Qt 默认字符串类
QString是 Unicode 的早期采用者(类似 Windows 或 macOS 上的 Cocoa),内部使用 2 字节编码(后适配为 UTF-16):- 把字符数据传给任何基于 C 的 API、或与
std::string实例交互时,QString需要在 UTF-16 与 UTF-8 之间转换; - 文档引用了“没有纯文本这回事”(There Ain't No Such Thing As Plain Text)这一业界共识,提醒开发者警惕 Unicode 处理。
- 把字符数据传给任何基于 C 的 API、或与
- 传给 Qt 的堆对象生命周期,必须保证匹配或超过使用/拥有它的 Qt 对象的生命周期;
- Lambda 表达式应当尽可能用于回调式编程,但要注意生命周期问题:lambda 捕获的任何引用,必须在 Qt 可能调用该 lambda 的整个时段内“活着”;标量值用按值捕获。注意指针“只是”标量值,因此按值捕获,但拷贝的指针与其指向的内存之间没有强关联——堆对象的生命周期必须自行保证;按引用捕获的指针可能变成
nullptr并在 lambda 体内检查,但指针引用本身的生命周期现在又需要保证。
仓库中 frontend 目录(OBS 的 Qt 前端)正是这套准则的主要适用区,例如 OBSApp.cpp、OBSBasicInteraction.cpp 等文件中的控件均遵循“父控件拥有子控件”的所有权模型。
C/C++ 头文件包含顺序
优先包含实现本身直接需要的头文件(例如直接使用某类型或定义),不要依赖别的头文件“碰巧”已经包含了同一文件——这遵循“include what you use”规则。包含顺序如下:
// Interface definition or "counterpart" of current file
#include "interface.h"
// File in the same directory as the current file _if_ header belongs to the
// same "implementation".
#include "file_in_working_directory.h"
// First party dependency from the same larger project that is "linked" with
// the implementation
#include <first_party_dependency/type_or_interface.h>
// Third party dependency not part of the same project and "linked" with the
// implementation.
#include <third_party_dependency/type_or_interface.h>
// C++ standard library includes
#include <string>
// C standard library includes
#include <sys/socket.h>
这个顺序的价值在于:它能帮助识别潜在“坏掉的”头文件,而不会通过先包含依赖来掩盖问题。文档给出一个简单的验证流程:创建新的接口/实现配对时,初始实现应当总是先包含其“对应”接口头文件,并且即使实现还是“空的”也应能顺利编译;同样,任何一个第一方/三方库头文件单独包含时不应引发编译问题、不应需要先包含某些标准库头(若是,说明那个库头文件设计得不好)。把标准库包含放在最后(且仅当实现确实需要时才包含)有助于暴露这类畸形头文件。该方案依赖约定而非语言强制,但换来的是自包含的头文件和更干净的包含集合。
附加规则:
- 每个包含块内部按区分大小写的字母序排序;
- 不要创建平台特定的“包含块”。如果一个源文件必须包含一堆平台特定头文件,通常说明这个源文件试图做“太多事”,规则带来的“丑陋”正是提醒你的信号。引用 Google C++ Style Guide 的话:“与其用宏条件编译代码……算了,干脆别那么做。”正确做法是把源文件重构为平台特定文件,用 CMake 把合适文件加入特定平台的 target;
- 不要对当前源/头文件所在工作目录之外的文件使用双引号包含。编译器虽然宽容地会对所有头文件尝试包含路径而仍能编译,但丢失了对读者的重要上下文(“该头文件来自项目中另一个模块/target”)。
确实需要打破规则的著名例外:
windows.h可能需要在实现中非常靠前的位置包含,尤其依赖WIN32_LEAN_AND_MEAN宏时——嵌套包含的同一头文件否则可能以严重方式破坏编译;- BSD 头文件
libprocstat.h要求其 man 页所记载的若干头文件按特定顺序先行包含; - 若能通过测试证明包含顺序存在影响,允许额外头文件打破这些规则——在 C/C++ 的遗留架构与预处理器设计下,这是不可避免的。
Objective-C/C++
遵循 Google Objective-C Style Guide(其本身基于 Apple 的 Cocoa 编码指南与 ObjC 编程约定):
项目当前的 Objective-C/C++ 语言标准为 Objective-C 2.0。
对 Google 指南的增改(按主题出现顺序):
- 命名:函数用
camelCase;允许使用g前缀表示文件作用域的全局变量; - 类型:始终使用原生 64 位类型(即
CGFloat用double); - 注释:文档使用 Apple 的 DocC 格式;行内注释使用 C++ 注释风格;
- Cocoa 与 Objective-C 特性:所有包含使用
#import,包含顺序同 C/C++; - 间距与格式:
clang-format检查并可应用;缩进 4 空格、对齐用空格;最大行宽 120;所有循环或分支语句必须使用花括号,无例外。
这与 .clang-format 中的 ObjC 段配置直接对应:该段设置 IndentWidth: 4、UseTab: Never、ColumnLimit: 120、AccessModifierOffset: 2、IndentCaseLabels: true 等。
Swift
遵循 Google Swift Style Guide 以及 Apple 的 Swift API Guidelines:
项目当前的 Swift 语言标准为 Swift 6。
对 Google 指南的增改:
- 通用格式:
swift-format检查并可应用;缩进 4 空格、对齐用空格;最大行宽 120。仓库根目录的 .swift-format 配置与之精确一致:"lineLength": 120、"indentation": { "spaces": 4 }。
补充要点:
- 使用
Unmanaged类型及其关联协议,创建保留或非保留的不透明指针以与 C API 共享:passRetained传递一个引用计数已递增的不透明指针给 C API;takeRetained在 Swift 侧接管该引用计数,让正常的生命周期管理接手;passUnretained/takeUnretained传递/接收不影响引用计数的指针;- 对象生命周期必须手动保证,因此是不受管理的(unmanaged)。
- 使用
extension在独立于核心类型实现的代码块中实现协议:
class MyType {
// Basic implementation
fileprivate let memberVariable: String
init(argumentOne: String) {
self.memberVariable = argumentOne
}
}
extension MyType : SomeProtocol {
func someProtocolMethod(argument: String) -> String {
return "\(memberVariable) \(argument)"
}
}
- 尽可能用函数式模式替代 C 风格循环;确需循环时,使用原生
Range类型,需要计数值时使用enumerated循环:
for i in 0..<someValue {
doTheThing()
}
for (i, theThing) in theCollection.enumerated() {
doTheThing(with: theThing);
print("I am on iteration \(i) here...")
}
仓库中 libobs-metal 目录(Metal 图形后端)是 Swift 代码的主要聚集地,如 OBSSwapChain.swift、MetalTexture.swift。
Objective-C/C++ 与 Swift 的共性准则
- 共享 ObjC/Swift 对象实例或其数据给外部代码时,务必留意引用计数:
- 显式递增引用计数,确保对象在当前函数作用域退出后不被释放,保证共享给 C 代码的指针保活;
- 外部代码请求“销毁”时递减引用计数与之配平——不要手动释放对象,而应让引用计数自然归零。
- 将可能创建大量新堆对象的代码包在循环体内的 autoreleasepool 中,让引用计数在每次迭代内尽早结算,有助于把每次迭代的内存占用维持在相近水平;
- 优先使用语言特定集合与字符串类型而非 C 数组/字符指针:ObjC 侧优先
NSString、NSDictionary、NSArray、NSSet等;Swift 侧优先String、Dictionary、Array、Set等;Swift 与 Cocoa 类型可桥接互操作; - 优先 ObjC 块(blocks)与 Swift 闭包而非 lambda 表达式。
CMake
CMake 3 建立了一个重要的哲学转变:以“target”及关联的“target 属性”(描述 target 的编译器参数、链接器标志、预处理器定义等需求)为中心。项目几年前重写了构建系统以充分利用这些“现代” CMake 模式,因此所有新 CMake 代码必须遵循相同原则:
- 参考 “Modern CMake” 指南:
- 始终优先 target 及其属性,而非“魔法全局变量”;
- 若能提升整体清晰度,**优先生成器表达式(generator expressions)**而非 CMake 代码中的显式分支;
- 缩进 2 个空格、空格用于对齐;
- 缓存变量、全局变量、文件局部变量使用
UPPER_SNAKE_CASE;函数名与函数局部变量使用snake_case;需要存在于全局或文件局部作用域的“私有”变量使用_UNDERSCORE_PREFIX;- 注意宏与函数的区别:宏没有函数局部作用域,宏体实际进入调用方的作用域并共享变量作用域;
- 变量按名称传给函数,用
${}展开传值; - 字符串一律使用双引号;
- 优先使用 CMake 本身或特定生成器提供的功能,而非自定义的配置期(configure time)运行代码——尤其在 Visual Studio 或 Xcode 等多配置生成器下,配置期代码无法访问 IDE 实际将使用的“真实”构建配置,因此根本无法达成其目标。
必须记住的一点:CMake 不是“构建系统”,而是“构建系统生成器”,把“target”的抽象依赖树翻译成实际的构建系统或 IDE 工程。这些规则在 .editorconfig 中有直接体现(CMakeLists.txt 与 cmake/**/*.cmake 均为 2 空格缩进),仓库的 cmake 目录(如 helpers_common.cmake、FindFFmpeg.cmake)则是现代 target 化写法的实例。
JSON 与 YAML
这两种格式主要用于构建系统配置与持续集成,但仍受一组有限规则约束:
- 尽可能使用 schema 文件,确保 JSON/YAML 文件的结构正确、取值尽可能合法。部分编辑器可根据特定文件名或所在目录名自动拉起常见 schema;
- 变量名使用
camelCase,例外有二:GitHub Actions 工作流的 job 名通常用dash-case;变量键用于底层系统时(例如定义 shell 环境中的环境变量,惯例为UPPER_SNAKE_CASE); - JSON 文件中,键名与字符串值使用双引号;
- YAML 文件中,对可能被错误解析的复杂字符串使用单引号,其余情况无需引号。
规范如何落地:格式化工具链与 CI 强制
文档中“格式由 clang-format 检查并可应用”并非空话,仓库提供了完整的工具链与 CI 强制:
- 配置文件:
- .clang-format——文件头注明“请使用 clang-format 16 及以上版本”。默认段对应 C/C++ 规则:
ColumnLimit: 120、TabWidth: 8、UseTab: ForContinuationAndIndentation(制表符用于缩进与续行)、InsertBraces: true(C++ 段,Standard: c++17,呼应“所有分支语句必须加花括号”)、PointerAlignment: Right、IncludeBlocks: Preserve(保留包含顺序,不自动重排——因为包含顺序规则由本文档而非工具执行)、StatementMacros中声明了Q_OBJECT等宏。文件末尾附一个完整的Language: ObjC段,4 空格缩进、UseTab: Never,与上文 ObjC 规则一致。 - .swift-format——Swift 格式配置(120 行宽、4 空格缩进)。
- .editorconfig——注释中直接说明“由于 OBS 遵循 Linux kernel 编码风格,本文件用于帮助各类文本编辑器自动满足这些要求”,把 tab=8(C 系)、2 空格(CMake)、4 空格(Python 等)等规则同步到编辑器层面。
- .clang-format——文件头注明“请使用 clang-format 16 及以上版本”。默认段对应 C/C++ 规则:
- 本地执行脚本:build-aux 目录提供三个 zsh 脚本 run-clang-format、run-gersemi、run-swift-format,支持
-c/--check(只检查不修改)、--fail-never|error|fast(失败策略)、-v/--verbose等选项。从脚本源码可以看到:clang-format 要求 22.1.3 及以上版本,使用-style=file -fallback-style=none参数;CMake 检查使用 gersemi 0.25.0 及以上;swift-format 要求 508.0.0 及以上。clang-format 的默认检查范围是(libobs|libobs-*|frontend|plugins|deps|shared|test)下的*.c|cpp|h|hpp|m|mm,并显式排除obs-websocket/deps、DeckLink SDK、mac-syphon/syphon-framework、libdshowcapture等第三方目录——这解释了为什么文档规则只约束“项目自己的代码”。 - CI 强制:check-format.yaml 工作流定义了 clang-format、swift-format、gersemi 三个格式化检查 job(外加 flatpak 清单与 Qt XML 校验),全部以
failCondition: error运行——即任何一处格式不符都会使 CI 失败。这意味着文档中“格式规则优先于指南文字”一句有了强制力:格式不合规的 PR 无法通过检查。
综上,对贡献者而言的实操路径是:按 CODESTYLE.md 的语义层规则写代码 → 用 build-aux/run-clang-format、build-aux/run-swift-format、build-aux/run-gersemi 本地格式化并验证 → 提交后由 CI 的 check-format.yaml 工作流做最终把关。风格细节(120 列、tab/空格、花括号)由工具统一,而文档真正要求贡献者投入心智的,是那套“减少潜在错误”的架构守则——这才是这套代码风格指南与纯格式化规则的本质区别。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00