复古 护眼 海天 深邃 暗黑 默认

以下行为准则以完全遵循 Apache 软件基金会行为准则为前提。

开发理念

  • 用心 保持责任心和敬畏心,以工匠精神持续雕琢。
  • 可读 代码及其命名必须清晰、无歧义地表达意图,通过阅读即可理解,无需借助调试推断。
  • 整洁 认同《重构》和《代码整洁之道》的理念,追求整洁优雅代码。
  • 一致 代码风格、命名以及使用方式保持完全一致。
  • 精简 极简代码,以最少的代码表达最正确的意思。高度复用,无重复代码和配置。及时删除无用代码。
  • 抽象 层次划分清晰,概念提炼合理。保持方法、类、包以及模块处于同一抽象层级。
  • 极致 拒绝随意,保证任何一行代码、任何一个字母、任何一个空格都有其存在价值。

代码提交行为规范

  • 确保构建流程中的各个步骤都成功完成,包括:Apache 协议文件头检查、Checkstyle 检查、编译、单元测试等。构建流程启动命令:./mvnw clean install -B -T1C -Pcheck
  • 通过 Spotless 统一代码风格,执行 ./mvnw spotless:apply -Pcheck 格式化代码。
  • 确保覆盖率不低于 master 分支,除去简单的 getter /setter 方法,单元测试需全覆盖。
  • 每个提交应保持小型、完整且可独立验证。 一个改动包含多个独立目标时,应拆分为多个提交。
  • 如果您使用 IDEA,可导入 src/resources/idea/code-style.xml,用于保持代码风格一致性。
  • 如果您使用 IDEA,可导入 src/resources/idea/inspections.xml,用于检测代码潜在问题。

编码规范

  • 每行代码不超过 200 字符无需换行。
  • 不应有无意义的空行。请提炼私有方法,代替方法体过长或代码段逻辑闭环而采用的空行间隔。
  • 命名规范:
    • 类、方法名避免使用缩写,部分变量名可以使用缩写。
      • 变量名 arguments 缩写为 args
      • 变量名 parameters 缩写为 params
      • 变量名 environment 缩写为 env
      • 变量名 properties 缩写为 props
      • 变量名 configuration 缩写为 config
    • 三位以内字符的专有名词缩写使用大写,超过三位字符的缩写采用驼峰形式。
      • 三位以内字符的类和方法名称缩写的示例:SQL92Lexer、XMLTransfer、MySQLAdminExecutorCreator;
      • 三位以上字符的类和方法名称缩写的示例:JdbcUrlAppender、YamlAgentConfigurationSwapper;
      • 变量应使用小驼峰形式:mysqlAuthenticationMethod、sqlStatement、mysqlConfig。
    • 符合下列条件的局部变量,应参照下列规则命名:
      • 除了直接返回方法入参,返回变量使用 result 命名;
      • 循环中使用 each 命名循环变量;
      • map 中使用 entry 代替 each
      • 捕获的异常名称命名为 ex
      • 捕获异常且不做任何事情,异常名称命名为 ignored
    • 方法入参名禁止使用 resulteachentry
    • 工具类名称命名为 xxUtils
    • 配置文件使用 Spinal Case 命名(一种使用 - 分割单词的特殊 Snake Case)。
  • 需要注释解释的代码应提取为小方法,并通过方法名称表达意图。
  • equals== 条件表达式中,常量在左,变量在右;大于小于等条件表达式中,变量在左,常量在右。
  • 除了构造器入参与全局变量名称相同的赋值语句外,避免使用 this 修饰符。
  • 局部变量不得声明为 final,包括普通局部变量、for 循环变量、增强 for 循环变量和 try-with-resources 资源变量。
  • Lambda 参数不应标记为 final
  • 除用于继承的抽象类外,所有类都应声明为 final
  • 嵌套循环应提取为独立方法。
  • 成员变量定义顺序以及参数传递顺序在各个类和方法中保持一致。
  • 对无效输入、缺失状态和异常条件使用卫语句提前返回,使正常执行路径使用正向条件并保持最少嵌套。
  • 类和方法的访问权限控制为最小。
  • 方法所用到的私有方法应紧跟该方法,如果有多个私有方法,书写私有方法应与私有方法在原方法的出现顺序相同。
  • 方法入参和返回值默认不得为 null
  • 仅当现有 API、SPI 或框架契约明确使用 null 表示缺失值时才允许使用 null,并必须通过 @Nullable 或 JAVADOC 明确其语义。
  • 方法入参不得使用 Optional
  • 仅当 Lombok 生成的签名、可见性和行为与手写代码一致时,才使用 Lombok 消除构造器、getter、setter 和日志变量等样板代码。
  • 手写成员包含校验、业务逻辑、文档、兼容性或框架语义时,必须保留手写实现。
  • 创建可变集合前能够确定预计元素数量时,必须通过容量参数或接收已有集合的构造器设置足够的初始容量。
  • if/else 分支分别只包含一个返回语句或对同一个变量赋值时,使用三目运算符;其他情况使用 if/else
  • 使用 @HighFrequencyInvocation 标注需要重点检查性能行为的高频生产代码。
    • 代码在以下任一情况中属于高频调用:
      • 每次 SQL 请求都会重复执行。
      • 每个 Pipeline 数据单元都会重复执行,包括记录、事件、数据包或批次。即使其所在方法仅调用一次或执行器仅启动一次,只要代码在内部循环中持续处理这些数据单元,仍属于高频调用。
    • 在能够覆盖高频行为的最小准确范围内标注类、方法或构造器。
      • 标注类时,规则适用于该类内全部方法和构造器的实现。
      • 标注方法或构造器时,规则适用于该实现及其调用的同类私有方法。
    • 仅当被标注目标是可复用的缓存资源时,才设置 canBeCached = true
    • 高频调用范围内不得执行可以预计算、缓存、复用或移到高频路径之外的高耗时操作。只有操作结果依赖当前 SQL 请求或 Pipeline 数据,并且无法在不改变正确性和生命周期的前提下移出高频路径时,才允许保留。高耗时操作包括重复 I/O、阻塞等待、反射、解析、序列化、全量遍历,以及创建大对象或大量对象。
    • 高频调用范围内:
      • 禁止使用 Java Stream API;
      • 禁止使用 + 拼接字符串;
      • 禁止调用 LinkedList#get(int)
  • 注释 & 日志规范:
    • 日志与注释一律使用英文。
    • 注释只能包含 JAVADOC,TODO 和 FIXME。
    • 公开的类和方法必须有 JAVADOC,对用户的 API 和 SPI 的 JAVADOC 需要写的清晰全面,其他类和方法以及覆盖自父类的方法无需 JAVADOC。
    • 默认不得添加构造器 JAVADOC。 仅当构造器 JAVADOC 用于说明非显然行为、兼容性约束、副作用,或类契约未表达的 public API 语义时才允许添加。

