regulations
SKILL.md
仓颉项目规范准则
本规范基于仓颉语言特性、工具链能力及业界软件工程通用规范,为仓颉项目提供编码层面和项目层面的推荐准则。
1. 项目结构规范
1.1 标准目录布局
project-root/
├── cjpm.toml # 项目配置(必要)
├── src/ # 源代码目录
│ ├── main.cj # 程序入口(executable 项目在根包中有 main 函数)
│ ├── kernal/ # 子包目录,与 package 声明一致
│ │ ├── engine.cj
│ │ └── driver.cj
│ └── utils/
│ ├── shell.cj
│ └── shell_test.cj # shell.cj 对应的单元测试程序(可选)
├── tests/ # 集成测试/端到端测试(可选)
├── bench/ # 基准测试(可选)
├── docs/ # 项目文档(可选)
├── examples/ # 示例代码(可选)
├── build.cj # 构建脚本(可选)
├── cangjie-format.toml # 格式化配置(可选)
├── target/ # 构建输出(不提交到版本控制)
├── .gitignore
└── README.md # 项目介绍文档(推荐)
1.2 项目初始化
- 使用
cjpm init --name <name> --type=executable|static|dynamic初始化项目,确保目录结构和cjpm.toml配置正确。 cjpm.toml中的name、version、cjc-version、output-type为必填字段。- 版本号遵循语义化版本(Semantic Versioning):
MAJOR.MINOR.PATCH。
1.3 包与模块组织
- 每个子目录对应一个包,包名与目录名一致。
package声明必须是文件中第一条非注释语句,且路径与src/下的目录结构匹配。- 单一职责原则:一个包聚焦于一个功能领域,避免包内包含不相关的类型或函数。
- 公共 API 放在包的顶层文件中,内部实现可通过
internal/private控制可见性。 - 避免循环依赖:包之间的依赖关系应形成有向无环图(DAG)。
1.4 源文件组织
- 每个源文件聚焦一个主要类型或一组紧密相关的功能。
- 文件内代码推荐按以下顺序组织:
package声明import语句- 常量定义
- 类型定义(
class/struct/enum/interface) - 扩展(
extend) - 顶层函数
- 单元测试文件
xxx_test.cj与被测文件xxx.cj同目录放置。
2. 命名规范
2.1 基本命名规则
| 元素 | 规则 | 示例 | 规则编号 |
|---|---|---|---|
| 包名 | 全小写,可含数字和下划线 | network, http_client |
G.NAM.01 |
| 源文件名 | 全小写加下划线,.cj 后缀 |
http_client.cj, user_service.cj |
G.NAM.02 |
| 类/接口/struct/枚举/类型别名 | 大驼峰(PascalCase) | HttpClient, UserService |
G.NAM.03 |
| 函数名 | 小驼峰(camelCase) | getData, parseResponse |
G.NAM.04 |
全局 let 和 static let 常量 |
全大写下划线分隔(SCREAMING_SNAKE_CASE) | MAX_RETRY_COUNT, DEFAULT_TIMEOUT |
G.NAM.05 |
| 变量名 | 小驼峰(camelCase) | itemCount, userName |
G.NAM.06 |
| 泛型类型参数 | 大写单字母或 PascalCase | T, K, V, Element |
— |
2.2 命名最佳实践
- 名称应具有描述性和自文档性,避免使用无意义的缩写。
- 布尔类型变量/函数使用
is/has/can/should等前缀,如isValid、hasPermission。 - 集合类型变量使用复数形式,如
users、orderItems。 - 工厂方法使用
create/from/of前缀,如createUser、fromJson。 - 转换方法使用
toXxx形式,如toString、toJson。 - 避免使用保留关键字作为标识符;如必须使用,用反引号转义(如
`type`),但应优先选择替代名称。
3. 格式化规范
3.1 工具强制规则
使用 cjfmt 自动格式化,确保团队代码风格一致。推荐配置:
# cangjie-format.toml
indentWidth = 4
linelimitLength = 120
lineBreakType = "LF"
allowMultiLineMethodChain = false
3.2 核心格式规则
- 缩进:统一使用 4 个空格,禁止使用 Tab。
- 行长度:不超过 120 个字符。
- 大括号:K&R 风格——左大括号在行末(前置 1 个空格),右大括号独占一行。
else/catch/finally与前一个右大括号同行。 - 空格:
- 冒号后加空格(类型声明、返回类型、命名参数)。
- 二元操作符两侧各留一个空格。
- 一元操作符与操作数之间不留空格。
- 圆括号和方括号内部不留空格。
- 区间操作符(
..、..=)两侧不留空格。
- 空行:移除多余的连续空行和大括号内首尾的空行。
- 修饰符排序:
public→open/abstract→override/redef→static。
3.3 代码组织格式
- 顶层声明之间保留一个空行。
- 函数体内通过空行分隔逻辑段落,增强可读性。
import语句分组:标准库导入在前,第三方库导入在后,组间用空行分隔。
4. 编码规范
4.1 变量与常量
- 优先使用
let声明不可变变量,仅在需要修改时使用var(G.VAR.01)。 - 编译期常量使用
const声明。 - 避免变量遮盖(shadow),防止同名变量在嵌套作用域中造成混淆(G.DCL.01)。
public变量和函数必须显式声明类型(G.DCL.02)。- 减少全局可变状态的使用(G.VAR.03)。
4.2 函数设计
- 单一职责:每个函数只完成一个明确的任务(G.FUN.01)。
- 禁止未使用的参数(G.FUN.02);如确实需要占位,使用
_命名。 - 函数参数数量建议不超过 5 个;参数过多时考虑使用结构体封装。
- 命名参数(
param!: Type)用于提升可读性,尤其当参数含义不明确时。 - 合理使用默认参数值减少重载。
- 使用管道操作符(
|>)和函数组合(~>)提升数据处理链的可读性。
4.3 类型设计
- struct(值类型):用于轻量、不可变的数据对象,如坐标、颜色、配置项。
- class(引用类型):用于需要继承、多态或共享引用的场景。
- interface:定义行为契约,支持多实现;优先面向接口编程。
- enum:表示有限的状态集合;利用参数化构造器携带关联数据。
- 类的继承层次不宜过深(建议不超过 3 层),优先使用组合而非继承。
- 使用
sealed修饰class/interface限制继承范围到同一包内,增强可控性。 - 充分利用
@Derive自动生成Equatable、Hashable等实现。
4.4 属性与访问控制
- 使用属性(
prop/mut prop)替代公开字段,便于后续添加校验逻辑。 - 遵循最小可见性原则:默认使用
private,按需提升到internal→protected→public。 - 安全检查方法禁止声明为
open,防止子类绕过安全逻辑(G.SEC.01)。
4.5 表达式与控制流
- 利用仓颉的表达式特性(
if/match/try均为表达式),简化赋值逻辑。 match表达式应覆盖所有分支,或显式使用_通配分支。- 避免过深的嵌套(建议不超过 4 层),通过提前返回(early return)简化逻辑。
for-in循环优先使用where子句过滤,替代循环体内的if判断。
4.6 扩展(extend)
- 扩展用于为已有类型添加新方法或实现新接口,不用于修改已有行为。
- 遵守孤儿规则:扩展类型和扩展接口至少有一个在当前包中定义。
- 扩展中不能添加存储型成员变量。
5. 错误处理规范
5.1 异常使用原则
- 自定义异常继承自
Exception,禁止继承Error(Error仅用于系统级故障)。 - 异常用于真正的异常情况,不用于正常的控制流。
catch块应处理具体异常类型,避免裸catch (_)吞掉所有异常。finally块用于资源清理,不在其中抛出新异常或执行业务逻辑。- 实现了
Resource接口的对象使用try-with-resources自动管理生命周期。
5.2 Option 类型使用
- 对于可预期的「无值」场景,优先使用
Option<T>而非异常。 - 使用
??提供默认值,使用?.安全链式访问。 - 使用
if-let/while-let进行条件解包。 - 避免滥用
getOrThrow(),仅在确定值存在时使用。
5.3 错误传播
- 函数若无法处理异常,应让异常自然传播给调用者。
- 在 API 边界层统一捕获和转换异常,提供用户友好的错误信息。
- 日志记录异常时包含完整上下文信息,但禁止记录敏感数据(G.OTH.01)。
6. 并发编程规范
6.1 线程安全
- 仓颉采用 M:N 线程模型(语言线程调度到 OS 线程),使用
spawn创建轻量级线程。 - 共享可变状态必须使用同步机制保护:
Mutex、synchronized、原子操作。 - 优先使用
synchronized块自动管理锁的获取和释放,避免手动lock/unlock遗漏。 Condition变量必须在持有对应Mutex锁时创建和使用。- 禁止暴露内部锁对象到外部(G.CON.01),防止外部代码破坏同步逻辑。
6.2 并发最佳实践
- 使用不可变数据(
let)减少共享可变状态的需求。 - 使用
Future<T>获取异步任务的执行结果。 - 使用
SyncCounter协调多线程的完成顺序。 - 使用
ThreadLocal存储线程局部数据,避免不必要的锁竞争。 - 合理使用协作式取消(
Thread.currentThread.requestCancel())实现线程的优雅终止。 - 使用并发集合(
std.collection.concurrent中的ConcurrentHashMap、NonBlockingQueue等)替代手动加锁的普通集合。
7. 测试规范
7.1 测试组织
- 单元测试文件命名为
xxx_test.cj,与被测文件xxx.cj同目录放置。 - 使用
@Test标注测试函数或测试类。 - 测试类中使用
@TestCase标注每个测试用例方法。 - 测试名称应清晰描述被测行为,如
addPositiveNumbers、throwOnInvalidInput。
7.2 断言使用
@Expect(软断言):断言失败后继续执行后续断言,用于收集多个失败信息。@Assert(硬断言):断言失败后立即停止当前测试。@AssertThrows[ExType](expr):验证特定异常类型的抛出。@PowerAssert:在复杂表达式断言失败时显示中间值,便于调试。- 每个测试用例至少包含一个断言。
7.3 测试最佳实践
- 测试遵循 AAA 模式:Arrange(准备)→ Act(执行)→ Assert(断言)。
- 每个测试用例独立,不依赖其他测试的执行顺序或状态。
- 使用
@BeforeEach/@AfterEach进行测试前后的初始化和清理。 - 使用参数化测试(
@Test[x in ...])覆盖多组输入数据,减少重复代码。 - 使用 Mock 框架(
mock<T>()/spy<T>()、@On、@Called)隔离外部依赖。 - 使用
cjpm test --filter "pattern"运行特定测试。 - 使用
cjcov生成覆盖率报告,关注分支覆盖率,目标覆盖率 ≥ 80%。
7.4 基准测试
- 使用
@Bench标注基准测试函数。 - 基准测试放在独立文件或目录中,与功能测试分离。
- 使用
cjpm bench运行基准测试。
8. 文档规范
8.1 代码注释
- 公共 API(
public类型、函数、属性)必须编写文档注释。 - 注释说明「为什么」而非「做了什么」——代码本身应表达意图。
- 多行注释中
*保持对齐(cjfmt会自动格式化)。 - 使用
@Deprecated[message: "...", since: "版本号"]标注废弃的 API,并提供替代方案说明。
8.2 项目文档
README.md:项目简介、快速开始、构建与运行方法、依赖说明。CHANGELOG.md(可选):记录版本变更历史。docs/(可选):存放详细的设计文档、API 文档、架构说明。- 示例代码放在
examples/目录中,保持可编译和可运行。
9. 依赖管理规范
9.1 依赖声明
- 所有外部依赖在
cjpm.toml的[dependencies]中显式声明。 - 测试专用依赖在
[test-dependencies]中声明,不打包到正式产物中。 - 指定明确的版本或版本范围,避免使用不固定版本。
[dependencies]
http_lib = { git = "https://example.com/http_lib.git", tag = "v1.2.0" }
utils = { path = "./libs/utils" }
[test-dependencies]
test_helper = { path = "./libs/test_helper" }
9.2 依赖管理实践
- 使用
cjpm check检查依赖状态,使用cjpm update更新依赖。 - 使用
cjpm tree查看依赖树,排查间接依赖冲突。 - 定期审查依赖的安全性和版本更新。
- 最小化外部依赖数量,优先使用标准库(
std.*)和扩展标准库(stdx.*)提供的功能。
10. 安全规范
10.1 输入校验
- 跨信任边界的数据必须进行校验(G.CHK.01),包括外部输入、文件内容、网络数据。
- 使用
Option类型和模式匹配安全地处理可能缺失的数据。
10.2 敏感信息保护
- 禁止在源代码中硬编码敏感信息(密码、密钥、Token 等)(G.OTH.02)。
- 禁止在日志中保存敏感数据(G.OTH.01)。
- 敏感配置通过环境变量或安全配置管理系统注入。
10.3 FFI 安全
- FFI 调用限制在
unsafe块中,最小化unsafe代码范围。 - 对 C 函数传入的数据进行严格校验。
- 使用
CPointerResource/CStringResource等 RAII 类型管理 FFI 内存,避免内存泄漏。 @FastNative仅用于不阻塞、不回调仓颉、执行时间短的 C 函数。
10.4 并发安全
- 遵循第 6 节并发规范,避免数据竞争和死锁。
- 安全检查方法禁止声明为
open(G.SEC.01)。
11. 工具链集成规范
11.1 持续集成流程
推荐在 CI/CD 流程中依次执行以下步骤:
cjpm build # 1. 编译构建
cjfmt -d src/ # 2. 代码格式化(格式化后检查是否有变更,有则说明代码未预先格式化)
cjlint -f src/ # 3. 静态代码检查
cjpm test # 4. 运行测试
cjcov --format html # 5. 生成覆盖率报告
11.2 工具配置
- 项目根目录维护
cangjie-format.toml统一格式化配置。 - 使用
config/cjlint_rule_list.json定义启用的检查规则集合。 - 启用 G.FMT.13 规则时,在源文件头添加版权注释。
- 使用构建脚本
build.cj的pre-build/post-build钩子集成自定义检查。
11.3 调试与性能分析
- 使用
cjdb进行断点调试和变量检查。 - 使用
cjprof进行 CPU 采样和堆内存分析,定位性能瓶颈。 - 使用
cjcov的分支覆盖率模式(--branch)检查测试覆盖质量。
Weekly Installs
2
Repository
kong-baiming/cangjie-devFirst Seen
5 days ago
Security Audits
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
codex1