跳转到主要内容
在本页

Deno 风格指南

注意

请注意,这是 Deno 运行时和 Deno 标准库中**内部运行时代码**的风格指南。这不是面向 Deno 用户的通用风格指南。

仓库中的大多数模块都应包含以下版权声明:

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

如果代码来源于其他地方,请确保该文件具有正确的版权声明。我们只允许使用 MIT、BSD 和 Apache 许可的代码。

文件名使用下划线,而不是连字符 跳转到标题

示例:使用 file_server.ts 而不是 file-server.ts

为新功能添加测试 跳转到标题

每个模块都应包含或附带对其公共功能的测试。

TODO 注释 跳转到标题

TODO 注释通常应在括号中包含问题编号或作者的 GitHub 用户名。例如:

// TODO(ry): Add tests.
// TODO(#123): Support Windows.
// FIXME(#349): Sometimes panics.

不鼓励使用元编程,包括使用 Proxy 跳转到标题

即使这意味着需要编写更多代码,也要明确表达。

在某些情况下,使用此类技术可能是有意义的,但在绝大多数情况下,并非如此。

包容性代码 跳转到标题

请遵循 https://chromium.googlesource.com/chromium/src/+/HEAD/styleguide/inclusive_code.md 中列出的包容性代码指南。

Rust 跳转到标题

遵循 Rust 规范,并与现有代码保持一致。

TypeScript 跳转到标题

代码库的 TypeScript 部分是标准库 std

使用 TypeScript 而不是 JavaScript 跳转到标题

不要使用文件名 index.ts/index.js 跳转到标题

Deno 不会以特殊方式处理 "index.js" 或 "index.ts"。使用这些文件名会暗示它们可以在模块说明符中省略,而实际上并非如此。这会造成混淆。

如果代码目录需要默认入口点,请使用文件名 mod.ts。文件名 mod.ts 遵循 Rust 的约定,比 index.ts 更短,并且没有任何关于其工作方式的先入为主的观念。

导出函数:最多 2 个参数,其余参数放入选项对象 跳转到标题

设计函数接口时,请遵循以下规则。

  1. 作为公共 API 一部分的函数接受 0-2 个必需参数,以及(如有必要)一个选项对象(因此总共最多 3 个参数)。

  2. 可选参数通常应放入选项对象中。

    如果只有一个可选参数,并且我们似乎无法想象将来会添加更多可选参数,那么不在选项对象中的可选参数可能是可以接受的。

  3. “options” 参数是唯一的常规“对象”参数。

    其他参数可以是对象,但它们必须与运行时的“普通”对象区分开来,方法是:

    • 一个可区分的原型(例如 ArrayMapDateclass MyThing)。
    • 一个众所周知的 Symbol 属性(例如,具有 Symbol.iterator 的可迭代对象)。

    这允许 API 以向后兼容的方式演进,即使选项对象的位置发生变化。

// BAD: optional parameters not part of options object. (#2)
export function resolve(
  hostname: string,
  family?: "ipv4" | "ipv6",
  timeout?: number,
): IPAddress[] {}
// GOOD.
export interface ResolveOptions {
  family?: "ipv4" | "ipv6";
  timeout?: number;
}
export function resolve(
  hostname: string,
  options: ResolveOptions = {},
): IPAddress[] {}
export interface Environment {
  [key: string]: string;
}

// BAD: `env` could be a regular Object and is therefore indistinguishable
// from an options object. (#3)
export function runShellWithEnv(cmdline: string, env: Environment): string {}

// GOOD.
export interface RunShellOptions {
  env: Environment;
}
export function runShellWithEnv(
  cmdline: string,
  options: RunShellOptions,
): string {}
// BAD: more than 3 arguments (#1), multiple optional parameters (#2).
export function renameSync(
  oldname: string,
  newname: string,
  replaceExisting?: boolean,
  followLinks?: boolean,
) {}
// GOOD.
interface RenameOptions {
  replaceExisting?: boolean;
  followLinks?: boolean;
}
export function renameSync(
  oldname: string,
  newname: string,
  options: RenameOptions = {},
) {}
// BAD: too many arguments. (#1)
export function pwrite(
  fd: number,
  buffer: ArrayBuffer,
  offset: number,
  length: number,
  position: number,
) {}
// BETTER.
export interface PWrite {
  fd: number;
  buffer: ArrayBuffer;
  offset: number;
  length: number;
  position: number;
}
export function pwrite(options: PWrite) {}

