# TSL 代码风格

文档类型：风格偏好页
是否可直接用于生成代码：仅部分
是否含可直接照写示例：否
是否含不可照写反例：否
遇到不确定时：风格/命名看 [naming.md](naming.md)；语法事实使用 [tsl-syntax-reference Skill](../../skills/tsl-syntax-reference/SKILL.md)；验证命令或自动化入口只看项目脚本、CI，或目标项目已真实填写且没有 `<...>` 占位符的 [toolchain.md](toolchain.md)

本页记录本仓库代码风格偏好，不代表 TSL 语法事实。

以下出现的 `<TOKEN>`、`<...>`、模板占位和单独标成 `反例 / 不可照写` 的块，只用于说明风格或反例，不等于可直接复制的源码字面量。

本页里的 `反例 / 不可照写` 仅表示风格上的不推荐写法，不等于语法主线里“会编译失败/运行失败”的反向边界；因此页头里的 `是否含不可照写反例` 仍保持为 `否`。

相关文档：

- 命名偏好：`docs/tsl/naming.md`
- 工具链与验证命令（模板）：`docs/tsl/toolchain.md`

## 1. 文件与组织

### 1.1 单一职责

- 一个文件只做一件事；职责明确。
- 文件模型和顶层声明规则由 [tsl-syntax-reference Skill](../../skills/tsl-syntax-reference/SKILL.md) 管理；本页只规定组织偏好。
- 可复用逻辑优先集中到可复用声明文件；入口/编排层只保留必要流程。
- 当一个顶层声明同时承担“协议适配 + 业务计算 +
  I/O/环境依赖 + 临时代码”时，优先拆分边界：核心纯逻辑 → 工具函数 → 边界适配（I/O）。

### 1.2 文件名与顶层声明

- 文件模型和顶层声明规则由 [tsl-syntax-reference Skill](../../skills/tsl-syntax-reference/SKILL.md) 管理；文件和实体如何命名见
  `docs/tsl/naming.md`。
- 文件名不强制表达完整职责，但应清晰可检索。
- 推荐文件名使用 `PascalCase` 以提升检索与协作一致性；扩展名按类型使用
  `.tsl`/`.tsf`（两者都属于 TSL 源文件，风格规则一致）。
- 重命名时同步检查引用、调用点、项目配置和文档链接。

### 1.3 依赖与分层

- 避免循环依赖；依赖方向应从“入口/业务层”指向“通用/底层模块”。
- 公共能力（类型/常量/纯函数）下沉到可复用模块；不要在多个文件里复制粘贴同一段工具逻辑。
- 发现环依赖时的默认处理顺序：
  1. 提取共享部分到更底层的 `*Common`/`*Shared` `unit`。
  2. 通过参数/回调注入反转依赖（让底层不再直接引用上层）。
  3. 必要时引入更明确的边界文件（例如 adapter 层），把依赖集中在边界处。

### 1.4 推荐布局（读者视角）

- 同类代码按“对外 API → 核心实现 → 辅助工具 → 测试/示例”的顺序组织。
- 对外 API：尽量靠前，读者先看到“怎么用”；实现细节与 helper 放到后面（例如
  `unit` 的 `interface`、`class` 的 `public`）。
- 对外声明尽量“收口”：
  - `unit` 的 `interface` 只放对外 `const/type/function`
    声明与必要注释；实现细节与 helper 放在后部。
  - `class` 的对外 API 放在 `public`；内部状态与实现细节放在 `private`。
- 测试/示例：优先独立文件/目录，避免夹在核心实现中间（减少无关 diff 干扰 review）。

## 2. 格式（Formatting）

### 2.1 缩进与空白

- 使用**空格缩进**，禁止 Tab。
- 默认缩进 **4 个空格**；继续缩进保持与上层语义一致。
- 行尾不留空格；文件以换行符结尾。
- 逻辑块之间用空行分隔，不要用空行堆砌。

### 2.2 行宽与换行

- 单行建议不超过 **100 字符**；超过时应换行以保持可读性。
- 换行遵循“**断在运算符后**、对齐到语义层级”的原则。
- 长字符串/URL 可适当超出，但避免影响阅读。

### 2.3 begin/end 与代码块

- 代码块使用统一的块结构（示例按常见 TSL 写法；若项目语法/约定有差异，以项目现有代码为准）：

代码块身份：配置片段 / 概念骨架

```tsl
if cond then
begin
    DoSomething();
end
else
begin
    DoOther();
end
```

- 多语句分支使用 `begin/end` 包裹：在 `then/else` 后换行写 `begin`，`end`
  单独成行。
- `else/elseif` 等分支关键字另起一行，与上一块的 `end` 对齐。
- 控制流块与顶层声明的分号规则属于语法事实，使用 [tsl-syntax-reference Skill](../../skills/tsl-syntax-reference/SKILL.md) 核对；本页只规定风格偏好。
- 单语句分支可省略 `begin/end`（保持清晰优先；一旦分支变复杂就回退到块结构）：

代码块身份：配置片段 / 概念骨架

```tsl
if cond then DoSomething()
else DoOther()
```

### 2.4 运算符与分隔符

