如何優(yōu)雅的編寫 JavaScript 代碼

轉(zhuǎn)載自知乎
幾乎每個大一點公司都有一個“運行時間長晚顷,維護的工程師換了一批又一批”的項目,如果參與到這樣的項目中來新翎,大部分人只有一個感覺——”climb the shit mountain“程帕。所以我們經(jīng)常會說誰誰誰寫的代碼就像排泄物一樣住练,為了避免成為別人嘴里的誰誰誰,所以我寫的代碼一般不注明作者日期信息(抖機靈愁拭,其實是因為 Git 能夠很好的管理這些信息)讲逛,所以在項目中,我們應(yīng)該編寫可維護性良好的代碼岭埠。同時盏混,對于工程師而言,提高自身的編碼能力和編寫易于閱讀和維護的代碼枫攀,是提高開發(fā)效率和職業(yè)身涯中必做的事情括饶。我在面試的時候發(fā)現(xiàn)很多面試者擁有所謂的多年工作經(jīng)驗,一直在平庸的寫著重復(fù)的代碼来涨,而從未去推敲图焰、提煉和優(yōu)化,這樣是不可能提高編程水平的蹦掐。

那么如何編寫出可維護的技羔、優(yōu)雅的代碼呢?

首先卧抗,我們應(yīng)該明確的認識到藤滥,代碼是寫給自己和別人看的,代碼應(yīng)該保持清晰的結(jié)構(gòu)社裆,方便后人閱讀和維護拙绊,假如有一天需要回頭修改代碼,別人和你都會感謝你泳秀!

其次标沪,不管公司大小,不管項目大小嗜傅,不管工期有多緊張金句,制定良好的編碼規(guī)范并落到實地。如果代碼質(zhì)量不夠好的話吕嘀,在需求較多的情況下违寞,就可能會牽一發(fā)動全身,大廈將傾偶房。所以在項目的開始或者 現(xiàn)在 制定良好的編碼規(guī)范趁曼,每個人都應(yīng)該有自己的或者團隊的編碼規(guī)范!

最后棕洋,嗅出代碼的 Bad Smell彰阴,比如重復(fù)的代碼、命名不規(guī)范拍冠、過長的函數(shù)尿这、數(shù)據(jù)泥團等等,然后在不改變代碼外在行為的前提下庆杜,不斷的優(yōu)化重構(gòu)射众,以改進代程序的內(nèi)部結(jié)構(gòu)。

接下來晃财,我總結(jié)整理了一大套理論和實操叨橱,以饗各位。

避免使用 js 糟粕和雞肋

這些年來断盛,隨著 HTML5 和 Node.js 的發(fā)展罗洗,JavaScript 在各個領(lǐng)域遍地開花,已經(jīng)從“世界上最被誤解的語言”變成了“世界上最流行的語言”钢猛。但是由于歷史原因伙菜,JavaScript 語言設(shè)計中還是有一些糟粕和雞肋,比如:全局變量命迈、自動插入分號贩绕、typeof、NaN壶愤、假值淑倾、==、eval 等等征椒,并不能被語言移除娇哆,開發(fā)者一定要避免使用這些特性,還好下文中的 ESLint 能夠檢測出這些特性勃救,給出錯誤提示(如果你遇到面試官還在考你這些特性的話碍讨,那就需要考量一下,他們的項目中是否仍在使用這些特性剪芥,同時你也應(yīng)該知道如何回答這類問題了)垄开。

編寫簡潔的 JavaScript 代碼

下這些準則來自 Robert C. Martin 的書 “Clean Code”,適用于 JavaScript税肪。 整個列表 很長溉躲,我選取了我認為最重要的一部分,也是我在項目用的最多的一部分益兄,但是還是推薦大家看一下原文锻梳。這不是風(fēng)格指南,而是 使用 JavaScript 生產(chǎn)可讀净捅、可重用和可重構(gòu)的軟件的指南疑枯。

變量

使用有意義,可讀性好的變量名

Bad:

var yyyymmdstr = moment().format('YYYY/MM/DD')

Good:

var yearMonthDay = moment().format('YYYY/MM/DD')

使用 ES6 的 const 定義常量

反例中使用"var"定義的"常量"是可變的蛔六,在聲明一個常量時,該常量在整個程序中都應(yīng)該是不可變的憋他。

Bad:

var FIRST_US_PRESIDENT = "George Washington"

Good:

const FIRST_US_PRESIDENT = "George Washington"

使用易于檢索的名稱

我們要閱讀的代碼比要寫的代碼多得多蚕脏, 所以我們寫出的代碼的可讀性和可檢索性是很重要的。使用沒有意義的變量名將會導(dǎo)致我們的程序難于理解豆村,將會傷害我們的讀者, 所以請使用可檢索的變量名骂删。 類似 buddy.jsESLint 的工具可以幫助我們找到未命名的常量掌动。

Bad:

// What the heck is 86400000 for?
setTimeout(blastOff, 86400000)

Good:

// Declare them as capitalized `const` globals.
const MILLISECONDS_IN_A_DAY = 86400000

setTimeout(blastOff, MILLISECONDS_IN_A_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)

方法

保持函數(shù)功能的單一性

這是軟件工程中最重要的一條規(guī)則,當(dāng)函數(shù)需要做更多的事情時宁玫,它們將會更難進行編寫粗恢、測試、理解和組合欧瘪。當(dāng)你能將一個函數(shù)抽離出只完成一個動作眷射,他們將能夠很容易的進行重構(gòu)并且你的代碼將會更容易閱讀。如果你嚴格遵守本條規(guī)則恋追,你將會領(lǐng)先于許多開發(fā)者凭迹。

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()
}

函數(shù)名應(yīng)明確表明其功能(見名知意)

Bad:

function addToDate(date, month) {
  // ...
}

const date = new Date()

// It's hard to to tell from the function name what is added
addToDate(date, 1)

Good:

function addMonthToDate(month, date) {
  // ...
}

const date = new Date()
addMonthToDate(1, date)

使用默認變量替代短路運算或條件

Bad:

function createMicrobrewery(name) {
  const breweryName = name || 'Hipster Brew Co.'
  // ...
}

Good:

function createMicrobrewery(breweryName = 'Hipster Brew Co.') {
  // ...
}

函數(shù)參數(shù) (理想情況下應(yīng)不超過 2 個)

限制函數(shù)參數(shù)數(shù)量很有必要,這么做使得在測試函數(shù)時更加輕松苦囱。過多的參數(shù)將導(dǎo)致難以采用有效的測試用例對函數(shù)的各個參數(shù)進行測試嗅绸。

應(yīng)避免三個以上參數(shù)的函數(shù)。通常情況下撕彤,參數(shù)超過三個意味著函數(shù)功能過于復(fù)雜鱼鸠,這時需要重新優(yōu)化你的函數(shù)。當(dāng)確實需要多個參數(shù)時羹铅,大多情況下可以考慮這些參數(shù)封裝成一個對象蚀狰。

Bad:

function createMenu(title, body, buttonText, cancellable) {
  // ...
}

Good:

function createMenu({ title, body, buttonText, cancellable }) {
  // ...
}

createMenu({
  title: 'Foo',
  body: 'Bar',
  buttonText: 'Baz',
  cancellable: true
})

移除重復(fù)代碼

重復(fù)代碼在 Bad Smell 中排在第一位,所以职员,竭盡你的全力去避免重復(fù)代碼麻蹋。因為它意味著當(dāng)你需要修改一些邏輯時會有多個地方需要修改。

重復(fù)代碼通常是因為兩個或多個稍微不同的東西焊切, 它們共享大部分扮授,但是它們的不同之處迫使你使用兩個或更多獨立的函數(shù)來處理大部分相同的東西。 移除重復(fù)代碼意味著創(chuàng)建一個可以處理這些不同之處的抽象的函數(shù)/模塊/類专肪。

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)
  })
}

避免副作用

