首发知乎:https://zhuanlan.zhihu.com/p/437877982
链接:Deno - A modern runtime for JavaScript and TypeScript
作者:Deno 团队
标题少说了其他人,其实这篇文章是 Deno 团队的脑力精华。
导读:全网有很多 TypeScript 的风格教程,但是都很冗长,涉及面广,也不好应用。这篇文章是我在学习 Deno 的时候看到了,这篇文章很短,英文只有 360 行,但是基本是最重要的一些问题。因为 Deno Style Guide 实际不是给初学者,而是为了给 Deno 标准库的作者准备的,与其说是面面俱到的风格约束,不如说是,往 Deno 标准库里贡献的标准。
版权页眉(Copyright Headers)
在标准仓库中的模块应该有以下的版权页眉:
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
如果代码起源于别的地方,确保文件有合适的版权页眉。我们只允许 MIT,BSD 和 Apache 协议的代码。
使用下划线作为文件名,而不使用中划线(Use underscores, not dashes in filenames.)
例子: 使用file_server.ts而不是file-server.ts.
给新功能加测试(Add tests for new features.)
每一个模块的对外功能都应该包含测试。
TODO 注释(TODO Comments)
TODO 注释应该用括号标注 issue 编号或者作者的 github 用户名。
// TODO(ry): Add tests.
// TODO(#123): Support Windows.
// FIXME(#349): Sometimes panics.
不鼓励元编程,包括 Proxy(Meta-programming is discouraged. Including the use of Proxy.)
清晰且易于理解是最重要的,哪怕会需要更多的代码。
有一些场景,可能必须要用元编程,但是绝大部分情况,是可以避免的。
Rust
遵守 Rust 的惯例,与现存代码风格一致。
TypeScript
TypeScript 主要是标准库 std 。
使用 TypeScript 代替 JavaScript(Use TypeScript instead of JavaScript)
使用术语 “module” 而不是 “library” 或者 “package”(Use the term "module" instead of "library" or "package".)
为了清晰和一致,避免词汇 “library” 和 “package”。使用 “module” 来描述一个 JS 或者 TS 文件,也只带一个 TS/JS 的代码目录。
不要使用文件名 index.ts/index.js(Do not use the filename index.ts/index.js.)
不要把 “index.js” 和 “index.ts” 弄得太特殊了。使用这些名字,似乎暗示了这些文件可以移出模块,但是实际不能。这会造成误解。
如果一个文件夹的代码需要一个默认的进入点(entry point),使用文件名mod.ts。文件名mod.ts遵守了 Rust 的习惯,比 index.ts 要短。并且这个名字没有暗示这个文件的作用。
导出函数:最多两个参数,把其他参数放入 options object(最多 3 个参数)(Exported functions: max 2 args, put the rest into an options object.)
当设计函数接口时,遵守以下原则
- 公开的函数 API 的参数,使用 0-2 个可选或者必选的参数,以及一个 options object。(所以,最多 3个参数)
- 所有的可选参数都应该放入 options object。
- 如果只有一个可选参数,也可以接受不放入 options object,单独作为一个参数。但是这么做,对于以后的扩展是不方便的。(言外之意,还是建议不这么做,因为潜在的,改这些公共库的接口是影响很大的事情。)
- option 参数是一个普通的 Object
- 其他参数也可以是 objects,但是他们应该和 ‘plain’ Object 区分开
- 一个可区分的原型(例如 Array,Map,Date,class MyThing)
- 一个大家都知道的 symbol(例如 Symbol.iterator)
- 这样可以让 API 在后续进化中保持前向兼容,即便 options object 的位置发生变化。
// BAD: 参数不是 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` 可能是一个普通的 Object 这样就
// 不能和 option 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: 超过了 3 个参数 (#1),多个可选参数 (#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: 太多参数了 (#1)
export function pwrite(
fd: number,
buffer: TypedArray,
offset: number,
length: number,
position: number,
) {}
// BETTER.
export interface PWrite {
fd: number;
buffer: TypedArray;
offset: number;
length: number;
position: number;
}
export function pwrite(options: PWrite) {}
所有的导出函数使用的 interface 也都应该导出(Export all interfaces that are used as parameters to an exported member)
当你导出一个函数的时候,这个函数使用了一些 interface,这些 interface 也应该导出。(使用者需要这些 interface!)
// 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";
最小化依赖;不要循环引用(Minimize dependencies; do not make circular imports.)
std 没有外部依赖,我们必须要小心处理内部依赖,保持其简单和可管理。注意不要引入循环依赖。
如果一个文件以下划线开头: _foo.ts,不要链接它们 (If a filename starts with an underscore: _foo.ts, do not link to it.)
一些时候,一些导出的 API 需要一些功能,但是这些功能还不足够稳定对外提供。这些时候把这些模块加一个下划线前缀,作为内部文件。按照惯例,只有这个文件夹内部的文件可以导入这些文件。
(如果你在另一个文件想使用这个文件,请复制一份。)
使用 JSDoc 描述导出的符号(Use JSDoc for exported symbols.)
我们渴求完整的文档。每一个导出的符号都应该有一个文档内容。
如果可以,使用一行 JSDoc
/** foo does bar. */
export function foo() {
// ...
}
文档要易于人类阅读,也要加一些必要的风格信息来保证自动生成的文档是富文本。所以书写 JSDoc 的时候,要符合 markdown 的规范。
虽然 markdown 支持 HTML 标签,但是我们禁止在 JSDoc 内部写这些标签。
代码提及的内容要加反引号(`)而不是引号。
/** Import something from the `deno` module. */
不要标记函数的参数,除非这个参数的意图还不足够明显(尽管这些参数意图不明显,API 还得考虑这些参数。)所以 @param 标签一般情况是禁止使用的。但是如果你必须要使用 @para ,不要加任何 Type,因为 TypeScript 已经做了更强的 type 信息。
/**
* 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 straight forward comment and an example:
* ```ts
* import { foo } from "deno";
* foo("bar");
* ```
*/
代码示例不要加额外的注释,前面已经加了注释了。
使用指令处理 linting 问题(Resolve linting problems using directives)
在 building 阶段处理 linting 问题使用 dlint。如果代码需要让 linter 忽略一些东西,使用 deno-lint-ignore 来提示 linter。
// deno-lint-ignore no-explicit-any
let x: any;
这样确保持续集成不会因为 linting 问题导致中断。但是使用时应该格外小心。
每一个模块,都应该有一个测试模块对应(Each module should come with a test module.)
每一个模块 foo.ts 都应该有一个测试模块 foo_test.ts。std 模块的测试模块应该在 std/tests 里,否则就应该在自己的测试模块里。
单元测试应该清晰(Unit Tests should be explicit.)
为了更好的让别人理解测试,函数名的命名要清晰。这些名称会弹出在命令行中。
test myTestFunction ... ok
测试的例子
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";
import { foo } from "./mod.ts";
Deno.test("myTestFunction", function () {
assertEquals(foo(), { bar: "bar" });
});
顶级导出函数不应该用箭头函数(Top level functions should not use arrow syntax.)
顶级导出函数应该用 function 关键字。箭头函数应该限制在闭包中。
不好的例子:
export const foo = (): string => {
return "bar";
};
好的例子:
export function foo(): string {
return "bar";
}
std
不要依赖外部代码(Do not depend on external code.)
https://deno.land/std/ 的目标是所有 Deno 程序的基础函数依赖。我们要保证用户用的这些代码不会包含没有审查的第三方代码。
文档和保持浏览器兼容性(Document and maintain browser compatibility.)
如果一个模块是浏览器兼容的,在模块的最上面应该添加以下注释:
// This module is browser compatible.
保持浏览器兼容要保证不使用 Deno 名字空间下的东西和特性。应该保证所有的依赖都是浏览器兼容的。
全文完
最后
这篇文章非常短,但是基本指出了一个希望长久给别人使用,且持续进化,且多人参与的项目应该注意的点。这些点的实践是不难的,收益是很大的。
长远看,有价值的东西都会变成网上的一个库,所以建议新手开始写第一个库就注意这些事情。
转载请注明本文知乎链接和译者 Hugo。