Clean Code JavaScript 代码规范以及建议
默认实参通常比短路更简洁。请注意,如果您使用它们,您的函数将只为未定义的参数提供默认值。其他“假”值,如''、""、false、null、0和NaN
,将不会被默认值替换。
Bad:
function createMicrobrewery(name) {
const breweryName = name || "Hipster Brew Co.";
// ...
}
Good:
function createMicrobrewery(name = "Hipster Brew Co.") {
// ...
}
限制函数参数的数量是非常重要的,因为它使测试函数更加容易。超过三个会导致组合爆炸,你必须用每个单独的参数测试大量不同的情况。一个或两个参数是理想的情况,如果可能的话应该避免三个参数。超过这一数字的内容应该加以巩固。通常,如果你有两个以上的参数,那么你的函数尝试做太多。在不需要参数的情况下,大多数情况下更高级的对象作为参数就足够了。因为JavaScript
允许你动态地创建对象,而不需要很多类样板,所以如果你发现自己需要很多参数,你可以使用对象。为了明确函数期望的属性,可以使用ES2015/ES6
解构语法。这有几个优点:当有人查看函数签名时,可以立即清楚地知道正在使用什么属性。它可以用来模拟命名参数。解构还克隆了传入函数的参数对象的指定原语值。这有助于预防副作用。注意:不克隆从实参对象析构的对象和数组。它可以警告您有关未使用属性的信息,这在不破坏的情况下是不可能的。
Bad:
function createMenu(title, body, buttonText, cancellable) {
// ...
}
createMenu("Foo", "Bar", "Baz", true);
Good:
function createMenu({ title, body, buttonText, cancellable }) {
// ...
}
createMenu({
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true
});
函数应该做一件事
这是到目前为止软件工程中最重要的规则。当函数做不止一件事情时,它们就更难组合、测试和推理。当你可以将一个函数隔离为一个动作时,它可以很容易地重构,你的代码读起来也会干净得多。
Bad:
function emailClients(clients) {
clients.forEach(client => {
const clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}
Good:
function emailActiveClients(clients) {
clients.filter(isActiveClient).forEach(email);
}
function isActiveClient(client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}
函数只应该是一个抽象级别
当你有一个以上的抽象层次时,你的函数通常做的太多了。分解功能可以实现可重用性和更容易的测试。
function parseBetterJSAlternative(code) {
const REGEXES = [
// ...
];
const statements = code.split(" ");
const tokens = [];
REGEXES.forEach(REGEX => {
statements.forEach(statement => {
// ...
});
});
const ast = [];
tokens.forEach(token => {
// lex...
});
ast.forEach(node => {
// parse...
});
}
Good:
function parseBetterJSAlternative(code) {
const tokens = tokenize(code);
const syntaxTree = parse(tokens);
syntaxTree.forEach(node => {
// parse...
});
}
function tokenize(code) {
const REGEXES = [
// ...
];
const statements = code.split(" ");
const tokens = [];
REGEXES.forEach(REGEX => {
statements.forEach(statement => {
tokens.push(/* ... */);
});
});
return tokens;
}
function parse(tokens) {
const syntaxTree = [];
tokens.forEach(token => {
syntaxTree.push(/* ... */);
});
return syntaxTree;
}
删除重复代码尽你最大的努力避免重复代码。
重复代码是不好的,因为这意味着如果您需要更改某些逻辑,可以在不止一个地方修改某些内容。想象一下,如果你经营一家餐馆,你跟踪你的库存:你所有的番茄、洋葱、大蒜、香料等等。如果你有多个这样的列表,那么当你提供有西红柿的菜肴时,所有的列表都必须更新。如果你只有一个列表,那就只有一个地方可以更新!通常您有重复的代码,因为您有两个或多个稍微不同的东西,它们有很多共同之处,但它们的不同迫使您有两个或多个独立的函数来做大部分相同的事情。**删除重复代码意味着创建一个抽象,它可以仅用一个函数/模块/类处理这组不同的事情。**获得正确的抽象是至关重要的,这就是为什么您应该遵循在Classes部分中列出的坚实原则。糟糕的抽象可能比重复代码更糟糕,所以要小心!话虽如此,如果你能做出一个好的抽象,那就去做吧!不要重复你自己,否则你会发现自己随时更新多个地方,你想改变一件事。
Bad:
function showDeveloperList(developers) {
developers.forEach(developer => {
const expectedSalary = developer.calculateExpectedSalary();
const experience = developer.getExperience();
const githubLink = developer.getGithubLink();
const data = {
expectedSalary,
experience,
githubLink
};
render(data);
});
}
function showManagerList(managers) {
managers.forEach(manager => {
const expectedSalary = manager.calculateExpectedSalary();
const experience = manager.getExperience();
const portfolio = manager.getMBAProjects();
const data = {
expectedSalary,
experience,
portfolio
};
render(data);
});
}
Good:
function showEmployeeList(employees) {
employees.forEach(employee => {
const expectedSalary = employee.calculateExpectedSalary();
const experience = employee.getExperience();
const data = {
expectedSalary,
experience
};
switch (employee.type) {
case "manager":
data.portfolio = employee.getMBAProjects();
break;
case "developer":
data.githubLink = employee.getGithubLink();
break;
}
render(data);
});
}
不要使用标志作为函数参数
标志告诉用户这个函数做了不止一件事。函数应该做一件事。如果你的函数遵循不同的代码路径,根据布尔值将它们分开。
Bad:
function createFile(name, temp) {
if (temp) {
fs.create(`./temp/${name}`);
} else {
fs.create(name);
}
}
Good:
function createFile(name) {
fs.create(name);
}
function createTempFile(name) {
createFile(`./temp/${name}`);
}
不要写入全局函数
在JavaScript
中,污染全局函数是一个不好的做法,因为你可能会与另一个库冲突,你的API
的用户将一无所知,直到他们在生产中得到一个异常。让我们考虑一个例子:如果您想扩展JavaScript的原生数组方法,使其具有一个diff
方法,可以显示两个数组之间的差异,该怎么办?你可以把你的新函数写入数组。原型,但它可能与另一个库冲突,试图做同样的事情。如果另一个库只是使用diff
来查找数组的第一个元素和最后一个元素之间的差异,那会怎么样呢?这就是为什么使用ES2015/ES6
类并简单地扩展数组全局会更好。
Bad:
Array.prototype.diff = function diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
};
Good:
class SuperArray extends Array {
diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
}
}
封装条件
Bad:
if (fsm.state === "fetching" && isEmpty(listNode)) {
// ...
}
Good:
function shouldShowSpinner(fsm, listNode) {
return fsm.state === "fetching" && isEmpty(listNode);
}
if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}
函数调用者和被调用者应该接近
如果一个函数调用另一个函数,在源文件中保持这些函数垂直靠近。理想情况下,让呼叫者位于被呼叫者的正上方。我们倾向于从上到下阅读代码,就像报纸一样。因此,让你的代码以这种方式阅读。