单元测试规范

  • 测试代码和生产代码需遵守相同代码规范。
  • 单元测试需遵循 AIR(Automatic, Independent, Repeatable)设计理念。
    • 自动化(Automatic):单元测试应全自动执行,而非交互式。禁止人工检查输出结果,不允许使用 System.outlog 等,必须使用断言进行验证。
    • 独立性(Independent):禁止单元测试用例间的互相调用,禁止依赖执行的先后次序。每个单元测试均可独立运行。
    • 可重复执行(Repeatable):单元测试不能受到外界环境的影响,可以重复执行。
  • 单元测试需遵循 BCDE(Border, Correct, Design, Error)设计原则。
    • 边界值测试(Border):通过循环边界、特殊数值、数据顺序等边界的输入,得到预期结果。
    • 正确性测试(Correct):通过正确的输入,得到预期结果。
    • 合理性设计(Design):与生产代码设计相结合,设计高质量的单元测试。
    • 容错性测试(Error):通过非法数据、异常流程等错误的输入,得到预期结果。
  • 单元测试必须通过公共 API 验证行为,禁止通过反射调用私有成员。 若测试必须通过反射访问字段,应使用 Plugins.getMemberAccessor(),且反射仅限于 Field 访问。
  • 测试修改静态状态时,必须在每个测试结束后恢复其原始状态。
  • 默认通过项目加载器获取 SPI 实现。 如果被测类实现了 TypedSPIDatabaseTypedSPI,应通过 TypedSPILoaderDatabaseTypedSPILoader 实例化,禁止使用 new
  • 每个单元测试类必须直接测试一个对应的生产类,测试类必须使用生产类的准确简单类名并命名为 <ProductionClassName>Test。 该测试类命名规则为强制要求,与面向场景的测试方法命名规则相互独立。
  • 当某个生产方法只由一个测试用例覆盖时,测试方法命名为 assert<MethodName>,无额外后缀;应优先使用独立测试方法覆盖单个公开的生产方法;可行时,测试方法顺序与对应的生产方法保持一致。
  • 参数化测试需通过参数提供显示名,并使用 "{0}" 作为展示名模板。
  • 测试名称应简洁并聚焦场景;避免使用 ReturnsXXX,也不要使用仅复述预期结果而未说明场景的措辞。
  • 断言必须直接表达被测契约。 仅当被测契约是不相等或包含指定子串时,才可使用 notcontainsString;可断言完整值或使用更具体的 matcher 时,不得使用这两个 matcher。
  • 默认直接使用 Mockito mock。 仅对重复的局部准备使用私有辅助方法,仅对稳定的外部测试边界或打包测试边界使用独立测试夹具。 测试夹具应采用实际可行的最小可见性,并放在最近的所属测试包或模块中;不要为了方便而创建跨模块测试 API。 删除或内联内容单薄的 mock 包装器。
  • 数据断言规范应遵循:
    • 布尔类型断言应使用 assertTrueassertFalse
    • 空值断言应使用 assertNullassertNotNull
    • 非布尔值、非空值的相等断言必须使用 assertThat(actual, is(expected))
    • 类型断言必须使用 assertThat(actual, isA(ExpectedType.class))
    • 引用同一性断言必须使用 assertThat(actual, sameInstance(expected))
    • 引用非同一性断言必须使用 assertThat(actual, not(sameInstance(expected)))
  • 测试用例的真实值应名为为 actual XXX,期望值应命名为 expected XXX。
  • 使用 mock 应遵循如下规范:
    • 数据库、缓存、注册中心、网络调用、时间以及其他重量级外部依赖应使用 mock,不要连接外部环境。
    • 与被测行为无关且嵌套超过两层的对象应使用 mock;不要构造深层无关对象图。
    • 优先使用 AutoMockExtension 及其静态 mock 或构造 mock 支持。 只有该扩展无法适用并且记录了原因时,才可直接使用 mockStaticmockConstruction,并且必须通过 try-with-resources 限定作用域。 如果某个类已列入 @StaticMockSettings,不要对它调用 mockStaticmockConstruction,而应通过 when(...) 设置桩行为。
    • 不要在一次调用中混用 Mockito 参数匹配器和原始参数。
    • 校验仅有一次调用时,无需使用 times(1) 参数,使用 verify 的单参数方法即可。
  • 不得对与当前测试所验证的行为或结果无关的方法进行 stub,也不得 verify 此类交互。当 Mockito 的默认返回值足以满足测试需要时,应省略 stub。
  • 深度链式交互使用 Mockito 的 RETURNS_DEEP_STUBS,不要层层手动 mock。
  • 测试数据应使用标准化前缀(如 foo_/bar_)明确标识其测试用途。
  • 使用 PropertiesBuilder 简化 Properties 构造。

