Squashed 'docs/standards/playbook/' changes from e504a68..b529012

b529012  test(tests): avoid hardcoded missing path
07583c8 🐛 fix(tests): make doc link check awk-portable
6244aa3 🔧 chore(ci): relax gitattributes check
31dd0c5 🐛 fix(scripts): require existing project root
15d4d63 🔧 chore(ci): install python3-pip
90c6313 🔧 chore(ci): run tests in a single job
d84eff0 🔧 chore(ci): make actions base url configurable
395598d 🔧 chore(ci): fix yaml step names
4ae2733  feat(sync_standards): auto-detect existing languages
e97fb00  feat(sync_standards): default gitattributes to append
da0ef2b  test: add automated tests and ci workflow
99bef30 🎨 style(markdown): format all markdown files with prettier
7e96bc8 ♻️ refactor(tsl): split function.md into 44 modular files
0d6b2a0 📝 docs(readme): add quick decision table and TL;DR for distribution methods
3b2188f 🎨 style(markdown): format markdown files with prettier
41fc43b 📝 docs(tsl): document {Unit.}Type source annotation
3dceaf7 📝 docs(tsl): clarify tsf-only top-level rules and type annotations
3a63829 📝 docs(skills): add create-plan skill
f02a707 🐛 fix(scripts): escape markdown backticks in vendor_playbook
064aa92 🐛 fix(scripts): correct bat scripts
4881feb 🔧 chore(gitattributes): enforce crlf for bat files
1fa3e2a 🐛 fix(scripts): escape parentheses in sync_standards output
3958cad 📝 docs(vendor_playbook): mention ci templates
9d059cf  feat(templates): add gitea ci example
a52bb24 📝 docs(codex_skills): normalize markdown formatting
cc8ad4c 📝 docs(skills): slim commit-message
2a98e15 🐛 fix(skills): quote YAML descriptions
8f78d22 🐛 fix(sync_standards): generate minimal AGENTS.md
5547665  feat(skills): add commit-message suggestion skill
3fe8bd7 🔧 chore(sync_standards): create AGENTS.md on sync
283d311  feat(playbook): add syntax book and codex skills tooling
5b97ed5 📝 docs(tsl): clarify syntax references
27e0700  feat(skills): add pdf/docx/pptx/xlsx wrapper workflows
5551363  feat(skills): add debugging and bulk refactor workflows
1d4f548  feat(skills): add built-in workflow skills
c0895dd 📝 docs(skills): add anthropics document-skills integration
cf9f80d 🔧 chore(playbook): add Claude Code skills guide and align python templates

