成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

編寫扁平化的代碼

lunaticf / 1506人閱讀

摘要:原文作者給你的代碼增加一點(diǎn)點(diǎn)函數(shù)式編程的特性最近我對(duì)函數(shù)式編程非常感興趣。對(duì)我而言,函數(shù)式編程最大的作用就是強(qiáng)制你編寫聲明性代碼代碼描述你做什么,而不是在描述如何做。事實(shí)證明,編寫聲明式代碼是函數(shù)式編程中最簡單的部分之一。

原文:Writing flat & declarative code
作者:Peeke Kuepers

-- 給你的代碼增加一點(diǎn)點(diǎn)函數(shù)式編程的特性

最近我對(duì)函數(shù)式編程非常感興趣。這個(gè)概念讓我著迷:應(yīng)用數(shù)學(xué)來增強(qiáng)抽象性和強(qiáng)制純粹性,以避免副作用,并實(shí)現(xiàn)代碼的良好可復(fù)用性。同時(shí),函數(shù)式編程非常復(fù)雜。

函數(shù)式編程有一個(gè)非常陡峭的學(xué)習(xí)曲線,因?yàn)樗鼇碓从跀?shù)學(xué)中的范疇論。接觸不久之后,就將遇到諸如組合(composition)、恒等(identity),函子(functor)、單子(monad),以及逆變(contravariant)等術(shù)語。我根本不太了解這些概念,可能這也是我從來沒有在實(shí)踐中運(yùn)用函數(shù)式編程的原因。

我開始思考:在常規(guī)的命令式編程和完全的函數(shù)式編程之間是否可能會(huì)有一些中間形式?既允許在代碼庫引入函數(shù)式編程的一些很好的特性,同時(shí)暫時(shí)保留已有的舊代碼。

對(duì)我而言,函數(shù)式編程最大的作用就是強(qiáng)制你編寫聲明性代碼:代碼描述你做什么,而不是在描述如何做。這樣就可以輕松了解特定代碼塊的功能,而無需了解其真正的運(yùn)行原理。事實(shí)證明,編寫聲明式代碼是函數(shù)式編程中最簡單的部分之一。

循環(huán)

...一個(gè)循環(huán)就是一個(gè)命令式控制結(jié)構(gòu),難以重用,并且難以插入到其他操作中。此外,它還得不斷變化代碼來響應(yīng)新的迭代需求。

-- Luis Atencio

所以,讓我們先看一下循環(huán),循環(huán)是命令式編程的一個(gè)很好的例子。循環(huán)涉及很多語法,都是描述它們的行為是如何工作,而不是它們?cè)谧鍪裁?。例如,看看這段代碼:

function helloworld(arr) {
    for (let i = 1; i < arr.length; i++) {
        arr[i] *= 2
        if (arr[i] % 2 === 0) {
            doSomething(arr[i])
        }
    }
}

這段代碼在做什么呢?它將數(shù)組內(nèi)除第一個(gè)數(shù)字 (let i = 1)的其他所有數(shù)字乘以 2,如果是偶數(shù)的話(if (arr % 2 === 0)),就進(jìn)行某些操作。在此過程中,原始數(shù)組的值會(huì)被改變。但這通常是不必要的,因?yàn)閿?shù)組可能還會(huì)在代碼庫中的其他地方用到,所以剛才所做的改變可能會(huì)導(dǎo)致意外的結(jié)果。

但最主要的原因是,這段代碼看起來很難一目了然。它是命令式的,for 循環(huán)告訴我們?nèi)绾伪闅v數(shù)組,在里面,使用一個(gè) if 語句有條件地調(diào)用一個(gè)函數(shù)。

我們可以通過使用數(shù)組方法以聲明式的方式重寫這段代碼。數(shù)組方法直接表達(dá)所做的事,比較常見的方法包括:forEachmap,filterreduceslice。

結(jié)果就像下面這樣:

function helloworld(arr) {
    const evenNumbers = n => n % 2 === 0

    arr
        .slice(1)
        .map(v => v * 2)
        .filter(evenNumbers)
        .forEach(v => doSomething(v))    
}

在這個(gè)例子中,我們使用一種很好的,扁平的鏈?zhǔn)浇Y(jié)構(gòu)去描述我們?cè)谧鍪裁?,明確表明意圖。此外,我們避免了改變?cè)紨?shù)組,從而避免不必要的副作用,因?yàn)榇蠖鄶?shù)數(shù)組方法會(huì)返回一個(gè)新數(shù)組。當(dāng)箭頭函數(shù)開始變得越來越復(fù)雜時(shí),可以地將其提取到一個(gè)特定的函數(shù)中,比如 evenNumbers,?從而盡量保持結(jié)構(gòu)簡單易讀。

