12 KiB
项目开发
新建最小项目:
dever new my_app
dever fmt my_app --check
dever check my_app
dever test my_app
dever run my_app -- hello.greeting.greet '{"name":"Dever"}'
dever build my_app --output my_app/program
./my_app/program hello.greeting.greet '{"name":"Dever"}'
创建 Markdown 项目用 dever new my_app --markdown。两种模板使用相同代码和行为,Markdown 另加编译器校验的说明。目标目录必须不存在,父目录必须已存在;不接受向现有项目合并或覆盖。build 输出也必须不存在。
模板包含 CMD、App、配置、一个测试和开发指引,无需数据库或 HTTP 服务。dever new 已生成 AGENTS.md;已有项目在根目录运行 dever skill install 即可写入同一份规则,保留文件中其他内容。
按需要替换模板的演示领域,删除不再使用的示例与测试。添加 HTTP 时须完成 站点与身份配置,不能只把 cmd 换成 get。
my_app/
AGENTS.md
README.md
config/setting.json
module/
news/article/
app.dever
domain.dever
model.dever
port.dever
adapter.dever
job.dever
api/
admin/manage.dever
front/browse.dever
test/news/article/publish.dever
这是可用职责位置,不是要求创建全部文件。component、domain 和文件主题使用有业务意义的 ASCII 小写蛇形命名;类型用 PascalCase,方法和字段用 snake_case。不要增加任意第三层领域目录。组件按业务能力划分,领域按一致的数据和业务边界划分。组件、领域和主题不能命名为 common/shared/utils/helper/helpers/base/service/services 等编译器禁止的通用桶。
| 位置 | 责任和访问规则 |
|---|---|
| App | 跨领域公开能力,校验与业务编排;同领域用 app.publish,外部用 news.article.publish |
| Domain | 同领域内部业务规则;纯计算优先放这里,不承接数据库/外部 I/O |
| Model | 存储字段、索引、关系、迁移、Seed、静态 SQL;同领域 App 使用 ORM |
| Port | 外部能力的类型、操作与失败合同 |
| Adapter | 实现 Port;私有,不反调 App/Domain/Model |
| API | HTTP/CMD 入口绑定与自动 REST,无业务函数体 |
| Job | 持久任务声明,绑定同领域 App |
| Test | 文件同名、零输入零输出的唯一测试入口 |
不擅自保留旧实现
默认只实现当前接口和语法。用户没有明确要求兼容时,不保留旧参数、旧配置读取、旧接口、别名或备用实现,不用“以后可能用到”作为保留理由。
改接口时,查清本项目调用方,一起修改实现和必要测试,删除本次废弃路径。外部调用方无法一起修改时,说明影响,由用户决定迁移安排;不能擅自加一套兼容流程。
项目通过 dever.version 使用某个编译器版本,是正常的版本选择,不要求业务代码同时支持新旧两套行为。业务明确要求的失败恢复也不是旧版本兼容,但必须有真实依据、明确结果和相应验证。
文件与函数组织
简单领域保留角色单文件;复杂角色可以拆为 app/publish.dever、app/search.dever 等内聚主题,调用名不带文件名。非 API topic 目录不递归。App/Domain/Port/Adapter/Job 单文件与同名目录二选一,选择目录时至少有两个内聚主题文件;Model/API 主文件与 topic 目录可按编译器合同共存,不受这个最少文件数限制。只有 api/ 可按站点和资源继续嵌套,目录会影响 URL。
一个文件可以包含多个相关类型、函数和分句。按完整业务主题命名,不机械地一个函数一个文件,也不为尚不存在的需求创建空 App、Domain、Port 或 Adapter。
抽取函数应表达独立业务规则、维护明确的不变量、复用已有逻辑,或隔离难以理解的复杂流程。只使用一次的函数也可以有这些作用;不能只因几行代码能起名字就再加一层。职责内聚的代码不按行数强拆,也不通过把所有逻辑塞进一个长函数来规避碎片化。
| 情况 | 处理方式 |
|---|---|
parse_id 重新手写数字解析并把失败悄悄变成 0 |
复用已有解析、名义 ID 和明确失败合同,不复制算法 |
trim_name 只原样转发一次标准 trim,没有新增合同 |
在使用处调用标准能力 |
page_slice 重复 Model 已有的分页规则 |
使用 Model 分页,不在各领域复制一份 |
publish 组织发布条件、状态变化和后续任务 |
保留完整 App 能力,把内聚业务规则放同领域 Domain |
| 单次使用的规则需要独立表达有效期或状态不变量 | 可以抽取并按业务含义命名 |
| 两处相似代码承担不同规则、未来变化原因不同 | 保持各自归属,不为表面相似增加通用调度层 |
相同规则先复用现有所有者;只有真实共享的逻辑才抽象。跨领域共享能力由负责该业务的 App 提供,不直接调用对方私有实现,不为缩短调用名建立空转发链。普通 CRUD 直接用 Model 和 rest model;需要超出 Model 字段合同的输入转换、业务校验、关联操作或输出合同才增加 App 编排,具体见 应用规则。
配置与硬编码
| 数据或规则 | 归属 |
|---|---|
| 随部署变化的服务地址、端口、数据库连接、凭据 | config/setting.json,不增加环境变量配置,不提交真实秘密 |
| 当前用户、租户、权限及资源归属 | 可信认证上下文、核心授权与 Model 策略,不接受客户端自报身份 |
| 有固定业务含义的状态、范围和规则 | 类型、互斥分句、字段约束或内聚 Domain 定义,复用同一规则 |
| 需要用户或运营维护的业务数据 | 项目 Model 与相应业务流程,不把具体记录写进源码分支 |
| 测试 ID、样例地址和固定结果 | 测试夹具,不作为生产失败时的兜底 |
禁止按某个账号、租户、记录的裸 ID、名字或地址开特例,例如用 user_id == 1 代替管理员授权。普通查询参数可以标识目标记录,但不能因此成为操作者身份或访问权限。
避免硬编码不等于搬走所有字面量。局部的 0、1、提示文字和稳定协议常量可以保留;有业务意义、难以理解或反复出现的值由现有类型或领域定义表达。只有确需运行时变化的值才增加配置。官方发行源是工具链固定合同,不能塞进业务 setting.json。
禁止绕过检查
- 不添加假入口、无意义调用或仅供测试触达的调用,掩盖生产入口不可达的 App/Domain。删除本次废弃代码或接通真实业务调用链。
- 不把重复业务逻辑转移到 Adapter、Python、JS 或 Go 来避开 Dever 检查。外部实现的质量同样需要审查。
- 不吞掉失败、用空值或假数据冒充成功;有业务依据的恢复须使用明确结果合同,并验证恢复行为。
- 不为通过检查擅自改
dever.api、删失败测试、弱化断言或关闭权限、类型及沙箱限制。确需改变公开合同,先说明调用方影响,在用户授权范围内同步实现、基线与测试。 - 普通应用开发不修改编译器或核心库来容纳违规代码;语言或核心维护须有相应任务授权。只处理当前改动相关问题,不借机重写无关代码。
测试与检查
test/hello/greeting/greet.dever:
源码:模板测试
greet() () {
message = app.greet("Dever")
assert_eq(message, "Hello, Dever")
}
测试只执行文件同名函数,不需要 main 或注解。assert 接受 Bool,assert_eq 要求同类型。业务失败用 result(call) 和 choice 分句断言。模板中的字符串以生成文件为准。
测试可调用同领域 App/Domain,跨领域只调用 App,不能直接写 Model CRUD。涉及 Model 时使用临时 SQLite,否则不需要数据库。命令会读取 setting.json 和锁中的依赖信息,但测试不连接部署数据库,也不使用项目 data。
常用命令:fmt --check 查格式,check 查语法/类型/目录/分句/配置,api 输出 API 合同,test 运行应用套件,run 开发执行,build 生成独立程序。不要发明 run api 或 dever init。
选一个测试运行:
dever test my_app --filter hello.greeting.greet --timeout-ms 5000
--filter 可以写组件、组件.领域或组件.领域.用例。news.article 不会选中 news.article_extra,不支持子串或通配符。筛选后仍检查全部源码;没有匹配项就报错,未执行用例显示 not run。
每例默认限时 60 秒,不含编译时间。--timeout-ms 必须是正整数毫秒;选项不能重复。超时后回收本例进程及后代,记录失败并继续下一例,最终命令返回失败。
验证范围
| 检查 | 能证明的范围 |
|---|---|
dever fmt <root> --check |
源码格式;不证明类型或业务正确 |
dever check <root> |
检查语法、类型、目录、配置和已有 API 基线;也检查业务入口无法调用到的代码及确定的冗余。不运行应用 |
dever test <root> [--filter ...] [--timeout-ms ...] |
检查全部源码,实际执行所选测试断言(默认全部);不证明生产入口完整 |
dever run/build |
通过与 check 相同的应用硬检查,再准备本地编译资源并执行或编译;成功不等于部署或性能验收 |
| AI 审查 | 业务含义、职责拆分、复用、硬编码与验证范围,不能替代程序检查 |
check/run/build 会拒绝这些 App/Domain 问题(C016):
- 接口、命令和后台任务等生产入口都调用不到的函数。
- 符合现有判定条件的私有空转发和 Model CRUD 包装。
- 生产入口可达、同一源码文件内契约和实现完全相同、没有副作用的私有函数。
- 生产入口可达的函数内,参数未变且前一次结果仍可复用的重复纯调用。直接复用已有结果;参数或结果被修改时不按这条拒绝。
这些规则不强迫合并有副作用、契约不同或跨文件的私有实现,也不能识别所有无用代码。
check 不准备 Worker 或运行资源,不生成原生程序,也不启动服务或数据库。test 只检查自己的入口和 Port fake,不套用生产应用硬检查;测试调用不能证明函数被实际业务使用,因此还要运行 check。
未使用赋值、部分多余条件、重复私有实现、重复纯调用和空转发还可能产生警告(W001–W005)。普通库和测试保留这些警告;应用满足上述条件时报 C016,同一源码证据的对应警告不重复显示。其他警告仍会显示,警告本身不使命令失败,但必须审查并处理本次引入的问题。编译器不能判断所有业务硬编码;skill 和 AGENTS 的要求也不自动变成编译错误。
按改动范围和用户授权选择检查。新模板可运行全部测试;已有大项目优先定向测试,不默认启动服务、接真实数据库或压测。未运行的必要检查应说明原因,不能报成通过。
已有 dever.api 时保留接口基线。需要比较新接口时,用 dever api <root> --output <新文件> 导出,不直接覆盖原文件。
源码放 module/,测试放 test/,部署配置放 config/,运行数据放 data/。生成程序从二进制同级 config/setting.json 读取配置。应用无需打包编译器;使用外部 Lib 时相关 runtime/依赖由 build 嵌入。只读 check 不证明真实数据库或部署验收。
核心与业务的区别
源码仓库 中,crates/dever-core 负责语言检查/编译,crates/dever-runtime 负责运行时,crates/dever-cli 负责命令,library/ 是随版本提供的标准 Dever 源码。维护中的完整业务示例是 examples/cms/dever 与 examples/cms/md;examples/old 是历史回归,不作新项目模板。
开发普通应用无需复制或修改这些核心目录。可复用业务组件作为 Dever Package;外部 Python/JS/Go 生态称 Lib。多个项目共享机器安装的 Dever。