1.概述
首先如何编写干净得代码,这是一个有争议的问题,答案也是众说纷纭,但总的来说,干净代码指的是易于阅读、理解和维护的代码。遵循最佳实践和行业标准,使得我们能够轻松编写干净代码,避免臃肿、冗余和复杂性。
今天我们要分享的就是这些最佳实践。
2. 命名约定
我们知道,变量名称对于代码的可读性和可维护性至关重要。在给变量、函数和对象命名时,使用描述性和有意义的名称,有助于快速理解和使用代码。避免使用缩写和其他不通用的快捷方式,也能够进一步提高代码的可读性。
例如,我们最好使用更具描述性的名称,例如customerData或productList,而不是模糊的变量名称,如data。
// ❌避免这种情况
const d = [1, 2, 3];
// ✅应该这样写
const data = [1, 2, 3];
// ❌ 避免这种情况
function add(a) {
return a + 1;
}
// ✅ 应该这样写
function addOne(number) {
return number + 1;
}
3. 给函数命名
不妨给每个函数,包括闭包和回调命名。尽量避免使用匿名函数,否则在分析app时会遇到困难。
同样的,使用监视工具调试生产问题可能会导致无法轻松找到问题的根本原因。
命名的函数允许我们在检查内存快照或其他内容时,轻松了解正在查看的内容。
4. 使用ES6特性
ES6是JavaScript的最新版本,它引入的功能可以大大提高JavaScript代码的简洁性和简洁性。比如,解构允许开发人员更轻松地从数组或对象中提取值,箭头函数为定义函数提供了更简洁的语法,模板字面量允许将表达式嵌入到字符串,而无需连接或转义字符。
简单的代码演示如下:
// const和let声明
const PI = 3.14;
let name = 'John Doe';
// 模板文字
const message = `Hello ${name}!`;
//箭头函数
const square = (x) => x * x;
// 默认参数
const add = (a, b = 0) => a + b;
// 解构赋值
const data = [1, 2, 3];
const [first, second, third] = data;
const person = {
firstName: 'John',
lastName: 'Doe'
};
const { firstName, lastName } = person;
// 扩展运算符
const lotus = [1, 2, 3];
const numbers = [...lotus, 4, 5, 6]; // [1, 2, 3, 4, 5, 6]
// rest参数
const sum = (...numbers) => numbers.reduce((total, current) => total + current, 0);
sum(...numbers) // 21
// 对象字面量
const firstName = 'John';
const lastName = 'Doe';
const person = { firstName, lastName };
// 类语法
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
}
5.避免全局变量
全局变量可能会带来问题,例如命名冲突。所以,我认为我们应该避免使用全局变量,除非你的目标是在整个app中共享变量,例如环境变量。
若要更有效地组织和管理代码,不妨考虑使用模块。模块提供了一种封装相关代码和数据的方法,最大限度地减少了命名冲突、意外副作用的风险,使维护和更新代码变得更加容易。
// ❌ 避免这种情况
var counter = 0;
function incrementCounter() {
counter++;
}
// ✅ 应该这样写
// module.js
let counter = 0;
function incrementCounter() {
counter++;
}
export { counter };
// ✅ 最好这样写
function createCounter() {
let counter = 0;
return {
increment: function () {
counter++;
},
getCount: function () {
return counter;
}
};
}
const counter = createCounter();
counter.increment();
错误示例:声明变量counter为全局变量,可能会导致命名冲突,使代码难以维护。
好的示例:导出之前在模块内声明变量,使变量成为模块的局部变量,从而避免了命名冲突。
更好的示例:变量被封装在函数中,创建闭包。这样,我们就无法从函数外部访问变量,使变量真正私有从而避免命名冲突。
6.小小的函数大大的作用
保持函数小型化且专注于单一职责非常重要。通过将复杂的逻辑分解为更小的函数,可以提高代码的可读性。较小的函数通常更可重用且更易于测试,从长远来看,可以节省我们的时间和精力。
经验法则,functions should only do one thing and do it well。
// ❌ 避免这种情况
function calculateOrderTotal(items, shippingMethod, discount) {
let subtotal = 0;
for (const item of items) {
subtotal += item.price * item.quantity;
}
let shippingCost = 0;
if (shippingMethod === 'standard') {
shippingCost = subtotal * 0.1;
} else if (shippingMethod === 'express') {
shippingCost = subtotal * 0.2;
}
let discountAmount = 0;
if (discount) {
discountAmount = subtotal * discount;
}
return subtotal + shippingCost - discountAmount;
}
// ✅ 应该这写
function calculateSubtotal(items) {
return items.reduce((acc, item) => acc + item.price * item.quantity, 0)
}
function calculateShippingCost(subtotal, shippingMethod) {
const shippingRates = {
standard: 0.1,
express: 0.2,
};
return Object.keys(shippingRates).reduce((shippingCost, method) => {
if (shippingMethod === method) {
shippingCost = subtotal * shippingRates[method];
}
return shippingCost;
}, 0);
}
function calculateDiscountAmount(subtotal, discount) {
let discountAmount = 0;
if (discount) {
discountAmount = subtotal * discount;
}
return discountAmount;
}
function calculateOrderTotal(items, shippingMethod, discount) {
const subtotal = calculateSubtotal(items);
const shippingCost = calculateShippingCost(subtotal, shippingMethod);
const discountAmount = calculateDiscountAmount(subtotal, discount);
return subtotal + shippingCost - discountAmount;
}
错误示例:函数calculateOrderTotal做了太多事情。如果不逐行浏览,我们很难理解函数的作用。
好的示例 :将代码重构为更小、更易于管理的函数。每个函数都有单一的职责,代码更易于理解、测试和维护。
7.使用linter
Linting工具非常有用,可以提高代码质量。它可以检查潜在的错误、bug和编码冲突,确保代码遵循最佳的实践和约定。
通过使用像ESLint这样的linting工具可以帮助保持代码干净、可维护,并在错误导致生产问题之前捕获错误。
下面是如何使用ESLint改进代码的简单示例:
npm install eslint --save-dev
在项目的根目录中创建名为.eslintrc的文件,并添加以下配置:
// .eslintrc
{
"extends": "eslint:recommended",
"rules": {
"no-console": "off"
}
}
现在我们可以使用以下命令在代码上运行 ESLint:
npx eslint your-file.js
更新代码:
// ❌ 原始代码
const name = 'John Doe';
console.log('Hello, ' + name);
// ✅ 修正后的代码
const name = 'John Doe';
console.log(`Hello, ${name}`);
虽然一开始有点麻烦,但随着所有红线的弹出,我们可以发现代码更易于理解和维护了。等到未来再需要搞这个项目时,无论是你自己还是其他开发人员,相信我,都会由衷感谢你。
8. 遵循单一责任原则
单一责任原则(SRP)是软件开发中的一个基本原则,即每个模块、类或函数都应该具有单一的、明确定义的职责。
举个例子:
class User {
constructor(name, email, password) {
this.name = name;
this.email = email;
this.password = password;
}
getName() {
return this.name;
}
getEmail() {
return this.email;
}
setPassword(password) {
this.password = password;
}
sendEmail() {
// 发送邮件给用户的代码
}
}
上面的User类有明确定义的职责:管理用户的信息。
getName和getEmail方法分别检索用户名和电子邮件地址。setPassword方法更新用户密码,sendEmail方法向用户发送电子邮件。
每个方法都遵循单一责任原则,职能既不重叠也不身兼多职。
9. 编写测试用例
我们希望编写的代码能够正常工作,在部署到生产环境时不会意外中断。这就是测试用例的用武之地。测试用例是复查代码、确保代码正确执行的一个方法。
编写测试用例时,必须涵盖代码的所有重要部分:包括正常情况,也包括棘手的边缘情况,也就是事情可能无法按预期工作的情况。我们还需要测试当出现问题时,例如当用户输入错误或API因任何原因关闭时,会发生什么事情。
虽然我们需要花时间编写好的测试用例,但测试用例可以帮助我们在流程的早期发现错误和bug。这意味着我们可以在这些错误和bug成为大麻烦之前修复它们。
此外,良好的测试用例还意味着你可以更有信心地对代码进行更改和更新,因为你知道你不会引入新的问题,也不会破坏当前有效的代码。
10. 使用描述性错误消息
编程时犯错误是难免的,没关系。但是,如果出现错误,那么最好快速进行调试。这就需要错误消息出马了,帮助我们快速识别和解决问题。
因此,创建清晰且信息丰富的错误消息至关重要,从长远来看,解释错误的原因并提供有关如何修复错误的指导,可以帮助我们快速诊断和解决代码中出现的任何问题,从而为我们节省大量时间。
因此,花点时间编写描述性错误消息,可以使调试过程事半功倍。
try {
// code that may throw an error
if (someCondition) {
throw new Error("Invalid argument: someCondition must be true");
}
} catch (error) {
console.error(error.message);
}
在上面的示例中,我们使用try-catch块来处理错误。如果try块中的代码抛出错误,那么console.error()会捕获错误消息并记录到控制台。
错误消息包括"Invalid argument: someCondition must be true"这样的描述,因此可以提供足够的信息来帮助开发人员快速了解错误的原因。
此外,如果是其他人使用我们的代码,那么描述性错误消息可以帮助用户快速了解出了什么问题,从而改善用户体验(但不要向前端提供太多信息,因为也可能帮助不友好的用户利用你的app)。
不要犹豫,快快使用try catch块吧,否则即使你发现了错误也会因为描述不够具体,而毫无意义。
11. 定期重构
重构是在不改变代码行为的情况下改进代码内部结构的过程。
随着代码的不断增加和变化,维护会变得越来越复杂、越来越困难。
重构有助于提高代码性能,减少错误和bug,方便你和其他开发人员将来理解和修改代码。
假设有这样一段已经变得难以管理和理解的代码:
function calculateSum(numbers) {
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
return sum;
}
这段代码目的是计算数字总和。但是,随着时间的推移,随着职责数量或代码复杂性的增加,代码可能会变得难以维护。
为了解决这个问题,我们可以重构代码,例如我们在这里使用了一个简单的reduce:
function calculateSum(numbers) {
return numbers.reduce((sum, number) => sum + number, 0);
}
12.结论
编写干净可读的代码对于生产高质量的软件至关重要。遵循上面提到的最佳实践,例如保持函数简短扼要并专注于单个职责、使用描述性变量和函数名称、以及使用错误消息可以使代码更易于维护、更易于理解。
谨记,编写干净代码不是一蹴而就的,而是一个需要练习和规范的持续过程。从长远来看,干净代码可以节省我们的时间和精力。通过不断提高代码质量,我们可以制作更加可靠、高效且易于使用的软件。
编码快乐!