- 二元运算符两侧加空格：`a + b`、`x == y`。
- 一元运算符不加空格：`!flag`、`-value`。
- 逗号后加空格：`f(a, b, c)`。
- 不要为了对齐而插入多余空格；让格式由缩进表达结构。

### 2.5 控制流

- 多语句分支必须使用 `begin/end`；单语句分支可省略 `begin/end`，写成单行（如
  `if cond then stmt`）。
- 复杂条件拆分为具名布尔变量或小函数。
- 早返回优于深层嵌套：

代码块身份：配置片段 / 概念骨架

```tsl
if !ok then return err;
// main path
```

## 3. 注释（Comments）

注释用于解释**为什么**以及必要的背景，而不是重复代码。

### 3.0 注释形式与语言

- 支持的注释形式：
  - 行注释：`// ...`（默认优先使用）
  - 块注释：`{ ... }`
  - 块注释：`/* ... */`
- 注释语言：跟随文件，可中英混写；同一文件内尽量保持一致的表达风格。
- 注释中不要写入明文密钥/Token/密码等敏感信息；示例使用占位符（如 `<TOKEN>`）。

### 3.1 文件级注释

- 文件开头说明用途、主要职责、关键依赖/约束。
- 若文件实现某个对外 API，写明入口与预期行为。

### 3.2 函数/接口注释

- “对外可见”的定义：
  - `unit`：`interface` 区域中的声明（对外 API）
  - `class`：`public` 区域的方法/property（对外 API）
  - 顶层 `function`：该函数本身（对外入口）
- 对外可见的函数必须写注释，包含：
  - 做什么（行为）
  - 入参/返回值含义（必要时含单位、范围）
  - 关键副作用与异常情况
- 注释使用完整句子，末尾带标点。
- 推荐模板（按需裁剪；语言可中英混写）：

代码块身份：配置片段 / 概念骨架

```tsl
// Summary: 一句话说明做什么（以及关键约束/边界）。
// Args:
// - foo: 含义（单位/范围/约束）。
// Returns: 返回值语义（以及错误/空值含义，如适用）。
// Side effects: 关键副作用（I/O/全局状态/缓存/日志等）。
// Errors: 失败条件与处理方式（返回/抛出/降级）。
```

### 3.3 行内注释

- 用于解释复杂逻辑、非直观边界条件、性能/安全考量。
- 避免“显而易见注释”：
- 尾随注释（写在代码行末）只用于非常短的补充；超过一行时改为写在语句上方，或重构代码提醒意图。

不推荐这样写：

代码块身份：反例 / 不可照写
代码块说明：风格反例，不是语法主线反例。

```tsl
count := count + 1;  // obvious
```

推荐直接删掉这类显而易见注释：

代码块身份：配置片段 / 概念骨架

```tsl
count := count + 1;
```

### 3.4 TODO/FIXME

- 统一格式：`TODO(name): ...` / `FIXME(name): ...`
- 写清原因和期望修复方向，而非“留个坑”。
- `name` 使用 Git 用户名；不强制附 issue/ticket（如有可追加在描述中）。

## 4. 代码实践（Best Practices）

> 本节偏“实践建议”（should），用于提升可读性/可测试性；若目标项目有更严格的约束与检查命令，以项目脚本、CI 配置，或已填写且没有 `<...>` 占位符的
> `docs/tsl/toolchain.md` 为准。如果项目对自动化或 AI 智能体有额外要求，应把约束直接写进仓库内可见的检查脚本、CI 配置或项目文档，而不是依赖隐藏规范。

### 4.1 变量与常量

- 默认使用不可变/只读：能用 `const` 就用
  `const`；可变状态尽量压到最小作用域，并让“更新点”集中且明显。
- 对外 API 优先只读：对外暴露用只读 property（只有 `read`，不写
  `write`），内部用私有成员保存。

代码块身份：配置片段 / 概念骨架

```tsl
type User = class
public
    property UserId read user_id_;  // readonly
private
    user_id_;
end;
```

- 变量声明与第一次使用尽量靠近。
- 避免隐式类型转换：TSL 为动态类型，但运行时仍有类型与单位；外部输入（参数/配置/文件/接口）应在边界处显式解析与校验，再进入核心逻辑。
- 避免隐式全局：函数尽量只依赖显式入参；若必须使用顶层全局/静态可变变量，必须在声明处写注释说明：它是什么、用于什么、以及（如不明显）为什么需要是全局/静态。

### 4.2 函数设计

- 签名尽量自解释：对外 API 的参数/返回值建议显式写类型注解；并用注释写清契约（可复用 3.2 的模板）。
- 类型注解不支持 `xxx.xxx` 形式；使用单一类型名。
- 若需标注类型来源，允许在类型名前用块注释写 `{Unit.}` 前缀，例如
  `style_: {DocxML.}Style;`。该前缀仅为注释，不参与语义或类型检查，工具可能忽略；类型名仍是
  `Style`（建议紧贴类型名书写）。
- 示例（`{Unit.}` 前缀仅用于阅读，不改变类型名）：

代码块身份：配置片段 / 概念骨架

