# Nodejs Code Style

> Use when writing, reviewing, or refactoring Node.js/TypeScript backend code and the task needs strict code style, concise implementation, naming discipline, type-driven design, discriminated unions, runtime validation, error modeling, async control, refactoring judgment, readable main flow, and senior-engineer code cleanliness independent of any specific framework.

- Skill: `ly0o0o/nodejs-code-style` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add ly0o0o/nodejs-code-style`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ly0o0o/nodejs-code-style/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: ly0o0o (https://skillmd.com/u/ly0o0o)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/ly0o0o/nodejs-code-style

---


# Node.js Code Style

## 定位

写像老练工程师写的 Node.js/TypeScript：代码短，但不是压缩；抽象少，但边界准确；主流程顺，但错误和状态不糊；类型不是装饰，而是把业务约束钉在代码里。

这个 Skill 管代码级设计和语法纪律。架构选择、模块边界、DDD、ports/adapters、任务队列和运行生命周期，使用 `nodejs-backend-engineering`。

## 先后顺序

1. 正确性和业务语义
2. 数据安全、权限、幂等、额度、租户隔离
3. 类型表达和运行时校验
4. 错误上下文和可观测性
5. 主流程可读
6. 紧凑和少抽象

简洁不是少写几行，而是让读者少猜。任何“省事写法”如果隐藏失败、状态、权限或副作用，都不算优雅。

## 工作方式

1. 先读相邻代码：命名、错误类型、schema、logger、测试风格以项目为准。
2. 找边界：API body、query、env、DB JSON、queue message、cache、第三方响应都不可信。
3. 先建模再写流程：有限状态用 union；成功/失败分支显式；ID 和业务值别全用 `string`。
4. 写主流程：入口做校验，业务函数拿干净输入，早返回减少嵌套。
5. 收紧抽象：只保留能隐藏复杂度、保护不变量、减少真实重复的函数或模块。
6. 自查验证：type-check/test/lint/build 按项目最小必要命令跑；不能跑就说明原因。

## 代码规则

### 1. 让非法状态尽量不可表示

不要靠注释解释“这个字段只在 completed 时有”。用 discriminated union 表达状态，让调用方必须处理分支。

```ts
type TaskResult =
  | { status: 'pending'; taskId: TaskId }
  | { status: 'running'; taskId: TaskId; startedAt: Date }
  | { status: 'completed'; taskId: TaskId; collectionId: CollectionId }
  | { status: 'failed'; taskId: TaskId; errorCode: string; message: string };
```

状态、错误码、provider、任务类型、权限类型都优先用有限集合。字符串散落判断是后端腐烂的早期信号。

### 2. 类型边界要窄，运行时边界要硬

TypeScript 不能校验线上输入。外部数据先 `unknown`，在边界用项目已有 schema/parser 校验，之后再进入 service/domain。

```ts
const input = CreateTaskSchema.parse(await req.json());
return taskService.createTask(input);
```

不要让业务函数到处接 `Request`、`Context`、`process.env`、第三方原始 response 或未解析 JSON blob。

### 3. 主流程连续，复杂度下沉

主流程应读得出业务动作顺序；复杂度放到有名字的深模块里，而不是拆成一堆浅 helper。

好函数边界：

- 隐藏外部协议、重试、分页、签名、响应清洗。
- 保护业务不变量，如配额预留、状态流转、金额计算、权限判定。
- 独立表达算法、规则、排序、合并、去重。
- 消除真实重复，且名字比代码更清楚。

坏函数边界：

- 只调用一次，名字只是复述实现。
- 把连续业务流程拆碎，读者必须来回跳。
- 为“以后可能复用”提前抽象。
- 为单测制造生产接口。

### 4. 命名必须带业务重量

- ID 写全：`userId`、`taskId`、`collectionId`，不要跨函数传裸 `id`。
- 布尔值用 `is/has/can/should`，避免反向布尔名。
- 数组用复数，map/set 带 key 语义，如 `tasksByCollectionId`。
- 函数动词准确：`fetch` 远程取，`load` 本地/DB 取，`create` 新建，`reserve` 预留，`consume` 扣减，`release` 回退，`enqueue` 投递。
- 少用 `data/info/result/item/temp/manager/helper/common/core`，除非局部语境极小。

### 5. 错误是接口的一部分

`catch` 只能做四件事：补上下文、分类、补偿、重新抛出。禁止空 `catch`，禁止失败后返回空数组/空对象装作成功，除非业务明确允许且有日志/指标。

```ts
try {
  await quotaRepository.consume(reservationId);
} catch (cause) {
  throw new AppError('Failed to consume reserved quota', {
    cause,
    code: 'QUOTA_CONSUME_FAILED',
    context: { reservationId, userId },
  });
}
```

对外返回稳定错误码；日志保留内部 cause 和定位字段；响应不要泄露 secret、token、完整敏感 payload。

### 6. async 要有超时、取消和并发语义

- 无依赖才 `Promise.all`。
- 允许部分失败才 `Promise.allSettled`，且逐项保留上下文。
- 第三方调用、批处理、流式处理要考虑 timeout、AbortSignal、限流和 backpressure。
- 有顺序、事务、锁、额度、幂等或短路语义时，不要机械并发。
- 后台任务必须能解释 retry、补偿、重复投递的结果。

### 7. 条件表达要显性

- 业务分支优先早返回或 `switch`。
- 有限状态分支用 exhaustive check，不要默认分支吞掉未来状态。
- 三元只用于短小纯表达式；嵌套三元和有副作用三元要拆。
- 可选链只用于确实可选的数据。已校验必传对象缺失，应暴露错误。

### 8. 注释记录约束，不复述代码

应该注释：

- 业务规则背后的原因。
- 历史数据、第三方怪癖、协议兼容。
- 幂等、事务、缓存、锁、重试的不变量。
- 不直观算法、正则、时间窗口。

不应该注释：

- `// get user by id`
- `// loop items`
- `// return result`

### 9. 依赖和文件改动要克制

新增依赖前先查标准库和项目已有依赖。只为几行代码引入一个包，通常不是好交易。

只动任务相关文件。不要顺手重排、批量格式化、重命名、删除无关注释。发现旁边问题，记录，不混入当前改动。

## 按需读取

- `references/type-design.md`：状态、错误、ID、Result、exhaustive check 的 TypeScript 建模。
- `references/refactoring-and-smells.md`：基于 Fowler、Ousterhout、Google 工程实践的重构判断和坏味道。
- `references/real-code-patterns.md`：Node.js 后端常用代码片段，包含边界校验、错误映射、并发、超时和日志。

## 交付自查

- 外部输入已校验或窄化。
- 状态和失败分支不是散落字符串。
- 没有无意义 `any`、空 `catch`、生产 `console.log`。
- 没有用可选链掩盖必传对象缺失。
- 没有无业务语义兜底。
- 没有把连续主流程拆成浅 helper。
- 错误有 cause/context，日志有定位字段且不泄露敏感数据。
- 改动跑过最小必要验证，或明确说明不能跑的原因。

