編碼如作文:寫(xiě)出高可讀 JS 的 7 條原則

共 5914 字喊递,讀完需 8 分鐘。編譯自 Eric Elliott文章阳似,好的程序員寫(xiě)出來(lái)的代碼就如同優(yōu)美的詩(shī)賦册舞,給閱讀的人帶來(lái)非常愉悅的享受。我們?cè)趺茨苓_(dá)到那樣的水平障般?要搞清楚這個(gè)問(wèn)題,先看看好的文章是怎么寫(xiě)出來(lái)的盛杰。

William Strunk 在 1920 年出版的《The Elements of Style》 一書(shū)中列出了寫(xiě)出好文章的 7 條原則挽荡,過(guò)了近一個(gè)世紀(jì),這些原則并沒(méi)有過(guò)時(shí)即供。對(duì)于工程師來(lái)說(shuō)定拟,代碼是寫(xiě)一遍、修改很多遍、閱讀更多遍的重要產(chǎn)出青自,可讀性至關(guān)重要株依,我們可以用這些寫(xiě)作原則指導(dǎo)日常的編碼,寫(xiě)出高可讀的代碼延窜。

需要注意的是恋腕,這些原則并不是法律,如果違背它們能讓代碼可讀性更高逆瑞,自然是沒(méi)問(wèn)題的荠藤,但我們需要保持警惕和自省,因?yàn)檫@些久經(jīng)時(shí)間考驗(yàn)的原則通常是對(duì)的获高,我們最好不要因?yàn)槠嫠籍愊牖騻€(gè)人偏好而違背這些原則哈肖。

7 條寫(xiě)作原則如下:

  1. 讓段落成為寫(xiě)作的基本單位,每個(gè)段落只說(shuō) 1 件事情念秧;
  2. 省略不必要的詞語(yǔ)淤井;
  3. 使用主動(dòng)式;
  4. 避免連串的松散句子摊趾;
  5. 把相關(guān)內(nèi)容放在一起币狠;
  6. 多用肯定語(yǔ)句;
  7. 善用平行結(jié)構(gòu)严就;

對(duì)應(yīng)的总寻,在編碼時(shí):

  1. 讓函數(shù)成為編碼的基本單位,每個(gè)函數(shù)只做 1 件事情梢为;
  2. 省略不必要的代碼渐行;
  3. 使用主動(dòng)式;
  4. 避免連串的松散表達(dá)式铸董;
  5. 把相關(guān)的代碼放在一起祟印;
  6. 多用肯定語(yǔ)句;
  7. 善用平行結(jié)構(gòu)粟害;

1. 讓函數(shù)成為編碼的基本單位蕴忆,每個(gè)函數(shù)只做 1 件事情

The essence of software development is composition. We build software by composing modules, functions, and data structures together.

軟件開(kāi)發(fā)的本質(zhì)是組合,我們通過(guò)組合模塊悲幅、函數(shù)套鹅、數(shù)據(jù)結(jié)構(gòu)來(lái)構(gòu)造軟件。理解如何編寫(xiě)和組合函數(shù)是軟件工程師的基本技能汰具。模塊通常是一個(gè)或多個(gè)函數(shù)和數(shù)據(jù)結(jié)構(gòu)的集合卓鹿,而數(shù)據(jù)結(jié)構(gòu)是我們表示程序狀態(tài)的方法,但是在我們調(diào)用一個(gè)函數(shù)之前留荔,通常什么也不會(huì)發(fā)生吟孙。在 JS 中,我們可以把函數(shù)分為 3 種:

  • I/O 型函數(shù) (Communicating Functions):進(jìn)行磁盤或者網(wǎng)絡(luò) I/O;
  • 過(guò)程型函數(shù) (Procedural Functions):組織指令序列杰妓;
  • 映射型函數(shù) (Mapping Functions):對(duì)輸入進(jìn)行計(jì)算藻治、轉(zhuǎn)換,返回輸出巷挥;

