如果函数做了较多的事情,它就难以组合、测试和推测。同时让函数只做一件事情的时候,它们就很容易重构。
// Bad
function
showStudent(ssn){
const
student = db.
get
(ssn);
if
(student !==
null
){
document.querySelector(
`#${
elementId}`
).innerHTML =
`${
student.ssn},
${
student.firstName},
${
student.lastName}`
}
else
{
thrownew
Error
(
'student not found'
)
}
}
showStudent(
'444-44-4444'
)
// Good
function
findStudent(db,id){
const
student = db.
get
(id);
if
(student ===
null
){
thrownew
Error
(
'student not found'
);
}
};
function
getStudentInfo(student){
return
`${
student.ssn},${
student.firstName},${
student.lastName}`
};
function
showStudentInfo(elementId,info){
document.querySelector(elementId).innerHTML = info;
}
function
showStudent(ssn){
const
student = findStudent(ssn);
let
studentInfo = getStudentInfo(student);
showStudentInfo(elementId,studentInfo);
}
只是做了些许的改进,但已开始展现出很多的优势:undefined
函数中混杂不同的抽象层级,往往让人迷惑。读者可能无法判断某个表达式是基础概念还是细节。更恶劣的是,就像破损的窗户,一旦细节和基础概念混杂,更多的细节就会在函数中纠结起来。理解抽象层次请参考:抽象层次
// Bad
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...
})
}
// Good
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...
})
}
函数越短小,功能越集中,就越便于取个好名字。长而具有描述性的名称,要比短而令人费解的名称好。长而具有描述性的名称,要比描述性的长注释好。使用某种命名约定,让函数名称中的多个单词容易阅读,然后使用这些单词给函数取个能说明其功能的名称。
// Bad
function
dateAdd(date, month) {
// ...
}
let
date =
new
Date
();
// 很难从函数名了解到加了什么
dateAdd(date,
1
);
function
write(name){
// ...
}
function
assertEqual(a,b){
// ...
}
// Good
function
dateAddMonth(date, month) {
// ...
}
let
date =
new
Date
();
dateAddMonth(date,
1
);
// 告诉我们 name 是一个 field
function
writeField(name){
// ...
}
// 能更好的解释参数的顺序和意图
function
assertExpectedEqualActual(expected,actual){
// ...
}
最理想的参数数量是零,其次是一(单参数函数),再次是二(双参数函数),应尽量避免三(三参数函数)。有足够的理由才能使用三个以上参数。如果函数需要三个以上参数,就说明其中一些参数应该放在一个对象中了。参数越多,函数越不容易理解,同时编写能确保参数的各种组合运行正常的测试用例越困难。
如果一个函数不是获取一个输入的值并返回其它值,它就有可能产生副作用。这些副作用可能是写入文件、修改一些全局变量、屏幕打印或者日志记录、查询HTML文档、浏览器的cookie或访问数据库。无论哪种情况,都具有破坏性,会导致古怪的时序性耦合及顺序依赖。现在你确实需要在程序中有副作用。像前面提到的那样,你可能需要写入文件。现在你需要做的事情是搞清楚在哪里集中完成这件事情。不要使用几个函数或类来完成写入某个特定文件的工作。采用一个,就一个服务来完成。
// Bad
// 下面的函数使用了全局变量。
// 如果有另一个函数在使用 name,现在可能会因为 name 变成了数组而不能正常运行。
var
name =
'Ryan McDermott'
;
function
splitIntoFirstAndLastName() {
name = name.split(
' '
);
}
splitIntoFirstAndLastName();
console.log(name);
// ['Ryan', 'McDermott'];
// Good
function
splitIntoFirstAndLastName(name) {
return
name.split(
' '
);
}
var
name =
'Ryan McDermott'
var
newName = splitIntoFirstAndLastName(name);
console.log(name);
// 'Ryan McDermott';
console.log(newName);
// ['Ryan', 'McDermott'];
重复代码意味着你要修改某些逻辑的时候要修改不止一个地方的代码。
// Bad
function
showDeveloperList(developers) {
developers.forEach(developers => {
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);
});
}
// Good
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);
});
}
// Bad
function
writeForumComment(subject, body) {
subject = subject ||
'No Subject'
;
body = body ||
'No text'
;
}
// Good
function
writeForumComment(subject =
'No subject'
, body =
'No text'
) {
...
}
// 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) {
config =
Object
.assign({
title:
'Foo'
,
body:
'Bar'
,
buttonText:
'Baz'
,
cancellable:
true
}, config);
// 现在 config 等于: {title: "Foo", body: "Bar", buttonText: "Baz", cancellable: true}
// ...
}
createMenu(menuConfig);
// Bad
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;
}
// Good
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
);
标记告诉你的用户这个函数做的事情不止一件。但是函数应该只做一件事。如果你的函数中会根据某个布尔参数产生不同的分支,那就拆分这个函数。
// Bad
function
createFile(name, temp) {
if
(temp) {
fs.create(
'./temp/'
+ name);
}
else
{
fs.create(name);
}
}
// Good
function
createTempFile(name) {
fs.create(
'./temp/'
+ name);
}
function
createFile(name) {
fs.create(name);
}