name: clean-code description: 编写可读、可维护软件的整洁代码原则
整洁代码原则
编写人类能理解的代码。代码被阅读的次数远多于被编写的次数。
命名
变量
// 差
const d = 86400000; // 这是什么?
const yyyymmdd = formatDate(date);
const list = getUsers();
// 好
const MILLISECONDS_PER_DAY = 86400000;
const formattedDate = formatDate(date);
const users = getUsers();
函数
// 差 - 不清楚做什么
function handle(data) { }
function process(item) { }
function doIt() { }
// 好 - 动词+名词,描述动作
function validateUserInput(input) { }
function calculateTotalPrice(items) { }
function sendWelcomeEmail(user) { }
布尔值
// 差
const open = true;
const write = false;
const fruit = true;
// 好 - 使用 is/has/can/should 前缀
const isOpen = true;
const canWrite = false;
const hasFruit = true;
函数
单一职责
// 差 - 做太多事情
function createUserAndSendEmailAndLogActivity(data) {
const user = db.insert(data);
mailer.send(user.email, '欢迎!');
logger.log(`用户 ${user.id} 已创建`);
return user;
}
// 好 - 每个函数只做一件事
function createUser(data) {
return db.insert(data);
}
function sendWelcomeEmail(user) {
mailer.send(user.email, '欢迎!');
}
function logUserCreation(user) {
logger.log(`用户 ${user.id} 已创建`);
}
减少参数
// 差 - 参数太多
function createUser(name, email, age, country, role, department, manager) { }
// 好 - 使用对象
function createUser(options: CreateUserOptions) { }
interface CreateUserOptions {
name: string;
email: string;
age?: number;
country?: string;
role: Role;
department?: string;
manager?: string;
}
避免标志参数
// 差 - 布尔值改变行为
function createFile(name: string, temp: boolean) {
if (temp) {
fs.create(`/tmp/${name}`);
} else {
fs.create(name);
}
}
// 好 - 分离函数
function createFile(name: string) {
fs.create(name);
}
function createTempFile(name: string) {
fs.create(`/tmp/${name}`);
}
提前返回
// 差 - 嵌套条件
function getPayAmount(employee) {
let result;
if (employee.isSeparated) {
result = 0;
} else {
if (employee.isRetired) {
result = employee.pension;
} else {
result = employee.salary;
}
}
return result;
}
// 好 - 提前返回
function getPayAmount(employee) {
if (employee.isSeparated) return 0;
if (employee.isRetired) return employee.pension;
return employee.salary;
}
注释
不要注释糟糕的代码 - 重写它
// 差
// 检查员工是否有资格享受福利
if ((employee.flags & 0x0F) && (employee.age > 65)) { }
// 好 - 代码自解释
const isEligibleForBenefits = employee.hasHealthPlan && employee.isRetired;
if (isEligibleForBenefits) { }
好的注释
// 正则匹配 ISO 8601 日期格式
const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
// TODO: API v2 发布时重构
// WARNING: 必须在数据库迁移前运行
// NOTE: 第三方 API 有 100ms 最小延迟
错误处理
不要返回 null
// 差
function getUser(id: string): User | null {
return db.find(id) || null;
}
// 好 - 抛出异常或返回 Result 类型
function getUser(id: string): User {
const user = db.find(id);
if (!user) throw new UserNotFoundError(id);
return user;
}
// 或使用 Result 类型
function getUser(id: string): Result<User, NotFoundError> { }
不要传递 null
// 差
function calculateArea(width: number | null, height: number | null) {
if (width === null || height === null) {
throw new Error('无效的尺寸');
}
return width * height;
}
// 好 - 要求有效输入
function calculateArea(width: number, height: number) {
return width * height;
}
格式化
一致的风格
- 选择一种风格指南(Prettier, StandardJS, Black)
- 使用格式化工具自动化
- 不要在代码审查中讨论风格
垂直密度
// 差 - 不相关的代码混在一起
const user = getUser(id);
const config = loadConfig();
const result = processData(user, config);
logger.log(result);
sendNotification(user);
// 好 - 分组相关代码
const user = getUser(id);
sendNotification(user);
const config = loadConfig();
const result = processData(user, config);
logger.log(result);
需要避免的代码异味
| 异味 | 问题 | 解决方案 |
|---|---|---|
| 长方法 | 难以理解 | 提取方法 |
| 长参数列表 | 复杂接口 | 参数对象 |
| 重复代码 | 多处需要修改 | 提取函数 |
| 死代码 | 混淆,维护困难 | 删除它 |
| 魔法数字 | 含义不明确 | 命名常量 |
| 深度嵌套 | 难以跟踪 | 提前返回,提取 |
| 特性依恋 | 位置错误 | 移动方法 |
| 数据泥团 | 总是同时出现 | 创建类 |
童子军规则
让代码比你发现时更整洁。
每次提交都应该稍微提高代码质量。小的、渐进的改进会随着时间的推移而累积。
检查清单
提交前,问自己:
- [ ] 6个月后我还能理解这段代码吗?
- [ ] 新团队成员能理解它吗?
- [ ] 名称是否具有描述性且可搜索?
- [ ] 每个函数是否只做一件事?
- [ ] 是否有任何魔法数字/字符串?
- [ ] 错误处理是否恰当?
- [ ] 我删除了所有死代码吗?