在上面的例子,鏈?zhǔn)秸{(diào)用并沒有返回值,而是以 forEach 結(jié)束。然而,我們可以輕松地剝離最后一部分,并返回結(jié)果,以便我們可以在其他地方處理它。如果還需要返回除數(shù)組以外的任何東西,可以使用 reduce 函數(shù)。

對(duì)于接下來的一個(gè)例子,假設(shè)我們有一組 JSON 數(shù)據(jù),其中包含在一個(gè)虛構(gòu)歌唱比賽中不同國家獲得的積分:

[
    {
        "country": "NL",
        "points": 12
    },
    {
        "country": "BE",
        "points": 3
    },
    {
        "country": "NL",
        "points": 0
    },
    ...
]

我們想計(jì)算荷蘭(NL)獲得的總積分,根據(jù)印象中其強(qiáng)大的音樂能力,我們可以認(rèn)為這是一個(gè)非常高的分?jǐn)?shù),但我們想要更精確地確認(rèn)這一點(diǎn)。

使用循環(huán)可能會(huì)是這樣:

function countVotes(votes) {
    let score = 0;

    for (let i = 0; i < votes.length; i++) {
        if (votes[i].country === "NL") {
            score += votes[i].points;
        }
    }

    return score;
}

使用數(shù)組方法重構(gòu),我們得到一個(gè)更干凈的代碼片段:

function countVotes(votes) {
    const sum = (a, b) => a + b;

    return votes
        .filter(vote => vote.country === "NL")
        .map(vote => vote.points)
        .reduce(sum);
}

有時(shí)候 reduce 可能有點(diǎn)難以閱讀,將 reduce 函數(shù)提取出來會(huì)在理解上有幫助。在上面的代碼片段中,我們定義了一個(gè) sum 函數(shù)來描述函數(shù)的作用,因此方法鏈仍然保持很好的可讀性。

if else 語句

接下來,我們來聊聊大家都很喜歡的 if else 語句,if else 語句也是命令式代碼里一個(gè)很好的例子。為了使我們的代碼更具聲明式,我們將使用三元表達(dá)式。

一個(gè)三元表達(dá)式是 if else 語句的替代語法。以下兩個(gè)代碼塊具有相同的效果:

// Block 1
if (condition) {
    doThis();
} else {
    doThat();
}

// Block 2
const value = condition ? doThis() : doThat();

當(dāng)在定義(或返回)一個(gè)常量時(shí),三元表達(dá)式非常有用。使用 if else 語句會(huì)將該變量的使用范圍限制在語句內(nèi),通過使用三元語句,我們可以避免這個(gè)問題:

if (condition) {
    const a = "foo";
} else {
    const a = "bar";
}

const b = condition ? "foo" : "bar";

console.log(a); // Uncaught ReferenceError: a is not defined
console.log(b); // "bar"

現(xiàn)在,我們來看看如何應(yīng)用這一點(diǎn)來重構(gòu)一些更重要的代碼:

const box = element.getBoundingClientRect();

if (box.top - document.body.scrollTop > 0 && box.bottom - document.body.scrollTop < window.innerHeight) {
    reveal();
} else {
    hide();
}

那么,上面的代碼發(fā)生了什么呢?if 語句檢查元素當(dāng)前是否在頁面的可見部分內(nèi),這個(gè)信息在代碼的任何地方都沒有表達(dá)出來?;诖瞬紶栔担僬{(diào)用 reveal() 或者 hide() 函數(shù)。

將這個(gè) if 語句轉(zhuǎn)換成三元表達(dá)式迫使我們將條件移動(dòng)到它自己的變量中。這樣我們可以將三元表達(dá)式組合在一行上,現(xiàn)在通過變量的名稱來傳達(dá)布爾值表示的內(nèi)容,這樣還不錯(cuò)。

const box = element.getBoundingClientRect();
const isInViewport = 
    box.top - document.body.scrollTop > 0 && 
    box.bottom - document.body.scrollTop < window.innerHeight;

isInViewport ? reveal() : hide();

通過這個(gè)例子,重構(gòu)帶來的好處可能看起來不大。接下來會(huì)有一個(gè)相比更復(fù)雜的例子:

elements
    .forEach(element => {
        const box = element.getBoundingClientRect();

        if (box.top - document.body.scrollTop > 0 && box.bottom - document.body.scrollTop < window.innerHeight) {
            reveal();
        } else {
            hide();
        }

    });

這很不好,打破了我們優(yōu)雅的扁平的調(diào)用鏈,從而使代碼更難讀。我們?cè)俅问褂萌僮鞣?,而在使用它的時(shí)候,使用 isInViewport 檢查,并跟它自己的動(dòng)態(tài)函數(shù)分開。

