跳至主要内容

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 部分的函数最多接受 2 个必填参数,再加上(如果需要)一个选项对象(总共最多 3 个)。

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

    如果只有一个可选参数,并且将来不太可能添加更多可选参数,则该参数可以不在选项对象中。

  3. “options”参数是唯一一个作为普通“Object”的参数。

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

    • 具有不同的原型(例如 ArrayMapDateclass MyThing)。
    • 具有众所周知的符号属性(例如,具有 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 块中禁止使用。

代码字符串文字应使用反引号 (`) 而不是引号括起来。例如

/** 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.tsstd 模块的测试应该放在 std/tests 中,因为它们有不同的上下文;否则,它应该只是被测试模块的同级目录。

单元测试应该明确。

为了更好地理解测试,函数应该被正确命名,因为它在整个测试命令中被提示。例如

foo() returns bar object ... ok

测试示例

import { assertEquals } from "https://deno.land/[email protected]/assert/mod.ts";
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";
}

std

不要依赖外部代码。

https://deno.land/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]+$/;

当名称为 `camelCase` 或 `PascalCase` 时,即使名称的各个部分是首字母缩略词,也始终遵循其规则。

注意: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;
}