雖然有用的程序都需要 I/O桩卵,大多數(shù)程序都會(huì)有過(guò)程指令,程序中的大多數(shù)函數(shù)都會(huì)是映射型函數(shù):給定輸入時(shí)句各,函數(shù)能返回對(duì)應(yīng)的輸出吸占。

每個(gè)函數(shù)只做一件事情: 如果你的函數(shù)是做網(wǎng)絡(luò)請(qǐng)求(I/O 型)的,就不要在其中混入數(shù)據(jù)轉(zhuǎn)換的代碼(映射型)凿宾。如果嚴(yán)格按照定義矾屯,過(guò)程型函數(shù)很明顯違背了這條原則,它同時(shí)也違背了另外一條原則:避免連串的松散表達(dá)式初厚。

理想的函數(shù)應(yīng)該是簡(jiǎn)單的件蚕、確定的、純粹的:

  • 輸入相同的情況下产禾,輸出始終相同排作;
  • 沒(méi)有任何副作用;

關(guān)于純函數(shù)的更多內(nèi)容可以參照這里亚情。

2. 省略不必要的代碼

“Vigorous writing is concise. A sentence should contain no unnecessary words, a paragraph no unnecessary sentences, for the same reason that a drawing should have no unnecessary lines and a machine no unnecessary parts. This requires not that the writer make all sentences short, or avoid all detail and treat subjects only in outline, but that every word tell.”

簡(jiǎn)潔的代碼對(duì)軟件質(zhì)量至關(guān)重要妄痪,因?yàn)楦嗟拇a等同于更多的 bug 藏身之所,換句話說(shuō):更少的代碼 = 更少的 bug 藏身之所 = 更少的 bug楞件。

簡(jiǎn)潔的代碼讀起來(lái)會(huì)更清晰衫生,是因?yàn)樗懈叩男旁氡?(Signal-to-Noise Ratio):閱讀代碼時(shí)更容易從較少的語(yǔ)法噪音中篩選出真正有意義的部分,可以說(shuō)土浸,更少的代碼 = 更少的語(yǔ)法噪音 = 更高的信號(hào)強(qiáng)度

借用《The Elements of Style》中的原話:簡(jiǎn)潔的代碼更有力罪针,比如下面的代碼:

function secret (message) {
    return function () {
        return message;
    }
};

可以被簡(jiǎn)化為:

const secret = msg => () => msg;

顯然,對(duì)熟悉箭頭函數(shù)的同學(xué)來(lái)說(shuō)黄伊,簡(jiǎn)化過(guò)的代碼可讀性更好泪酱,因?yàn)樗÷粤瞬槐匾恼Z(yǔ)法元素:花括號(hào)、function 關(guān)鍵字、return 關(guān)鍵字。而簡(jiǎn)化前的代碼包含的語(yǔ)法要素對(duì)于傳達(dá)代碼意義本身作用并不大舆驶。當(dāng)然,如果你不熟悉 ES6 的語(yǔ)法斯撮,這對(duì)你來(lái)說(shuō)可能顯得比較怪異,但 ES6 從 2015 年之后已經(jīng)成為新的語(yǔ)言標(biāo)準(zhǔn)悦即,如果你還不熟悉,是時(shí)候去升級(jí)了

省略不必要的變量

我們常常忍不住去給實(shí)際上不需要命名的東西強(qiáng)加上名字辜梳。問(wèn)題在于人的工作記憶是有限的粱甫,閱讀代碼時(shí),每個(gè)變量都會(huì)占用工作記憶的存儲(chǔ)空間作瞄。因?yàn)檫@個(gè)原因茶宵,有經(jīng)驗(yàn)的程序員會(huì)盡可能的消除不必要的變量命名。

比如宗挥,在大多數(shù)情況下乌庶,你可以不用給只是作為返回值的變量命名,函數(shù)名應(yīng)該足夠說(shuō)明你要返回的是什么內(nèi)容契耿,考慮下面的例子:

// 稍顯累贅的寫(xiě)法
const getFullName = ({firstName, lastName}) => {
  const fullName = firstName + ' ' + lastName;
  return fullName;
};

