首页
/ OBS Studio 代码风格规范深度解读:clang-format 强制规则、各语言守则与架构设计准则

OBS Studio 代码风格规范深度解读:clang-format 强制规则、各语言守则与架构设计准则

2026-09-04 15:07:29作者:冯爽妲Honey

OBS Studio 的 CODESTYLE.md 是该项目所有贡献必须遵循的代码风格与架构守则:它不仅规定了 C、C++、Objective-C/C++、Swift、CMake、JSON/YAML 各语言的格式化工具与版本要求,还给出了一整套用于“减少潜在错误”的架构级编程准则。读完本文,你可以掌握 OBS Studio 代码规范的完整骨架——哪些规则由 clang-format/swift-format/gersemi 在 CI 中自动强制执行、哪些需要在人工层面自觉遵守,以及提交代码前如何本地验证格式合规。

总则:可自动执行的格式 + 不可自动执行的架构守则

文档开宗明义地指出,项目要求所有贡献的源代码都使用合适的格式化工具进行格式化,目的是把风格变更对结构化“diff”视图的潜在影响降到最低。在自动可执行规则之外,项目还偏好贡献者遵循一组架构准则——这些准则被刻意设计为减少未定义或意外行为,使代码在评审与维护时更容易推理。

也就是说,OBS Studio 的风格体系分为两层:

  1. 格式层:由 clang-format(C/C++/ObjC)、swift-format(Swift)、gersemi(CMake)自动检查并可自动应用,工具生成的格式优先于指南中的任何文字规则
  2. 语义层:命名习惯、架构原则、头文件包含顺序等,依赖贡献者与评审者共同遵守。

减少潜在错误:一组经过实践检验的架构守则

文档的核心章节“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 = tabindent_size = 8insert_final_newline = truetrim_trailing_whitespace = truecharset = utf-8,并针对 plugins/obs-outputs/librtmp/* 等第三方代码(4 空格)、CMakeLists.txtcmake/**/*.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 那样使用 inlinestatic

文档对 static 关键字有一段专门的“避坑”说明:它是现代 C++ 中最令人困惑的关键字之一,含义随用途(函数、全局变量、函数级变量、类方法、类成员)而变。一般应把它的 C++ 用途限制在描述变量的“存储期”或用于类方法(如工厂方法)。须知自 C++11 起函数局部 static 变量的初始化是非并发安全的,但这不代表后续访问也是线程安全的,同时警惕“Static Initialization Order Fiasco”。constexpr 定义不需要 staticconstexpr 隐含 const,const 默认隐含静态存储期)。混用 staticinline 要格外小心: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 处理。
  • 传给 Qt 的堆对象生命周期,必须保证匹配或超过使用/拥有它的 Qt 对象的生命周期;
  • Lambda 表达式应当尽可能用于回调式编程,但要注意生命周期问题:lambda 捕获的任何引用,必须在 Qt 可能调用该 lambda 的整个时段内“活着”;标量值用按值捕获。注意指针“只是”标量值,因此按值捕获,但拷贝的指针与其指向的内存之间没有强关联——堆对象的生命周期必须自行保证;按引用捕获的指针可能变成 nullptr 并在 lambda 体内检查,但指针引用本身的生命周期现在又需要保证。

仓库中 frontend 目录(OBS 的 Qt 前端)正是这套准则的主要适用区,例如 OBSApp.cppOBSBasicInteraction.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 位类型(即 CGFloatdouble);
  • 注释:文档使用 Apple 的 DocC 格式;行内注释使用 C++ 注释风格;
  • Cocoa 与 Objective-C 特性:所有包含使用 #import,包含顺序同 C/C++;
  • 间距与格式clang-format 检查并可应用;缩进 4 空格、对齐用空格;最大行宽 120;所有循环或分支语句必须使用花括号,无例外。

这与 .clang-format 中的 ObjC 段配置直接对应:该段设置 IndentWidth: 4UseTab: NeverColumnLimit: 120AccessModifierOffset: 2IndentCaseLabels: 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.swiftMetalTexture.swift

Objective-C/C++ 与 Swift 的共性准则

  • 共享 ObjC/Swift 对象实例或其数据给外部代码时,务必留意引用计数
    • 显式递增引用计数,确保对象在当前函数作用域退出后不被释放,保证共享给 C 代码的指针保活;
    • 外部代码请求“销毁”时递减引用计数与之配平——不要手动释放对象,而应让引用计数自然归零。
  • 将可能创建大量新堆对象的代码包在循环体内的 autoreleasepool 中,让引用计数在每次迭代内尽早结算,有助于把每次迭代的内存占用维持在相近水平;
  • 优先使用语言特定集合与字符串类型而非 C 数组/字符指针:ObjC 侧优先 NSStringNSDictionaryNSArrayNSSet 等;Swift 侧优先 StringDictionaryArraySet 等;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.txtcmake/**/*.cmake 均为 2 空格缩进),仓库的 cmake 目录(如 helpers_common.cmakeFindFFmpeg.cmake)则是现代 target 化写法的实例。

JSON 与 YAML

这两种格式主要用于构建系统配置与持续集成,但仍受一组有限规则约束:

  • 尽可能使用 schema 文件,确保 JSON/YAML 文件的结构正确、取值尽可能合法。部分编辑器可根据特定文件名或所在目录名自动拉起常见 schema;
  • 变量名使用 camelCase,例外有二:GitHub Actions 工作流的 job 名通常用 dash-case;变量键用于底层系统时(例如定义 shell 环境中的环境变量,惯例为 UPPER_SNAKE_CASE);
  • JSON 文件中,键名与字符串值使用双引号
  • YAML 文件中,对可能被错误解析的复杂字符串使用单引号,其余情况无需引号。

规范如何落地:格式化工具链与 CI 强制

文档中“格式由 clang-format 检查并可应用”并非空话,仓库提供了完整的工具链与 CI 强制:

  1. 配置文件
    • .clang-format——文件头注明“请使用 clang-format 16 及以上版本”。默认段对应 C/C++ 规则:ColumnLimit: 120TabWidth: 8UseTab: ForContinuationAndIndentation(制表符用于缩进与续行)、InsertBraces: true(C++ 段,Standard: c++17,呼应“所有分支语句必须加花括号”)、PointerAlignment: RightIncludeBlocks: 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 等)等规则同步到编辑器层面。
  2. 本地执行脚本build-aux 目录提供三个 zsh 脚本 run-clang-formatrun-gersemirun-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-frameworklibdshowcapture 等第三方目录——这解释了为什么文档规则只约束“项目自己的代码”。
  3. CI 强制check-format.yaml 工作流定义了 clang-format、swift-format、gersemi 三个格式化检查 job(外加 flatpak 清单与 Qt XML 校验),全部以 failCondition: error 运行——即任何一处格式不符都会使 CI 失败。这意味着文档中“格式规则优先于指南文字”一句有了强制力:格式不合规的 PR 无法通过检查。

综上,对贡献者而言的实操路径是:按 CODESTYLE.md 的语义层规则写代码 → 用 build-aux/run-clang-formatbuild-aux/run-swift-formatbuild-aux/run-gersemi 本地格式化并验证 → 提交后由 CI 的 check-format.yaml 工作流做最终把关。风格细节(120 列、tab/空格、花括号)由工具统一,而文档真正要求贡献者投入心智的,是那套“减少潜在错误”的架构守则——这才是这套代码风格指南与纯格式化规则的本质区别。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
981
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384