當(dāng)函數(shù)產(chǎn)生了除了“接受一個值并返回一個結(jié)果”之外的行為時刹勃,稱該函數(shù)產(chǎn)生了副作用。比如寫文件嚎尤、修改全局變量或?qū)⒛愕腻X全轉(zhuǎn)給了一個陌生人等荔仁。程序在某些情況下確實需要副作用這一行為,這時應(yīng)該將這些功能集中在一起,不要用多個函數(shù)/類修改某個文件乏梁。用且只用一個 service 完成這一需求次洼。

Bad:

const addItemToCart = (cart, item) => {
  cart.push({ item, date: Date.now() })
}

Good:

const addItemToCart = (cart, item) => {
  return [...cart, { item, date: Date.now() }]
}

避免條件判斷

這看起來似乎不太可能。大多人聽到這的第一反應(yīng)是:“怎么可能不用 if 完成其他功能呢掌呜?”許多情況下通過使用多態(tài)(polymorphism)可以達到同樣的目的滓玖。第二個問題在于采用這種方式的原因是什么。答案是我們之前提到過的:保持函數(shù)功能的單一性质蕉。

Bad:

class Airplane {
  //...
  getCruisingAltitude() {
    switch (this.type) {
      case '777':
        return getMaxAltitude() - getPassengerCount()
      case 'Air Force One':
        return getMaxAltitude()
      case 'Cessna':
        return getMaxAltitude() - getFuelExpenditure()
    }
  }
}

Good:

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()
  }
}

使用 ES6/ES7 新特性

箭頭函數(shù)

Bad:

function foo() {
  // code
}

Good:

let foo = () => {
  // code
}

模板字符串

Bad:

var message = 'Hello ' + name + ', it\'s ' + time + ' now'

Good:

const message = `Hello ${name}, it's ${time} now`

解構(gòu)

Bad:

var data = { name: 'dys', age: 1 }
var name = data.name,
    age = data.age

Good:

const data = {name:'dys', age:1} 
const {name, age} = data 

使用 ES6 的 classes 而不是 ES5 的 Function

典型的 ES5 的類(function)在繼承、構(gòu)造和方法定義方面可讀性較差翩肌,當(dāng)需要繼承時模暗,優(yōu)先選用 classes。

Bad:

// 那個復(fù)雜的原型鏈繼承就不貼代碼了

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() { /* ... */ }
}

Async/Await 是比 Promise 和回調(diào)更好的選擇

回調(diào)不夠整潔念祭,并會造成大量的嵌套兑宇,ES6 內(nèi)嵌了 Promises,但 ES7 中的 async 和 await 更勝過 Promises粱坤。

Promise 代碼的意思是:“我想執(zhí)行這個操作隶糕,然后(then)在其他操作中使用它的結(jié)果”。await 有效地反轉(zhuǎn)了這個意思站玄,使得它更像:“我想要取得這個操作的結(jié)果”枚驻。我喜歡,因為這聽起來更簡單株旷,所以盡可能的使用 async/await再登。

Bad:

require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
  .then(function(response) {
    return require('fs-promise').writeFile('article.html', response)
  })
  .then(function() {
    console.log('File written')
  })
  .catch(function(err) {
    console.error(err)
  })

Good:

async function getCleanCodeArticle() {
  try {
    var request = await require('request-promise')
    var response = await request.get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
    var fileHandle = await require('fs-promise')

    await fileHandle.writeFile('article.html', response)
    console.log('File written')
  } catch(err) {
    console.log(err)
  }
}

Babel

ES6 標(biāo)準發(fā)布后,前端人員也開發(fā)漸漸了解到了ES6晾剖,但是由于兼容性的問題锉矢,仍然沒有得到廣泛的推廣,不過業(yè)界也用了一些折中性的方案來解決兼容性和開發(fā)體系問題齿尽。其中最有名的莫過于 Babel 了沽损,Babel 是一個廣泛使用的轉(zhuǎn)碼器,他的目標(biāo)是使用 Babel 可以轉(zhuǎn)換所有 ES6 新語法循头,從而在現(xiàn)有環(huán)境執(zhí)行绵估。

Use next generation JavaScript, today