```tsl
type DocxContext = class
public
    property Style read style_ write style_;
private
    style_: {DocxML.}Style;
end;

function RenderParagraph(para_: {DocxML.}Paragraph): void;
```

- 无返回值函数显式标注返回类型为 `void`；`create`/`destroy`
  作为构造/析构函数不写返回类型。

代码块身份：配置片段 / 概念骨架

```tsl
function Func(a: string; b: ClassName): void;
```

- 参数默认可读写（引用语义）；输入参数如果不应被修改，优先使用 `const`
  修饰符让意图与约束更明确。
- 单一职责：函数过长说明拆分点已出现（建议 ≤
  40–60 行）；把“纯计算”与“I/O/环境依赖（文件/网络/数据库/全局状态）”分离，降低耦合、便于测试。
- 参数组织与顺序：
  - 输入参数在前；可选配置/选项（如 `*Options`/`*Config`）居中；输出/回调在后。
  - 避免堆叠多个布尔开关参数；优先收敛到 `*Options`/`*Config`（按需在 `class` 或
    `unit` 中定义）。
- 示例：避免多个开关参数直接堆在签名里（调用点难以理解各参数含义），改为
  `*Options`/`*Config`：

不推荐这样写：

代码块身份：反例 / 不可照写
代码块说明：风格反例，不是语法主线反例。

```tsl
function ExportReport(
    path: string;
    rows: array;
    include_header;
    compress;
    dry_run
): void;
```

推荐把可选开关收敛到 `Options` / `Config`：

代码块身份：配置片段 / 概念骨架

```tsl
type ExportOptions = class
public
    property IncludeHeader read include_header_ write include_header_;
    property Compress read compress_ write compress_;
    property DryRun read dry_run_ write dry_run_;
private
    include_header_;
    compress_;
    dry_run_;
end;

function ExportReport(path: string; rows: array; options: ExportOptions): void;
```

- 尽量避免超过 5 个参数；必要时封装为对象（`class`/`unit`）。

### 4.3 错误处理

- 错误必须显式处理：返回失败/错误、抛出异常、或记录并降级（best-effort）。禁止“看起来成功了但其实失败了”的隐式路径。
- **不设默认策略**：按场景选择返回/抛出/降级，并在对外注释里写清契约（参考 3.2 模板的
  `Errors`）。
  - **返回失败/错误**：调用方有能力恢复/重试/改参数时（参数不合法、外部输入解析失败、依赖不可用等）。
  - **抛出异常**：不应发生的内部错误/不变量被破坏，继续执行风险更大时。
  - **记录并降级**：功能可选、失败不影响主流程时（例如缓存读取失败 → 当作 cache
    miss），必须在代码旁注释说明“为什么允许”。
- 不要吞掉异常/错误：`try/except`
  之后如果继续执行，必须有明确替代行为（返回/重试/降级）以及理由；否则应将错误继续向上抛出或返回。
- 错误信息与日志（允许在库里打日志，但要克制）：
  - 错误/日志至少包含：**做什么失败** +
    **关键上下文（脱敏）**，便于定位；避免只有“failed”。
  - 禁止把 Token、密码、个人数据等敏感信息写入日志、注释或错误信息；如需记录标识，优先记录脱敏后的业务 ID 或可复现的上下文键。
  - 避免重复记录：同一个错误链路尽量只在**边界层**记录一次（库里记录后，上层通常不再重复打一遍同等级日志）。
- 示例：`try/except/end` + 降级（best-effort）：
  - `LoadCacheValue` / `RecordCacheWarning` 代表项目自有函数，重点在结构而不是函数名本身。

代码块身份：配置片段 / 概念骨架

```tsl
// 读取可选缓存：失败允许降级为 cache miss（必须可观测，并说明原因）。
function ReadOptionalCache(path: string);
begin
    try
        return LoadCacheValue(path);
    except
        // best-effort: cache 仅用于提速，失败不应影响主流程
        RecordCacheWarning(path);
        return nil;
    end
end;
```

### 4.4 性能与可测试性

- 避免过早优化：先写清晰正确的代码，再用项目实际可用的 profile、trace、日志或基准数据定位瓶颈，并做最小化改动。
- 复杂逻辑要可测试：把“纯计算/解析/规则”与“I/O/环境依赖（文件/网络/DB/全局状态）”分离；I/O 层做薄封装，核心逻辑保持可单测。
- 避免在热路径里做隐式昂贵操作：循环内重复 I/O、重复解析/格式化、无界缓存、隐式复制等；缓存如必须引入，明确生命周期与上限（大小/TTL/清理点）。
- 示例：薄 I/O + 厚纯逻辑（便于测试与复用）：
  - `LoadConfigText` 代表项目自己的 I/O 边界函数；`ParseConfig` 只负责解析与校验。

代码块身份：配置片段 / 概念骨架

```tsl
// pure: 只做解析/校验，不做 I/O，便于单元测试
function ParseConfig(text: string): array;

// I/O: 只负责读文件与兜底处理，把逻辑交给 ParseConfig
function LoadConfig(path: string): array;
begin
    text := LoadConfigText(path);
    return ParseConfig(text);
end;
```
