playbook/docs/tsl/naming.md

12 KiB
Raw Blame History

TSL 命名规范Naming

本仓库命名规则与 Google C++ Style Guide 对齐:通过名字的“形状”快速判断实体类型(类型/函数/变量/常量等),减少阅读成本。

1. 选名原则

  • 可读一致:名字清晰可读,并随可见范围调整具体程度。
    • 可见范围越大(越对外),名字越应具体、少省略。
    • 本指南中“对外可见”指:unit interfaceclass public、顶层 function
  • 标识符语言:标识符(类型/函数/property/变量/参数等)统一使用英文;禁止中文与拼音(注释可中英混合,见 docs/tsl/code_style.md)。
  • 少用生僻缩写:能写全称就写全称。
    • 允许使用团队已约定、大家都懂的常见缩写;若缩写不够通用,优先写全称或在评审/文档中先达成约定。
  • 驼峰/帕斯卡中的缩写规则:缩写(首字母缩写/词组缩写)在 PascalCase/camelCase按一个单词处理,写成“首字母大写其余小写”,不要写一串全大写。
    • 示例:UserId(不是 UserID)、UrlTable(不是 URLTable)、 StartRpcServer(不是 StartRPCServer)、HttpClient(不是 HTTPClient)。
  • 避免无意义词:如 datainfotmphandle 等。
    • 可以作为限定词的一部分(例如 user_data),但不要单独用作名字(例如仅叫 data)。

2. 命名风格总览

对于以下规则,“单词”指英文中不带空格的词。

  • snake_case:全小写,下划线分隔单词,用于普通变量/参数等;私有类成员变量使用 snake_case_(见 5.2)。
  • PascalCaseUpperCamelCase):每个单词首字母大写,无下划线,用于类型、顶层函数/“动作型”方法、property以及少量公有成员字段访问器/设置器方法见 7 的例外约定)。
  • 自定义标识符只使用本指南约定的 PascalCase/snake_caselowerCamelCase 仅用于沿用内置/标准库/第三方 API 的既有命名。