Babel 不僅能夠轉(zhuǎn)換 ES6 代碼,同時還是 ES7 的試驗場贷岸。比如已經(jīng)支持 async/await壹士,使開發(fā)者更容易編寫異步代碼,代碼邏輯和可讀性簡直不能太好了偿警。雖然主流瀏覽器可能還需要一段時間才能支持這個異步編碼方式躏救,但是基于 Babel,開發(fā)者現(xiàn)在就可以在生產(chǎn)環(huán)境使用上它。這得益于 Babel 與 JavaScript 技術(shù)委員會保持高度一致盒使,能夠在 ECMAScript 新特性在標(biāo)準化之前提供現(xiàn)實世界可用的實現(xiàn)崩掘。因此開發(fā)者能 在生產(chǎn)環(huán)境大量使用未發(fā)布或未廣泛支持的語言特性,ECMAScript 也能夠在規(guī)范最終定稿前獲得現(xiàn)實世界的反饋少办,這種正向反饋又進一步推動了 JavaScript 語言向前發(fā)展苞慢。

Babel 最簡單的使用方式如下:

# 安裝 babel-cli 和 babel-preset-es2015 插件
npm install -g babel-cli
npm install --save babel-preset-es2015

在當(dāng)前目錄下建立文件.babelrc,寫入:

{
  "presets": ['es2015']
}

更多的功能請參考官網(wǎng)英妓。

ESLint

一個高質(zhì)量的項目必須包含完善的 lint挽放,如果一個項目中還是 tab、兩個空格蔓纠、四個空格各種混搭風(fēng)辑畦,一個函數(shù)動不動上百行,各種 if腿倚、嵌套纯出、回調(diào)好幾層。加上前面提到的各種 JavaScript 糟粕和雞肋敷燎,一股濃厚的城鄉(xiāng)結(jié)合部風(fēng)撲面而來暂筝,這還怎么寫代碼,每天調(diào)調(diào)代碼格式好了硬贯。

這又怎么行呢焕襟,拿工資就得好好寫代碼,因此 lint 非常有必要澄成,特別是對于大型項目胧洒,他可以保證代碼符合一定的風(fēng)格,有起碼的可讀性墨状,團隊里的其他人可以盡快掌握他人的代碼卫漫。對于 JavaScript 項目而言,目前 ESLint 將是一個很好的選擇肾砂。ESLint 的安裝過程就不介紹了列赎,請參考官網(wǎng),下面講一個非常嚴格的 ESLint 的配置镐确,這是對上面編寫簡潔的 JavaScript 代碼一節(jié)最好的回應(yīng)包吝。

{
  "parser": "babel-eslint",
  "env": {
    "es6": true,
    "browser": true
  },
  "extends": ["airbnb", "prettier", "plugin:react/recommended"],
  "plugins": ["react", "prettier"],
  "rules": {
    "prettier/prettier": [
      "error",
      {
        "semi": false,
        "singleQuote": true,
        "trailingComma": "es5"
      }
    ],
    // 一個函數(shù)的復(fù)雜性不超過 10,所有分支源葫、循環(huán)诗越、回調(diào)加在一起,在一個函數(shù)里不超過 10 個
    "complexity": [2, 10],
    // 一個函數(shù)的嵌套不能超過 4 層息堂,多個 for 循環(huán)嚷狞,深層的 if-else块促,都是罪惡之源
    "max-depth": [2, 4],
    // 一個函數(shù)最多有 3 層 callback,使用 async/await
    "max-nested-callbacks": [2, 3],
    // 一個文件的最大行數(shù)
    "max-lines": ["error", {"max": 400}],
    // 一個函數(shù)最多 5 個參數(shù)床未。參數(shù)太多的函數(shù)竭翠,意味著函數(shù)功能過于復(fù)雜,請拆分
    "max-params": [2, 5],
    // 一個函數(shù)最多有 10 個變量薇搁,如果超過了斋扰,請拆分之,或者精簡之
    "max-statements": [2, 10],
    // 堅定的 semicolon-less 擁護者
    "semi": [2, "never"],

    "class-methods-use-this": 0,
    "jsx-a11y/anchor-is-valid": [
      "error",
      {
        "components": ["Link"],
        "specialLink": ["to"]
      }
    ],
    "jsx-a11y/click-events-have-key-events": 0,
    "jsx-a11y/no-static-element-interactions": 0,
    "arrow-parens": 0,
    "arrow-body-style": 0,
    "import/extensions": 0,
    "import/no-extraneous-dependencies": 0,
    "import/no-unresolved": 0,
    "react/display-name": 0,
    "react/jsx-filename-extension": [1, {"extensions": [".js", ".jsx"]}],
    "react/prop-types": 0
  }
}

