将 Clean Code 的概念适用到 TypeScript,灵感来自 clean-code-javascript。
原文地址: clean-code-typescript
中文地址: clean-code-typescript
简介
image这不是一份 TypeScript 设计规范,而是将 Robert C. Martin 的软件工程著作 《Clean Code》 适用到 TypeScript,指导读者使用 TypeScript 编写易读、可复用和易重构的软件。
类
小、小、小!要事情说三遍
类的大小是由它的职责来度量的。按照单一职责原则,类要小。
反例:
class Dashboard {
getLanguage(): string { /* ... */ }
setLanguage(language: string): void { /* ... */ }
showProgress(): void { /* ... */ }
hideProgress(): void { /* ... */ }
isDirty(): boolean { /* ... */ }
disable(): void { /* ... */ }
enable(): void { /* ... */ }
addSubscription(subscription: Subscription): void { /* ... */ }
removeSubscription(subscription: Subscription): void { /* ... */ }
addUser(user: User): void { /* ... */ }
removeUser(user: User): void { /* ... */ }
goToHomePage(): void { /* ... */ }
updateProfile(details: UserDetails): void { /* ... */ }
getVersion(): string { /* ... */ }
// ...
}
正例:
class Dashboard {
disable(): void { /* ... */ }
enable(): void { /* ... */ }
getVersion(): string { /* ... */ }
}
// split the responsibilities by moving the remaining methods to other classes
// ...
高内聚低耦合
内聚:定义了类成员之间相互关联的程度。理想情况下,高内聚类的每个方法都应该使用类中的所有字段,实际上这不可能也不可取。但我们依然提倡高内聚。
耦合:指的是两个类之间的关联程度。如果其中一个类的更改不影响另一个类,则称为低耦合类。
好的软件设计具有高内聚性和低耦合性。
反例:
class UserManager {
// Bad: each private variable is used by one or another group of methods.
// It makes clear evidence that the class is holding more than a single responsibility.
// If I need only to create the service to get the transactions for a user,
// I'm still forced to pass and instance of emailSender.
constructor(
private readonly db: Database,
private readonly emailSender: EmailSender) {
}
async getUser(id: number): Promise<User> {
return await db.users.findOne({ id })
}
async getTransactions(userId: number): Promise<Transaction[]> {
return await db.transactions.find({ userId })
}
async sendGreeting(): Promise<void> {
await emailSender.send('Welcome!');
}
async sendNotification(text: string): Promise<void> {
await emailSender.send(text);
}
async sendNewsletter(): Promise<void> {
// ...
}
}
正例:
class UserService {
constructor(private readonly db: Database) {
}
async getUser(id: number): Promise<User> {
return await db.users.findOne({ id })
}
async getTransactions(userId: number): Promise<Transaction[]> {
return await db.transactions.find({ userId })
}
}
class UserNotifier {
constructor(private readonly emailSender: EmailSender) {
}
async sendGreeting(): Promise<void> {
await emailSender.send('Welcome!');
}
async sendNotification(text: string): Promise<void> {
await emailSender.send(text);
}
async sendNewsletter(): Promise<void> {
// ...
}
}
组合大于继承
正如“四人帮”在设计模式中所指出的那样,您尽可能使用组合而不是继承。组合和继承各有优劣。这个准则的主要观点是,如果你潜意识地倾向于继承,试着想想组合是否能更好地给你的问题建模,在某些情况下可以。
什么时候应该使用继承?这取决于你面临的问题。以下场景使用继承更好:
- 继承代表的是“is-a”关系,而不是“has-a”关系 (人 -> 动物 vs. 用户 -> 用户详情)。
- 可复用基类的代码 (人类可以像所有动物一样移动)。
- 希望通过更改基类对派生类进行全局更改(改变所有动物在运动时的热量消耗)。
反例:
class Employee {
constructor(
private readonly name: string,
private readonly email:string) {
}
// ...
}
// Bad because Employees "have" tax data. EmployeeTaxData is not a type of Employee
class EmployeeTaxData extends Employee {
constructor(
name: string,
email:string,
private readonly ssn: string,
private readonly salary: number) {
super(name, email);
}
// ...
}
正例:
class Employee {
private taxData: EmployeeTaxData;
constructor(
private readonly name: string,
private readonly email:string) {
}
setTaxData(ssn: string, salary: number): Employee {
this.taxData = new EmployeeTaxData(ssn, salary);
return this;
}
// ...
}
class EmployeeTaxData {
constructor(
public readonly ssn: string,
public readonly salary: number) {
}
// ...
}
使用方法链
非常有用的模式,在许多库中都可以看到。它让代码表达力更好,也更简洁。
反例:
class QueryBuilder {
private collection: string;
private pageNumber: number = 1;
private itemsPerPage: number = 100;
private orderByFields: string[] = [];
from(collection: string): void {
this.collection = collection;
}
page(number: number, itemsPerPage: number = 100): void {
this.pageNumber = number;
this.itemsPerPage = itemsPerPage;
}
orderBy(...fields: string[]): void {
this.orderByFields = fields;
}
build(): Query {
// ...
}
}
// ...
const query = new QueryBuilder();
query.from('users');
query.page(1, 100);
query.orderBy('firstName', 'lastName');
const query = queryBuilder.build();
正例:
class QueryBuilder {
private collection: string;
private pageNumber: number = 1;
private itemsPerPage: number = 100;
private orderByFields: string[] = [];
from(collection: string): this {
this.collection = collection;
return this;
}
page(number: number, itemsPerPage: number = 100): this {
this.pageNumber = number;
this.itemsPerPage = itemsPerPage;
return this;
}
orderBy(...fields: string[]): this {
this.orderByFields = fields;
return this;
}
build(): Query {
// ...
}
}
// ...
const query = new QueryBuilder()
.from('users')
.page(1, 100)
.orderBy('firstName', 'lastName')
.build();
上一章:TypeScript 代码整洁之道 - 对象和数据结构
下一章:TypeScript 代码整洁之道 - SOLID原则
网友评论