大小写与关键字约定

  • TSL 语言大小写无关,但本指南仍要求按约定使用大小写以提升可读性;不要用仅大小写不同的名字区分不同实体;同一标识符在仓库中应保持一致写法。
  • 所有语法关键字统一使用全小写书写,例如 ifforclassfunctionunitreturn 等。
  • 调用内置/标准库/第三方 API 时,推荐保持对方官方大小写形式(aaBBCC/lowerCamelCase例如 getSysParams("xxx");自定义 wrapper 仍按本指南使用 PascalCase

3. 类型命名Type Names

TSL 的顶层声明只有三种:classunitfunction。 因此文件基名必须与顶层声明同名见“4. 文件命名与顶层声明”)。

  • **类class与单元unit**使用 PascalCase,不带下划线;名称应为名词/名词短语(通常单数),避免动词开头。
  • 不推荐 *Unit 作为 unit 的后缀(unit 本身已表达语义);需要表达用途时,可使用 *Shared/*Common/*Enums 等更具体后缀(按团队约定)。
  • **顶层函数function**使用 PascalCase;名称优先动词/动词短语(例如 Load/Parse/Build),详见函数命名章节。
  • 示例:UserAccountOrderSharedLoadMarketData()

4. 文件命名与顶层声明File Names

TSL 的语法要求:每个文件只能有一个顶层声明,且文件基名必须与该顶层声明名字一致

  • 顶层声明可能是 classunitfunction(见类型命名)。
  • .tsf 代码文件:用于库/模块等“顶层声明”的承载文件;顶层声明可为 class/unit/function,文件基名需与之同名。
  • .tsl 脚本文件:用于入口/编排层;顶层声明只能是 function,因此文件基名 = 顶层函数名;可复用逻辑应下沉到 .tsf(见 docs/tsl/code_style.md)。
    • 注:.tsf 也是 TSL 源文件,命名/风格与 .tsl 遵循同一套规则。
  • 硬规则:重命名顶层声明时必须同步重命名文件基名,否则语法/加载规则无法识别;批量重命名可参考 $bulk-refactor-workflow

命名建议:

  • 基名统一使用 PascalCase,与顶层声明的推荐写法一致。
  • 示例:
    • LoadMarketData.tsl 中定义 function LoadMarketData(...).
    • UserAccount.tsf 中定义 type UserAccount = class ... end;.
    • DocxEnumerations.tsf 中定义 unit DocxEnumerations; ... end.
    • ParseConfig.tsf 中定义 function ParseConfig(...).

TSL 大小写无关,实际编译时按大小写比较不会出错,但仍应保持文件名与声明名的推荐写法一致以便检索与协作。

5. 变量命名Variable Names

5.1 普通变量与参数

  • 局部变量、函数参数、非成员变量使用 snake_case
  • 若参数名与 TSL 关键字冲突导致编译失败,使用前导下划线的 snake_case 作为例外,例如 _typetype 是常见冲突关键字)。
  • 前导下划线 _ 仅用于上述关键字冲突的参数场景,不要用于其他局部变量、成员变量、函数/类型/单元名称或全局变量。
  • 建议把单位写进名字(尤其时间/金额/比例):例如 timeout_msspread_bpratio_pct
  • 示例:table_namemax_retry_countuser_id(短名例外见 5.5)。

5.2 类成员Class Data Members

  • 私有成员变量使用 snake_case_(尾随下划线)。
  • 尾随下划线 _ 仅用于私有成员变量不要用于局部变量、参数、公有成员字段、property 名称或顶层全局变量。
  • 公有成员变量若必须存在,使用 PascalCase;但不推荐外部直接访问公有字段
  • 对外暴露的成员优先使用 property
    • property 名称使用 PascalCase(视为对外 API
    • property 的 read/write 指向真实成员(通常为私有 snake_case_)。
    • 布尔 property 使用 Is/Has/Can/Should 等前缀的 PascalCase(例如 IsReady),对应私有成员可用 is_ready_ 等。
  • 示例:
type User = class
public
    property UserId read user_id_ write user_id_;
    property IsReady read is_ready_;  // bool property example
private
    user_id_;
    is_ready_;
end;

5.3 全局/静态变量

  • 不推荐使用顶层全局/静态可变变量;优先封装到 unit/class 中,通过函数或 property 访问。
  • 合理例外(仍需集中管理,避免到处读写):
    • 进程级只读配置缓存(启动后不再变)。
    • 有上限/可清理的缓存(明确容量/TTL/清理点)。
    • 指标/计数器(只增不减,或集中在少数写入点更新)。
  • 若必须声明顶层全局/静态可变变量:
    • 命名仍使用 snake_case(不使用 g_ 前缀)。
    • 必须在声明处写注释说明:它是什么、用于什么、以及(如不明显)为什么需要是全局/静态。
    • 建议补充写入点与生命周期:谁会写、何时写、何时清理/重置;如涉及并发,写明并发假设/保护方式。
    • 不要在注释/日志中写入任何敏感信息(参考 .agents/tsl/auth.md)。
  • 示例(注释模板,按需裁剪):
// <var_name>: <what it is>
// Used for: <what it is used for>
// Global because: <why it needs to be global (if unclear)>
  • 全局/静态常量仍按常量规则使用 kPascalCase(建议同样写一句用途注释,便于检索与维护)。

5.4 布尔变量

  • 布尔变量建议区分两类语义:
    • 状态/谓词predicate:描述“是否满足某条件/是否处于某状态”,使用 is_ / has_ / can_ / should_ 等前缀表达语义。
      • 示例:is_readyhas_errorcan_retry
    • 选项/开关flag/option:描述“是否启用某行为/模式”,允许使用不带 is_snake_case 短语(更贴近配置项语义)。
      • 示例:dry_runinclude_headerenable_cacheuse_cache
  • 尽量使用正向命名,避免双重否定:is_valid 优于 is_not_validdisable_cache 这类命名需谨慎(容易在调用点读错)。

5.5 短名例外

  • 在极小作用域内可用习惯短名:仅限 for/while 的索引变量或约 510 行内的临时值。
  • 允许短名清单(建议严格执行):i/j/k(索引)、n(计数);其他一律使用有含义的名字。
  • 作用域一旦扩大(跨多个分支/循环、跨函数、跨文件),必须改为有含义的名字。

5.6 集合与复数命名Collections

  • 数组/列表/可迭代集合使用复数名词的 snake_caseusersorder_items
  • 若复数形式不直观或为不可数名词,使用后缀明确类型:news_listprice_items
  • **映射/字典key→value**使用 snake_case 并加后缀 _map;必要时可用 _by_<key> 表达键语义(仍需保留 _mapuser_mapprice_by_symbol_map
  • 集合/去重集合使用后缀 _setuser_id_setsymbol_set

6. 常量命名Constant Names

  • 模块级/全局级固定常量(写死、与入参无关、加载后不变)使用 kPascalCase,以 k 开头。
  • 示例:kDaysInAWeekkAndroid8_0_0
  • 常量名建议带单位(尤其时间/金额/比例):例如 kTimeoutMskSpreadBpkRatioPct
  • 对于局部 const 但值来自参数/运行时的变量:
    • 可用普通变量名 snake_case
    • 不要用 k 前缀误导读者认为其全局固定。

6.1 单元枚举模拟Unit Enumerations

TSL 没有内置 enum,推荐使用 unit + constinterface 区域模拟枚举集合。

  • unit 名称使用 PascalCase,建议以 Enumerations 结尾表达用途(例如 DocxEnumerations)。
  • 枚举值使用 const 定义并放在 interface 中:
    • 项目自定义枚举值:默认使用 kPascalCase(属于模块级固定常量)。
    • 外部/互操作枚举值:允许沿用对方既有前缀与命名(例外场景)。

示例:

unit AlertEnumerations;
interface

    const kAlertLevelAll = -1;
    const kAlertLevelNone = 0;
end.
unit DocxEnumerations;
interface

    // WdAlertLevel
    const wdAlertsAll = -1;
end.

7. 函数与方法命名Function Names

  • 顶层函数与“动作型/业务型”方法使用 PascalCase
  • 访问器/设置器方法(仅当 property 无法表达语义时才使用)允许使用 snake_case 的小写形式,以贴近“字段/状态”的语义(按团队约定的例外)。
  • 特殊函数/运算符重载为语法固定名,必须使用全小写
    • 构造/初始化函数:create
    • 析构/释放函数:destroy
    • 运算符重载:operator+() 等,按语法使用小写 operator<op>() 形式。
  • 示例:AddTableEntry()DeleteUrl()OpenFileOrDie()
  • 推荐使用 property 语法对外暴露访问器property 名 PascalCaseread/write 绑定成员变量(见类成员章节)。
  • 不推荐新增显式 getter/setter仅当 property 无法表达语义时,才使用 getter/setter
    • getter与字段同形的 snake_case(如 count()is_ready())。
    • setter使用 set_ 前缀的 snake_case(如 set_count(x)set_ready(x))。

8. 宏与编译期开关Macro Names

  • 能不用宏就不用。
  • 若项目不支持宏/预处理,本节可忽略;若支持且必须使用,命名为全大写加下划线,并带项目/业务前缀:
    • <PROJECT>_ENABLE_FOO<PROJECT>_USE_BAR<PROJECT>_ROUND(x)

9. 例外Exceptions

  • 当命名需要与外部既有 API/协议保持一致时,可沿用对方风格(例如对接 C/C++ 库、历史接口、跨语言互操作代码等)。
  • 不要为了“命名隔离”引入不必要的 wrapper/嵌套;优先保证语义清晰、可检索,并在必要处用注释说明“该命名来自外部约束/协议”。