Prettier

Prettier 一個 JavaScript 格式化工具. 它的靈感來源于 refmt啃洋,它對于 ES6传货、ES7、 JSX 和 Flow 的語言特性有著高級的支持宏娄。通過將 JavaScript 解析為 AST 并且基于 AST 美化和打印损离,Prettier 會丟掉幾乎全部的原始的代碼風(fēng)格,從而保證 JavaScript 代碼風(fēng)格的一致性绝编,你可以先感受一下。

image

自動格式化代碼貌踏,不管你原先的代碼格式亂成什么樣十饥,他都會格式化成一樣的,這個功能 非常棒祖乳,真的非常棒逗堵。以后我們再也不用關(guān)心代碼格式的問題了。

ESLint 和 Prettier 確定了以后眷昆,一定要加到 pre commit hook 里面蜒秤,因為人都是懶惰的,不要指望所有工程師都會主動去執(zhí)行 ESLint 和 Prettier亚斋,所以新建了下面的 .pre-commit 文件作媚,在 package.json 的scripts 的 postinstall 時 soft link 到 .git/hooks/pre-commit,這樣在 pre commit 時會自動執(zhí)行以下腳本帅刊。盡量在項目初始階段就加入 pre commit hook纸泡,在項目中途加入可能會遇到團隊的反對,執(zhí)行起來較難赖瞒。這也是面試的時候可以關(guān)注的一個地方女揭,我們提高效率需要切實可行的手段,需要落到實處栏饮。

ESLint 和 Prettier 確定了以后吧兔,一定要加到 pre commit hook 里面,因為人都是懶惰的袍嬉,不要指望所有工程師都會主動去執(zhí)行 ESLint 和 Prettier境蔼,所以新建了下面的 .pre-commit 文件,在 package.json 的scripts 的 postinstall 時 soft link 到 .git/hooks/pre-commit,這樣在 pre commit 時會自動執(zhí)行以下腳本欧穴。盡量在項目初始階段就加入 pre commit hook民逼,在項目中途加入可能會遇到團隊的反對,執(zhí)行起來較難涮帘。這也是面試的時候可以關(guān)注的一個地方拼苍,我們提高效率需要切實可行的手段,需要落到實處调缨,以上操作可以通過 husky 方便解決疮鲫。

Install it along with husky:

yarn add lint-staged husky --dev

and add this config to your package.json:

{
  "scripts": {
    "precommit": "lint-staged"
  },
  "lint-staged": {
    "*.{js,json,css,md}": ["prettier --write", "git add"]
  }
}

以上命令會在 pre commit 時先執(zhí)行 Prettier 格式化,然后再執(zhí)行 ESLint 的校驗弦叶。如果想要在編輯時就格式化代碼俊犯,Prettier 針對當(dāng)前主流編輯器也有插件,請參考 這里 伤哺,另外 ESLint 可以和 Prettier 很好的搭配使用燕侠,參考 eslint-plugin-prettier ,以上所有的配置和文件我都整理到了 這個項目 里立莉,為了讓大伙能夠好好寫代碼绢彤,真的是操碎了心。

采用函數(shù)式編程

在談到函數(shù)式編程及其有什么優(yōu)點之前蜓耻,我們先看我們常見的編程方式茫舶,imperative programming(命令式編程)有什么缺點。

function getData(col) {
  var results = []
  for (var i = 0; i < col.length; i++) {
    if (col[i] && col[i].data) {
      results.push(col[i].data)
    }
  }
  return results
}