注意:当其中一个参数是函数时,您可以灵活地调整顺序。参见示例,例如 Deno.serveDeno.testDeno.addSignalListener。另请参阅这篇文章

导出所有用作导出成员参数的接口 跳转到标题

当您使用包含在导出成员的参数或返回类型中的接口时,您应该导出所使用的接口。这是一个例子

// my_file.ts
export interface Person {
  name: string;
  age: number;
}

export function createPerson(name: string, age: number): Person {
  return { name, age };
}

// mod.ts
export { createPerson } from "./my_file.ts";
export type { Person } from "./my_file.ts";

最小化依赖关系;不要创建循环导入 跳转到标题

尽管 std 没有外部依赖项,但我们仍然必须小心保持内部依赖项简单且易于管理。尤其要注意不要引入循环导入。

在某些情况下,可能需要内部模块,但其 API 不稳定或不应被链接。在这种情况下,请在其前面加上下划线。按照惯例,只有其自身目录中的文件才能导入它。

对导出的符号使用 JSDoc 跳转到标题

我们力求提供完整的文档。理想情况下,每个导出的符号都应该有一行文档。

如果可能,JSDoc 使用单行。例如

/** foo does bar. */
export function foo() {
  // ...
}

文档易于阅读很重要,但也需要提供额外的样式信息,以确保生成的文档具有更丰富的文本格式。因此,JSDoc 通常应遵循 Markdown 标记来丰富文本。

虽然 Markdown 支持 HTML 标签,但在 JSDoc 块中禁止使用 HTML 标签。

