本文共 6461 字,大约阅读时间需要 21 分钟。
如果函数做了较多的事情,它就难以组合、测试和推测。同时让函数只做一件事情的时候,它们就很容易重构。
// Badfunction showStudent(ssn){ const student = db.get(ssn);if(student !== null){ document.querySelector(`#${ elementId}`).innerHTML =`${ student.ssn}, ${ student.firstName}, ${ student.lastName}`} else{ thrownewError('student not found')}}showStudent('444-44-4444')// Goodfunction findStudent(db,id){ const student = db.get(id);if(student === null){ thrownewError('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
函数中混杂不同的抽象层级,往往让人迷惑。读者可能无法判断某个表达式是基础概念还是细节。更恶劣的是,就像破损的窗户,一旦细节和基础概念混杂,更多的细节就会在函数中纠结起来。理解抽象层次请参考:抽象层次
// Badfunction 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...})}// Goodfunction 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...})}
函数越短小,功能越集中,就越便于取个好名字。长而具有描述性的名称,要比短而令人费解的名称好。长而具有描述性的名称,要比描述性的长注释好。使用某种命名约定,让函数名称中的多个单词容易阅读,然后使用这些单词给函数取个能说明其功能的名称。
// Badfunction dateAdd(date, month) { // ...}let date = newDate();// 很难从函数名了解到加了什么dateAdd(date, 1);function write(name){ // ...}function assertEqual(a,b){ // ...}// Goodfunction dateAddMonth(date, month) { // ...}let date = newDate();dateAddMonth(date, 1);// 告诉我们 name 是一个 fieldfunction 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'];// Goodfunction splitIntoFirstAndLastName(name) { return name.split(' ');}var name = 'Ryan McDermott'var newName = splitIntoFirstAndLastName(name);console.log(name); // 'Ryan McDermott';console.log(newName); // ['Ryan', 'McDermott'];
重复代码意味着你要修改某些逻辑的时候要修改不止一个地方的代码。
// Badfunction 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);});}// Goodfunction 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);});}
// Badfunction writeForumComment(subject, body) { subject = subject || 'No Subject'; body = body || 'No text';}// Goodfunction writeForumComment(subject = 'No subject', body = 'No text') { ...}
// Badconst 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);// Goodconst 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);
// Badconst 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;}// Goodconst 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);
标记告诉你的用户这个函数做的事情不止一件。但是函数应该只做一件事。如果你的函数中会根据某个布尔参数产生不同的分支,那就拆分这个函数。
// Badfunction createFile(name, temp) { if(temp) { fs.create('./temp/'+ name);} else{ fs.create(name);}}// Goodfunction createTempFile(name) { fs.create('./temp/'+ name);}function createFile(name) { fs.create(name);}
转载地址:http://isozi.baihongyu.com/