轉(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.js 和 ESLint 的工具可以幫助我們找到未命名的常量掌动。
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)格的一致性绝编,你可以先感受一下。
自動格式化代碼貌踏,不管你原先的代碼格式亂成什么樣十饥,他都會格式化成一樣的,這個功能 非常棒祖乳,真的非常棒逗堵。以后我們再也不用關(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ì)量所需付出的努力蓖救。長此以往,你不僅會看得懂自己半年前寫的代碼印屁,還將獲得同行的贊許循捺,你的程序之路會走的更遠!