将 Clean Code 的概念适用到 TypeScript,灵感来自 clean-code-javascript。
原文地址: clean-code-typescript
中文地址: clean-code-typescript
简介

这不是一份 TypeScript 设计规范,而是将 Robert C. Martin 的软件工程著作 《Clean Code》 适用到 TypeScript,指导读者使用 TypeScript 编写易读、可复用和易重构的软件。
格式化
就像这里的许多规则一样,没有什么是硬性规定,格式化也是。重点是不要争论格式,使用自动化工具实现格式化。对于工程师来说,争论格式就是浪费时间和金钱。通用的原则是保持一致的格式规则。
对于 TypeScript ,有一个强大的工具叫做 TSLint。它是一个静态分析工具,可以帮助您显著提高代码的可读性和可维护性。项目中使用可以参考以下 TSLint 配置:
-
TSLint Config Standard - 标准格式规则
-
TSLint Config Airbnb - Airbnb 格式规则
-
TSLint Clean Code - 灵感来自于Clean Code: A Handbook of Agile Software Craftsmanship 的 TSLint 规则。
-
TSLint react - React 相关的Lint规则
-
TSLint + Prettier - Prettier 代码格式化相关的 lint 规则
-
ESLint rules for TSLint - TypeScript 的 ESLint
-
Immutable - 在 TypeScript 中禁用 mutation 的规则
还可以参考TypeScript 风格指南和编码约定的源代码。
大小写一致
大写可以告诉你很多关于变量、函数等的信息。这些都是主观规则,由你的团队做选择。关键是无论怎么选,都要一致。
反例:
const DAYS_IN_WEEK = 7;
const daysInMonth = 30;
const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
const Artists = ['ACDC', 'Led Zeppelin', 'The Beatles'];
function eraseDatabase() {}
function restore_database() {}
class animal {}
class Container {}
正例:
const DAYS_IN_WEEK = 7;
const DAYS_IN_MONTH = 30;
const SONGS = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
const ARTISTS = ['ACDC', 'Led Zeppelin', 'The Beatles'];
function eraseDatabase() {}
function restoreDatabase() {}
class Animal {}
class Container {}
类名、接口名、类型名和命名空间名最好使用“帕斯卡命名”。
变量、函数和类成员使用“驼峰式命名”。
调用函数的函数和被调函数应靠近放置
当函数间存在相互调用的情况时,应将两者靠近放置。最好是应将调用者写在被调者的上方。这就像读报纸一样,我们都是从上往下读,那么读代码也是。
反例:
class PerformanceReview {
constructor(private readonly employee: Employee) {
}
private lookupPeers() {
return db.lookup(this.employee.id, 'peers');
}
private lookupManager() {
return db.lookup(this.employee, 'manager');
}
private getPeerReviews() {
const peers = this.lookupPeers();
// ...
}
review() {
this.getPeerReviews();
this.getManagerReview();
this.getSelfReview();
// ...
}
private getManagerReview() {
const manager = this.lookupManager();
}
private getSelfReview() {
// ...
}
}
const review = new PerformanceReview(employee);
review.review();
正例:
class PerformanceReview {
constructor(private readonly employee: Employee) {
}
review() {
this.getPeerReviews();
this.getManagerReview();
this.getSelfReview();
// ...
}
private getPeerReviews() {
const peers = this.lookupPeers();
// ...
}
private lookupPeers() {
return db.lookup(this.employee.id, 'peers');
}
private getManagerReview() {
const manager = this.lookupManager();
}
private lookupManager() {
return db.lookup(this.employee, 'manager');
}
private getSelfReview() {
// ...
}
}
const review = new PerformanceReview(employee);
review.review();
组织导入
使用整洁且易于阅读的import
语句,您可以快速查看当前代码的依赖关系。导入语句应遵循以下做法:
-
Import
语句应该按字母顺序排列和分组。 - 应该删除未使用的导入语句。
- 命名导入必须按字母顺序(例如:
import {A, B, C} from 'foo';
)。 - 导入源必须在组中按字母顺序排列。 例如:
import * as foo from 'a'; import * as bar from 'b';
- 导入组用空行隔开。
- 组内按照如下排序:
- Polyfills (例如:
import 'reflect-metadata';
) - Node 内置模块 (例如:
import fs from 'fs';
) - 外部模块 (例如:
import { query } from 'itiriri';
) - 内部模块 (例如:
import { UserService } from 'src/services/userService';
) - 父目录中的模块 (例如:
import foo from '../foo'; import qux from '../../foo/qux';
) - 来自相同或兄弟目录的模块 (例如:
import bar from './bar'; import baz from './bar/baz';
)
- Polyfills (例如:
反例:
import { TypeDefinition } from '../types/typeDefinition';
import { AttributeTypes } from '../model/attribute';
import { ApiCredentials, Adapters } from './common/api/authorization';
import fs from 'fs';
import { ConfigPlugin } from './plugins/config/configPlugin';
import { BindingScopeEnum, Container } from 'inversify';
import 'reflect-metadata';
正例:
import 'reflect-metadata';
import fs from 'fs';
import { BindingScopeEnum, Container } from 'inversify';
import { AttributeTypes } from '../model/attribute';
import { TypeDefinition } from '../types/typeDefinition';
import { ApiCredentials, Adapters } from './common/api/authorization';
import { ConfigPlugin } from './plugins/config/configPlugin';
使用 typescript 别名
为了创建整洁漂亮的导入语句,可以在tsconfig.json
中设置编译器选项的paths
和baseUrl
属性。
这样可以避免导入时使用较长的相对路径。
反例:
import { UserService } from '../../../services/UserService';
正例:
import { UserService } from '@services/UserService';
// tsconfig.json
...
"compilerOptions": {
...
"baseUrl": "src",
"paths": {
"@services": ["services/*"]
}
...
}
...
网友评论