const isInViewport = element => {
    const box = element.getBoundingClientRect();
    const topInViewport = box.top - document.body.scrollTop > 0;
    const bottomInViewport = box.bottom - document.body.scrollTop < window.innerHeight;
    return topInViewport && bottomInViewport;
};

elements
    .forEach(elem => isInViewport(elem) ? reveal() : hide());

此外,現(xiàn)在我們將 isInViewport 移動(dòng)到一個(gè)獨(dú)立函數(shù),可以很容易地把它放在它自己的 helper 類/對(duì)象之內(nèi):

import { isInViewport } from "helpers";

elements
    .forEach(elem => isInViewport(elem) ? reveal() : hide());

雖然上面的例子依賴于所處理的是數(shù)組,但是在不明確是在數(shù)組的情況下,也可以采用這種編碼風(fēng)格。

例如,看看下面的函數(shù),它通過三條規(guī)則來驗(yàn)證密碼的有效性。

import { passwordRegex as requiredChars } from "regexes"
import { getJson } from "helpers"

const validatePassword = async value => {
  if (value.length < 6) return false
  if (!requiredChars.test(value)) return false

  const forbidden = await getJson("/forbidden-passwords")
  if (forbidden.includes(value)) return false

  return value
}

validatePassword(someValue).then(persist)

如果我們使用數(shù)組包裝初始值,就可以使用在上面的例子中里面所用到的所有數(shù)組方法。此外,我們已經(jīng)將驗(yàn)證函數(shù)打包成 validationRules 使其可重用。

import { minLength, matchesRegex, notBlacklisted } from "validationRules"
import { passwordRegex as requiredChars } from "regexes"
import { getJson } from "helpers"

const validatePassword = async value => {
  const result = Array.from(value)
    .filter(minLength(6))
    .filter(matchesRegex(requiredChars))
    .filter(await notBlacklisted("/forbidden-passwords"))
    .shift()

  if (result) return result
  throw new Error("something went wrong...")
}

validatePassword(someValue).then(persist)

目前在 JavaScript 中有一個(gè) 管道操作符 的提案。使用這個(gè)操作符,就不用再把原始值換成數(shù)組了??梢灾苯釉谇懊娴闹嫡{(diào)用管道操作符之后的函數(shù),有點(diǎn)像 Arraymap 功能。修改之后的代碼大概就像這樣:

import { minLength, matchesRegex, notBlacklisted } from "validationRules"
import { passwordRegex as requiredChars } from "regexes"
import { getJson } from "helpers"

const validatePassword = async value =>
  value
    |> minLength(6)
    |> matchesRegex(requiredChars)
    |> await notBlacklisted("/forbidden-passwords")

try { someValue |> await validatePassword |> persist }
catch(e) {
  // handle specific error, thrown in validation rule
}

但需要注意的是,這仍然是一個(gè)非常早期的提案,不過可以稍微期待一下。

事件

最后,我們來看看事件處理。一直以來,事件處理很難以扁平化的方式編寫代碼??梢?Promise 化來保持一種鏈?zhǔn)降模馄交木幊田L(fēng)格,但 Promise 只能 resolve 一次,而事件絕對(duì)會(huì)多次觸發(fā)。

在下面的示例中,我們創(chuàng)建一個(gè)類,它對(duì)用戶的每個(gè)輸入值進(jìn)行檢索,結(jié)果是一個(gè)自動(dòng)補(bǔ)全的數(shù)組。首先檢查字符串是否長于給定的閾值長度。如果滿足條件,將從服務(wù)器檢索自動(dòng)補(bǔ)全的結(jié)果,并將其渲染成一系列標(biāo)簽。

注意代碼的不“純”,頻繁地使用 this 關(guān)鍵字。幾乎每個(gè)函數(shù)都在訪問 this 這個(gè)關(guān)鍵字:

譯注:作者在這里使用 "this keyword",有一種雙關(guān)的意味

import { apiCall } from "helpers"

class AutoComplete {

  constructor (options) {

    this._endpoint = options.endpoint
    this._threshold = options.threshold
    this._inputElement = options.inputElement
    this._containerElement = options.list

    this._inputElement.addEventListener("input", () =>
      this._onInput())

  }

  _onInput () {

    const value = this._inputElement.value

    if (value > this._options.threshold) {
      this._updateList(value)
    }

  }

  _updateList (value) {

    apiCall(this._endpoint, { value })
      .then(items => this._render(items))
      .then(html => this._containerElement = html)

  }

  _render (items) {

    let html = ""

    items.forEach(item => {
      html += `${ item.label }`
    })

    return html

  }

}

通過使用 Observable,我們將用一種更好的方式對(duì)這段代碼進(jìn)行重寫??梢院唵螌?Observable 理解成一個(gè)能夠多次 resolvePromise。

