有时候看别人的项目的代码或者自己以前写的代码,总感觉如屎山一样,一点都不清爽干净~~~
干净代码要的是简洁、清晰且易于维护,它遵循一定的规范和实践,避免了复杂性和冗余。
干净代码风格是一致的,例如缩进、命名风格(驼峰命名或下划线命名)、括号位置等。
干净代码的重要性在于它提高了开发效率,减少了错误,并确保了代码库能够长期得到有效维护和更新。
尽管对干净代码的理解存在主观性,但遵循通用的编码约定可以帮助我们写出更好的代码。
1、使用有意义的变量、函数和类的名字
避免 x 或 temp 这样的通用变量名,而使用 userAge 或 tempFilePath:
不推荐的通用变量名:
// 不明确的变量名,不清楚变量的用途
let x = 10;
let temp = "example.txt";
推荐的描述性变量名:
// 明确的变量名,一目了然变量的用途
let userAge = 10; // 表示用户的年龄
let tempFilePath = "example.txt"; // 表示临时文件的路径
2、注释只解释“为什么”
避免在代码中写“显而易见”的注释,注释应当说明设计决策和背景,而不是解释代码做了什么。
避免的“显而易见”的注释:
// 将数组中的每个元素乘以2
let doubledArray = array.map(item => item * 2);
在这个例子中,注释直接解释了代码的行为,这是显而易见的,因为从map函数和乘法操作就可以直接看出意图。
更有价值的注释:
// 根据用户的权限等级调整定价策略
// 如果用户是VIP,则应用额外的折扣
let finalPrice = calculatePriceWithDiscount(basePrice, user.isVIP);
另一个例子:
// 检查用户是否已登录
if (user.isLoggedIn) {
// 用户已登录,允许访问受保护的资源
allowAccessToProtectedResource();
} else {
// 用户未登录,重定向到登录页面
redirectToLogin();
}
这里的注释解释了代码块的意图和设计决策,而不是简单地重述代码做了什么。
更好的做法:
// 为了避免未来代码修改时忘记更新注释,最好让代码本身尽可能清晰
// 并且只在必要时添加注释
function applyVipDiscount(price) {
return price * 0.9; // VIP用户享受10%的折扣
}
// 检查用户是否有权访问受保护的资源
// 这个检查包括了用户登录状态和权限等级的验证
if (userHasAccess(user)) {
allowAccessToProtectedResource();
} else {
redirectToLogin();
}
applyVipDiscount 函数的注释解释了折扣的逻辑,这是未来可能需要了解的信息,尤其是如果折扣逻辑发生变化时。
userHasAccess 函数则封装了登录状态和权限等级的检查,减少了重复代码和注释,使得代码更加清晰。
3、代码风格一致
代码风格一致,遵循团队的代码规范,例如缩进、命名风格(驼峰命名或下划线命名)、括号位置等。
缩进和空格:
// 正确的缩进和空格使用
function calculateSum(a, b) {
let sum = a + b;
return sum;
}
// 错误的缩进和空格使用
function calculateProduct(a, b){
let product= a * b;
return product;
}
驼峰命名(CamelCase):
// 使用驼峰命名的变量和函数
let userFirstName;
let userLastName;
function getFullName() {
return userFirstName + ' ' + userLastName;
}
下划线命名(snake_case):
// 使用下划线命名的变量和函数
let user_first_name;
let user_last_name;
function get_full_name() {
return user_first_name + ' ' + user_last_name;
}
团队应该选择一种命名风格,并在整个项目中一致地使用它。
4、简洁性与清晰性
简洁性有助于提高代码的可读性和可维护性,但同样重要的是确保代码清晰易懂。
简洁但不清晰:
const countVowels = s => (s.match(/[aeiou]/gi) || []).length;
这行代码虽然简洁,但对不熟悉正则表达式的开发者来说,可能不清楚它的作用。
清晰但较长:
function countVowels(s) {
const vowelRegex = /[aeiou]/gi; // 定义一个正则表达式匹配元音
const matches = s.match(vowelRegex) || []; // 使用正则表达式找到所有匹配项
return matches.length; // 返回匹配项的数量
}
这个函数虽然代码行数更多,但它清晰地展示了每一步的操作,使得其他开发者即使不熟悉正则表达式,也能理解代码的意图和功能。
通过这两个例子,我们可以看到,虽然简洁的代码节省空间,但清晰的代码更容易被理解和维护。
在编程中,我们需要在代码的简洁性和清晰性之间找到平衡。
5、代码可复用
代码可复用可以提高开发效率、减少错误、节省时间和资源。
通过复现有代码,开发人员可以节省时间和精力,提升代码质量和一致性,减少引入错误的风险。
可复用码还使得软件架构更加模块化和可扩展,从而在维护和更新代码库时更加容易。
不可复用的代码:
// 不可重用的代码,重复逻辑
function sendWelcomeEmail(user) {
const welcomeMessage = `Welcome, ${user.name}!`;
sendEmail(user.email, "Welcome", welcomeMessage);
}
function sendGoodbyeEmail(user) {
const goodbyeMessage = `Goodbye, ${user.name}!`;
sendEmail(user.email, "Goodbye", goodbyeMessage);
}
在这个例子中,sendWelcomeEmail 和 sendGoodbyeEmail 函数执行相似的任务,但包含了复用的逻辑,这降低了代码的可复用性。
可复用的代码:
// 可重用的代码,提取公共逻辑
function sendEmailToUser(user, subject, message) {
sendEmail(user.email, subject, message);
}
// 使用可重用的函数
function sendWelcomeEmail(user) {
const welcomeMessage = `Welcome, ${user.name}!`;
sendEmailToUser(user, "Welcome", welcomeMessage);
}
function sendGoodbyeEmail(user) {
const goodbyeMessage = `Goodbye, ${user.name}!`;
sendEmailToUser(user, "Goodbye", goodbyeMessage);
}
在这个例子中,我们提取了发送电子邮件的公共逻辑到 sendEmailToUser 函数中,这样它就可以被 sendWelcomeEmail 和 sendGoodbyeEmail 函数复用。
6、单一职责原则 (SRP)
每个函数或类只负责一个任务,避免“万能函数”或“上帝类”。
// 不好
function processOrder(order) {
validateOrder(order);
saveOrderToDatabase(order);
sendOrderNotification(order);
}
// 好
function validateOrder(order) { /*...*/ }
function saveOrder(order) { /*...*/ }
function sendNotification(order) { /*...*/ }
7、模块化
模块化是编写清晰代码的关键概念,它涉及将大型复杂代码分解成更小、更易管理的模块或函数,便于理解、测试和维护。
模块化优势:
可重用性:模块可以在应用的不同部分或其他应用中重用,节省开发时间和精力。
封装性:模块隐藏函数或对象的内部细节,只暴露必要的接口,减少代码耦合,提升代码质量。
可扩展性:将大型代码分解成小模块,方便添加或移除功能,不影响整个代码库。
// 无模块化
function calculatePrice(quantity, price, tax) {
let subtotal = quantity * price;
let total = subtotal + (subtotal * tax);
return total;
}
// 有模块化
function calculateSubtotal(quantity, price) {
return quantity * price;
}
function calculateTotal(subtotal, tax) {
return subtotal + (subtotal * tax);
}
8、错误处理
正确的错误处理能够防止程序意外崩溃,并提供有用的调试信息。
不好的处理方式是仅仅记录错误而不进行适当响应。
不好的错误处理方式:
try {
result = divide(x, y);
} catch (error) {
console.error("An error occurred"); // 仅仅记录了一个通用的错误信息,没有具体说明问题
}
好的错误处理方式:
try {
result = divide(x, y);
} catch (error) {
if (error instanceof ZeroDivisionError) {
console.error("Division by zero error:", error.message); // 指出了具体的错误类型和信息
} else if (error instanceof ValueError) {
console.error("Invalid input:", error.message); // 提供了错误输入的具体信息
} else {
console.error("An unexpected error occurred:", error.message); // 处理其他未预期的错误
}
}
9、测试
编写单元测试可以确保代码的正确性,并在重构时提供信心。
测试驱动开发(TDD)迫使开发者预先考虑各种情况和预期行为,从而编写出更清晰的代码。
使用 JavaScript 和 Jest 测试框架的单元测试:
// 测试加法函数的正确性
test('addition works correctly', () => {
expect(add(2, 3)).toBe(5); // 测试正常的加法
expect(add(-1, 1)).toBe(0); // 测试负数和正数相加
expect(add(0, 0)).toBe(0); // 测试零值相加
});
10、文件夹结构
选择良好的文件夹结构是编写清晰代码的重要组成部分。
良好的项目结构帮助开发者轻松查找和修改代码,降低代码复杂性,提高项目的可扩展性和可维护性。
不好的目录结构:
my-app/
├── App.js
├── index.js
├── components/
│ ├── Button.js
│ ├── Card.js
│ └── Navbar.js
├── containers/
│ ├── Home.js
│ ├── Login.js
│ └── Profile.js
├── pages/
│ ├── Home.js
│ ├── Login.js
│ └── Profile.js
└── utilities/
├── api.js
└── helpers.js
好的目录结构:
my-app/
├── src/
│ ├── components/
│ │ ├── Button/
│ │ │ ├── Button.js
│ │ │ └── index.js
│ ├── pages/
│ │ ├── Home/
│ │ │ ├── Home.js
│ │ │ └── index.js
│ ├── utils/
│ │ ├── api.js
│ │ └── helpers.js
│ ├── App.js
│ └── index.js
└── public/
├── index.html
└── favicon.ico
虽然规范很多,但如果项目在稳定运行的时候,千万切记不要随便改动,稳定大于一切,不然改动史就会成为血泪史~~~