本文从变量、函数、对象、数据结构、类、测试、并发、错误处理、格式化等方面进行介绍。
变量
使用有意义,可读性好的变量名
反例:
代码语言:javascript复制var yyyymmdstr = moment().format('YYYY/MM/DD');
正例:
代码语言:javascript复制var yearMonthDay = moment().format('YYYY/MM/DD');
使用 ES6 的 const 定义常量
反例中使用"var"定义的"常量"是可变的。
在声明一个常量时,该常量在整个程序中都应该是不可变的。
反例:
代码语言:javascript复制var FIRST_US_PRESIDENT = "George Washington";
正例:
代码语言:javascript复制const FIRST_US_PRESIDENT = "George Washington";
对功能类似的变量名采用统一的命名风格
反例:
代码语言:javascript复制getUserInfo();
getClientData();
getCustomerRecord();
正例:
代码语言:javascript复制getUser();
使用易于检索名称
我们需要阅读的代码远比自己写的要多,使代码拥有良好的可读性且易于检索非常重要。阅读变量名晦涩难懂的代码对读者来说是一种相当糟糕的体验。 让你的变量名易于检索。
反例:
代码语言:javascript复制// 525600 是什么?
for (var i = 0; i < 525600; i ) {
runCronJob();
}
正例:
代码语言:javascript复制// Declare them as capitalized `var` globals.
var MINUTES_IN_A_YEAR = 525600;
for (var i = 0; i < MINUTES_IN_A_YEAR; i ) {
runCronJob();
}
使用说明变量(即有意义的变量名
反例:
代码语言:javascript复制const cityStateRegex = /^(. )[,s] (. ?)s*(d{5})?$/;
saveCityState(cityStateRegex.match(cityStateRegex)[1], cityStateRegex.match(cityStateRegex)[2]);
正例:
代码语言:javascript复制const ADDRESS = 'One Infinite Loop, Cupertino 95014';
var cityStateRegex = /^(. )[,s] (. ?)s*(d{5})?$/;
var match = ADDRESS.match(cityStateRegex)
var city = match[1];
var state = match[2];
saveCityState(city, state);
不要绕太多的弯子
显式优于隐式。
反例:
代码语言:javascript复制var locations = ['Austin', 'New York', 'San Francisco'];
locations.forEach((l) => {
doStuff();
doSomeOtherStuff();
...
...
...
// l是什么?
dispatch(l);
});
正例:
代码语言:javascript复制var locations = ['Austin', 'New York', 'San Francisco'];
locations.forEach((location) => {
doStuff();
doSomeOtherStuff();
...
...
...
dispatch(location);
});
避免重复描述
当类/对象名已经有意义时,对其变量进行命名不需要再次重复。
反例:
代码语言:javascript复制var Car = {
carMake: 'Honda',
carModel: 'Accord',
carColor: 'Blue'
};
function paintCar(car) {
car.carColor = 'Red';
}
正例:
代码语言:javascript复制var Car = {
make: 'Honda',
model: 'Accord',
color: 'Blue'
};
function paintCar(car) {
car.color = 'Red';
}
避免无意义的条件判断
反例:
代码语言:javascript复制function createMicrobrewery(name) {
var breweryName;
if (name) {
breweryName = name;
} else {
breweryName = 'Hipster Brew Co.';
}
}
正例:
代码语言:javascript复制function createMicrobrewery(name) {
var breweryName = name || 'Hipster Brew Co.'
}
函数
函数参数(不超过两个)
限制函数参数数量很有必要,这么做使得在测试函数时更加轻松。过多的参数将导致难以采用有效的测试用例对函数的各个参数进行测试。
应避免三个以上参数的函数。通常情况下,参数超过两个意味着函数功能过于复杂,这时需要重新优化你的函数。当确实需要多个参数时,大多情况下可以考虑这些参数封装成一个对象。
JS 定义对象非常方便,当需要多个参数时,可以使用一个对象进行替代。
反例:
代码语言:javascript复制function createMenu(title, body, buttonText, cancellable) {
...
}
正例:
代码语言:javascript复制var menuConfig = {
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
}
function createMenu(menuConfig) {
...
}
函数功能的单一性 这是软件功能中最重要的原则之一。
功能不单一的函数将导致难以重构、测试和理解。功能单一的函数易于重构,并使代码更加干净。
反例:
代码语言:javascript复制function emailClients(clients) {
clients.forEach(client => {
let clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}
正例:
代码语言:javascript复制function emailClients(clients) {
clients.forEach(client => {
emailClientIfNeeded(client);
});
}
function emailClientIfNeeded(client) {
if (isClientActive(client)) {
email(client);
}
}
function isClientActive(client) {
let clientRecord = database.lookup(client);
return clientRecord.isActive();
}
函数名应明确表明其功能
反例:
代码语言:javascript复制function dateAdd(date, month) {
// ...
}
let date = new Date();
// 很难理解dateAdd(date, 1)是什么意思
dateAdd(date, 1);
正例:
代码语言:javascript复制function dateAddMonth(date, month) {
// ...
}
let date = new Date();
dateAddMonth(date, 1);
函数名应该只做一层抽象
当函数的需要的抽象多于一层时通常意味着函数功能过于复杂,需将其进行分解以提高其可重用性和可测试性。
反例:
代码语言:javascript复制function parseBetterJSAlternative(code) {
let REGEXES = [
// ...
];
let statements = code.split(' ');
let tokens;
REGEXES.forEach((REGEX) => {
statements.forEach((statement) => {
// ...
})
});
let ast;
tokens.forEach((token) => {
// lex...
});
ast.forEach((node) => {
// parse...
})
}
正例:
代码语言:javascript复制function tokenize(code) {
let REGEXES = [
// ...
];
let statements = code.split(' ');
let tokens;
REGEXES.forEach((REGEX) => {
statements.forEach((statement) => {
// ...
})
});
return tokens;
}
function lexer(tokens) {
let ast;
tokens.forEach((token) => {
// lex...
});
return ast;
}
function parseBetterJSAlternative(code) {
let tokens = tokenize(code);
let ast = lexer(tokens);
ast.forEach((node) => {
// parse...
})
}
移除重复代码
永远、永远、永远不要在任何循环下有重复的代码。
这种做法毫无意义且潜在危险极大。重复的代码意味着逻辑变化时需要对不止一处进行修改。JS 弱类型的特点使得函数拥有更强的普适性。好好利用这一优点吧。
反例:
代码语言:javascript复制function showDeveloperList(developers) {
developers.forEach(developer => {
var expectedSalary = developer.calculateExpectedSalary();
var experience = developer.getExperience();
var githubLink = developer.getGithubLink();
var data = {
expectedSalary: expectedSalary,
experience: experience,
githubLink: githubLink
};
render(data);
});
}
function showManagerList(managers) {
managers.forEach(manager => {
var expectedSalary = manager.calculateExpectedSalary();
var experience = manager.getExperience();
var portfolio = manager.getMBAProjects();
var data = {
expectedSalary: expectedSalary,
experience: experience,
portfolio: portfolio
};
render(data);
});
}
正例:
代码语言:javascript复制function showList(employees) {
employees.forEach(employee => {
var expectedSalary = employee.calculateExpectedSalary();
var experience = employee.getExperience();
var portfolio;
if (employee.type === 'manager') {
portfolio = employee.getMBAProjects();
} else {
portfolio = employee.getGithubLink();
}
var data = {
expectedSalary: expectedSalary,
experience: experience,
portfolio: portfolio
};
render(data);
});
}
采用默认参数精简代码
反例:
代码语言:javascript复制function writeForumComment(subject, body) {
subject = subject || 'No Subject';
body = body || 'No text';
}
正例:
代码语言:javascript复制function writeForumComment(subject = 'No subject', body = 'No text') {
...
}
使用object.assign设置默认对象
反例:
代码语言:javascript复制var 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);
正例:
代码语言:javascript复制var menuConfig = {
title: 'Order',
// User did not include 'body' key
buttonText: 'Send',
cancellable: true
}
function createMenu(config) {
config = Object.assign({
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
}, config);
// config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
// ...
}
createMenu(menuConfig);
不要使用flag作为函数参数
这通常意味着函数的功能的单一性已经被破坏。此时应考虑对函数进行再次划分。
反例:
代码语言:javascript复制function createFile(name, temp) {
if (temp) {
fs.create('./temp/' name);
} else {
fs.create(name);
}
}
正例:
代码语言:javascript复制function createTempFile(name) {
fs.create('./temp/' name);
}
----------
function createFile(name) {
fs.create(name);
}
避免副作用
当函数产生了除了“接受一个值并返回一个结果”之外的行为时,称该函数产生了副作用。比如写文件、修改全局变量或将你的钱全转给了一个陌生人等。
程序在某些情况下确实需要副作用这一行为,如先前例子中的写文件。这时应该将这些功能集中在一起,不要用多个函数/类修改某个文件。用且只用一个 service 完成这一需求。
反例:
代码语言:javascript复制// 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.
var name = 'Ryan McDermott';
function splitIntoFirstAndLastName() {
name = name.split(' ');
}
splitIntoFirstAndLastName();
console.log(name); // ['Ryan', 'McDermott'];
正例:
代码语言:javascript复制function splitIntoFirstAndLastName(name) {
return name.split(' ');
}
var name = 'Ryan McDermott'
var newName = splitIntoFirstAndLastName(name);
console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];
不要写全局函数
在 JS 中污染全局是一个非常不好的实践,这么做可能和其他库起冲突,且调用你的 API 的用户在实际环境中得到一个 exception 前对这一情况是一无所知的。
想象以下例子:如果你想扩展 JS 中的 Array,为其添加一个 diff
函数显示两个数组间的差异,此时应如何去做?你可以将 diff 写入 Array.prototype
,但这么做会和其他有类似需求的库造成冲突。如果另一个库对 diff 的需求为比较一个数组中首尾元素间的差异呢?
使用 ES6 中的 class 对全局的 Array 做简单的扩展显然是一个更棒的选择。
反例:
代码语言:javascript复制Array.prototype.diff = function(comparisonArray) {
var values = [];
var hash = {};
for (var i of comparisonArray) {
hash[i] = true;
}
for (var i of this) {
if (!hash[i]) {
values.push(i);
}
}
return values;
}
正例:
代码语言:javascript复制class SuperArray extends Array {
constructor(...args) {
super(...args);
}
diff(comparisonArray) {
var values = [];
var hash = {};
for (var i of comparisonArray) {
hash[i] = true;
}
for (var i of this) {
if (!hash[i]) {
values.push(i);
}
}
return values;
}
}
采用函数式编程
函数式的编程具有更干净且便于测试的特点。尽可能的使用这种风格吧。
反例:
代码语言:javascript复制const programmerOutput = [
{
name: 'Uncle Bobby',
linesOfCode: 500
}, {
name: 'Suzie Q',
linesOfCode: 1500
}, {
name: 'Jimmy Gosling',
linesOfCode: 150
}, {
name: 'Gracie Hopper',
linesOfCode: 1000
}
];
var totalOutput = 0;
for (var i = 0; i < programmerOutput.length; i ) {
totalOutput = programmerOutput[i].linesOfCode;
}
正例:
代码语言:javascript复制const programmerOutput = [
{
name: 'Uncle Bobby',
linesOfCode: 500
}, {
name: 'Suzie Q',
linesOfCode: 1500
}, {
name: 'Jimmy Gosling',
linesOfCode: 150
}, {
name: 'Gracie Hopper',
linesOfCode: 1000
}
];
var totalOutput = programmerOutput
.map((programmer) => programmer.linesOfCode)
.reduce((acc, linesOfCode) => acc linesOfCode, 0);
封装判断条件
反例:
代码语言:javascript复制if (fsm.state === 'fetching' && isEmpty(listNode)) {
/// ...
}
正例:
代码语言:javascript复制function shouldShowSpinner(fsm, listNode) {
return fsm.state === 'fetching' && isEmpty(listNode);
}
if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}
避免否定情况的判断
反例:
代码语言:javascript复制function isDOMNodeNotPresent(node) {
// ...
}
if (!isDOMNodeNotPresent(node)) {
// ...
}
正例:
代码语言:javascript复制function isDOMNodePresent(node) {
// ...
}
if (isDOMNodePresent(node)) {
// ...
}
避免条件判断
这看起来似乎不太可能。
大多人听到这的第一反应是:“怎么可能不用 if 完成其他功能呢?”许多情况下通过使用多态(polymorphism)可以达到同样的目的。
第二个问题在于采用这种方式的原因是什么。答案是我们之前提到过的:保持函数功能的单一性。
反例:
代码语言:javascript复制class Airplane {
//...
getCruisingAltitude() {
switch (this.type) {
case '777':
return getMaxAltitude() - getPassengerCount();
case 'Air Force One':
return getMaxAltitude();
case 'Cessna':
return getMaxAltitude() - getFuelExpenditure();
}
}
}
正例:
代码语言:javascript复制class Airplane {
//...
}
class Boeing777 extends Airplane {
//...
getCruisingAltitude() {
return getMaxAltitude() - getPassengerCount();
}
}
class AirForceOne extends Airplane {
//...
getCruisingAltitude() {
return getMaxAltitude();
}
}
class Cessna extends Airplane {
//...
getCruisingAltitude() {
return getMaxAltitude() - getFuelExpenditure();
}
}
避免类型判断(1)
JS 是弱类型语言,这意味着函数可接受任意类型的参数。
有时这会对你带来麻烦,你会对参数做一些类型判断。有许多方法可以避免这些情况。
反例:
代码语言:javascript复制function travelToTexas(vehicle) {
if (vehicle instanceof Bicycle) {
vehicle.peddle(this.currentLocation, new Location('texas'));
} else if (vehicle instanceof Car) {
vehicle.drive(this.currentLocation, new Location('texas'));
}
}
正例:
代码语言:javascript复制function travelToTexas(vehicle) {
vehicle.move(this.currentLocation, new Location('texas'));
}
避免类型判断(2)
如果需处理的数据为字符串,整型,数组等类型,无法使用多态并仍有必要对其进行类型检测时,可以考虑使用 TypeScript。
反例:
代码语言:javascript复制function combine(val1, val2) {
if (typeof val1 == "number" && typeof val2 == "number" ||
typeof val1 == "string" && typeof val2 == "string") {
return val1 val2;
} else {
throw new Error('Must be of type String or Number');
}
}
正例:
代码语言:javascript复制function combine(val1, val2) {
return val1 val2;
}
避免过度优化
现代的浏览器在运行时会对代码自动进行优化。有时人为对代码进行优化可能是在浪费时间。 反例:
代码语言:javascript复制// 这里使用变量len是因为在老式浏览器中,
// 直接使用正例中的方式会导致每次循环均重复计算list.length的值,
// 而在现代浏览器中会自动完成优化,这一行为是没有必要的
for (var i = 0, len = list.length; i < len; i ) {
// ...
}
正例:
代码语言:javascript复制for (var i = 0; i < list.length; i ) {
// ...
}
删除无效代码
不再被调用的代码应及时删除。
反例:
代码语言:javascript复制function oldRequestModule(url) {
// ...
}
function newRequestModule(url) {
// ...
}
var req = newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');
正例:
代码语言:javascript复制function newRequestModule(url) {
// ...
}
var req = newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');
文章篇幅较长,剩下内容将在下一篇文章介绍。