git-subtree-dir: docs/standards/playbook
git-subtree-split: b529012c59c5d67c3073884d7b617763647417fd
This commit is contained in:
csh
2026-01-08 15:56:36 +08:00
parent eada742d75
commit ac794a6a70
113 changed files with 254751 additions and 176 deletions
+204 -16
View File
@@ -2,12 +2,49 @@
本章节规定 TSL 代码的结构与格式约定。
相关文档:
- 命名规范:`docs/tsl/naming.md`
- 工具链与验证命令(模板):`docs/tsl/toolchain.md`
## 1. 文件与组织
### 1.1 单一职责
- 一个文件只做一件事;职责明确。
- 文件名使用 `PascalCase`,并与文件内唯一的顶层声明同名(语法要求)。扩展名按类型使用 `.tsl`/`.tsf`(两者都属于 TSL 源文件,风格规则一致)。
- 避免循环依赖;公共能力下沉到可复用模块
- `.tsl` 建议作为“入口/编排层”:聚合参数/配置、串起流程;可复用逻辑下沉到
`.tsf``unit`/`class`/`function`)中
- 当一个顶层声明同时承担“协议适配 + 业务计算 +
I/O/环境依赖 + 临时代码”时,优先拆分边界:核心纯逻辑 → 工具函数 → 边界适配(I/O)。
### 1.2 文件名与顶层声明(硬约束)
- TSL 语法要求(仅 `.tsf`):每个 `.tsf`
文件只能有一个顶层声明,且文件基名必须与顶层声明同名。
- `.tsl` 允许直接写语句,不要求顶层声明;文件名不强制,但建议清晰可检索。
- 推荐文件名使用 `PascalCase` 以提升检索与协作一致性;扩展名按类型使用
`.tsl`/`.tsf`(两者都属于 TSL 源文件,风格规则一致)。
- 详细约束与命名细则见 `docs/tsl/naming.md`
### 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
@@ -26,7 +63,7 @@
### 2.3 begin/end 与代码块
- 代码块使用统一的块结构(示例为伪代码,按 TSL 语法调整):
- 代码块使用统一的块结构(示例按常见 TSL 写法;若项目语法/约定有差异,以项目现有代码为准):
```tsl
if cond then
@@ -39,8 +76,15 @@ begin
end
```
- 多语句分支使用 `begin/end` 包裹:在 `then/else` 后换行写 `begin``end` 单独成行。
- 多语句分支使用 `begin/end` 包裹:在 `then/else` 后换行写 `begin``end`
单独成行。
- `else/elseif` 等分支关键字另起一行,与上一块的 `end` 对齐。
- 单语句分支可省略 `begin/end`(保持清晰优先;一旦分支变复杂就回退到块结构):
```tsl
if cond then DoSomething()
else DoOther()
```
### 2.4 运算符与分隔符
@@ -51,7 +95,8 @@ end
### 2.5 控制流
- 多语句分支必须使用 `begin/end`;单语句分支可省略 `begin/end`,写成单行(如 `if cond then stmt`)。
- 多语句分支必须使用 `begin/end`;单语句分支可省略 `begin/end`,写成单行(如
`if cond then stmt`)。
- 复杂条件拆分为具名布尔变量或小函数。
- 早返回优于深层嵌套:
@@ -64,6 +109,15 @@ if !ok then return err
注释用于解释**为什么**以及必要的背景,而不是重复代码。
### 3.0 注释形式与语言
- 支持的注释形式:
- 行注释:`// ...`(默认优先使用)
- 块注释:`{ ... }`
- 块注释:`/* ... */`
- 注释语言:跟随文件,可中英混写;同一文件内尽量保持一致的表达风格。
- 注释中不要写入明文密钥/Token/密码等敏感信息;示例使用占位符(如 `<TOKEN>`)。
### 3.1 文件级注释
- 文件开头说明用途、主要职责、关键依赖/约束。
@@ -71,16 +125,31 @@ if !ok then return err
### 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 // bad: obvious
@@ -90,34 +159,153 @@ count = count + 1 // bad: obvious
- 统一格式:`TODO(name): ...` / `FIXME(name): ...`
- 写清原因和期望修复方向,而非“留个坑”。
- `name` 使用 Git 用户名;不强制附 issue/ticket(如有可追加在描述中)。
## 4. 代码实践(Best Practices
> 本节偏“实践建议”(should),用于提升可读性/可测试性;若目标项目有更严格的约束与检查命令,以项目落地的工具链为准(参考
> `docs/tsl/toolchain.md`)。如需给自动化/AI 代理配置强约束,可参考
> `.agents/tsl/code_quality.md` 与 `.agents/tsl/testing.md`。
### 4.1 变量与常量
- 默认使用不可变/只读(如语法支持 `const` 或等价机制)。
- 默认使用不可变/只读:能用 `const` 就用
`const`;可变状态尽量压到最小作用域,并让“更新点”集中且明显。
- 对外 API 优先只读:对外暴露用只读 property(只有 `read`,不写
`write`),内部用私有成员保存。
```tsl
type User = class
public
property UserId read user_id_; // readonly
private
user_id_;
end;
```
- 变量声明与第一次使用尽量靠近。
- 避免隐式类型转换与隐式全局
- 避免隐式类型转换:TSL 为动态类型,但运行时仍有类型与单位;外部输入(参数/配置/文件/接口)应在边界处显式解析与校验,再进入核心逻辑
- 避免隐式全局:函数尽量只依赖显式入参;若必须使用顶层全局/静态可变变量,必须在声明处写注释说明:它是什么、用于什么、以及(如不明显)为什么需要是全局/静态。
### 4.2 函数设计
- 函数参数建议显式写类型注解,提升可读性与工具检查能力
- 无返回值函数显式标注返回类型为 `void`
- 签名尽量自解释:对外 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;
```
- 单一职责;函数过长说明拆分点已出现(建议 ≤ 40–60 行)。
- 参数顺序:输入参数在前,输出/回调在后
- 尽量避免超过 5 个参数;必要时封装为对象(class/unit)。
- 参数默认可读写(引用语义);输入参数如果不应被修改,优先使用 `const`
修饰符让意图与约束更明确
- 单一职责:函数过长说明拆分点已出现(建议 ≤
40–60 行);把“纯计算”与“I/O/环境依赖(文件/网络/数据库/全局状态)”分离,降低耦合、便于测试。
- 参数组织与顺序:
- 输入参数在前;可选配置/选项(如 `*Options`/`*Config`)居中;输出/回调在后。
- 避免堆叠多个布尔开关参数;优先收敛到 `*Options`/`*Config`(按需在 `class`
`unit` 中定义)。
- 示例:避免多个布尔开关参数(调用点难以理解 `true/false` 的含义),改为
`*Options`/`*Config`
```tsl
// 注:参数类型名按项目实际替换(此处 bool/Any 仅为示例占位)。
// bad: 多个 bool 参数在调用点难读、易传错
function ExportReport(
path: string;
data: Any;
include_header: bool;
compress: bool;
dry_run: bool
): void;
// good: 将可选开关收敛到 Options(调用点更自解释、后续扩展更稳定)
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; data: Any; options: ExportOptions): void;
```
- 尽量避免超过 5 个参数;必要时封装为对象(`class`/`unit`)。
### 4.3 错误处理
- 错误必须显式处理:返回错误、抛出异常或记录并降级(按项目约定)
- 不要吞掉异常/错误;必须加注释说明原因。
- 错误必须显式处理:返回失败/错误、抛出异常或记录并降级(best-effort)。禁止“看起来成功了但其实失败了”的隐式路径
- **不设默认策略**:按场景选择返回/抛出/降级,并在对外注释里写清契约(参考 3.2 模板的
`Errors`)。
- **返回失败/错误**:调用方有能力恢复/重试/改参数时(参数不合法、外部输入解析失败、依赖不可用等)。
- **抛出异常**:不应发生的内部错误/不变量被破坏,继续执行风险更大时。
- **记录并降级**:功能可选、失败不影响主流程时(例如缓存读取失败 → 当作 cache
miss),必须在代码旁注释说明“为什么允许”。
- 不要吞掉异常/错误:`try/except`
之后如果继续执行,必须有明确替代行为(返回/重试/降级)以及理由;否则应将错误继续向上抛出或返回。
- 错误信息与日志(允许在库里打日志,但要克制):
- 错误/日志至少包含:**做什么失败** +
**关键上下文(脱敏)**,便于定位;避免只有“failed”。
- 禁止把 Token/密码/个人数据等敏感信息写入日志、注释或错误信息(参考
`.agents/tsl/auth.md`)。
- 避免重复记录:同一个错误链路尽量只在**边界层**记录一次(库里记录后,上层通常不再重复打一遍同等级日志)。
- 示例:`try/except/end` + 降级(best-effort):
- 注:示例中的 `Any`/`nil`/`LogWarn`/`ReadCacheFromFile`
为占位,按项目实际类型与函数替换。
```tsl
// 读取可选缓存:失败允许降级为 cache miss(必须可观测,并说明原因)。
function ReadOptionalCache(path: string): Any;
begin
try
return ReadCacheFromFile(path)
except
// best-effort: cache 仅用于提速,失败不应影响主流程
LogWarn("ReadOptionalCache failed; fallback to miss. path=" + path)
return nil
end
end;
```
### 4.4 性能与可测试性
- 避免过早优化先写清晰正确的代码,再用数据驱动优化。
- 复杂逻辑要可测试:拆成纯函数或可注入依赖的模块
- 避免过早优化先写清晰正确的代码,再用数据profile/trace/log/基准)定位瓶颈并做最小化改动(参考
`.agents/tsl/performance.md`
- 复杂逻辑要可测试:把“纯计算/解析/规则”与“I/O/环境依赖(文件/网络/DB/全局状态)”分离;I/O 层做薄封装,核心逻辑保持可单测(参考
`.agents/tsl/testing.md`)。
- 避免在热路径里做隐式昂贵操作:循环内重复 I/O、重复解析/格式化、无界缓存、隐式复制等;缓存如必须引入,明确生命周期与上限(大小/TTL/清理点)。
- 示例:薄 I/O + 厚纯逻辑(便于测试与复用):
- 注:示例中的 `Any`/`ReadAllText` 为占位,按项目实际类型与函数替换。
```tsl
// pure: 只做解析/校验,不做 I/O,便于单元测试
function ParseConfig(text: string): Any;
// I/O: 只负责读文件与兜底处理,把逻辑交给 ParseConfig
function LoadConfig(path: string): Any;
begin
text = ReadAllText(path)
return ParseConfig(text)
end;
```