// 更簡(jiǎn)潔的寫(xiě)法
const getFullName = ({firstName, lastName}) => (
  firstName + ' ' + lastName
);

減少變量的另外一種方法是利用 point-free-style瞒大,這是函數(shù)式編程里面的概念。

point-free-style 是不引用函數(shù)所操作參數(shù)的一種函數(shù)定義方式搪桂,實(shí)現(xiàn) point-free-style 的常見(jiàn)方法包括函數(shù)組合(function composotion)函數(shù)科里化(function currying)透敌。

先看函數(shù)科里化的例子:

const add = a => b => a + b;

// Now we can define a point-free inc()
// that adds 1 to any number.
const inc = add(1);

inc(3); // 4

細(xì)心的同學(xué)會(huì)發(fā)現(xiàn)并沒(méi)有使用 function 關(guān)鍵字或者箭頭函數(shù)語(yǔ)法來(lái)定義 inc 函數(shù)。add 也沒(méi)有列出所 inc 需要的參數(shù)踢械,因?yàn)?add 函數(shù)自己內(nèi)部不需要使用這些參數(shù)酗电,只是返回了能自己處理參數(shù)的新函數(shù)。

函數(shù)組合是指把一個(gè)函數(shù)的輸出作為另一個(gè)函數(shù)輸入的過(guò)程内列。不管你有沒(méi)有意識(shí)到撵术,你已經(jīng)在頻繁的使用函數(shù)組合了,鏈?zhǔn)秸{(diào)用的代碼基本都是這個(gè)模式话瞧,比如數(shù)組操作時(shí)使用的 map嫩与,Promise 操作時(shí)的 then。函數(shù)組合在函數(shù)式語(yǔ)言中也被稱之為高階函數(shù)移稳,其基本形式為:f(g(x))蕴纳。

把兩個(gè)函數(shù)組合起來(lái)的時(shí)候,就消除了把中間結(jié)果存在變量中的需要个粱,下面來(lái)看看函數(shù)組合讓代碼變簡(jiǎn)潔的例子:

先定義兩個(gè)基本操作函數(shù):

const g = n => n + 1;
const f = n => n * 2;

我們的計(jì)算需求是:給定輸入古毛,先對(duì)其 +1,再對(duì)結(jié)果 x2都许,普通做法是:

// 需要操作參數(shù)稻薇、并且存儲(chǔ)中間結(jié)果
const incThenDoublePoints = n => {
  const incremented = g(n);
  return f(incremented);
};

incThenDoublePoints(20); // 42

使用函數(shù)組合的寫(xiě)法是:

// 接受兩個(gè)函數(shù)作為參數(shù),直接返回組合
const compose = (f, g) => x => f(g(x));
const incThenDoublePointFree = compose(f, g);
incThenDoublePointFree(20); // 42

使用仿函數(shù) (funcot) 也能實(shí)現(xiàn)類似的效果胶征,在仿函數(shù)中把參數(shù)封裝成可遍歷的數(shù)組塞椎,然后使用 map 或者 Promise 的 then 實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用,具體的代碼如下:

const compose = (f, g) => x => [x].map(g).map(f).pop();
const incThenDoublePointFree = compose(f, g);
incThenDoublePointFree(20); // 42

如果你選擇使用 Promise 鏈睛低,代碼看起來(lái)也會(huì)非常的像案狠。

基本所有提供函數(shù)式編程工具的庫(kù)都提供至少 2 種函數(shù)組合模式:

  • compose:從右向左執(zhí)行函數(shù)服傍;
  • pipe:從左向右執(zhí)行函數(shù);

lodash 中的 compose()flow() 分別對(duì)應(yīng)這 2 種模式骂铁,下面是使用 flow() 的例子:

import pipe from 'lodash/fp/flow';
pipe(g, f)(20); // 42

如果不用 lodash吹零,用下面的代碼也可以實(shí)現(xiàn)相同的功能:

const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);
pipe(g, f)(20); // 42

