干净的代码不仅仅是可以工作的代码,而是可以被其他人轻松阅读、重用和重构的代码。编写干净的代码很重要,因为在日常工作中,我们的代码还要被其他人阅读、修改、重构~
本文的重点是编写干净的JavaScrip代码
1. 变量
使用语义化的名称
变量的名称应该是语义化的
// 不建议 ❌
const foo = "zhangsan@example.com";
const bar = "Zhang";
const age = 23;
const qux = true;
// 建议 ✅
const email = "zhangsan@example.com";
const firstName = "Zhang";
const age = 23;
const isActive = true
请注意,布尔名称通常会回答特定问题,例如:
isActive
didSubscribe
hasLinkedAccount
避免添加不必要的上下文
当上下文已经由包含的对象或类提供时,不要将多余的上下文添加到变量名中
// 不建议 ❌
const user = {
userId: "296e2589-7b33-400a-b762-007b730c8e6d",
userEmail: "zhangsan@example.com",
userFirstName: "Zhang",
userLastName: "San",
userAge: 23,
};
user.userId;
// 建议 ✅
const user = {
id: "296e2589-7b33-400a-b762-007b730c8e6d",
email: "zhangsan@example.com",
firstName: "Zhang",
lastName: "San",
age: 23,
};
user.id;
避免硬编码值
不要插入常量值,而是确保声明有意义且可搜索的常量
// 不建议 ❌
setTimeout(clearSessionData, 900000);
// 建议 ✅
const SESSION_DURATION_MS = 15 * 60 * 1000;
setTimeout(clearSessionData, SESSION_DURATION_MS);
2. 功能
使用描述性名称
函数名称可以很长,只要它们描述了函数的实际作用。函数名称通常具有动作动词的形式,返回布尔值的函数可能例外——它可以具有“是或否”问题的形式。函数名也应该是驼峰式。
// 不建议 ❌
function toggle() {
// ...
}
function agreed(user) {
// ...
}
// 建议 ✅
function toggleThemeSwitcher() {
// ...
}
function didAgreeToAllTerms(user) {
// ...
}
使用默认参数
默认参数比在函数体内使用额外的条件语句更干净。
// 不建议 ❌
function printAllFilesInDirectory(dir) {
const directory = dir || "./";
// ...
}
// 建议 ✅
function printAllFilesInDirectory(dir = "./") {
// ...
}
限制参数的数量
尽管这条规则可能存在争议,但函数应该有 0、1 或 2 个参数。拥有三个参数已经过分了,除此之外还意味着以下两种情况之一:
- 功能已经做了很多,应该拆分。
- 传递给函数的数据在某种程度上是相关的,可以作为专用数据结构传递。
// 不建议 ❌
function sendPushNotification(title, message, image, isSilent, delayMs) {
// ...
}
sendPushNotification("新消息", "...", "http://...", false, 1000);
// 建议 ✅
function sendPushNotification({ title, message, image, isSilent, delayMs }) {
// ...
}
const notificationConfig = {
title: "新消息",
message: "...",
image: "http://...",
isSilent: false,
delayMs: 1000,
};
sendPushNotification(notificationConfig);
避免在一个函数中执行多个事务
一个函数一次应该做一件事。此规则有助于减少函数的大小和复杂性,从而更容易测试、调试和重构。函数中的行数是一个强有力的指标,它应该引发一个关于函数是否正在执行许多操作的标志。通常,尝试针对少于 20-30 行代码的内容。
// 不建议 ❌
function pingUsers(users) {
users.forEach((user) => {
const userRecord = database.lookup(user);
if (!userRecord.isActive()) {
ping(user);
}
});
}
// 建议 ✅
function pingInactiveUsers(users) {
users.filter(!isUserActive).forEach(ping);
}
function isUserActive(user) {
const userRecord = database.lookup(user);
return userRecord.isActive();
}
避免使用标志作为参数
其中一个参数中的标志有效地意味着该函数仍然可以被简化。
// 不建议 ❌
function createFile(name, isPublic) {
if (isPublic) {
fs.create(`./public/${name}`);
} else {
fs.create(name);
}
}
// 建议 ✅
function createFile(name) {
fs.create(name);
}
function createPublicFile(name) {
createFile(`./public/${name}`);
}
不要重复自己
重复的代码会使你的代码看起来很low。如果你重复代码过多,则每当逻辑发生变化时,您都必须更新多个位置。
// 不建议 ❌
function renderCarsList(cars) {
cars.forEach((car) => {
const price = car.getPrice();
const make = car.getMake();
const brand = car.getBrand();
const nbOfDoors = car.getNbOfDoors();
render({ price, make, brand, nbOfDoors });
});
}
function renderMotorcyclesList(motorcycles) {
motorcycles.forEach((motorcycle) => {
const price = motorcycle.getPrice();
const make = motorcycle.getMake();
const brand = motorcycle.getBrand();
const seatHeight = motorcycle.getSeatHeight();
render({ price, make, brand, seatHeight });
});
}
// 建议 ✅
function renderVehiclesList(vehicles) {
vehicles.forEach((vehicle) => {
const price = vehicle.getPrice();
const make = vehicle.getMake();
const brand = vehicle.getBrand();
const data = { price, make, brand };
switch (vehicle.type) {
case "car":
data.nbOfDoors = vehicle.getNbOfDoors();
break;
case "motorcycle":
data.seatHeight = vehicle.getSeatHeight();
break;
}
render(data);
});
}
避免副作用
// 不建议 ❌
let date = "21-8-2021";
function splitIntoDayMonthYear() {
date = date.split("-");
}
splitIntoDayMonthYear();
// 改变了date的原始值
console.log(date); // ['21', '8', '2021'];
// 建议 ✅
function splitIntoDayMonthYear(date) {
return date.split("-");
}
const date = "21-8-2021";
const newDate = splitIntoDayMonthYear(date);
// 原始值未改变
console.log(date); // '21-8-2021';
console.log(newDate); // ['21', '8', '2021'];
3. 条件
使用非负条件
// 不建议 ❌
function isUserNotVerified(user) {
// ...
}
if (!isUserNotVerified(user)) {
// ...
}
// 建议 ✅
function isUserVerified(user) {
// ...
}
if (isUserVerified(user)) {
// ...
}
尽可能简写
// 不建议 ❌
if (isActive === true) {
// ...
}
if (firstName !== "" && firstName !== null && firstName !== undefined) {
// ...
}
const isUserEligible = user.isVerified() && user.didSubscribe() ? true : false;
// 建议 ✅
if (isActive) {
// ...
}
if (!!firstName) {
// ...
}
const isUserEligible = user.isVerified() && user.didSubscribe();
避免嵌套分支并尽快返回
提早返回将使您的代码线性化、更具可读性。
// 不建议 ❌
function addUserService(db, user) {
if (!db) {
if (!db.isConnected()) {
if (!user) {
return db.insert("users", user);
} else {
throw new Error("插入失败");
}
} else {
throw new Error("数据库未连接");
}
} else {
throw new Error("未发现数据库");
}
}
// 建议 ✅
function addUserService(db, user) {
if (!db) throw new Error("未发现数据库");
if (!db.isConnected()) throw new Error("数据库未连接");
if (!user) throw new Error("插入失败");
return db.insert("users", user);
}
优先使用对象字面量或映射而不是 switch 语句
使用对象或映射进行索引将减少代码并提高性能。
// 不建议 ❌
const getColorByStatus = (status) => {
switch (status) {
case "success":
return "green";
case "failure":
return "red";
case "warning":
return "yellow";
case "loading":
default:
return "blue";
}
};
// 建议 ✅
const statusColors = {
success: "green",
failure: "red",
warning: "yellow",
loading: "blue",
};
const getColorByStatus = (status) => statusColors[status] || "blue";
使用可选链接
const user = {
email: "JDoe@example.com",
billing: {
iban: "...",
swift: "...",
address: {
street: "上海市徐汇区",
state: "SH",
},
},
};
// 不建议 ❌
const email = (user && user.email) || "N/A";
const street =
(user &&
user.billing &&
user.billing.address &&
user.billing.address.street) ||
"N/A";
const state =
(user &&
user.billing &&
user.billing.address &&
user.billing.address.state) ||
"N/A";
// 建议 ✅
const email = user?.email || "N/A";
const street = user?.billing?.address?.street || "N/A";
const state = user?.billing?.address?.state || "N/A";
4. 并发
避免回调
// 不建议 ❌
getUser(function (err, user) {
getProfile(user, function (err, profile) {
getAccount(profile, function (err, account) {
getReports(account, function (err, reports) {
sendStatistics(reports, function (err) {
console.error(err);
});
});
});
});
});
// 建议 ✅
getUser()
.then(getProfile)
.then(getAccount)
.then(getReports)
.then(sendStatistics)
.catch((err) => console.error(err));
// 或者使用 Async/Await ✅✅
async function sendUserStatistics() {
try {
const user = await getUser();
const profile = await getProfile(user);
const account = await getAccount(profile);
const reports = await getReports(account);
return sendStatistics(reports);
} catch (e) {
console.error(err);
}
}
5. 错误处理
处理抛出的错误和reject Promise
现在花时间正确处理错误将减少以后不得不寻找错误的可能性,尤其是线上环境。
// 不建议 ❌
try {
// ...
} catch (e) {
console.log(e);
}
// 建议 ✅
try {
// ...
} catch (e) {
console.error(e);
// 发送到服务器
reportErrorToServer(e);
}
6. 注释
只注释业务逻辑
可读的代码使您免于过度注释您的代码。因此,您应该只注释复杂的逻辑。
// 不建议 ❌
function generateHash(str) {
// Hash变量
let hash = 0;
// 获取字符串长度
let length = str.length;
// 如果字符串为空,直接返回hash
if (!length) {
return hash;
}
// 循环遍历
for (let i = 0; i < length; i++) {
// 获取 character code.
const char = str.charCodeAt(i);
// 生成 hash
hash = (hash << 5) - hash + char;
// 转换为32位整数
hash = hash & hash;
}
}
// 建议 ✅
function generateHash(str) {
let hash = 0;
let length = str.length;
if (!length) {
return hash;
}
for (let i = 0; i < length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash; // 转换为32位整数
}
return hash;
}
使用版本控制
没必要保留注释代码或日志注释,版本控制已经可以处理这个问题。
// 不建议 ❌
/**
* 2022-7-21: 删除
* 2022-7-15: 更新
* 2022-7-10: 新增
*/
function generateCanonicalLink(user) {
// const session = getUserSession(user)
const session = user.getSession();
// ...
}
// 建议 ✅
function generateCanonicalLink(user) {
const session = user.getSession();
// ...
}
本文简要讨论了一些编码风格,采用这些做法可能需要一些时间,尤其是对于较大的代码库,但从长远来看,您的代码将变得可读、可扩展且易于重构。