在寫JavaScript代碼時,寫好函數很關鍵,本文通過9個方面詳細的討論瞭如何寫好函數,寫好函數,就會讓你的代碼讀起來清晰得多,值得學習一下。
作者:xcheng
鏈接:https://juejin.im/post/5ed201cbe51d45786e496629
系統由程序、子程序和函數組成。寫好函數,就會讓你的代碼讀起來清晰得多。接下來討論如何寫好函數
1、函數要短小,一個函數只做一件事
如果函數做了較多的事情,它就難以組合、測試和推測。同時讓函數只做一件事情的時候,它們就很容易重構。
// 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
2、每個函數一個抽象層級
函數中混雜不同的抽象層級,往往讓人迷惑。讀者可能無法判斷某個表達式是基礎概念還是細節。更惡劣的是,就像破損的窗戶,一旦細節和基礎概念混雜,更多的細節就會在函數中糾結起來。理解抽象層次請參考:抽象層次
// 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...})}
3、使用描述性的名稱
函數越短小,功能越集中,就越便於取個好名字。長而具有描述性的名稱,要比短而令人費解的名稱好。長而具有描述性的名稱,要比描述性的長註釋好。使用某種命名約定,讓函數名稱中的多個單詞容易閱讀,然後使用這些單詞給函數取個能說明其功能的名稱。
// 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){// ...}
4、函數參數
最理想的參數數量是零,其次是一(單參數函數),再次是二(雙參數函數),應儘量避免三(三參數函數)。有足夠的理由才能使用三個以上參數。如果函數需要三個以上參數,就說明其中一些參數應該放在一個對象中了。參數越多,函數越不容易理解,同時編寫能確保參數的各種組合運行正常的測試用例越困難。
5、避免副作用
如果一個函數不是獲取一個輸入的值並返回其它值,它就有可能產生副作用。這些副作用可能是寫入文件、修改一些全局變量、屏幕打印或者日誌記錄、查詢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'];
6、刪除重複代碼
重複代碼意味着你要修改某些邏輯的時候要修改不止一個地方的代碼。
// 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);});}
7、使用更優雅寫法
1、使用默認參數代替短路表達式
// Badfunction writeForumComment(subject, body) { subject = subject || 'No Subject'; body = body || 'No text';}// Goodfunction writeForumComment(subject = 'No subject', body = 'No text') {...}
2、用 Object.assign 設置默認對象
// 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);
3、喜歡上命令式編程之上的函數式編程
// 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);
8、不要把標記用作函數參數
標記告訴你的用戶這個函數做的事情不止一件。但是函數應該只做一件事。如果你的函數中會根據某個布爾參數產生不同的分支,那就拆分這個函數。
// 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);}
9、對函數中條件處理
1、封裝條件
// Badif(fsm.state === 'fetching'&& isEmpty(listNode)) {/// ...}// Goodfunction shouldShowSpinner(fsm, listNode) {return fsm.state === 'fetching'&& isEmpty(listNode);}if(shouldShowSpinner(fsmInstance, listNodeInstance)) {// ...}
2、避免否定條件
// Badfunction isDOMNodeNotPresent(node) {// ...}if(!isDOMNodeNotPresent(node)) {// ...}// Goodfunction isDOMNodePresent(node) {// ...}if(isDOMNodePresent(node)) {// ...}
專注分享當下最實用的前端技術。關注前端達人,與達人一起學習進步!
長按關注"前端達人"