如果上面介紹的函數(shù)組合你覺(jué)得很異類,并且你不確定你會(huì)怎么使用它們拉庵,請(qǐng)仔細(xì)思考下面這句話:

The essence of software development is composition. We build applications by composing smaller modules, functions, and data structures.

從這句話灿椅,我們不難推論,理解函數(shù)和對(duì)象的組合方式對(duì)工程師的重要程度就像理解電鉆和沖擊鉆對(duì)搞裝修的人重要程度钞支。當(dāng)你使用命令式代碼把函數(shù)和中間變量組合在一起的時(shí)候茫蛹,就如同使用膠帶把他們強(qiáng)行粘起來(lái),而函數(shù)組合的方式看起來(lái)更自然流暢烁挟。

在不改變代碼作用,不降低代碼可讀性的情況下信夫,下面兩條是永遠(yuǎn)應(yīng)該謹(jǐn)記的:

  • 使用更少的代碼窃蹋;
  • 使用更少的變量;

3. 使用主動(dòng)式

“The active voice is usually more direct and vigorous than the passive.”

主動(dòng)式通常比被動(dòng)式更直接静稻、有力警没,變量命名時(shí)要盡可能的直接,不拐彎抹角振湾,例如:

  • myFunction.wasCalled() 優(yōu)于 myFunction.hasBeenCalled()杀迹;
  • createUser() 優(yōu)于User.create()`;
  • notify() 優(yōu)于 Notifier.doNotification()押搪;

命名布爾值時(shí)將其當(dāng)做只有 “是” 和 “否” 兩種答案的問(wèn)題來(lái)命名:

  • isActive(user) 優(yōu)于 getActiveStatus(user)树酪;
  • isFirstRun = false; 優(yōu)于 firstRun = false;

函數(shù)命名時(shí)盡可能使用動(dòng)詞:

  • increment() 優(yōu)于 plusOne()
  • unzip() 優(yōu)于 filesFromZip()
  • filter(fn, array) 優(yōu)于 matchingItemsFromArray(fn, array)

事件監(jiān)聽(tīng)函數(shù)(Event Handlers)和生命周期函數(shù)(Licecycle Methods)比較特殊因?yàn)樗麄兏蟪潭仁怯脕?lái)說(shuō)明什么時(shí)候該執(zhí)行而不是應(yīng)該做什么,它們的命名方式可以簡(jiǎn)化為:"<時(shí)機(jī)>大州,<動(dòng)詞>"续语。

下面是事件監(jiān)聽(tīng)函數(shù)的例子:

  • element.onClick(handleClick) 優(yōu)于 element.click(handleClick)
  • component.onDragStart(handleDragStart) 優(yōu)于 component.startDrag(handleDragStart)

仔細(xì)審視上面兩例的后半部分,你會(huì)發(fā)現(xiàn)厦画,它們讀起來(lái)更像是在觸發(fā)事件疮茄,而不是對(duì)事件做出響應(yīng)。

至于生命周期函數(shù)根暑,考慮 React 中組件更新之前應(yīng)該調(diào)用的函數(shù)該怎么命名:

  • componentWillBeUpdated(doSomething)
  • componentWillUpdate(doSomething)
  • beforeUpdate(doSomething)

componentWillBeUpdated 用了被動(dòng)式力试,意指將要被更新,而不是將要更新排嫌,有些饒舌畸裳,明顯不如后面兩個(gè)好。

componentWillUpdate 更好點(diǎn)淳地,但是這個(gè)命名更像是去調(diào)用 doSomething怖糊,我們的本意是:在 Component 更新之前帅容,調(diào)用 doSomethingbeforeComponentUpdate 能更清晰的表達(dá)我們的意圖伍伤。

進(jìn)一步簡(jiǎn)化丰嘉,因?yàn)檫@些生命周期方法都是 Component 內(nèi)置的,在方法中加上 Component 顯得多余嚷缭,可以腦補(bǔ)下直接在 Componenent 實(shí)例上調(diào)用這個(gè)方法的語(yǔ)法:component.componentWillUpdate,我們不需要把主語(yǔ)重復(fù)兩次耍贾。顯然阅爽,component.beforeUpdate(doSomething)component.beforeComponentUpdate(doSomething) 更直接、簡(jiǎn)潔荐开、準(zhǔn)確付翁。

還有一種函數(shù)叫 [Functional Mixins][8],它們就像裝配流水線給傳進(jìn)來(lái)的對(duì)象加上某些方法或者屬性晃听,這種函數(shù)的命名通常會(huì)使用形容詞百侧,如各種帶 "ing""able" 后綴的詞匯,示例:

const duck = composeMixins(flying, quacking);   // 會(huì)像鴨子叫
const box = composeMixins(iterable, mappable);  // 可遍歷的

4. 避免連串的松散表達(dá)式

“…a series soon becomes monotonous and tedious.”

連串的松散代碼常常會(huì)變的單調(diào)乏味能扒,而把不強(qiáng)相關(guān)但按先后順序執(zhí)行的語(yǔ)句組合到過(guò)程式的函數(shù)中很容易寫(xiě)出意大利面式的代碼(spaghetti code)佣渴。這種寫(xiě)法常常會(huì)重復(fù)很多次,即使不是嚴(yán)格意義上的重復(fù)初斑,也只有細(xì)微的差別辛润。

比如,界面上的不同組件之間幾乎共享完全相同的邏輯結(jié)構(gòu)见秤,考慮下面的例子:

const drawUserProfile = ({ userId }) => {
  const userData = loadUserData(userId);
  const dataToDisplay = calculateDisplayData(userData);
  renderProfileData(dataToDisplay);
};

drawUserProfile 函數(shù)實(shí)際上做了 3 件不同的事情:加載數(shù)據(jù)砂竖、根據(jù)數(shù)據(jù)計(jì)算視圖狀態(tài)、渲染視圖鹃答。在大多數(shù)現(xiàn)代的前端框架里面乎澄,這 3 件事情都做了很好的分離。通過(guò)把關(guān)注點(diǎn)分離测摔,每個(gè)關(guān)注點(diǎn)的擴(kuò)展和組合方式就多了很多置济。

比如說(shuō),我們可以把渲染部分完全替換掉而不影響程序的其他部分避咆,實(shí)例就是 React 家族的各種渲染引擎:ReactNative 用來(lái)在 iOS 和 Android 中渲染 APP舟肉,AFrame 來(lái)渲染 WebVR,ReactDOM/Server 來(lái)做服務(wù)端渲染查库。

drawUserProfile 函數(shù)的另一個(gè)問(wèn)題是:在數(shù)據(jù)加載完成之前路媚,沒(méi)有辦法計(jì)算視圖狀態(tài)完成渲染,如果數(shù)據(jù)已經(jīng)在其他地方加載過(guò)了會(huì)怎么樣樊销,就會(huì)做很多重復(fù)和浪費(fèi)的事情整慎。

關(guān)注點(diǎn)分離的設(shè)計(jì)能夠使每個(gè)環(huán)節(jié)能夠被獨(dú)立的測(cè)試脏款,我喜歡為應(yīng)用添加單元測(cè)試,并在每次修改代碼時(shí)查看測(cè)試結(jié)果裤园。試想撤师,如果把數(shù)據(jù)獲取和視圖渲染代碼寫(xiě)在一起,單元測(cè)試將會(huì)變的困難拧揽,要么需要傳入偽造的數(shù)據(jù)剃盾,要么轉(zhuǎn)而采用比較笨重的 E2E 測(cè)試,而后者通常比較難立即給反饋淤袜,因?yàn)樗鼈兊倪\(yùn)行比較耗時(shí)痒谴。

在使用 React 的場(chǎng)景下,drawUserProfile 中已經(jīng)有了 3 個(gè)獨(dú)立的函數(shù)可以接入到 Component 生命周期方法上铡羡,數(shù)據(jù)加載可以在 Component 掛載之后觸發(fā)积蔚,而數(shù)據(jù)計(jì)算和渲染則可以在視圖狀態(tài)發(fā)生變化時(shí)觸發(fā)。結(jié)果是烦周,程序不同部分的職責(zé)被做了清晰的劃分尽爆,每個(gè) Component 都有相同的結(jié)構(gòu)和生命周期方法,這樣的程序運(yùn)行起來(lái)會(huì)更穩(wěn)定读慎,我們也會(huì)少很多重復(fù)的代碼漱贱。

5. 把相關(guān)代碼放在一起

很多框架和項(xiàng)目腳手架都規(guī)定了按代碼類別來(lái)組織文件的方式,如果僅僅是開(kāi)發(fā)一個(gè)簡(jiǎn)單的 TODO 應(yīng)用夭委,這樣做無(wú)可厚非饱亿,但是在大型項(xiàng)目中,按照業(yè)務(wù)功能去組織代碼通常更好闰靴”肓可能很多同學(xué)會(huì)忽略代碼組織與代碼可讀性的關(guān)系,想想看是否接手過(guò)看了半天還不知道自己要修改的代碼在哪里的項(xiàng)目呢蚂且?是什么原因造成的配猫?

下面分別是按代碼類別和業(yè)務(wù)功能來(lái)組織一個(gè) TODO 應(yīng)用代碼的兩種方式:

按代碼類別組織

├── components
│   ├── todos
│   └── user
├── reducers
│   ├── todos
│   └── user
└── tests
    ├── todos
    └── user

按業(yè)務(wù)功能組織

├── todos
│   ├── component
│   ├── reducer
│   └── test
└── user
    ├── component
    ├── reducer
    └── test

當(dāng)按業(yè)務(wù)功能組織代碼的時(shí)候,我們修改某個(gè)功能的時(shí)候不用在整個(gè)文件樹(shù)上跳來(lái)跳去的找代碼了杏死。關(guān)于代碼組織泵肄,《The Art of Readable Code》中也有部分介紹,感興趣的同學(xué)可以去閱讀淑翼。

6. 多用肯定語(yǔ)句

“Make definite assertions. Avoid tame, colorless, hesitating, non-committal language. Use the word > not> as a means of denial or in antithesis, never as a means of evasion.”

要做出確定的斷言腐巢,避免使用溫順、無(wú)色玄括、猶豫的語(yǔ)句冯丙,必要時(shí)使用 not 來(lái)否定、拒絕或逃避遭京。典型的:

  • isFlying 優(yōu)于 isNotFlying
  • late 優(yōu)于 notOnTime

If 語(yǔ)句

先處理錯(cuò)誤情況胃惜,而后處理正常邏輯:

if (err) return reject(err);
// do something...

優(yōu)于先處理正常后處理錯(cuò)誤:(對(duì)錯(cuò)誤取反的判斷讀起來(lái)確實(shí)累)

if (!err) {
  // ... do something
} else {
  return reject(err);
}

三元表達(dá)式

把肯定的放在前面:

{
  [Symbol.iterator]: iterator ? iterator : defaultIterator
}

優(yōu)于把否定的放在前面(有個(gè)設(shè)計(jì)原則叫 Do not make me think泞莉,用到這里恰如其分):

{
  [Symbol.iterator]: (!iterator) ? defaultIterator : iterator
}

恰當(dāng)?shù)氖褂梅穸?/h3>

有些時(shí)候我們只關(guān)心某個(gè)變量是否缺失,如果使用肯定的命名會(huì)強(qiáng)迫我們對(duì)變量取反船殉,這種情況下使用 "not" 前綴和取反操作符不如使用否定語(yǔ)句直接鲫趁,比如:

  • if (missingValue) 優(yōu)于 if (!hasValue)
  • if (anonymous) 優(yōu)于 if (!user)
  • if (isEmpty(thing)) 優(yōu)于 if (notDefined(thing))

善用命名參數(shù)對(duì)象

不要期望函數(shù)調(diào)用者傳入 undefined、null 來(lái)填補(bǔ)可選參數(shù)利虫,要學(xué)會(huì)使用命名的參數(shù)對(duì)象挨厚,比如:

const createEvent = ({
  title = 'Untitled',
  timeStamp = Date.now(),
  description = ''
}) => ({ title, description, timeStamp });

// later...
const birthdayParty = createEvent({
  title: 'Birthday Party',
  description: 'Best party ever!'
});

就比下面這種形式好:

const createEvent = (
  title = 'Untitled',
  timeStamp = Date.now(),
  description = ''
) => ({ title, description, timeStamp });

// later...
const birthdayParty = createEvent(
  'Birthday Party',
  undefined, // 要盡可能避免這種情況
  'Best party ever!'
);

7. 善用平行結(jié)構(gòu)

“…parallel construction requires that expressions of similar content and function should be outwardly similar. The likeness of form enables the reader to recognize more readily the likeness of content and function.”

平行結(jié)構(gòu)是語(yǔ)法中的概念,英語(yǔ)中的平行結(jié)構(gòu)指:內(nèi)容相似糠惫、結(jié)構(gòu)相同幽崩、無(wú)先后順序、無(wú)因果關(guān)系的并列句寞钥。不管是設(shè)計(jì)模式還是編程范式,都可以放在這個(gè)范疇中思考和理解陌选,如果有重復(fù)理郑,就肯定有模式,平行結(jié)構(gòu)對(duì)閱讀理解非常重要咨油。

軟件開(kāi)發(fā)中遇到的絕大多數(shù)問(wèn)題前人都遇到并解決過(guò)您炉,如果發(fā)現(xiàn)在重復(fù)做同樣的事情,是時(shí)候停下來(lái)做抽象了:找到相同的地方役电,構(gòu)建一個(gè)能夠很方便的添加不同的抽象層赚爵,很多庫(kù)和框架的本質(zhì)就是在做這類事情。

組件化是非常不錯(cuò)的例子:10 年前法瑟,使用 jQuery 寫(xiě)出把界面更新冀膝、應(yīng)用邏輯和數(shù)據(jù)加載混在一起的代碼是再常見(jiàn)不過(guò)的,隨后人們意識(shí)到霎挟,我們可以把 MVC 模式應(yīng)用到客戶端窝剖,于是就開(kāi)始從界面更新中剝離數(shù)據(jù)層。最后酥夭,我們有了組件化這個(gè)東西赐纱,有了組件化,我們就能用完全相同的方式去表達(dá)所有組件的更新邏輯熬北、生命周期疙描,而不用再寫(xiě)一堆命令式的代碼。

對(duì)于熟悉組件化概念的同學(xué)讶隐,很容易理解組件是如何工作的:部分代碼負(fù)責(zé)聲明界面起胰、部分負(fù)責(zé)在組件生命周期做我們期望它做的事情。當(dāng)我們?cè)谥貜?fù)的問(wèn)題上使用相同的編碼模式巫延,熟悉這種模式的同學(xué)很快就能理解代碼在干什么待错。

總結(jié):代碼應(yīng)該簡(jiǎn)單而不是過(guò)于簡(jiǎn)化

Vigorous writing is concise. A sentence should contain no unnecessary words, a paragraph no unnecessary sentences, for the same reason that a drawing should have no unnecessary lines and a machine no unnecessary parts. This requires not that the writer make all sentences short, or avoid all detail and treat subjects only in outline, but that every word tell.

簡(jiǎn)潔的代碼是有力的籽孙,它不應(yīng)該包含不必要的變量、語(yǔ)法結(jié)構(gòu)火俄,不要求程序員一定要把代碼寫(xiě)的最短犯建,或者省略很多細(xì)節(jié),而是要求代碼中出現(xiàn)的每個(gè)變量瓜客、函數(shù)都能清晰适瓦、直觀的傳達(dá)我們的意圖和想法。

代碼應(yīng)該是簡(jiǎn)潔的谱仪,因?yàn)楹?jiǎn)潔的代碼更容易寫(xiě)(通常代碼量更少)玻熙、更容易讀、更好維護(hù)疯攒,簡(jiǎn)潔的代碼就是更難出 bug嗦随、更容易調(diào)試的代碼。bug 修復(fù)通常會(huì)費(fèi)時(shí)費(fèi)力敬尺,而修復(fù)過(guò)程可能引發(fā)更多的 bug枚尼,修復(fù) bug 也會(huì)影響正常的開(kāi)發(fā)進(jìn)度。

認(rèn)為寫(xiě)出熟悉的代碼才是可讀性更高的代碼的同學(xué)砂吞,實(shí)際上是大錯(cuò)特錯(cuò)署恍,可讀性高的代碼必然是簡(jiǎn)潔和簡(jiǎn)單的,雖然 ES6 早在 2015 年已經(jīng)成為新的標(biāo)準(zhǔn)蜻直,但到了 2017 年盯质,還是有很多同學(xué)不會(huì)使用諸如箭頭函數(shù)、隱式 return概而、rest 和 spread 操作符之類的簡(jiǎn)潔語(yǔ)法呼巷。對(duì)新語(yǔ)法的熟悉需要不斷的練習(xí),投入時(shí)間去學(xué)習(xí)和熟悉新語(yǔ)法以及函數(shù)組合的思想和技術(shù)赎瑰,熟悉之后朵逝,就會(huì)發(fā)現(xiàn)代碼原來(lái)還可以這樣寫(xiě)。

最后需要注意的是乡范,代碼應(yīng)該簡(jiǎn)潔配名,而不是過(guò)于簡(jiǎn)化。

One More Thing

本文作者王仕軍晋辆,商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)渠脉,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。如果你覺(jué)得本文對(duì)你有幫助瓶佳,請(qǐng)點(diǎn)贊芋膘!如果對(duì)文中的內(nèi)容有任何疑問(wèn),歡迎留言討論。想知道我接下來(lái)會(huì)寫(xiě)些什么为朋?歡迎訂閱我的掘金專欄知乎專欄:《前端周刊:讓你在前端領(lǐng)域跟上時(shí)代的腳步》臂拓。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市习寸,隨后出現(xiàn)的幾起案子胶惰,更是在濱河造成了極大的恐慌,老刑警劉巖霞溪,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孵滞,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡鸯匹,警方通過(guò)查閱死者的電腦和手機(jī)坊饶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)殴蓬,“玉大人匿级,你說(shuō)我怎么就攤上這事∪咎” “怎么了痘绎?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)糟秘。 經(jīng)常有香客問(wèn)我,道長(zhǎng)球散,這世上最難降的妖魔是什么尿赚? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮蕉堰,結(jié)果婚禮上凌净,老公的妹妹穿的比我還像新娘。我一直安慰自己屋讶,他們只是感情好冰寻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著皿渗,像睡著了一般斩芭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上乐疆,一...
    開(kāi)封第一講書(shū)人閱讀 51,698評(píng)論 1 305
  • 那天划乖,我揣著相機(jī)與錄音,去河邊找鬼挤土。 笑死琴庵,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播迷殿,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼儿礼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了庆寺?” 一聲冷哼從身側(cè)響起蚊夫,我...
    開(kāi)封第一講書(shū)人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤止邮,失蹤者是張志新(化名)和其女友劉穎这橙,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體屈扎,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年忍疾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片则披。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡菠镇,死狀恐怖绸硕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情烦秩,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站钻趋,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏猾昆。R本人自食惡果不足惜楷扬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望磨德。 院中可真熱鬧炫刷,春花似錦、人聲如沸拴曲。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)兑障。三九已至,卻和暖如春蕉汪,著一層夾襖步出監(jiān)牢的瞬間流译,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工者疤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留福澡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓驹马,卻偏偏與公主長(zhǎng)得像革砸,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子糯累,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

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