這段代碼很簡單刹淌,它過濾一個傳入的數(shù)組饶氏,取出里面每個元素的 data 域,然后插入新的數(shù)組返回有勾。相信很多人都會撰寫類似的代碼疹启。它有很多問題:

  • 我們在告訴計算機怎么樣一步步完成一件事情,引入了循環(huán)柠衅,使用一個無關(guān)緊要的局部變量 i 控制循環(huán)(或者迭代器)皮仁。事實上我根本不需要關(guān)心這個變量怎么開始,怎么結(jié)束菲宴,怎么增長贷祈,這和我要解決的問題無關(guān)。
  • 我們引入了一個狀態(tài) results喝峦,并不斷變更這個狀態(tài)势誊。在每次循環(huán)的時候,它的值都會發(fā)生改變谣蠢。
  • 當(dāng)我們的問題稍微改變的時候粟耻,比如我要添加一個函數(shù)查近,返回有關(guān) data 長度的一個數(shù)組,那么我們需要仔細研讀已有的代碼挤忙,搞清楚整個邏輯霜威,然后新寫一個函數(shù)(多數(shù)情況下,工程師會啟用「復(fù)制-粘貼-修改」大法册烈。
  • 這樣的代碼可讀性很差戈泼,一旦內(nèi)部狀態(tài)超過 10 個,且互相依賴赏僧,要讀懂它的邏輯并不容易大猛。
  • 這樣的代碼無法輕易復(fù)用。

如果是函數(shù)式編程淀零,你大概會這么寫:

function extract(filterFn, mapFn, col) {
  return col.filter(filterFn).map(mapFn)
}

有沒有覺得世界都清凈了挽绩,這段代碼非常簡潔、明了驾中,如果你了解 filter / map唉堪,幾乎很難寫錯。這幾乎就是一個通解肩民,一臺 machine巨坊,有了它,你可以解決任何數(shù)據(jù)集過濾和映射的問題此改。當(dāng)然,你還可以這么抽象:

function extract(filterFn, mapFn) {
  return function process(col) {
    return col.filter(filterFn).map(mapFn)
  }
}

注意侄柔,這兩者雖然抽象出來的結(jié)果相似共啃,但應(yīng)用范圍是不盡相同的。后者更像一臺生產(chǎn) machine 的 machine(函數(shù)返回函數(shù))暂题,它將問題進一步解耦移剪。這種解耦使得代碼不僅泛化(generalization),而且將代碼的執(zhí)行過程分成兩階段薪者,在時序上和接口上也進行了解耦纵苛。于是,你可以在上下文 A 中調(diào)用extract言津,在上下文 B 中調(diào)用 process攻人,產(chǎn)生真正的結(jié)果。上下文 A 和上下文 B 可以毫不相干悬槽,A 的上下文只需提供 filterFn 和 mapFn(比如說怀吻,系統(tǒng)初始化),B 的上下文只需提供具體的數(shù)據(jù)集col(比如說 web request 到達時)初婆。這種時序上的解耦使得代碼的威力大大增強蓬坡。接口上的解耦猿棉,就像旅游中用的萬國插座一樣,讓你的函數(shù)能夠一頭對接上下文 A 中的系統(tǒng)屑咳,另一頭對接上下文 B 中的系統(tǒng)萨赁。

講到這里我們大致已經(jīng)能看出函數(shù)式編程的一些特點:

  • 提倡組合(composition),組合是王道兆龙。
  • 每個函數(shù)盡可能完成單一的功能杖爽。
  • 屏蔽細節(jié),告訴計算機我要做什么详瑞,而不是怎么做掂林。我們看 filter / map,它們并未暴露自身的細節(jié)坝橡。一個 filter 函數(shù)的實現(xiàn)泻帮,在單核 CPU 上可能是一個循環(huán),在多核 CPU 上可能是一個 dispatcher 和 aggregator计寇,但我們可以暫時忽略它的實現(xiàn)細節(jié)锣杂,只需了解它的功能即可。
  • 盡可能不引入或者少引入狀態(tài)番宁。

這些特點運用得當(dāng)?shù)脑捲軌驗檐浖恚?/p>

  • 更好的設(shè)計和實現(xiàn)。
  • 更加清晰可讀的代碼蝶押。由于狀態(tài)被大大減少踱蠢,代碼更容易維護,也帶來更強的穩(wěn)定性棋电。
  • 在分布式系統(tǒng)下有更好的性能茎截。函數(shù)式編程一般都在一個較高的層次進行抽象,map / filter / reduce 就是其基礎(chǔ)指令赶盔,如果這些指令為分布式而優(yōu)化企锌,那么系統(tǒng)無需做任何改動,就可以提高性能于未。
  • 使得惰性運算成為可能撕攒。在命令式編程中,由于你明確告訴了 CPU 一步步該怎么操作烘浦,CPU 只能俯首聽命抖坪,優(yōu)化的空間已經(jīng)被擠壓;而在函數(shù)式編程里闷叉,每個函數(shù)只是封裝了運算柳击,一組數(shù)據(jù)從輸入經(jīng)歷一系列運算到輸出,如果沒有人處理這些輸出片习,則運算不會被真正執(zhí)行捌肴。

