- Introduction
- Variables
- Functions
- Objects and Data Structures
- Classes
- SOLID
- Testing
- Concurrency
- Error Handling
- Formatting
- Comments
- Translation
Software engineering principles, from Robert C. Martin's book Clean Code, adapted for JavaScript. This is not a style guide. It's a guide to producing readable, reusable, and refactorable software in JavaScript.
软件工程原则,来自Robert C. Martin的《Clean Code》一书, 适合于JavaScript。这不是一个风格指南。它是一份用JavaScript制作可读的、可重用的和可重构的软件的指南。
Not every principle herein has to be strictly followed, and even fewer will be universally agreed upon. These are guidelines and nothing more, but they are ones codified over many years of collective experience by the authors of Clean Code.
并非必须严格遵循此处的每个原则,甚至更少会被普遍认同。这些只是指导方针,但它们是 Clean Code 的作者多年集体经验所编纂的。
Our craft of software engineering is just a bit over 50 years old, and we are still learning a lot. When software architecture is as old as architecture itself, maybe then we will have harder rules to follow. For now, let these guidelines serve as a touchstone by which to assess the quality of the JavaScript code that you and your team produce.
我们的软件工程技术只有50多年历史,仍在不断学习中。当软件架构像建筑一样悠久时,或许我们会有更严格的规则需要遵循。现在,请将这些指南作为评估您和团队所编写JavaScript代码质量的试金石。
One more thing: knowing these won't immediately make you a better software developer, and working with them for many years doesn't mean you won't make mistakes. Every piece of code starts as a first draft, like wet clay getting shaped into its final form. Finally, we chisel away the imperfections when we review it with our peers. Don't beat yourself up for first drafts that need improvement. Beat up the code instead!
还有一件事:知道这些并不会立即让你成为更好的软件开发人员,而且与它们共事多年并不意味着你不会犯错误。每段代码都是第一稿,就像湿泥被塑造成最终形态一样。最后,在与同行审查时我们去除其中的缺陷。不要因需要改进的初稿而自责,相反应该把精力放在修改代码上!
Bad:
const yyyymmdstr = moment().format("YYYY/MM/DD");
Good:
const currentDate = moment().format("YYYY/MM/DD");
Bad:
getUserInfo();
getClientData();
getCustomerRecord();
Good:
getUser();
We will read more code than we will ever write. It's important that the code we do write is readable and searchable. By not naming variables that end up being meaningful for understanding our program, we hurt our readers. Make your names searchable. Tools like buddy.js and ESLint can help identify unnamed constants.
我们将会阅读比写更多的代码。重要的是,我们所编写的代码应该易于阅读和搜索。如果不为那些最终对理解程序有意义的变量命名,就会给读者带来困难。让你的名称可搜索化。像buddy.js和ESLint这样的工具可以帮助识别未命名常量。
Bad:
// What the heck is 86400000 for?
setTimeout(blastOff, 86400000);
Good:
// Declare them as capitalized named constants.
// 将它们声明为大写的命名常量。
const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000; //86400000;
setTimeout(blastOff, MILLISECONDS_PER_DAY);
Bad:
const address = "One Infinite Loop, Cupertino 95014";
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
saveCityZipCode(
address.match(cityZipCodeRegex)[1],
address.match(cityZipCodeRegex)[2]
);
Good:
const address = "One Infinite Loop, Cupertino 95014";
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
const [_, city, zipCode] = address.match(cityZipCodeRegex) || [];
saveCityZipCode(city, zipCode);
Explicit is better than implicit. 明确比隐含的好
Bad:
const locations = ["Austin", "New York", "San Francisco"];
locations.forEach(l => {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
// Wait, what is `l` for again?
dispatch(l);
});
Good:
const locations = ["Austin", "New York", "San Francisco"];
locations.forEach(location => {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
dispatch(location);
});
If your class/object name tells you something, don't repeat that in your variable name.
如果你的类/对象名称已经表达了某个意思,那么在变量名中不要重复它。
Bad:
const Car = {
carMake: "Honda",
carModel: "Accord",
carColor: "Blue"
};
function paintCar(car, color) {
car.carColor = color;
}
Good:
const Car = {
make: "Honda",
model: "Accord",
color: "Blue"
};
function paintCar(car, color) {
car.color = color;
}
Default parameters are often cleaner than short circuiting. Be aware that if you
use them, your function will only provide default values for undefined
arguments. Other "falsy" values such as ''
, ""
, false
, null
, 0
, and
NaN
, will not be replaced by a default value.
默认参数通常比短路更简洁。请注意,如果您使用它们,则函数仅为未定义的参数提供默认值。其他“假值”(如''、""、false、null、0和NaN)将不会被替换为默认值。
Bad:
function createMicrobrewery(name) {
const breweryName = name || "Hipster Brew Co.";
// ...
}
Good:
function createMicrobrewery(name = "Hipster Brew Co.") {
// ...
}
Limiting the amount of function parameters is incredibly important because it makes testing your function easier. Having more than three leads to a combinatorial explosion where you have to test tons of different cases with each separate argument.
限制函数参数的数量非常重要,因为这样可以使测试函数变得更容易。如果超过三个参数,就会导致组合爆炸,需要对每个单独的参数进行大量不同情况的测试。
One or two arguments is the ideal case, and three should be avoided if possible. Anything more than that should be consolidated. Usually, if you have more than two arguments then your function is trying to do too much. In cases where it's not, most of the time a higher-level object will suffice as an argument.
一个或两个参数是理想情况,如果可能的话应该避免使用三个以上的参数。超过两个参数通常意味着函数尝试做太多事情。在不是这种情况下,大多数时候高级对象作为参数就足够了。
Since JavaScript allows you to make objects on the fly, without a lot of class boilerplate, you can use an object if you are finding yourself needing a lot of arguments.
由于JavaScript允许您在不需要大量类样板的情况下即可动态创建对象,因此如果您发现自己需要很多参数,则可以使用对象。
To make it obvious what properties the function expects, you can use the ES2015/ES6 destructuring syntax. This has a few advantages:
使用ES2015/ES6解构语法可以清晰地表明函数所需的属性,这有几个优点:
- When someone looks at the function signature, it's immediately clear what properties are being used.
- It can be used to simulate named parameters.
- Destructuring also clones the specified primitive values of the argument object passed into the function. This can help prevent side effects. Note: objects and arrays that are destructured from the argument object are NOT cloned.
- Linters can warn you about unused properties, which would be impossible without destructuring.
- 当有人查看函数签名时,可以立即清楚地知道正在使用哪些属性。
- 它可以用于模拟命名参数。
- 解构还会克隆传递到函数中的参数对象的指定原始值。这有助于防止副作用。注意:从参数对象解构出来的对象和数组不会被克隆。
- 代码检查工具可以警告您未使用的属性,而没有解构将无法实现。
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
});
This is by far the most important rule in software engineering. When functions do more than one thing, they are harder to compose, test, and reason about. When you can isolate a function to just one action, it can be refactored easily and your code will read much cleaner. If you take nothing else away from this guide other than this, you'll be ahead of many developers.
这是软件工程中最重要的规则。当函数执行多个任务时,它们更难组合、测试和理解。如果您可以将一个函数隔离到只执行一个操作,那么它可以轻松地进行重构,并且您的代码会更加清晰易读。如果你从本指南中除此之外什么都没学到,你也比许多开发者领先了。
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();
}
Bad:
function addToDate(date, month) {
// ...
}
const date = new Date();
// It's hard to tell from the function name what is added
// 很难从函数名中看出添加了什么
addToDate(date, 1);
Good:
function addMonthToDate(month, date) {
// ...
}
const date = new Date();
addMonthToDate(1, date);
When you have more than one level of abstraction your function is usually doing too much. Splitting up functions leads to reusability and easier testing.
当您有多个抽象层级时,您的函数通常做了太多事情。拆分函数可以实现可重用性和更容易的测试。
Bad:
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;
}
Do your absolute best to avoid duplicate code. Duplicate code is bad because it means that there's more than one place to alter something if you need to change some logic.
尽你所能避免重复代码。重复代码是不好的,因为这意味着如果您需要修改某些逻辑,那么就有多个地方要修改。
Imagine if you run a restaurant and you keep track of your inventory: all your tomatoes, onions, garlic, spices, etc. If you have multiple lists that you keep this on, then all have to be updated when you serve a dish with tomatoes in them. If you only have one list, there's only one place to update!
想象一下,如果你经营一家餐厅,你要跟踪你的库存:所有的西红柿、洋葱、大蒜、香料等。如果您有多个列表,那么当您提供一道菜时,所有这些列表都必须更新。如果你只有一个列表,那么只有一个地方需要更新!
Oftentimes you have duplicate code because you have two or more slightly different things, that share a lot in common, but their differences force you to have two or more separate functions that do much of the same things. Removing duplicate code means creating an abstraction that can handle this set of different things with just one function/module/class.
通常你有重复的代码,因为你有两个或更多略有不同的东西,它们有很多共同点,但它们的不同之处迫使你有两个或更多的单独函数,它们做了很多相同的事情。删除重复的代码意味着创建一个抽象,可以使用一个函数/模块/类来处理这些不同的东西。
Getting the abstraction right is critical, that's why you should follow the SOLID principles laid out in the Classes section. Bad abstractions can be worse than duplicate code, so be careful! Having said this, if you can make a good abstraction, do it! Don't repeat yourself, otherwise you'll find yourself updating multiple places anytime you want to change one thing.
正确的抽象是至关重要的,这就是为什么你应该遵循“类”部分中列出的SOLID原则。坏的抽象可能比重复的代码更糟糕,所以要小心!话虽如此,如果你能做一个好的抽象,那就去做吧!不要重复自己,否则你会发现每当你想改变一件事情的时候,你会发现自己更新多个地方。
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:
const menuConfig = {
title: null,
body: "Bar",
buttonText: null,
cancellable: true
};
function createMenu(config) {
config.title = config.title || "Foo";
config.body = config.body || "Bar";
config.buttonText = config.buttonText || "Baz";
config.cancellable =
config.cancellable !== undefined ? config.cancellable : true;
}
createMenu(menuConfig);
Good:
const menuConfig = {
title: "Order",
// User did not include 'body' key
buttonText: "Send",
cancellable: true
};
function createMenu(config) {
let finalConfig = Object.assign(
{
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true
},
config
);
return finalConfig
// config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
// ...
}
createMenu(menuConfig);
Flags tell your user that this function does more than one thing. Functions should do one thing. Split out your functions if they are following different code paths based on a boolean.
标记告诉用户这个函数做了不止一件事。函数应该只做一件事。如果函数根据布尔值遵循不同的代码路径,请将其拆分出来。
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}`);
}
A function produces a side effect if it does anything other than take a value in and return another value or values. A side effect could be writing to a file, modifying some global variable, or accidentally wiring all your money to a stranger.
如果一个函数除了接受一个值并返回另一个值或值之外,它还会产生副作用。副作用可能是写入一个文件,修改一些全局变量,或者意外地将你所有的钱都转给一个陌生人。
Now, you do need to have side effects in a program on occasion. Like the previous example, you might need to write to a file. What you want to do is to centralize where you are doing this. Don't have several functions and classes that write to a particular file. Have one service that does it. One and only one.
现在,你确实需要在程序中偶尔产生副作用。就像前面的例子,你可能需要写入一个文件。在一个地方集中处理你想做的事情。不要有几个函数和类写入一个特定的文件。只有一个服务可以做到这一点。只有一个。
The main point is to avoid common pitfalls like sharing state between objects without any structure, using mutable data types that can be written to by anything, and not centralizing where your side effects occur. If you can do this, you will be happier than the vast majority of other programmers.
重点是避免常见的陷阱,比如在没有任何结构的对象之间共享状态,使用可被任何东西写入的可变数据类型,以及不集中副作用发生的地方。如果你能做到这一点,你会比大多数其他程序员更快乐。
Bad:
// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
// 全局变量由以下函数引用。如果我们有另一个函数使用这个名字,现在它将是一个数组,它可能会打破它。
let name = "Ryan McDermott";
function splitIntoFirstAndLastName() {
name = name.split(" ");
}
splitIntoFirstAndLastName();
console.log(name); // ['Ryan', 'McDermott'];
Good:
function splitIntoFirstAndLastName(name) {
return name.split(" ");
}
const name = "Ryan McDermott";
const newName = splitIntoFirstAndLastName(name);
console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];
In JavaScript, some values are unchangeable (immutable) and some are changeable (mutable). Objects and arrays are two kinds of mutable values so it's important to handle them carefully when they're passed as parameters to a function. A JavaScript function can change an object's properties or alter the contents of an array which could easily cause bugs elsewhere.
在JavaScript中,有些值是不可改变的(不可变的),有些值是可变的(可变的)。对象和数组是两种可变值,因此当它们作为参数传递给函数时,处理它们时要小心。JavaScript函数可以改变对象的属性或改变数组的内容,这很容易导致其他地方的错误。
Suppose there's a function that accepts an array parameter representing a
shopping cart. If the function makes a change in that shopping cart array -
by adding an item to purchase, for example - then any other function that
uses that same cart
array will be affected by this addition. That may be
great, however it could also be bad. Let's imagine a bad situation:
假设有一个函数,它接受一个数组参数,表示一个购物车。如果函数对购物车数组进行了更改——例如,添加了一个要购买的项目——那么使用相同cart
数组的任何其他函数都会受到这个添加的影响。这可能很好,但也可能很糟糕。让我们想象一个糟糕的情况:
The user clicks the "Purchase" button which calls a purchase
function that
spawns a network request and sends the cart
array to the server. Because
of a bad network connection, the purchase
function has to keep retrying the
request. Now, what if in the meantime the user accidentally clicks an "Add to Cart"
button on an item they don't actually want before the network request begins?
If that happens and the network request begins, then that purchase function
will send the accidentally added item because the cart
array was modified.
用户点击“购买”按钮,调用一个purchase
函数,该函数生成一个网络请求,并将cart
数组发送到服务器。由于网络连接不好,purchase
函数不得不不断重试请求。现在,如果与此同时用户在网络请求开始之前意外地点击了一个他们实际上不想要的项目的“添加到购物车”按钮会怎么样?如果发生这种情况,网络请求开始,那么购买函数将发送意外添加的项目,因为cart
数组被修改了。
A great solution would be for the addItemToCart
function to always clone the
cart
, edit it, and return the clone. This would ensure that functions that are still
using the old shopping cart wouldn't be affected by the changes.
一个很好的解决方案是addItemToCart
函数总是克隆cart
,编辑它,并返回克隆。这将确保仍在使用旧购物车的函数不会受到更改的影响。
Two caveats to mention to this approach:
这种方法有两个注意事项:
-
There might be cases where you actually want to modify the input object, but when you adopt this programming practice you will find that those cases are pretty rare. Most things can be refactored to have no side effects!
-
Cloning big objects can be very expensive in terms of performance. Luckily, this isn't a big issue in practice because there are great libraries that allow this kind of programming approach to be fast and not as memory intensive as it would be for you to manually clone objects and arrays.
- 也许有些情况下你确实想要修改输入对象,但是当你采用这种编程实践时,你会发现这些情况非常少。大多数事情都可以重构,没有副作用!
- 克隆大对象在性能方面可能非常昂贵。幸运的是,在实践中这不是一个大问题,因为有一些很棒的库允许这种编程方法快速运行,并且不像你手动克隆对象和数组那样占用内存。
Bad:
const addItemToCart = (cart, item) => {
cart.push({ item, date: Date.now() });
};
Good:
const addItemToCart = (cart, item) => {
return [...cart, { item, date: Date.now() }];
};
Polluting globals is a bad practice in JavaScript because you could clash with another
library and the user of your API would be none-the-wiser until they get an
exception in production. Let's think about an example: what if you wanted to
extend JavaScript's native Array method to have a diff
method that could
show the difference between two arrays? You could write your new function
to the Array.prototype
, but it could clash with another library that tried
to do the same thing. What if that other library was just using diff
to find
the difference between the first and last elements of an array? This is why it
would be much better to just use ES2015/ES6 classes and simply extend the Array
global.
在JavaScript中污染全局是一种不好的实践,因为你可能会与另一个库冲突,而你的API的用户却不知道,直到他们在生产中得到一个异常。让我们来看一个例子:如果你想扩展JavaScript的本地数组方法,使其具有一个diff
方法,该方法可以显示两个数组之间的差异怎么办?你可以将你的新函数写到Array.prototype
中,但它可能会与另一个试图做同样事情的库冲突。如果那个其他库只是使用diff
来查找数组的第一个和最后一个元素之间的差异怎么办?这就是为什么最好只使用ES2015/ES6类并简单地扩展Array
全局。
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));
}
}
JavaScript isn't a functional language in the way that Haskell is, but it has a functional flavor to it. Functional languages can be cleaner and easier to test. Favor this style of programming when you can.
JavaScript不是像Haskell那样的函数式语言,但它有一种函数式的风格。函数式语言可以更清晰,更容易测试。尽可能使用这种编程风格。
Bad:
const programmerOutput = [
{
name: "Uncle Bobby",
linesOfCode: 500
},
{
name: "Suzie Q",
linesOfCode: 1500
},
{
name: "Jimmy Gosling",
linesOfCode: 150
},
{
name: "Gracie Hopper",
linesOfCode: 1000
}
];
let totalOutput = 0;
for (let i = 0; i < programmerOutput.length; i++) {
totalOutput += programmerOutput[i].linesOfCode;
}
Good:
const programmerOutput = [
{
name: "Uncle Bobby",
linesOfCode: 500
},
{
name: "Suzie Q",
linesOfCode: 1500
},
{
name: "Jimmy Gosling",
linesOfCode: 150
},
{
name: "Gracie Hopper",
linesOfCode: 1000
}
];
const totalOutput = programmerOutput.reduce(
(totalLines, output) => totalLines + output.linesOfCode,
0
);
Bad:
if (fsm.state === "fetching" && isEmpty(listNode)) {
// ...
}
Good:
function shouldShowSpinner(fsm, listNode) {
return fsm.state === "fetching" && isEmpty(listNode);
}
if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}
Bad:
function isDOMNodeNotPresent(node) {
// ...
}
if (!isDOMNodeNotPresent(node)) {
// ...
}
Good:
function isDOMNodePresent(node) {
// ...
}
if (isDOMNodePresent(node)) {
// ...
}
This seems like an impossible task. Upon first hearing this, most people say,
"how am I supposed to do anything without an if
statement?" The answer is that
you can use polymorphism to achieve the same task in many cases. The second
question is usually, "well that's great but why would I want to do that?" The
answer is a previous clean code concept we learned: a function should only do
one thing. When you have classes and functions that have if
statements, you
are telling your user that your function does more than one thing. Remember,
just do one thing.
这似乎是一个不可能的任务。第一次听到这个问题时,大多数人会说,“如果没有if
语句,我该怎么做任何事情?”答案是你可以使用多态来实现许多情况下的相同任务。第二个问题通常是,“那很好,但我为什么要这样做?”答案是我们之前学过的一个clean code概念:一个函数只应该做一件事。当你有类和函数有if
语句时,你告诉你的用户你的函数做了不止一件事。记住,只做一件事。
Bad:
class Airplane {
// ...
getCruisingAltitude() {
switch (this.type) {
case "777":
return this.getMaxAltitude() - this.getPassengerCount();
case "Air Force One":
return this.getMaxAltitude();
case "Cessna":
return this.getMaxAltitude() - this.getFuelExpenditure();
}
}
}
Good:
class Airplane {
// ...
}
class Boeing777 extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getPassengerCount();
}
}
class AirForceOne extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude();
}
}
class Cessna extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getFuelExpenditure();
}
}
JavaScript is untyped, which means your functions can take any type of argument. Sometimes you are bitten by this freedom and it becomes tempting to do type-checking in your functions. There are many ways to avoid having to do this. The first thing to consider is consistent APIs.
JavaScript是无类型的,这意味着你的函数可以接受任何类型的参数。有时你会被这种自由所困扰,这时候就会想在你的函数中做类型检查。有很多方法可以避免这样做。首先要考虑的是一致的API。
Bad:
function travelToTexas(vehicle) {
if (vehicle instanceof Bicycle) {
vehicle.pedal(this.currentLocation, new Location("texas"));
} else if (vehicle instanceof Car) {
vehicle.drive(this.currentLocation, new Location("texas"));
}
}
Good:
function travelToTexas(vehicle) {
vehicle.move(this.currentLocation, new Location("texas"));
}
If you are working with basic primitive values like strings and integers, and you can't use polymorphism but you still feel the need to type-check, you should consider using TypeScript. It is an excellent alternative to normal JavaScript, as it provides you with static typing on top of standard JavaScript syntax. The problem with manually type-checking normal JavaScript is that doing it well requires so much extra verbiage that the faux "type-safety" you get doesn't make up for the lost readability. Keep your JavaScript clean, write good tests, and have good code reviews. Otherwise, do all of that but with TypeScript (which, like I said, is a great alternative!).
如果你正在使用基本的原始值,如字符串和整数,并且你不能使用多态,但你仍然觉得需要类型检查,你应该考虑使用TypeScript。它是普通JavaScript的一个很好的替代品,因为它在标准JavaScript语法之上为你提供了静态类型。手动检查正常的JavaScript的问题是,做得很好需要很多额外的冗余,你得到的伪“类型安全”并不能弥补失去的可读性。保持你的JavaScript干净,写好测试,有好的代码审查。否则,做所有的事情,利用TypeScript(就像我说的,这是一个很好的替代品!)。
Bad:
function combine(val1, val2) {
if (
(typeof val1 === "number" && typeof val2 === "number") ||
(typeof val1 === "string" && typeof val2 === "string")
) {
return val1 + val2;
}
throw new Error("Must be of type String or Number");
}
Good:
function combine(val1, val2) {
return val1 + val2;
}
Modern browsers do a lot of optimization under-the-hood at runtime. A lot of times, if you are optimizing then you are just wasting your time. There are good resources for seeing where optimization is lacking. Target those in the meantime, until they are fixed if they can be.
现代浏览器在运行时做了很多优化。很多时候,如果你正在优化,那么你只是在浪费时间。有很好的资源,可以看到哪里缺少优化。在此期间,聚焦到这些地方,如果可以的话,直到它们被修复。
Bad:
// On old browsers, each iteration with uncached `list.length` would be costly
// because of `list.length` recomputation. In modern browsers, this is optimized.
// 在旧的浏览器上,每次迭代未缓存的`list.length`都会很昂贵,因为`list.length`重新计算。在现代浏览器中,这已经被优化了。
for (let i = 0, len = list.length; i < len; i++) {
// ...
}
Good:
for (let i = 0; i < list.length; i++) {
// ...
}
Dead code is just as bad as duplicate code. There's no reason to keep it in your codebase. If it's not being called, get rid of it! It will still be safe in your version history if you still need it.
死代码和重复代码一样糟糕。没有理由把它留在你的代码库里。如果它没有被调用,就把它扔掉!如果你仍然需要它,它依然可以在你的版本历史中安全得到它。
Bad:
function oldRequestModule(url) {
// ...
}
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");
Good:
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");
Using getters and setters to access data on objects could be better than simply looking for a property on an object. "Why?" you might ask. Well, here's an unorganized list of reasons why:
使用getter和setter来访问对象上的数据可能比简单地在对象上查找属性更好。你可能会问:“为什么?”好吧,这里有一个无组织的理由列表:
-
When you want to do more beyond getting an object property, you don't have to look up and change every accessor in your codebase.
-
Makes adding validation simple when doing a
set
. -
Encapsulates the internal representation.
-
Easy to add logging and error handling when getting and setting.
-
You can lazy load your object's properties, let's say getting it from a server.
-
当你想做更多的事情超出了获取一个对象属性,你不必查找和改变你代码库中的每一个访问器。
-
当做一个
set
时,使添加验证变得简单。 -
封装内部表示。
-
在获取和设置时添加日志记录和错误处理很容易。
-
你可以懒加载你的对象属性,比如从服务器获取。
Bad:
function makeBankAccount() {
// ...
return {
balance: 0
// ...
};
}
const account = makeBankAccount();
account.balance = 100;
Good:
function makeBankAccount() {
// this one is private
let balance = 0;
// a "getter", made public via the returned object below
function getBalance() {
return balance;
}
// a "setter", made public via the returned object below
function setBalance(amount) {
// ... validate before updating the balance
balance = amount;
}
return {
// ...
getBalance,
setBalance
};
}
const account = makeBankAccount();
account.setBalance(100);
This can be accomplished through closures (for ES5 and below).
这可以通过闭包来实现(对于ES5及以下)。
Bad:
const Employee = function(name) {
this.name = name;
};
Employee.prototype.getName = function getName() {
return this.name;
};
const employee = new Employee("John Doe");
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;
console.log(`Employee name: ${employee.getName()}`); // Employee name: undefined
Good:
function makeEmployee(name) {
return {
getName() {
return name;
}
};
}
const employee = makeEmployee("John Doe");
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
It's very difficult to get readable class inheritance, construction, and method definitions for classical ES5 classes. If you need inheritance (and be aware that you might not), then prefer ES2015/ES6 classes. However, prefer small functions over classes until you find yourself needing larger and more complex objects.
对于经典的ES5类,很难获得可读的类继承、构造和方法定义。如果你需要继承(并且要知道你可能不需要),那么优先使用ES2015/ES6类。然而,在你需要更大更复杂的对象之前,优先使用小函数而不是类。
Bad:
const Animal = function(age) {
if (!(this instanceof Animal)) {
throw new Error("Instantiate Animal with `new`");
}
this.age = age;
};
Animal.prototype.move = function move() {};
const Mammal = function(age, furColor) {
if (!(this instanceof Mammal)) {
throw new Error("Instantiate Mammal with `new`");
}
Animal.call(this, age);
this.furColor = furColor;
};
Mammal.prototype = Object.create(Animal.prototype);
Mammal.prototype.constructor = Mammal;
Mammal.prototype.liveBirth = function liveBirth() {};
const Human = function(age, furColor, languageSpoken) {
if (!(this instanceof Human)) {
throw new Error("Instantiate Human with `new`");
}
Mammal.call(this, age, furColor);
this.languageSpoken = languageSpoken;
};
Human.prototype = Object.create(Mammal.prototype);
Human.prototype.constructor = Human;
Human.prototype.speak = function speak() {};
Good:
class Animal {
constructor(age) {
this.age = age;
}
move() {
/* ... */
}
}
class Mammal extends Animal {
constructor(age, furColor) {
super(age);
this.furColor = furColor;
}
liveBirth() {
/* ... */
}
}
class Human extends Mammal {
constructor(age, furColor, languageSpoken) {
super(age, furColor);
this.languageSpoken = languageSpoken;
}
speak() {
/* ... */
}
}
This pattern is very useful in JavaScript and you see it in many libraries such
as jQuery and Lodash. It allows your code to be expressive, and less verbose.
For that reason, I say, use method chaining and take a look at how clean your code
will be. In your class functions, simply return this
at the end of every function,
and you can chain further class methods onto it.
这种模式在JavaScript中非常有用,你可以在许多库中看到它,比如jQuery和Lodash。它允许你的代码表达性强,而且不冗长。因此,我说,使用方法链,看看你的代码有多干净。在你的类函数中,只需在每个函数的末尾返回this
,你就可以在它上面链接更多的类方法。
Bad:
class Car {
constructor(make, model, color) {
this.make = make;
this.model = model;
this.color = color;
}
setMake(make) {
this.make = make;
}
setModel(model) {
this.model = model;
}
setColor(color) {
this.color = color;
}
save() {
console.log(this.make, this.model, this.color);
}
}
const car = new Car("Ford", "F-150", "red");
car.setColor("pink");
car.save();
Good:
class Car {
constructor(make, model, color) {
this.make = make;
this.model = model;
this.color = color;
}
setMake(make) {
this.make = make;
// NOTE: Returning this for chaining
return this;
}
setModel(model) {
this.model = model;
// NOTE: Returning this for chaining
return this;
}
setColor(color) {
this.color = color;
// NOTE: Returning this for chaining
return this;
}
save() {
console.log(this.make, this.model, this.color);
// NOTE: Returning this for chaining
return this;
}
}
const car = new Car("Ford", "F-150", "red").setColor("pink").save();
As stated famously in Design Patterns by the Gang of Four, you should prefer composition over inheritance where you can. There are lots of good reasons to use inheritance and lots of good reasons to use composition. The main point for this maxim is that if your mind instinctively goes for inheritance, try to think if composition could model your problem better. In some cases it can.
正如四人帮在《设计模式》中所说,你应该优先使用组合而不是继承。使用继承和使用组合都有很多好的理由。这个金句的主要观点是,如果你的思维本能地去继承,试着想想组合是否能更好地模拟你的问题。在某些情况下,它可以。
You might be wondering then, "when should I use inheritance?" It depends on your problem at hand, but this is a decent list of when inheritance makes more sense than composition:
你可能会想,“那我什么时候应该使用继承呢?”这取决于你手头的问题,但这是一个比较好的列表,说明了什么时候继承比组合更有意义:
- Your inheritance represents an "is-a" relationship and not a "has-a" relationship (Human->Animal vs. User->UserDetails).
- You can reuse code from the base classes (Humans can move like all animals).
- You want to make global changes to derived classes by changing a base class. (Change the caloric expenditure of all animals when they move).
- 你的继承代表了一种“是一个”关系,而不是一种“有一个”关系(Human->Animal vs. User->UserDetails)。
- 你可以从基类复用代码(人类可以像所有动物一样移动)。
- 你想通过改变基类来对派生类进行全局更改(当动物移动时,改变所有动物的热量消耗)。
Bad:
class Employee {
constructor(name, email) {
this.name = name;
this.email = email;
}
// ...
}
// Bad because Employees "have" tax data. EmployeeTaxData is not a type of Employee
// 坏的,因为员工“有”税务数据。EmployeeTaxData不是Employee的一种类型
class EmployeeTaxData extends Employee {
constructor(ssn, salary) {
super();
this.ssn = ssn;
this.salary = salary;
}
// ...
}
Good:
class EmployeeTaxData {
constructor(ssn, salary) {
this.ssn = ssn;
this.salary = salary;
}
// ...
}
class Employee {
constructor(name, email) {
this.name = name;
this.email = email;
}
setTaxData(ssn, salary) {
this.taxData = new EmployeeTaxData(ssn, salary);
}
// ...
}
As stated in Clean Code, "There should never be more than one reason for a class to change". It's tempting to jam-pack a class with a lot of functionality, like when you can only take one suitcase on your flight. The issue with this is that your class won't be conceptually cohesive and it will give it many reasons to change. Minimizing the amount of times you need to change a class is important. It's important because if too much functionality is in one class and you modify a piece of it, it can be difficult to understand how that will affect other dependent modules in your codebase.
正如《代码简洁之道》中所说的,"一个类的变化不应该有一个以上的理由"。用大量的功能来塞满一个类是很诱人的,就像你在飞机上只能带一个手提箱一样。这样做的问题是,你的类在概念上不会有凝聚力,而且会让它有很多理由去改变。尽量减少你需要改变一个类的次数是很重要的。这很重要,因为如果有太多的功能在一个类中,而你修改了其中的一个部分,那么就很难知道这将如何影响你代码库中的其他依赖模块。
Bad:
class UserSettings {
constructor(user) {
this.user = user;
}
changeSettings(settings) {
if (this.verifyCredentials()) {
// ...
}
}
verifyCredentials() {
// ...
}
}
Good:
class UserAuth {
constructor(user) {
this.user = user;
}
verifyCredentials() {
// ...
}
}
class UserSettings {
constructor(user) {
this.user = user;
this.auth = new UserAuth(user);
}
changeSettings(settings) {
if (this.auth.verifyCredentials()) {
// ...
}
}
}
As stated by Bertrand Meyer, "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification." What does that mean though? This principle basically states that you should allow users to add new functionalities without changing existing code.
正如Bertrand Meyer所说,"软件实体(类、模块、函数等)应该是开放的,可以扩展,但不可以修改"。但这是什么意思呢?这个原则基本上说明,你应该允许用户在不改变现有代码的情况下增加新的功能。
Bad:
class AjaxAdapter extends Adapter {
constructor() {
super();
this.name = "ajaxAdapter";
}
}
class NodeAdapter extends Adapter {
constructor() {
super();
this.name = "nodeAdapter";
}
}
class HttpRequester {
constructor(adapter) {
this.adapter = adapter;
}
fetch(url) {
if (this.adapter.name === "ajaxAdapter") {
return makeAjaxCall(url).then(response => {
// transform response and return
});
} else if (this.adapter.name === "nodeAdapter") {
return makeHttpCall(url).then(response => {
// transform response and return
});
}
}
}
function makeAjaxCall(url) {
// request and return promise
}
function makeHttpCall(url) {
// request and return promise
}
Good:
class AjaxAdapter extends Adapter {
constructor() {
super();
this.name = "ajaxAdapter";
}
request(url) {
// request and return promise
}
}
class NodeAdapter extends Adapter {
constructor() {
super();
this.name = "nodeAdapter";
}
request(url) {
// request and return promise
}
}
class HttpRequester {
constructor(adapter) {
this.adapter = adapter;
}
fetch(url) {
return this.adapter.request(url).then(response => {
// transform response and return
});
}
}
This is a scary term for a very simple concept. It's formally defined as "If S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., objects of type S may substitute objects of type T) without altering any of the desirable properties of that program (correctness, task performed, etc.)." That's an even scarier definition.
这是一个非常简单的概念,但却有一个可怕的术语。它被正式定义为:“如果S是T的子类型,则可以用类型S的对象替换类型T的对象(即,类型S的对象可以代替类型T的对象),而不会改变该程序任何期望具备的属性(正确性、执行任务等)。” 这个定义甚至更加可怕。
The best explanation for this is if you have a parent class and a child class, then the base class and child class can be used interchangeably without getting incorrect results. This might still be confusing, so let's take a look at the classic Square-Rectangle example. Mathematically, a square is a rectangle, but if you model it using the "is-a" relationship via inheritance, you quickly get into trouble.
最好的解释是,如果你有一个父类和一个子类,那么基类和子类可以互换使用而不会得到错误的结果。这可能仍然令人困惑,因此让我们来看一下经典的正方形-矩形例子。从数学上讲,正方形是矩形,但如果你使用继承中的“is-a”关系对其进行建模,则很快就会遇到麻烦。
Bad:
class Rectangle {
constructor() {
this.width = 0;
this.height = 0;
}
setColor(color) {
// ...
}
render(area) {
// ...
}
setWidth(width) {
this.width = width;
}
setHeight(height) {
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
class Square extends Rectangle {
setWidth(width) {
this.width = width;
this.height = width;
}
setHeight(height) {
this.width = height;
this.height = height;
}
}
function renderLargeRectangles(rectangles) {
rectangles.forEach(rectangle => {
rectangle.setWidth(4);
rectangle.setHeight(5);
const area = rectangle.getArea(); // BAD: Returns 25 for Square. Should be 20.
rectangle.render(area);
});
}
const rectangles = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles(rectangles);
Good:
class Shape {
setColor(color) {
// ...
}
render(area) {
// ...
}
}
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
class Square extends Shape {
constructor(length) {
super();
this.length = length;
}
getArea() {
return this.length * this.length;
}
}
function renderLargeShapes(shapes) {
shapes.forEach(shape => {
const area = shape.getArea();
shape.render(area);
});
}
const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)];
renderLargeShapes(shapes);
JavaScript doesn't have interfaces so this principle doesn't apply as strictly as others. However, it's important and relevant even with JavaScript's lack of type system.
JavaScript没有接口,所以这个原则并不像其他原则那样严格适用。然而,即使JavaScript没有类型系统,它也是重要的,而且是相关的。
ISP states that "Clients should not be forced to depend upon interfaces that they do not use." Interfaces are implicit contracts in JavaScript because of duck typing.
ISP指出,"不应该强迫客户依赖他们不使用的接口"。接口是JavaScript中的隐性契约,由于鸭子的类型。
A good example to look at that demonstrates this principle in JavaScript is for classes that require large settings objects. Not requiring clients to setup huge amounts of options is beneficial, because most of the time they won't need all of the settings. Making them optional helps prevent having a "fat interface".
在JavaScript中展示这一原则的一个好例子是需要大量设置对象的类。不要求客户端设置大量的选项是有益的,因为大多数时候他们不需要所有的设置。让它们是可选的,有助于防止出现 "肥胖接口"。
Bad:
class DOMTraverser {
constructor(settings) {
this.settings = settings;
this.setup();
}
setup() {
this.rootNode = this.settings.rootNode;
this.settings.animationModule.setup();
}
traverse() {
// ...
}
}
const $ = new DOMTraverser({
rootNode: document.getElementsByTagName("body"),
animationModule() {} // Most of the time, we won't need to animate when traversing.
// ...
});
Good:
class DOMTraverser {
constructor(settings) {
this.settings = settings;
this.options = settings.options;
this.setup();
}
setup() {
this.rootNode = this.settings.rootNode;
this.setupOptions();
}
setupOptions() {
if (this.options.animationModule) {
// ...
}
}
traverse() {
// ...
}
}
const $ = new DOMTraverser({
rootNode: document.getElementsByTagName("body"),
options: {
animationModule() {}
}
});
This principle states two essential things:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend upon details. Details should depend on abstractions.
这个原则阐述了两个重要的事情:
- 高层模块不应该依赖于低层模块,它们都应该依赖于抽象。
- 抽象不应该依赖于细节,而是细节应该依赖于抽象。
This can be hard to understand at first, but if you've worked with AngularJS, you've seen an implementation of this principle in the form of Dependency Injection (DI). While they are not identical concepts, DIP keeps high-level modules from knowing the details of its low-level modules and setting them up. It can accomplish this through DI. A huge benefit of this is that it reduces the coupling between modules. Coupling is a very bad development pattern because it makes your code hard to refactor.
一开始可能很难理解,但如果你使用过AngularJS,就已经在依赖注入(DI)的形式中看到了这个原则的实现。虽然它们不是完全相同的概念,但DIP可以通过DI来避免高级模块知道其低级模块的细节并设置它们。其中一个巨大的好处是减少了模块之间的耦合度。耦合度是一种非常糟糕的开发模式,因为它使得代码难以重构。
As stated previously, JavaScript doesn't have interfaces so the abstractions
that are depended upon are implicit contracts. That is to say, the methods
and properties that an object/class exposes to another object/class. In the
example below, the implicit contract is that any Request module for an
InventoryTracker
will have a requestItems
method.
如前所述,JavaScript 没有接口,因此依赖的抽象是隐式契约。也就是说,一个对象/类向另一个对象/类公开的方法和属性。在下面的示例中,隐式契约是任何 InventoryTracker
的 Request 模块都将具有 requestItems
方法。
Bad:
class InventoryRequester {
constructor() {
this.REQ_METHODS = ["HTTP"];
}
requestItem(item) {
// ...
}
}
class InventoryTracker {
constructor(items) {
this.items = items;
// BAD: We have created a dependency on a specific request implementation.
// We should just have requestItems depend on a request method: `request`
this.requester = new InventoryRequester();
}
requestItems() {
this.items.forEach(item => {
this.requester.requestItem(item);
});
}
}
const inventoryTracker = new InventoryTracker(["apples", "bananas"]);
inventoryTracker.requestItems();
Good:
class InventoryTracker {
constructor(items, requester) {
this.items = items;
this.requester = requester;
}
requestItems() {
this.items.forEach(item => {
this.requester.requestItem(item);
});
}
}
class InventoryRequesterV1 {
constructor() {
this.REQ_METHODS = ["HTTP"];
}
requestItem(item) {
// ...
}
}
class InventoryRequesterV2 {
constructor() {
this.REQ_METHODS = ["WS"];
}
requestItem(item) {
// ...
}
}
// By constructing our dependencies externally and injecting them, we can easily
// substitute our request module for a fancy new one that uses WebSockets.
// 通过在外部构建我们的依赖关系并将其注入,我们可以很容易地
// 将我们的请求模块替换成一个使用WebSockets的新模块。
const inventoryTracker = new InventoryTracker(
["apples", "bananas"],
new InventoryRequesterV2()
);
inventoryTracker.requestItems();
Testing is more important than shipping. If you have no tests or an inadequate amount, then every time you ship code you won't be sure that you didn't break anything. Deciding on what constitutes an adequate amount is up to your team, but having 100% coverage (all statements and branches) is how you achieve very high confidence and developer peace of mind. This means that in addition to having a great testing framework, you also need to use a good coverage tool.
测试比交付更重要。如果你没有足够的测试或者根本没有测试,那么每次你发布代码时都无法确定是否出现了问题。什么是足够的测试量由团队决定,但是达到100%覆盖率(所有语句和分支)可以让你获得非常高的信心和开发人员的安心感。这意味着除了拥有一个很好的测试框架外,还需要使用一个良好的覆盖工具。
There's no excuse to not write tests. There are plenty of good JS test frameworks, so find one that your team prefers. When you find one that works for your team, then aim to always write tests for every new feature/module you introduce. If your preferred method is Test Driven Development (TDD), that is great, but the main point is to just make sure you are reaching your coverage goals before launching any feature, or refactoring an existing one.
没有理由不写测试。有很多好的JS测试框架,所以你可以找到一个你团队喜欢的。当你找到适合你团队的框架后,就要始终为引入的每个新功能/模块编写测试。如果您首选的方法是测试驱动开发(TDD),那太棒了,但主要目标是确保在启动任何功能或重构现有功能之前达到覆盖率目标。
Bad:
import assert from "assert";
describe("MomentJS", () => {
it("handles date boundaries", () => {
let date;
date = new MomentJS("1/1/2015");
date.addDays(30);
assert.equal("1/31/2015", date);
date = new MomentJS("2/1/2016");
date.addDays(28);
assert.equal("02/29/2016", date);
date = new MomentJS("2/1/2015");
date.addDays(28);
assert.equal("03/01/2015", date);
});
});
Good:
import assert from "assert";
describe("MomentJS", () => {
it("handles 30-day months", () => {
const date = new MomentJS("1/1/2015");
date.addDays(30);
assert.equal("1/31/2015", date);
});
it("handles leap year", () => {
const date = new MomentJS("2/1/2016");
date.addDays(28);
assert.equal("02/29/2016", date);
});
it("handles non-leap year", () => {
const date = new MomentJS("2/1/2015");
date.addDays(28);
assert.equal("03/01/2015", date);
});
});
Callbacks aren't clean, and they cause excessive amounts of nesting. With ES2015/ES6, Promises are a built-in global type. Use them!
回调函数不够简洁,会导致过多的嵌套。在ES2015/ES6中,Promise是一种内置的全局类型。请使用它们!
Bad:
import { get } from "request";
import { writeFile } from "fs";
get(
"https://en.wikipedia.org/wiki/Robert_Cecil_Martin",
(requestErr, response, body) => {
if (requestErr) {
console.error(requestErr);
} else {
writeFile("article.html", body, writeErr => {
if (writeErr) {
console.error(writeErr);
} else {
console.log("File written");
}
});
}
}
);
Good:
import { get } from "request-promise";
import { writeFile } from "fs-extra";
get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin")
.then(body => {
return writeFile("article.html", body);
})
.then(() => {
console.log("File written");
})
.catch(err => {
console.error(err);
});
Promises are a very clean alternative to callbacks, but ES2017/ES8 brings async and await
which offer an even cleaner solution. All you need is a function that is prefixed
in an async
keyword, and then you can write your logic imperatively without
a then
chain of functions. Use this if you can take advantage of ES2017/ES8 features
today!
Promise是回调函数的一种非常干净的替代方案,但ES2017/ES8引入了async和await,提供了更加简洁的解决方案。你只需要在一个函数前面添加async关键字,然后就可以编写命令式逻辑而不需要then链式函数。如果您今天可以利用ES2017/ES8功能,请使用此方法!
Bad:
import { get } from "request-promise";
import { writeFile } from "fs-extra";
get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin")
.then(body => {
return writeFile("article.html", body);
})
.then(() => {
console.log("File written");
})
.catch(err => {
console.error(err);
});
Good:
import { get } from "request-promise";
import { writeFile } from "fs-extra";
async function getCleanCodeArticle() {
try {
const body = await get(
"https://en.wikipedia.org/wiki/Robert_Cecil_Martin"
);
await writeFile("article.html", body);
console.log("File written");
} catch (err) {
console.error(err);
}
}
getCleanCodeArticle()
Thrown errors are a good thing! They mean the runtime has successfully identified when something in your program has gone wrong and it's letting you know by stopping function execution on the current stack, killing the process (in Node), and notifying you in the console with a stack trace.
抛出的错误是一件好事!它们意味着运行时已经成功地识别了你的程序中出现的问题,并通过停止当前堆栈上的函数执行、杀死进程(在Node中)以及在控制台中用堆栈跟踪通知你来让你知道。
Doing nothing with a caught error doesn't give you the ability to ever fix
or react to said error. Logging the error to the console (console.log
)
isn't much better as often times it can get lost in a sea of things printed
to the console. If you wrap any bit of code in a try/catch
it means you
think an error may occur there and therefore you should have a plan,
or create a code path, for when it occurs.
对捕捉到的错误不做任何处理,就没有能力修复或应对上述错误。将错误记录到控制台(console.log)并没有好到哪里去,因为很多时候它可能会在打印到控制台的大量内容中丢失。如果你用try/catch包住任何一段代码,这意味着你认为那里可能会发生错误,因此你应该有一个计划,或创建一个代码路径,以应对错误的发生。
Bad:
try {
functionThatMightThrow();
} catch (error) {
console.log(error);
}
Good:
try {
functionThatMightThrow();
} catch (error) {
// One option (more noisy than console.log):
console.error(error);
// Another option:
notifyUserOfError(error);
// Another option:
reportErrorToService(error);
// OR do all three!
}
For the same reason you shouldn't ignore caught errors
from try/catch
.
出于相同的原因, 你不应该忽略try/catch捕获的错误。
Bad:
getdata()
.then(data => {
functionThatMightThrow(data);
})
.catch(error => {
console.log(error);
});
Good:
getdata()
.then(data => {
functionThatMightThrow(data);
})
.catch(error => {
// One option (more noisy than console.log):
console.error(error);
// Another option:
notifyUserOfError(error);
// Another option:
reportErrorToService(error);
// OR do all three!
});
Formatting is subjective. Like many rules herein, there is no hard and fast rule that you must follow. The main point is DO NOT ARGUE over formatting. There are tons of tools to automate this. Use one! It's a waste of time and money for engineers to argue over formatting.
格式化是主观的。像这里的许多规则一样,没有硬性的规则必须遵循。主要的观点是不要为格式而争论。有大量的工具可以自动化这个过程。使用一个!工程师为格式而争论是浪费时间和金钱。
For things that don't fall under the purview of automatic formatting (indentation, tabs vs. spaces, double vs. single quotes, etc.) look here for some guidance.
对于不属于自动格式化范围的事情(缩进、制表符与空格、双引号与单引号等),请在这里寻找一些指导。
JavaScript is untyped, so capitalization tells you a lot about your variables, functions, etc. These rules are subjective, so your team can choose whatever they want. The point is, no matter what you all choose, just be consistent.
JavaScript是无类型的,所以大小写告诉你很多信息关于你的变量、函数等。这些规则是主观的,所以你的团队可以选择任何他们想要的。关键是,无论你们选择什么,都要保持一致。
Bad:
const DAYS_IN_WEEK = 7;
const daysInMonth = 30;
const songs = ["Back In Black", "Stairway to Heaven", "Hey Jude"];
const Artists = ["ACDC", "Led Zeppelin", "The Beatles"];
function eraseDatabase() {}
function restore_database() {}
class animal {}
class Alpaca {}
Good:
const DAYS_IN_WEEK = 7;
const DAYS_IN_MONTH = 30;
const SONGS = ["Back In Black", "Stairway to Heaven", "Hey Jude"];
const ARTISTS = ["ACDC", "Led Zeppelin", "The Beatles"];
function eraseDatabase() {}
function restoreDatabase() {}
class Animal {}
class Alpaca {}
If a function calls another, keep those functions vertically close in the source file. Ideally, keep the caller right above the callee. We tend to read code from top-to-bottom, like a newspaper. Because of this, make your code read that way.
如果一个函数调用另一个函数,在源文件中保持这些函数垂直靠近。理想情况下,将调用者放在被调用者的正上方。我们倾向于从上到下阅读代码,就像读报纸一样。因此,让你的代码也这样阅读。
Bad:
class PerformanceReview {
constructor(employee) {
this.employee = employee;
}
lookupPeers() {
return db.lookup(this.employee, "peers");
}
lookupManager() {
return db.lookup(this.employee, "manager");
}
getPeerReviews() {
const peers = this.lookupPeers();
// ...
}
perfReview() {
this.getPeerReviews();
this.getManagerReview();
this.getSelfReview();
}
getManagerReview() {
const manager = this.lookupManager();
}
getSelfReview() {
// ...
}
}
const review = new PerformanceReview(employee);
review.perfReview();
Good:
class PerformanceReview {
constructor(employee) {
this.employee = employee;
}
perfReview() {
this.getPeerReviews();
this.getManagerReview();
this.getSelfReview();
}
getPeerReviews() {
const peers = this.lookupPeers();
// ...
}
lookupPeers() {
return db.lookup(this.employee, "peers");
}
getManagerReview() {
const manager = this.lookupManager();
}
lookupManager() {
return db.lookup(this.employee, "manager");
}
getSelfReview() {
// ...
}
}
const review = new PerformanceReview(employee);
review.perfReview();
Comments are an apology, not a requirement. Good code mostly documents itself.
注释是一种道歉,而不是一种要求。好的代码主要是自我说明的。
Bad:
function hashIt(data) {
// The hash
let hash = 0;
// Length of string
const length = data.length;
// Loop through every character in data
for (let i = 0; i < length; i++) {
// Get character code.
const char = data.charCodeAt(i);
// Make the hash
hash = (hash << 5) - hash + char;
// Convert to 32-bit integer
hash &= hash;
}
}
Good:
function hashIt(data) {
let hash = 0;
const length = data.length;
for (let i = 0; i < length; i++) {
const char = data.charCodeAt(i);
hash = (hash << 5) - hash + char;
// Convert to 32-bit integer
hash &= hash;
}
}
Version control exists for a reason. Leave old code in your history.
版本控制是有原因的。把旧代码留在你的历史中。
Bad:
doStuff();
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();
Good:
doStuff();
Remember, use version control! There's no need for dead code, commented code,
and especially journal comments. Use git log
to get history!
记住,使用版本控制!没有死代码、注释代码,尤其是日志注释。使用git log
来获取历史记录!
Bad:
/**
* 2016-12-20: Removed monads, didn't understand them (RM)
* 2016-10-01: Improved using special monads (JP)
* 2016-02-03: Removed type-checking (LI)
* 2015-03-14: Added combine with type-checking (JR)
*/
function combine(a, b) {
return a + b;
}
Good:
function combine(a, b) {
return a + b;
}
They usually just add noise. Let the functions and variable names along with the proper indentation and formatting give the visual structure to your code.
它们通常只会增加噪音。让函数和变量名以及适当的缩进和格式给你的代码提供视觉结构。
Bad:
////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
$scope.model = {
menu: "foo",
nav: "bar"
};
////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
const actions = function() {
// ...
};
Good:
$scope.model = {
menu: "foo",
nav: "bar"
};
const actions = function() {
// ...
};
This is also available in other languages:
Armenian: hanumanum/clean-code-javascript/
Bangla(বাংলা): InsomniacSabbir/clean-code-javascript/
Brazilian Portuguese: fesnt/clean-code-javascript
Simplified Chinese:
Traditional Chinese: AllJointTW/clean-code-javascript
French: eugene-augier/clean-code-javascript-fr
German: marcbruederlin/clean-code-javascript
Indonesia: andirkh/clean-code-javascript/
Italian: frappacchio/clean-code-javascript/
Japanese: mitsuruog/clean-code-javascript/
Korean: qkraudghgh/clean-code-javascript-ko
Polish: greg-dev/clean-code-javascript-pl
Russian:
Spanish: tureey/clean-code-javascript
Spanish: andersontr15/clean-code-javascript
Serbian: doskovicmilos/clean-code-javascript/
Turkish: bsonmez/clean-code-javascript
Ukrainian: mindfr1k/clean-code-javascript-ua
Vietnamese: hienvd/clean-code-javascript/
Persian: hamettio/clean-code-javascript