Observable 類型可用于基于推送模型的數(shù)據(jù)源,如 DOM 事件,定時(shí)器和套接字

Observable 提案目前處于 Stage-1。在下面 listen 函數(shù)的實(shí)現(xiàn)是從 GitHub 上的提案中直接復(fù)制的,主要是將事件監(jiān)聽器轉(zhuǎn)換成 Observable。可以看到,我們可以將整個(gè) AutoComplete 類重寫為單個(gè)方法的函數(shù)鏈。

import { apiCall, listen } from "helpers";
import { renderItems } from "templates"; 

function AutoComplete ({ endpoint, threshold, input, container }) {

  listen(input, "input")
    .map(e => e.target.value)
    .filter(value => value.length >= threshold)
    .forEach(value => apiCall(endpoint, { value }))
    .then(items => renderItems(items))
    .then(html => container.innerHTML = html)

}

由于大多數(shù) Observable 庫的實(shí)現(xiàn)過于龐大,我很期待 ES 原生的實(shí)現(xiàn)。mapfilterforEach方法還不是規(guī)范的一部分,但是在 zen-observable 已經(jīng)在擴(kuò)展 API 實(shí)現(xiàn),而 zen-observable 本身是 ES Observables 的一種實(shí)現(xiàn) 。

--

我希望你會(huì)對(duì)這些“扁平化”模式感興趣。就個(gè)人而言,我很喜歡以這種方式重寫我的程序。你接觸到的每一段代碼都可以更易讀。使用這種技術(shù)獲得的經(jīng)驗(yàn)越多,就越來越能認(rèn)識(shí)到這一點(diǎn)。記住這個(gè)簡單的法則:

The flatter the better!

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/89449.html

相關(guān)文章

  • 筆記 編寫可讀性代碼藝術(shù)

    閱讀原文 showImg(https://segmentfault.com/img/remote/1460000013763036?w=196&h=257); 1. 代碼應(yīng)當(dāng)易于理解 唯一標(biāo)準(zhǔn):讓別人理解的時(shí)間最小 2. 把信息裝到名字里 選擇專業(yè)的詞 避免泛泛的名字 使用具體的名字代替抽象的名字 為名字?jǐn)y帶更多的信息 名字的作用域越大,最好名字越長 丟掉沒用的詞 單詞 更多選擇 ...

    xiaochao 評(píng)論0 收藏0
  • React.js 最佳實(shí)踐(2016)_鏈接修正版

    摘要:譯者按最近依舊如火如荼相信大家都躍躍欲試我們團(tuán)隊(duì)也開始在領(lǐng)域有所嘗試年應(yīng)該是逐漸走向成熟的一年讓我們一起來看看國外的開發(fā)者們都總結(jié)了哪些最佳實(shí)踐年在全世界都有很多關(guān)于新的更新和開發(fā)者大會(huì)的討論關(guān)于去年的重要事件請(qǐng)參考那么年最有趣的問題來了我 譯者按:最近React(web/native)依舊如火如荼,相信大家都躍躍欲試,我們團(tuán)隊(duì)也開始在React領(lǐng)域有所嘗試. 2016年應(yīng)該是Reac...

    syoya 評(píng)論0 收藏0
  • co.js - 讓異步代碼同步化

    摘要:前端開發(fā)群是大神所編寫的異步解決方案的庫,用于讓異步的代碼同步化。對(duì)于異步代碼來說,回調(diào)函數(shù)是最基礎(chǔ)的方案,帶來的弊端也顯而易見。讓代碼扁平化,而讓代碼同步化。 近期在全力開發(fā)個(gè)人網(wǎng)站,并且又沉淀了一些前后端的技術(shù)。近期會(huì)頻繁更新。 這篇文章首發(fā)于我的個(gè)人網(wǎng)站:聽說 - https://tasaid.com,建議在我的個(gè)人網(wǎng)站閱讀,擁有更好的閱讀體驗(yàn)。 這篇文章與 博客園 和 Segm...

    lanffy 評(píng)論0 收藏0
  • CSS方法論(一)

    摘要:由于年提出,這基于她在雅虎的工作。但是這很難做到解決的問題樣式全局性造成的樣式?jīng)_突問題多人協(xié)作的命名問題解決層疊問題,使的優(yōu)先級(jí)保持相對(duì)扁平的模塊化,使更具有復(fù)用的能力于年由提出,當(dāng)時(shí)他在雅虎工作。 編寫CSS會(huì)遇到什么問題? 其實(shí)CSS很好寫,只要知道css語法,你就可以寫出來,通過各種學(xué)習(xí),你也可以做出一個(gè)很美麗的頁面。對(duì)能熟練編寫網(wǎng)頁的人來說,可以很簡單的將設(shè)計(jì)圖變成網(wǎng)頁。但是在...

    haoguo 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<