517 lines
11 KiB
Markdown
517 lines
11 KiB
Markdown
|
|
---
|
|||
|
|
name: clean-code
|
|||
|
|
description: Clean Code 代码质量审查和重构指南。用于代码审查、重构、提升代码质量、减少技术债务。触发词:代码审查、clean code、重构、代码质量、技术债务、代码规范。
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Clean Code 技能
|
|||
|
|
|
|||
|
|
帮助编写高质量、可维护、可读性强的代码。
|
|||
|
|
|
|||
|
|
## 核心原则
|
|||
|
|
|
|||
|
|
### 1. 有意义的命名
|
|||
|
|
```typescript
|
|||
|
|
// ❌ 坏
|
|||
|
|
const d = new Date();
|
|||
|
|
const ymd = date.split('-');
|
|||
|
|
|
|||
|
|
// ✅ 好
|
|||
|
|
const currentDate = new Date();
|
|||
|
|
const [year, month, day] = date.split('-');
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**规则:**
|
|||
|
|
- 使用描述性名称,避免缩写
|
|||
|
|
- 名称应该表达意图
|
|||
|
|
- 避免误导性名称
|
|||
|
|
- 做有意义的区分(不是 a1, a2)
|
|||
|
|
- 使用可搜索的名称
|
|||
|
|
- 类名用名词,方法名用动词
|
|||
|
|
|
|||
|
|
### 2. 函数
|
|||
|
|
```typescript
|
|||
|
|
// ❌ 坏 - 做太多事
|
|||
|
|
function processUser(user: User) {
|
|||
|
|
// 验证
|
|||
|
|
if (!user.email) throw new Error('Email required');
|
|||
|
|
if (!user.name) throw new Error('Name required');
|
|||
|
|
|
|||
|
|
// 保存
|
|||
|
|
database.save(user);
|
|||
|
|
|
|||
|
|
// 发送邮件
|
|||
|
|
emailService.send(user.email, 'Welcome!');
|
|||
|
|
|
|||
|
|
// 记录日志
|
|||
|
|
logger.log(`User ${user.name} created`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ✅ 好 - 单一职责
|
|||
|
|
function validateUser(user: User): void {
|
|||
|
|
if (!user.email) throw new Error('Email required');
|
|||
|
|
if (!user.name) throw new Error('Name required');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function saveUser(user: User): void {
|
|||
|
|
database.save(user);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function sendWelcomeEmail(user: User): void {
|
|||
|
|
emailService.send(user.email, 'Welcome!');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function logUserCreation(user: User): void {
|
|||
|
|
logger.log(`User ${user.name} created`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function processUser(user: User) {
|
|||
|
|
validateUser(user);
|
|||
|
|
saveUser(user);
|
|||
|
|
sendWelcomeEmail(user);
|
|||
|
|
logUserCreation(user);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**规则:**
|
|||
|
|
- 函数应该小(<20行)
|
|||
|
|
- 只做一件事
|
|||
|
|
- 每个函数一个抽象层级
|
|||
|
|
- 使用描述性名称
|
|||
|
|
- 函数参数越少越好(理想0-3个)
|
|||
|
|
- 避免副作用
|
|||
|
|
- 分隔指令与询问
|
|||
|
|
|
|||
|
|
### 3. 注释
|
|||
|
|
```typescript
|
|||
|
|
// ❌ 坏 - 多余的注释
|
|||
|
|
// 检查用户是否已激活
|
|||
|
|
if (user.isActive) { ... }
|
|||
|
|
|
|||
|
|
// ✅ 好 - 代码自解释
|
|||
|
|
if (user.isActive) { ... }
|
|||
|
|
|
|||
|
|
// ✅ 好 - 解释为什么
|
|||
|
|
// 使用 setTimeout 而不是 setInterval 避免重叠执行
|
|||
|
|
setTimeout(processQueue, 1000);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**规则:**
|
|||
|
|
- 好的代码是自解释的
|
|||
|
|
- 注释不能弥补糟糕的代码
|
|||
|
|
- 用代码表达意图
|
|||
|
|
- 好的注释:法律信息、解释意图、警示、TODO
|
|||
|
|
- 坏的注释:喃喃自语、多余的、误导的、注释掉的代码
|
|||
|
|
|
|||
|
|
### 4. 格式
|
|||
|
|
```typescript
|
|||
|
|
// ❌ 坏
|
|||
|
|
export class User{constructor(private name:string,private age:number){}
|
|||
|
|
getName(){return this.name;}}
|
|||
|
|
|
|||
|
|
// ✅ 好
|
|||
|
|
export class User {
|
|||
|
|
constructor(
|
|||
|
|
private name: string,
|
|||
|
|
private age: number
|
|||
|
|
) {}
|
|||
|
|
|
|||
|
|
getName(): string {
|
|||
|
|
return this.name;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**规则:**
|
|||
|
|
- 垂直格式:概念之间用空行分隔
|
|||
|
|
- 水平格式:行宽<120字符
|
|||
|
|
- 缩进:统一使用2或4空格
|
|||
|
|
- 团队规则:遵循项目既定风格
|
|||
|
|
|
|||
|
|
### 5. 对象和数据结构
|
|||
|
|
```typescript
|
|||
|
|
// ❌ 坏 - 暴露内部结构
|
|||
|
|
class User {
|
|||
|
|
public name: string;
|
|||
|
|
public age: number;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ✅ 好 - 隐藏实现
|
|||
|
|
class User {
|
|||
|
|
private _name: string;
|
|||
|
|
private _age: number;
|
|||
|
|
|
|||
|
|
get name(): string {
|
|||
|
|
return this._name;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
set age(value: number) {
|
|||
|
|
if (value < 0) throw new Error('Invalid age');
|
|||
|
|
this._age = value;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**规则:**
|
|||
|
|
- 数据抽象:隐藏实现
|
|||
|
|
- 数据、对象的反对称性
|
|||
|
|
- 得墨忒耳定律:模块不应知道它操作对象的内部细节
|
|||
|
|
|
|||
|
|
### 6. 错误处理
|
|||
|
|
```typescript
|
|||
|
|
// ❌ 坏 - 返回 null
|
|||
|
|
function getUser(id: string): User | null {
|
|||
|
|
return database.find(id);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ✅ 好 - 抛出异常
|
|||
|
|
function getUser(id: string): User {
|
|||
|
|
const user = database.find(id);
|
|||
|
|
if (!user) throw new UserNotFoundError(id);
|
|||
|
|
return user;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ✅ 好 - 使用 Special Case 模式
|
|||
|
|
class NullUser implements User {
|
|||
|
|
name = 'Guest';
|
|||
|
|
age = 0;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**规则:**
|
|||
|
|
- 使用异常而非返回码
|
|||
|
|
- 先写 Try-Catch-Finally
|
|||
|
|
- 给出异常的上下文
|
|||
|
|
- 别返回 null 值
|
|||
|
|
- 别传递 null 值
|
|||
|
|
|
|||
|
|
### 7. 边界
|
|||
|
|
```typescript
|
|||
|
|
// ❌ 坏 - 直接依赖第三方类
|
|||
|
|
import { Map } from 'third-party-lib';
|
|||
|
|
|
|||
|
|
class UserCollection {
|
|||
|
|
private users: Map<string, User>;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ✅ 好 - 使用适配器模式
|
|||
|
|
interface UserMap {
|
|||
|
|
get(key: string): User;
|
|||
|
|
set(key: string, user: User): void;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class ThirdPartyUserMap implements UserMap {
|
|||
|
|
private map: Map<string, User>;
|
|||
|
|
|
|||
|
|
get(key: string): User {
|
|||
|
|
return this.map.get(key);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
set(key: string, user: User): void {
|
|||
|
|
this.map.set(key, user);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**规则:**
|
|||
|
|
- 隐藏第三方代码
|
|||
|
|
- 使用适配器模式
|
|||
|
|
- 边界处的代码需要清晰的分割和测试
|
|||
|
|
|
|||
|
|
### 8. 单元测试
|
|||
|
|
```typescript
|
|||
|
|
// ❌ 坏 - 不清晰的测试
|
|||
|
|
test('user', () => {
|
|||
|
|
const u = new User('John', 25);
|
|||
|
|
expect(u.getName()).toBe('John');
|
|||
|
|
expect(u.getAge()).toBe(25);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// ✅ 好 - BUILD-OPERATE-CHECK 模式
|
|||
|
|
test('shouldReturnUserName', () => {
|
|||
|
|
// BUILD
|
|||
|
|
const user = new User('John', 25);
|
|||
|
|
|
|||
|
|
// OPERATE
|
|||
|
|
const name = user.getName();
|
|||
|
|
|
|||
|
|
// CHECK
|
|||
|
|
expect(name).toBe('John');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// ✅ 好 - Given-When-Then 模式
|
|||
|
|
test('shouldReturnUserName', () => {
|
|||
|
|
// Given
|
|||
|
|
const user = new User('John', 25);
|
|||
|
|
|
|||
|
|
// When
|
|||
|
|
const name = user.getName();
|
|||
|
|
|
|||
|
|
// Then
|
|||
|
|
expect(name).toBe('John');
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**规则:**
|
|||
|
|
- F.I.R.S.T 原则:
|
|||
|
|
- Fast(快速)
|
|||
|
|
- Independent(独立)
|
|||
|
|
- Repeatable(可重复)
|
|||
|
|
- Self-Validating(自验证)
|
|||
|
|
- Timely(及时)
|
|||
|
|
- 每个测试一个断言
|
|||
|
|
- 单一概念
|
|||
|
|
- 测试代码和生产代码一样重要
|
|||
|
|
|
|||
|
|
### 9. 类
|
|||
|
|
```typescript
|
|||
|
|
// ❌ 坏 - 大类
|
|||
|
|
class UserManager {
|
|||
|
|
createUser() {}
|
|||
|
|
deleteUser() {}
|
|||
|
|
updateUser() {}
|
|||
|
|
sendEmail() {}
|
|||
|
|
validateEmail() {}
|
|||
|
|
generateReport() {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ✅ 好 - 单一职责
|
|||
|
|
class UserService {
|
|||
|
|
create(user: User) {}
|
|||
|
|
delete(id: string) {}
|
|||
|
|
update(user: User) {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class EmailService {
|
|||
|
|
send(email: string, content: string) {}
|
|||
|
|
validate(email: string): boolean {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class ReportService {
|
|||
|
|
generate(userId: string): Report {}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**规则:**
|
|||
|
|
- 类应该小
|
|||
|
|
- 单一职责原则(SRP)
|
|||
|
|
- 内聚性:方法和数据互相依赖
|
|||
|
|
- 保持内聚性就会得到许多短小的类
|
|||
|
|
|
|||
|
|
### 10. 系统
|
|||
|
|
```typescript
|
|||
|
|
// ❌ 坏 - 硬编码依赖
|
|||
|
|
class UserService {
|
|||
|
|
private db = new Database(); // 硬编码
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ✅ 好 - 依赖注入
|
|||
|
|
class UserService {
|
|||
|
|
constructor(private db: Database) {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ✅ 好 - 工厂模式
|
|||
|
|
class ServiceFactory {
|
|||
|
|
static createUserService(): UserService {
|
|||
|
|
return new UserService(new Database());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**规则:**
|
|||
|
|
- 分离构造和使用
|
|||
|
|
- 依赖注入
|
|||
|
|
- 扩充:AOP(面向切面编程)
|
|||
|
|
- 测试驱动系统架构
|
|||
|
|
|
|||
|
|
## 代码审查清单
|
|||
|
|
|
|||
|
|
### 命名
|
|||
|
|
- [ ] 变量名是否描述了其用途?
|
|||
|
|
- [ ] 函数名是否描述了其行为?
|
|||
|
|
- [ ] 类名是否描述了其职责?
|
|||
|
|
- [ ] 名称是否一致?
|
|||
|
|
|
|||
|
|
### 函数
|
|||
|
|
- [ ] 函数是否小于20行?
|
|||
|
|
- [ ] 函数是否只做一件事?
|
|||
|
|
- [ ] 函数参数是否<=3个?
|
|||
|
|
- [ ] 函数是否有副作用?
|
|||
|
|
- [ ] 函数名是否描述性?
|
|||
|
|
|
|||
|
|
### 结构
|
|||
|
|
- [ ] 代码是否有清晰的层次结构?
|
|||
|
|
- [ ] 类是否遵循单一职责原则?
|
|||
|
|
- [ ] 是否有重复代码?
|
|||
|
|
- [ ] 依赖是否清晰?
|
|||
|
|
|
|||
|
|
### 测试
|
|||
|
|
- [ ] 是否有足够的测试覆盖?
|
|||
|
|
- [ ] 测试是否快速?
|
|||
|
|
- [ ] 测试是否独立?
|
|||
|
|
- [ ] 测试是否清晰?
|
|||
|
|
|
|||
|
|
### 错误处理
|
|||
|
|
- [ ] 是否处理了所有可能的错误?
|
|||
|
|
- [ ] 错误信息是否清晰?
|
|||
|
|
- [ ] 是否避免了返回 null?
|
|||
|
|
|
|||
|
|
## 重构技巧
|
|||
|
|
|
|||
|
|
### 提取方法
|
|||
|
|
```typescript
|
|||
|
|
// Before
|
|||
|
|
function printOwing(invoice: Invoice) {
|
|||
|
|
let outstanding = 0;
|
|||
|
|
|
|||
|
|
// 打印横幅
|
|||
|
|
console.log('***********************');
|
|||
|
|
console.log('*** Customer Owes ***');
|
|||
|
|
console.log('***********************');
|
|||
|
|
|
|||
|
|
// 计算未付款
|
|||
|
|
for (const order of invoice.orders) {
|
|||
|
|
outstanding += order.amount;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 打印详情
|
|||
|
|
console.log(`name: ${invoice.customer}`);
|
|||
|
|
console.log(`amount: ${outstanding}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// After
|
|||
|
|
function printOwing(invoice: Invoice) {
|
|||
|
|
printBanner();
|
|||
|
|
const outstanding = calculateOutstanding(invoice);
|
|||
|
|
printDetails(invoice, outstanding);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function printBanner() {
|
|||
|
|
console.log('***********************');
|
|||
|
|
console.log('*** Customer Owes ***');
|
|||
|
|
console.log('***********************');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function calculateOutstanding(invoice: Invoice): number {
|
|||
|
|
return invoice.orders.reduce((sum, order) => sum + order.amount, 0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function printDetails(invoice: Invoice, outstanding: number) {
|
|||
|
|
console.log(`name: ${invoice.customer}`);
|
|||
|
|
console.log(`amount: ${outstanding}`);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 内联方法
|
|||
|
|
```typescript
|
|||
|
|
// Before
|
|||
|
|
function getRating(driver: Driver): number {
|
|||
|
|
return moreThanFiveLateDeliveries(driver) ? 2 : 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function moreThanFiveLateDeliveries(driver: Driver): boolean {
|
|||
|
|
return driver.numberOfLateDeliveries > 5;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// After
|
|||
|
|
function getRating(driver: Driver): number {
|
|||
|
|
return driver.numberOfLateDeliveries > 5 ? 2 : 1;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 提取变量
|
|||
|
|
```typescript
|
|||
|
|
// Before
|
|||
|
|
if (platform.toUpperCase().indexOf('MAC') > -1 &&
|
|||
|
|
browser.toUpperCase().indexOf('IE') > -1 &&
|
|||
|
|
wasInitialized() && resize > 0) {
|
|||
|
|
// do something
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// After
|
|||
|
|
const isMacOs = platform.toUpperCase().indexOf('MAC') > -1;
|
|||
|
|
const isIE = browser.toUpperCase().indexOf('IE') > -1;
|
|||
|
|
const wasResized = resize > 0;
|
|||
|
|
|
|||
|
|
if (isMacOs && isIE && wasInitialized() && wasResized) {
|
|||
|
|
// do something
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 分解条件
|
|||
|
|
```typescript
|
|||
|
|
// Before
|
|||
|
|
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
|
|||
|
|
charge = quantity * winterRate + winterServiceCharge;
|
|||
|
|
} else {
|
|||
|
|
charge = quantity * summerRate;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// After
|
|||
|
|
if (isSummer(date)) {
|
|||
|
|
charge = summerCharge(quantity);
|
|||
|
|
} else {
|
|||
|
|
charge = winterCharge(quantity);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function isSummer(date: Date): boolean {
|
|||
|
|
return !date.before(SUMMER_START) && !date.after(SUMMER_END);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function summerCharge(quantity: number): number {
|
|||
|
|
return quantity * summerRate;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function winterCharge(quantity: number): number {
|
|||
|
|
return quantity * winterRate + winterServiceCharge;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 以多态取代条件
|
|||
|
|
```typescript
|
|||
|
|
// Before
|
|||
|
|
function calculatePay(employee: Employee): number {
|
|||
|
|
switch (employee.type) {
|
|||
|
|
case 'ENGINEER':
|
|||
|
|
return employee.monthlySalary;
|
|||
|
|
case 'SALESMAN':
|
|||
|
|
return employee.monthlySalary + employee.commission;
|
|||
|
|
case 'MANAGER':
|
|||
|
|
return employee.monthlySalary + employee.bonus;
|
|||
|
|
default:
|
|||
|
|
throw new Error('Invalid employee type');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// After
|
|||
|
|
abstract class Employee {
|
|||
|
|
abstract calculatePay(): number;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class Engineer extends Employee {
|
|||
|
|
calculatePay(): number {
|
|||
|
|
return this.monthlySalary;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class Salesman extends Employee {
|
|||
|
|
calculatePay(): number {
|
|||
|
|
return this.monthlySalary + this.commission;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class Manager extends Employee {
|
|||
|
|
calculatePay(): number {
|
|||
|
|
return this.monthlySalary + this.bonus;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 使用方法
|
|||
|
|
|
|||
|
|
1. **代码审查时**:参考清单逐项检查
|
|||
|
|
2. **重构时**:应用重构技巧
|
|||
|
|
3. **新功能开发时**:遵循核心原则
|
|||
|
|
4. **代码坏味道识别**:参考常见问题
|
|||
|
|
|
|||
|
|
告诉我需要审查的代码,我会帮你识别问题并提供改进建议!
|