以上節(jié)選自陳天老師的 抽象的能力函數(shù)式編程簡介蹬叭。這里只是管中窺豹,通過一個短小的案例示范一下函數(shù)式編程的威力状知,許多高級的概念和深入的學(xué)習(xí)請參考原文秽五。

總結(jié)

軟件工程已經(jīng)發(fā)展了 50 多年,至今仍在不斷前進〖玻現(xiàn)在坦喘,把這些原則當(dāng)作試金石并運用在團隊實際開發(fā)中,嘗試將他們作為團隊代碼質(zhì)量考核的標(biāo)準之一吧西设。

One more thing:這些準則不會讓你立刻變成一個優(yōu)秀的工程師瓣铣,長期奉行他們也并不意味著你能夠高枕無憂。千里之行贷揽,始于足下棠笑。我們需要時常和同行們進行代碼評審,不斷優(yōu)化自己的代碼禽绪,不要懼怕改善代碼質(zhì)量所需付出的努力蓖救。長此以往,你不僅會看得懂自己半年前寫的代碼印屁,還將獲得同行的贊許循捺,你的程序之路會走的更遠!

參考

clean-code-javascript

如何使用一門新的語言

Prettier

函數(shù)式編程簡介

抽象的能力

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末雄人,一起剝皮案震驚了整個濱河市从橘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌础钠,老刑警劉巖洋满,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異珍坊,居然都是意外死亡,警方通過查閱死者的電腦和手機正罢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門阵漏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人翻具,你說我怎么就攤上這事履怯。” “怎么了裆泳?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵叹洲,是天一觀的道長。 經(jīng)常有香客問我工禾,道長运提,這世上最難降的妖魔是什么蝗柔? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮民泵,結(jié)果婚禮上癣丧,老公的妹妹穿的比我還像新娘。我一直安慰自己栈妆,他們只是感情好胁编,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鳞尔,像睡著了一般嬉橙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上寥假,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天市框,我揣著相機與錄音,去河邊找鬼昧旨。 笑死拾给,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的兔沃。 我是一名探鬼主播蒋得,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼乒疏!你這毒婦竟也來了额衙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤怕吴,失蹤者是張志新(化名)和其女友劉穎窍侧,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體转绷,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡伟件,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了议经。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斧账。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖煞肾,靈堂內(nèi)的尸體忽然破棺而出咧织,到底是詐尸還是另有隱情,我是刑警寧澤籍救,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布习绢,位于F島的核電站,受9級特大地震影響蝙昙,放射性物質(zhì)發(fā)生泄漏闪萄。R本人自食惡果不足惜梧却,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望桃煎。 院中可真熱鬧篮幢,春花似錦、人聲如沸为迈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽葫辐。三九已至搜锰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間耿战,已是汗流浹背蛋叼。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留剂陡,地道東北人狈涮。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像鸭栖,于是被迫代替她去往敵國和親歌馍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容