代码字符串字面量应该用反引号 (`) 括起来,而不是引号。例如

/** Import something from the `deno` module. */

不要记录函数参数,除非它们的意图不明显(尽管如果它们的意图不明显,也应该考虑 API)。因此,通常不应使用 @param。如果使用 @param,则不应包含 type,因为 TypeScript 已经是强类型的。

/**
 * Function with non-obvious param.
 * @param foo Description of non-obvious parameter.
 */

应尽可能减少垂直间距。因此,单行注释应写成

/** This is a good single-line JSDoc. */

而不是

/**
 * This is a bad single-line JSDoc.
 */

代码示例应使用 Markdown 格式,如下所示

/** A straightforward comment and an example:
 * ```ts
 * import { foo } from "deno";
 * foo("bar");
 * ```
 */

代码示例不应包含其他注释,并且不得缩进。它已经在注释内。如果它需要进一步的注释,那它就不是一个好例子。

使用指令解决 linting 问题 跳转到标题

目前,构建过程使用 dlint 来验证代码中的 linting 问题。如果任务需要的代码不符合 linter 规范,请使用 deno-lint-ignore <code> 指令来抑制警告。

// deno-lint-ignore no-explicit-any
let x: any;

这可以确保持续集成过程不会因 linting 问题而失败,但应谨慎使用。

每个模块都应该附带一个测试模块 跳转到标题

每个具有公共功能的模块 foo.ts 都应该附带一个测试模块 foo_test.ts。由于上下文不同,std 模块的测试应该放在 std/tests 中;否则,它应该与被测试的模块放在同一目录下。

单元测试应该清晰明确 跳转到标题

为了更好地理解测试,函数应该按照测试命令中的提示正确命名。例如:

foo() returns bar object ... ok

测试示例

import { assertEquals } from "@std/assert";
import { foo } from "./mod.ts";

Deno.test("foo() returns bar object", function () {
  assertEquals(foo(), { bar: "bar" });
});

注意:更多信息请参见 跟踪问题

顶级函数不应使用箭头语法 跳转到标题

顶级函数应该使用 function 关键字。箭头语法应该仅限于闭包。

错误示例

export const foo = (): string => {
  return "bar";
};

正确示例

export function foo(): string {
  return "bar";
}

错误信息 跳转到标题

JavaScript/TypeScript 引发的面向用户的错误信息应该清晰、简洁且一致。错误信息应该使用句子大小写,但不以句号结尾。错误信息应该没有语法错误和拼写错误,并使用美式英语编写。

注意

请注意,错误信息风格指南仍在完善中,并非所有错误信息都已更新以符合当前的风格。

应遵循的错误信息风格

  1. 信息应以大写字母开头
Bad: cannot parse input
Good: Cannot parse input
  1. 信息不应以句号结尾
Bad: Cannot parse input.
Good: Cannot parse input
  1. 信息应使用引号表示字符串值
Bad: Cannot parse input hello, world
Good: Cannot parse input "hello, world"
  1. 信息应说明导致错误的操作
Bad: Invalid input x
Good: Cannot parse input x
  1. 应使用主动语态
Bad: Input x cannot be parsed
Good: Cannot parse input x
  1. 信息不应使用缩写
Bad: Can't parse input x
Good: Cannot parse input x
  1. 提供附加信息时,信息应使用冒号。切勿使用句号。其他标点符号可根据需要使用
Bad: Cannot parse input x. value is empty
Good: Cannot parse input x: value is empty
  1. 附加信息应描述当前状态,如果可能,还应以肯定的语气描述期望状态
Bad: Cannot compute the square root for x: value must not be negative
Good: Cannot compute the square root for x: current value is ${x}
Better: Cannot compute the square root for x as x must be >= 0: current value is ${x}

标准库 (std) 跳转到标题

不要依赖外部代码。 跳转到标题

https://jsr.deno.org.cn/@std 旨在提供所有 Deno 程序都可以依赖的基本功能。我们希望向用户保证,此代码不包含可能未经审查的第三方代码。

记录并维护浏览器兼容性。 跳转到标题

如果模块与浏览器兼容,请在模块顶部的 JSDoc 中包含以下内容

// This module is browser-compatible.

通过不使用全局 Deno 命名空间或对其进行特性测试来维护此类模块的浏览器兼容性。确保所有新的依赖项也与浏览器兼容。

优先使用 # 而不是 private 关键字 跳转到标题

在标准模块代码库中,我们更喜欢私有字段 (#) 语法而不是 TypeScript 的 private 关键字。私有字段即使在运行时也能使属性和方法保持私有。另一方面,TypeScript 的 private 关键字仅在编译时保证私有,而在运行时可以公开访问这些字段。

正确示例

class MyClass {
  #foo = 1;
  #bar() {}
}

错误示例

class MyClass {
  private foo = 1;
  private bar() {}
}

命名约定 跳转到标题

函数、方法、字段和局部变量使用`驼峰式命名法 (camelCase)`。类、类型、接口和枚举使用`帕斯卡命名法 (PascalCase)`。静态顶级项(例如 `string`、`number`、`bigint`、`boolean`、`RegExp`、静态项数组、静态键值对记录等)使用`全大写蛇形命名法 (UPPER_SNAKE_CASE)`。

正确示例

function generateKey() {}

let currentValue = 0;

class KeyObject {}

type SharedKey = {};

enum KeyType {
  PublicKey,
  PrivateKey,
}

const KEY_VERSION = "1.0.0";

const KEY_MAX_LENGTH = 4294967295;

const KEY_PATTERN = /^[0-9a-f]+$/;

错误示例

function generate_key() {}

let current_value = 0;

function GenerateKey() {}

class keyObject {}

type sharedKey = {};

enum keyType {
  publicKey,
  privateKey,
}

const key_version = "1.0.0";

const key_maxLength = 4294967295;

const KeyPattern = /^[0-9a-f]+$/;

当名称采用`驼峰式命名法`或`帕斯卡命名法`时,即使名称的组成部分是首字母缩略词,也始终遵循这些命名法的规则。

注意:Web API 使用大写首字母缩略词(`JSON`、`URL`、`URL.createObjectURL()` 等)。Deno 标准库不遵循此约定。

正确示例

class HttpObject {
}

错误示例

class HTTPObject {
}

正确示例

function convertUrl(url: URL) {
  return url.href;
}

错误示例

function convertURL(url: URL) {
  return url.href;
}