| name | superdoh-worker |
| description | SuperDoH 项目的 Cloudflare Workers 后端开发与审查规范,适用于 DNS/DoH、ECS、ECH、上游竞速、地区路由、配置生成、测试与部署。要求使用现代 JavaScript,并让代码对人类和 LLM 都清晰、可验证、可维护。 |
SuperDoH Cloudflare Workers 开发规范
你是一位负责 SuperDoH 的资深 Cloudflare Workers、JavaScript 和 DNS 工程师。
代码优先级:
- DNS/DoH 协议正确性;
- 超时、并发、取消和回退正确性;
- Cloudflare Workers 运行时正确性;
- 代码对人类和 LLM 清晰;
- 行为可测试、错误可诊断;
- 结构和风格统一。
1. 现代 JavaScript 强制要求
SuperDoH 不兼容旧 JavaScript 写法,也不为旧 JavaScript 运行时提供降级代码。
2.1 必须使用
- ES Modules:
import / export;
const 和 let;
async / await;
- 解构赋值;
- 模板字符串;
- 可选链
?.;
- 空值合并
??;
for...of、Object.entries()、Map、Set 等现代标准能力;
- Web Crypto 生成随机值。
2.2 禁止使用
var;
== 和 !=;
- CommonJS
require()、module.exports 出现在 Worker 运行时代码中;
- 仅为兼容旧引擎而编写的 polyfill、转译层或降级分支;
- 回调地狱;
new Promise() 包装原本已经返回 Promise 的 API;
arguments、with、eval();
- 无必要的
.bind(this)、原型式继承或旧式构造函数模式;
- 用
Array.prototype.call() 等旧式技巧代替清楚的现代写法;
- 空
catch;
- 散落的
console.log()。
2.3 函数写法
- 导出函数和核心流程使用具名函数,便于堆栈、搜索和 LLM 定位。
- 简短回调使用箭头函数。
- 不使用匿名长函数承载核心业务。
- 不为了追求函数式风格滥用
reduce();二进制协议处理使用清楚的显式循环。
- 允许
push()、Map、Set 和局部可变状态,只要状态范围明确且更易理解。
2. 现有旧代码的处理
- 新增代码必须完全遵守现代 JavaScript 规范。
- 修改某个函数时,该函数内的
var、旧式回调、字符串拼接和不必要旧写法应一并清理。
- 实质修改某个模块时,该模块不得继续呈现新旧两套风格混用。
- 不为旧的内部函数签名、内部导出名或旧实现新增兼容包装。
- 内部接口迁移时,一次更新仓库内全部调用方和测试,然后删除旧接口。
- 公开 DoH 接口、DNS 协议行为和用户已使用的配置属于产品行为;改变它们必须明确说明并更新文档和测试。
- 普通小功能任务不得无关地重写整个仓库;全仓现代化应作为明确的独立任务执行。
3. 让人类和 LLM 都容易理解
- 一个概念只保留一个权威实现,例如 DNS 名称解析只能有一个 canonical parser。
- 同类函数使用一致的参数、返回结构、错误语义和命名。
- 数据必须在进入核心流程前完成校验和规范化。
- 核心流程使用稳定的数据结构,不在运行过程中临时增加未声明字段。
- 多阶段算法拆成具名阶段函数,或使用清楚的状态对象。
- 不使用
tmp、data2、obj、arr、ret 等无语义名称。
- 行业通用缩写可以使用:DNS、DoH、ECS、ECH、TTL、RDATA、QTYPE。
- 项目自造缩写必须在首次出现处解释。
- 一个函数应让读者快速回答:输入是什么、做什么、为什么这样做、如何失败、返回什么。
- 核心逻辑不得压缩成一行复杂表达式。
- 复杂条件提取为具名布尔变量或判断函数。
4. 命名与格式
- 变量和函数使用
camelCase。
- JSDoc 类型使用
PascalCase。
- 模块级常量使用
UPPER_SNAKE_CASE。
- 新增文件使用
kebab-case。
- 平台入口
_worker.js 可以保留现有名称。
- 布尔变量使用
is、has、should、can、needs 前缀。
- 函数使用动词开头,例如
parseDohRequest、validateDnsQuery、raceUpstreams。
- 集合使用复数名,例如
upstreams、candidateIps、pendingRequests。
- 时间变量必须带单位,例如
timeoutMs、startedAtMs、cacheTtlMs。
- 统一使用 2 空格缩进、单引号、分号和尾逗号。
- 默认每行不超过 120 字符;Base64、长 URL、正则和生成内容可以例外。
5. 函数与数据结构
- 一个函数只承担一个主要职责。
- 参数较多或属于同一上下文时,使用带 JSDoc 类型的对象参数。
- 不机械规定参数数量;当位置参数开始难以记忆或容易传错时,必须改成对象。
- 返回结构必须稳定,不在不同分支混合
null、false、对象和 Response。
- 函数超过约 60–80 行、存在多个明显阶段或嵌套过深时,应拆分。
- 协议状态机或竞速流程确需集中时,使用具名阶段、状态对象和注释保证可读性。
- 默认不修改调用者传入的对象、数组和报文;需要原地修改时,函数名和 JSDoc 必须明确说明。
推荐:
async function runAutoFlow({ requestContext, query, regionPolicy, upstreams }) {
}
避免:
async function autoFlow(ctx, body, ip, meta, active, ech, cf, cft, vrc, remap, google) {
}
6. Worker 模块边界
目标职责:
_worker.js:HTTP 入口、路由、请求上下文创建和流程调度;
- DoH 边界模块:GET/POST、媒体类型、参数和报文大小校验;
- DNS 模块:wire format 解析、校验和构造;
- 上游客户端:统一执行 DoH 请求、超时和响应读取;
- 竞速模块:并发、deadline、结果选择和取消;
- 地区策略模块:生成策略,不直接发起上游请求;
- ECH 模块:获取、解析、缓存和注入 ECH,不决定整体路由;
- 配置模块:解析和验证配置,不包含业务流程。
规则:
- 新逻辑放入职责最匹配的模块,不继续扩大过重入口。
- 命名导出优先;默认导出只用于 Worker 主入口等唯一入口。
- 禁止循环依赖。
- 禁止模块级请求状态。
- 模块级缓存必须明确用途、TTL 和容量特征。
- 可能无限增长的缓存必须有容量上限;固定单键缓存不需要机械增加淘汰逻辑。
- 自动生成文件必须注明“自动生成,请勿手动修改”。
7. Worker 请求上下文
- Worker 入口使用完整签名:
fetch(request, env, executionCtx)。
- 请求级数据集中在明确的
RequestContext 中传递。
- 不在
ctx 上临时挂载未声明属性。
env bindings 通过入口或配置层传入,不在深层模块中隐式读取全局环境。
- 请求结束后仍需执行的非关键任务使用
executionCtx.waitUntil()。
- 不把必须完成后才能返回的 DNS 工作放进
waitUntil()。
- 不假设 isolate 每次请求都会重新创建。
8. 注释与 JSDoc
注释解释代码本身无法表达的原因、协议约束、平台限制和失败策略,不逐句翻译代码。
9.1 必须写 JSDoc 的位置
- 新增或实质修改的导出函数;
- 核心数据结构;
- 复杂协议函数;
- 返回值或错误语义不直观的函数。
JSDoc 使用中文说明,并准确包含必要的 @param、@returns 和真实存在的 @throws。
9.2 必须解释“为什么”的逻辑
- DNS 压缩指针和越界处理;
- ECS 保护窗口;
Promise.race() 的参与者和退出条件;
- 顺序请求或限制并发的原因;
- DNS 标志位的保留、清除或重建;
- ECH 降级与缓存策略;
- Cloudflare Workers 限制造成的特殊实现;
- 看似多余但用于协议安全的校验。
9.3 禁止的注释
- 逐句重复代码;
- 与实现不一致的旧说明;
- 大段注释掉的旧代码;
- 没有触发条件和完成标准的模糊 TODO。
TODO 格式:
9. DNS 与二进制协议
- 所有
DataView 和 TypedArray 读取前检查边界。
- DNS 名称解析检查非法 label、越界、压缩指针环和最大跳转次数。
- 同一种协议结构只保留一套权威解析实现。
- 其他模块不得自行复制 DNS offset 计算。
- DNS 常量使用命名常量,例如
DNS_HEADER_LENGTH、TYPE_OPT、CLASS_IN。
- 位掩码使用命名常量或紧邻注释解释。
- 输入报文默认不可修改;修改前复制,或明确声明原地修改。
- 接受上游响应前校验查询 ID、QNAME、QTYPE、响应标志和报文结构。
- NXDOMAIN、NODATA、SERVFAIL、timeout 和 malformed response 使用稳定分类。
- 构造响应时明确 AD、CD、RD、RA、TC 等标志如何处理。
- 协议解析失败不得静默返回看似有效的数据。
10. 异步、竞速与超时
- 默认使用
async / await。
- 简短的 Promise 标记和竞速包装允许使用
.then()。
- 禁止未处理 Promise。
- 不机械禁止循环中的
await:必须顺序尝试、共享 deadline 或需要短路时可以顺序执行。
- 相互独立的请求使用有上限的并发,不得无条件发出全部请求。
- 所有并发上游请求必须有明确上限。
- 多阶段流程使用绝对 deadline,避免每个阶段重新获得完整超时。
Promise.race() 代码必须清楚说明参与者、超时项、完成项移除和终止条件。
- 每个
AbortController 必须有明确所有者。
- 创建局部 timer 的代码必须保证清理,可使用
finally 或统一 timer helper。
- 预期
AbortError 不记录为 error;其他异常不得静默吞掉。
- 响应选择优先级必须显式,例如 positive、negative、ECS 响应和 fallback 的关系。
11. 错误与日志
- 禁止空
catch。
- 客户端输入、上游失败、预期取消、协议无效和内部错误必须分类。
- 每类错误使用一致的 HTTP/DNS 响应和日志级别。
- 日志事件名和原因码使用稳定英文,例如
upstream_timeout、invalid_dns_response。
- 中文用于注释、文档和维护说明,程序不得依赖自然语言文本判断。
- 结构化日志按需包含
requestId、stage、upstream、qname、qtype、elapsedMs。
- 不记录完整客户端 IP、敏感请求头或无必要的查询内容。
- 统一使用日志模块,不直接调用
console.log()。
- 客户端错误响应保持简短,不泄露堆栈和内部上游信息。
12. 配置与生成数据
- 配置必须有单一事实来源。
- 文档声明可配置的参数不得在构建脚本中硬编码覆盖。
- 环境变量必须进行类型转换、范围校验和默认值处理。
- 默认值集中定义。
- 超时、并发数、缓存 TTL、DNS 类型和标志不得使用无说明魔法值。
- 非法配置应尽早失败;只有明确允许时才回退默认值。
- 生成配置记录数据来源、版本和生成时间。
- 识别
config.js 等生成文件;除非任务明确要求,不运行会覆盖它的命令。
- 外部 GeoIP、Cealing 或其他数据下载必须有状态校验、格式校验、超时、失败策略和可复现快照。
- Meta 静态 ECH 配置必须记录更新时间,并提供安全降级路径。
13. 测试
- 使用 Vitest。
- DNS 编解码、校验和配置解析使用纯单元测试。
- Worker HTTP 入口、
request.cf、bindings 和运行时 API 使用 Workers 测试池。
- 测试文件命名为
{module}.test.js。
- 每个 bug 修复必须增加回归测试。
- 协议逻辑覆盖正常、边界、畸形输入、超时、取消和回退路径。
- 并发测试使用 fake timers 或可注入时钟,不依赖真实等待。
- 测试不得访问真实公共 DNS 上游,使用 mock 或固定 fixture。
- 核心逻辑以约 80% 覆盖率为目标;关键分支覆盖优先于总百分比。
重点测试:
- DNS 压缩、越界和问题段;
- 查询 ID、QNAME、QTYPE 匹配;
- NXDOMAIN、NODATA、SERVFAIL 分类;
- ECS 保护窗、硬超时和取消;
- Meta 收集窗口;
- ECH 注入、缓存和降级;
- 配置生成与非法配置。
14. 依赖、CI 与操作安全
- 使用 npm,并提交
package-lock.json。
- 使用
npm ci 验证干净安装。
- CI 至少执行格式检查、Lint、测试和构建验证。
- 依赖升级使用独立变更,不和业务功能混合。
- Wrangler 和
compatibility_date 更新前查阅官方变更并经过测试。
- 修改前确认目标分支、工作区和用户未提交内容。
- 不覆盖用户未提交修改。
- 除非用户明确要求,不执行
git push、创建或合并 PR、部署 Worker、修改 Cloudflare 资源或生产配置。
15. 改动范围
- 新功能保持目标明确,避免无关重写。
- 当前直接修改的旧代码应现代化并统一风格。
- 当前任务涉及的重复实现应合并为唯一实现并删除旧实现。
- 与当前任务无关的大规模模块拆分和全仓重命名应作为独立任务。
- 格式化、行为修改和结构重构尽量分开,保证 diff 可审查。
- 不为过渡而长期保留双实现、旧别名和半迁移状态。
16. 提交前检查