SQL 解析规范

维护规范

  • SQL 解析模块涉及的 G4 语法文件以及 SQLVisitor 实现类,需要根据如下的数据库关系进行差异代码标记。当数据库 A 不提供对应的数据库驱动和协议,而是直接使用数据库 B 的驱动和协议时,可以认为数据库 A 是数据库 B 的分支数据库。 通常分支数据库会直接使用主干数据库的 SQL 解析逻辑,但是为了适配分支数据库的特有语法,部分分支数据库会从主干数据库复制并维护自己的 SQL 解析逻辑,此时对于分支数据库的特有语法,需要使用注释进行标记,其他部分需要和主干数据库的实现保持一致;

    主干数据库 分支数据库
    MySQL MariaDB、Doris
    PostgreSQL -
    openGauss -
    Oracle -
    SQLServer -
    ClickHouse -
    Hive -
    Presto -
    SQL92 -
  • 差异代码标记语法,增加时将 {DatabaseType} 替换为数据库类型大写名,例如:DORIS

    • 新增语法:// {DatabaseType} ADDED BEGIN// {DatabaseType} ADDED END
    • 修改语法:// {DatabaseType} CHANGED BEGIN// {DatabaseType} CHANGED END

G4 规范

  • 词法解析规范
    • 每个规则一行,规则间无需空行。
    • 规则名称使用大写字母。如果名称由多个单词组成,用 下划线 间隔。DataTypeSymbol 的规则命名以 下划线 结尾。与 ANTLR 内置变量或关键字重名的规则在结尾加 下划线 以示区分。
    • 不对外暴露的规则使用 fragmentfragment 定义的规则需在其服务的规则之后声明。
    • 公用规则定义放在 Keyword.g4,每个数据库可以有自己特有的规则定义。例如:MySQLKeyword.g4
  • 语法解析规范
    • 每个规则结束后空一行,空行无需缩进。
    • 规则名称前面不空格,冒号 后空一格再开始写规则,分号 在单独一行并保持和上一行相同缩进。
    • 如果一个规则的分支超过 5 个,则每个分支一行。
    • 规则命名采用 java 变量的驼峰形式。
    • 为每种 SQL 语句类型定义一个独立的语法文件,文件名称由 数据库名称 + 语句类型名称 + Statement。例如:MySQLDQLStatement.g4

GitHub Action 规范

  • Workflow 文件名以 .yml 结尾。
  • Workflow 文件名由 触发方式-执行操作 的小写字母组成。例如:nightly-check.yml。pull_request 触发的任务省略触发方式,例如:check.yml
  • 触发方式包括:pull_request(不加前缀)、nightly、schedule。
  • 执行操作包括:check、ci、e2e 、build、report。
  • Workflow 文件内的 name 属性命名与文件名一致,单词以 - 作为分隔符,分隔符两侧要加空格,每个单词首字母大写。例如:Nightly - Check
  • Step 下的 name 属性应该描述 step 的功能,每个单词首字母大写,介词小写。例如:Build Project with Maven
  • Workflow 中的 job 属性命名,须在 Workflow 中保持唯一。
  • 使用 matrix 的时候,必须添加作业并行度限制为 20。例如:max-parallel: 20
  • 必须为作业设置超时时间,最大不超过 1 小时。例如:timeout-minutes: 10