自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

JavaScript代碼風(fēng)格要素

開發(fā) 前端
清楚自己的目標(biāo),不要毫無頭緒。毫無頭緒只會浪費時間以及精力。投入精力去訓(xùn)練,讓自己熟悉,去學(xué)習(xí)更好的編程方式,以及更有更有活力的代碼風(fēng)格。

[[196365]]

1920年,由威廉·斯特倫克(William Strunk jr .)撰寫的《英語寫作手冊:風(fēng)格的要素(The Elements of Style)》出版了,這本書列舉了7條英文寫作的準(zhǔn)則,過了一個世紀(jì),這些準(zhǔn)則并沒有過時。對于工程師來說,你可以在自己的編碼風(fēng)格中應(yīng)用類似的建議來指導(dǎo)日常的編碼,提高自己的編碼水平。

需要注意的是,這些準(zhǔn)則不是一成不變的法則。如果違背它們,能夠讓代碼可讀性更高,那么便沒有問題,但請?zhí)貏e小心并時刻反思。這些準(zhǔn)繩是經(jīng)受住了時間考驗的,有充分的理由說明:它們通常是正確的。如果要違背這些規(guī)則,一定要有充足的理由,而不要單憑一時的興趣或者個人的風(fēng)格偏好。

書中的寫作準(zhǔn)則如下:

  • 以段落為基本單位:一段文字,一個主題。
  • 刪減無用的語句。
  • 使用主動語態(tài)。
  • 避免一連串松散的句子。
  • 相關(guān)的內(nèi)容寫在一起。
  • 從正面利用肯定語句去發(fā)表陳述。
  • 不同的概念采用不同的結(jié)構(gòu)去闡述。

我們可以應(yīng)用相似的理念到代碼編寫上面:

  • 一個function只做一件事,讓function成為代碼組合的最小單元。
  • 刪除不必要的代碼。
  • 使用主動語態(tài)。
  • 避免一連串結(jié)構(gòu)松散的,不知所云的代碼。
  • 將相關(guān)的代碼寫在一起。
  • 利用判斷true值的方式來編寫代碼。
  • 不同的技術(shù)方案利用不同的代碼組織結(jié)構(gòu)來實現(xiàn)。

1.一個function只做一件事,讓function成為代碼組合的最小單元

軟件開發(fā)的本質(zhì)是“組合”。 我們通過組合模塊,函數(shù)和數(shù)據(jù)結(jié)構(gòu)來構(gòu)建軟件。

理解如果編寫以及組合方法是軟件開發(fā)人員的基本技能。

模塊是一個或多個function和數(shù)據(jù)結(jié)構(gòu)的簡單集合,我們用數(shù)據(jù)結(jié)構(gòu)來表示程序狀態(tài),只有在函數(shù)執(zhí)行之后,程序狀態(tài)才會發(fā)生一些有趣的變化。

JavaScript中,可以將函數(shù)分為3種:

  • I/O 型函數(shù) (Communicating Functions):函數(shù)用來執(zhí)行I/O。
  • 過程型函數(shù) (Procedural Functions):對一系列的指令序列進(jìn)行分組。
  • 映射型函數(shù) (Mapping Functions):給定一些輸入,返回對應(yīng)的輸出。

有效的應(yīng)用程序都需要I/O,并且很多程序都遵循一定的程序執(zhí)行順序,這種情況下,程序中的大部分函數(shù)都會是映射型函數(shù):給定一些輸入,返回相應(yīng)的輸出。

每個函數(shù)只做一件事情:如果你的函數(shù)主要用于I/O,就不要在其中混入映射型代碼,反之亦然。嚴(yán)格根據(jù)定義來說,過程型函數(shù)違反了這一指導(dǎo)準(zhǔn)則,同時也違反了另一個指導(dǎo)準(zhǔn)則:避免一連串結(jié)構(gòu)松散,不知所云的代碼。

理想中的函數(shù)是一個簡單的、明確的純函數(shù):

同樣的輸入,總是返回同樣的輸出。

無副作用。

也可以查看,“什么是純函數(shù)?”

2. 刪除不必要的代碼

簡潔的代碼對于軟件而言至關(guān)重要。更多的代碼意味更多的bug隱藏空間。更少的代碼 = 更少的bug隱藏空間 = 更少的bug

簡潔的代碼讀起來更清晰,因為它擁有更高的“信噪比”:閱讀代碼時更容易從較少的語法噪音中篩選出真正有意義的部分??梢哉f,更少的代碼 = 更少的語法噪聲 = 更強(qiáng)的代碼含義信息傳達(dá)

借用《風(fēng)格的元素》這本書里面的一句話就是:簡潔的代碼更健壯。

  1. function secret (message) { 
  2.   return function () { 
  3.     return message; 
  4.   } 
  5. }; 

可以簡化成:

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

對于那些熟悉簡潔箭頭函數(shù)寫法的開發(fā)來說,可讀性更好。它省略了不必要的語法:大括號,function關(guān)鍵字以及return語句。

而簡化前的代碼包含的語法要素對于傳達(dá)代碼意義本身作用并不大。它存在的唯一意義只是讓那些不熟悉ES6語法的開發(fā)者更好的理解代碼。

ES6自2015年已經(jīng)成為語言標(biāo)準(zhǔn),是時候去學(xué)習(xí)它了。

刪除不必要的代碼

有時候,我們試圖為不必要的事物命名。問題是人類的大腦在工作中可用的記憶資源有限,每個名稱都必須作為一個單獨的變量存儲,占據(jù)工作記憶的存儲空間。

由于這個原因,有經(jīng)驗的開發(fā)者會盡可能地刪除不必要的變量。

例如,大多數(shù)情況下,你應(yīng)該省略僅僅用來當(dāng)做返回值的變量。你的函數(shù)名應(yīng)該已經(jīng)說明了關(guān)于函數(shù)返回值的信息。看看下面的:

  1. const getFullName = ({firstName, lastName}) => { 
  2.   const fullName = firstName + ' ' + lastName; 
  3.   return fullName; 
  4. }; 

對比

  1. const getFullName = ({firstName, lastName}) => ( 
  2.   firstName + ' ' + lastName 
  3. ); 

另一個開發(fā)者通常用來減少變量名的做法是,利用函數(shù)組合以及point-free-style。

Point-free-style是一種定義函數(shù)方式,定義成一種與參數(shù)無關(guān)的合成運算。實現(xiàn)point-free風(fēng)格常用的方式包括函數(shù)科里化以及函數(shù)組合。

讓我們來看一個函數(shù)科里化的例子:

  1. const add2 = a => b => a + b; 
  2. // Now we can define a point-free inc() 
  3. // that adds 1 to any number. 
  4. const inc = add2(1); 
  5. inc(3); // 4 

看一下inc()函數(shù)的定義方式。注意,它并未使用function關(guān)鍵字,或者=>語句。add2也沒有列出一系列的參數(shù),因為該函數(shù)不在其內(nèi)部處理一系列的參數(shù),相反,它返回了一個知道如何處理參數(shù)的新函數(shù)。

函數(shù)組合是將一個函數(shù)的輸出作為另一函數(shù)的輸入的過程。 也許你沒有意識到,你一直在使用函數(shù)組合。鏈?zhǔn)秸{(diào)用的代碼基本都是這個模式,比如數(shù)組操作時使用的.map(),Promise 操作時的promise.then()。函數(shù)組合在函數(shù)式語言中也被稱之為高階函數(shù),其基本形式為:f(g(x))。

當(dāng)兩個函數(shù)組合時,無須創(chuàng)建一個變量來保存兩個函數(shù)運行時的中間值。我們來看看函數(shù)組合是怎么減少代碼的:

  1. const g = n => n + 1; 
  2. const f = n => n * 2; 
  3. // 需要操作參數(shù)、并且存儲中間結(jié)果 
  4. const incThenDoublePoints = n => { 
  5.   const incremented = g(n); 
  6.   return f(incremented); 
  7. }; 
  8. incThenDoublePoints(20); // 42 
  9.  
  10. // compose2 - 接受兩個函數(shù)作為參數(shù),直接返回組合 
  11. const compose2 = (f, g) => x => f(g(x)); 
  12. const incThenDoublePointFree = compose2(f, g); 
  13. incThenDoublePointFree(20); // 42 

你可以利用函子(functor)來做同樣的事情。在函子中把參數(shù)封裝成可遍歷的數(shù)組。讓我們利用函子來寫另一個版本的compose2:

  1. const compose2 = (f, g) => x => [x].map(g).map(f).pop(); 
  2. const incThenDoublePointFree = compose2(f, g); 
  3. incThenDoublePointFree(20); // 42 

當(dāng)每次使用promise鏈時,你就是在做這樣的事情。

幾乎每一個函數(shù)式編程類庫都提供至少兩種函數(shù)組合方法:從右到左依次運行的compose();從左到右依次運行的pipe()。

Lodash中的compose()以及flow()分別對應(yīng)這兩個方法。下面是使用pipe 的例子:

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

下面的代碼也做著同樣的事情,但代碼量并未增加太多:

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

如果函數(shù)組合這個名詞聽起來很陌生,你不知道如何使用它,請仔細(xì)想一想:

軟件開發(fā)的本質(zhì)是組合,我們通過組合較小的模塊,方法以及數(shù)據(jù)結(jié)構(gòu)來構(gòu)建應(yīng)用程序。

不難推論,工程師理解函數(shù)和對象組合這一編程技巧就如同搞裝修需要理解鉆孔機(jī)以及氣槍一樣重要。

當(dāng)你利用“命令式”代碼將功能以及中間變量拼湊在一起時,就像瘋狂使用膠帶和膠水將這些部分胡亂粘貼起來一樣,而函數(shù)組合看上去更流暢。

記?。?/p>

  • 用更少的代碼。
  • 用更少的變量。

3. 使用主動語態(tài)

主動語態(tài)比被動語態(tài)更直接,跟有力量,盡量多直接命名事物:

  • myFunction.wasCalled()優(yōu)于myFunction.hasBeenCalled()
  • createUser優(yōu)于User.create()
  • notify()優(yōu)于Notifier.doNotification()

命名布爾返回值時最好直接反應(yīng)其輸出的類型:

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

函數(shù)名采用動詞形式:

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

事件處理

事件處理以及生命周期函數(shù)由于是限定符,比較特殊,就不適用動詞形式這一規(guī)則;相比于“做什么”,它們主要用來表達(dá)“什么時候做”。對于它們,可以“<什么時候去做>,<動作>”這樣命名,朗朗上口。

  • element.onClick(handleClick)優(yōu)于element.click(handleClick)
  • element.onDragStart(handleDragStart)優(yōu)于component.startDrag(handleDragStart)上面兩例的后半部分,它們讀起來更像是正在嘗試去觸發(fā)一個事件,而不是對其作出回應(yīng)。

生命周期函數(shù)

對于組件生命周期函數(shù)(組件更新之前調(diào)用的方法),考慮一下以下的命名:

  • componentWillBeUpdated(doSomething)
  • componentWillUpdate(doSomething)
  • beforeUpdate(doSomething)第一個種我們使用了被動語態(tài)(將要被更新而不是將要更新)。這種方式很口語化,但含義表達(dá)并沒有比其它兩種方式更清晰。

第二種就好多了,但生命周期函數(shù)的重點在于觸發(fā)處理事件。componentWillUpdate(handler)讀起來就好像它將立即觸發(fā)一個處理事件,但這不是我們想要表達(dá)的。我們想說,“在組件更新之前,觸發(fā)事件”。beforeComponentUpdate()能更清楚的表達(dá)這一想法。

進(jìn)一步簡化,因為這些方法都是組件內(nèi)置的。在方法名中加入component是多余的。想一想如果你直接調(diào)用這些方法時:component.componentWillUpdate()。這就好像在說,“吉米吉米在晚餐吃牛排。”你沒有必要聽到同一個對象的名字兩次。顯然,

component.beforeUpdate(doSomething)優(yōu)于component.beforeComponentUpdate(doSomething)

函數(shù)混合是指將方法作為屬性添加到一個對象上面,它們就像裝配流水線給傳進(jìn)來的對象加上某些方法或者屬性。

我喜歡用形容詞來命名函數(shù)混合。你也可以經(jīng)常使用"ing"或者"able"后綴來找到有意義的形容詞。例如:

const duck = composeMixins(flying, quacking);

const box = composeMixins(iterable, mappable)

4.避免一連串結(jié)構(gòu)松散的,不知所云的代碼

開發(fā)人員經(jīng)常將一系列事件串聯(lián)在一個進(jìn)程中:一組松散的、相關(guān)度不高的代碼被設(shè)計依次運行。從而很容易形成“意大利面條”代碼。

這種寫法經(jīng)常被重復(fù)調(diào)用,即使不是嚴(yán)格意義上的重復(fù),也只有細(xì)微的差別。例如,界面不同組件之間幾乎共享相同的核心需求。 其關(guān)注點可以分解成不同生命周期階段,并由單獨的函數(shù)方法進(jìn)行管理。

考慮以下的代碼:

  1. const drawUserProfile = ({ userId }) => { 
  2.   const userData = loadUserData(userId); 
  3.   const dataToDisplay = calculateDisplayData(userData); 
  4.   renderProfileData(dataToDisplay); 
  5. }; 

這個方法做了三件事:獲取數(shù)據(jù),根據(jù)獲取的數(shù)據(jù)計算view的狀態(tài),以及渲染。

在大部分現(xiàn)代前端應(yīng)用中,這些關(guān)注點中的每一個都應(yīng)該考慮分拆開。通過分拆這些關(guān)注點,我們可以輕松地為每個問題提供不同的函數(shù)。

比如,我們可以完全替換渲染器,它不會影響程序的其他部分。例如,React的豐富的自定義渲染器:適用于原生iOS和Android應(yīng)用程序的ReactNative,WebVR的AFrame,用于服務(wù)器端渲染的ReactDOM/Server 等等...

drawUserProfile的另一個問題就是你不能在沒有數(shù)據(jù)的情況下,簡單地計算要展示的數(shù)據(jù)并生成標(biāo)簽。如果數(shù)據(jù)已經(jīng)在其他地方加載過了會怎么樣,就會做很多重復(fù)和浪費的事情。

分拆關(guān)注點也使得它們更容易進(jìn)行測試。我喜歡對我的應(yīng)用程序進(jìn)行單元測試,并在每次修改代碼時查看測試結(jié)果。但是,如果我們將渲染代碼和數(shù)據(jù)加載代碼寫在一起,我不能簡單地將一些假數(shù)據(jù)傳遞給渲染代碼進(jìn)行測試。我必須從端到端測試整個組件。而這個過程中,由于瀏覽器加載,異步I/O請求等等會耗費時間。

上面的drawUserProfile代碼不能從單元測試測試中得到即時反饋。而分拆功能點允許你進(jìn)行單獨的單元測試,得到測試結(jié)果。

上文已經(jīng)已經(jīng)分析出單獨的功能點,我們可以在應(yīng)用程序中提供不同的生命周期鉤子給其調(diào)用。 當(dāng)應(yīng)用程序開始裝載組件時,可以觸發(fā)數(shù)據(jù)加載??梢愿鶕?jù)響應(yīng)視圖狀態(tài)更新來觸發(fā)計算和渲染。

這么做的結(jié)果是軟件的職責(zé)進(jìn)一步明確:每個組件可以復(fù)用相同的結(jié)構(gòu)和生命周期鉤子,并且軟件性能更好。在后續(xù)開發(fā)中,我們不需要重復(fù)相同的事。

5.功能相連的代碼寫在一起

許多框架以及boilerplates規(guī)定了程序文件組織的方法,其中文件按照代碼類別分組。如果你正在構(gòu)建一個小的計算器,獲取一個待辦事宜的app,這樣做是很好的。但是對于較大的項目,通過業(yè)務(wù)功能特性將文件分組在一起是更好的方法。

按代碼類別分組:

  1. ├── components 
  2. │   ├── todos 
  3. │   └── user 
  4. ├── reducers 
  5. │   ├── todos 
  6. │   └── user 
  7. └── tests 
  8.     ├── todos 
  9.     └── user 

按業(yè)務(wù)功能特性分組:

  1. ├── todos 
  2. │   ├── component 
  3. │   ├── reducer 
  4. │   └── test 
  5. └── user 
  6.     ├── component 
  7.     ├── reducer 
  8.     └── test 

當(dāng)你通過功能特性來將文件分組,你可以避免在文件列表上下滾動,查找編輯所需要的文件這種情況。

6.利用判斷true值的方式來編寫代碼

要做出確定的斷言,避免使用溫順、無色、猶豫的語句,必要時使用 not 來否定、拒絕。典型的

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

if 語句

  1. if (err) return reject(err); 
  2.  
  3. // do something 

優(yōu)于

  1. if (!err) { 
  2.   // ... do something 
  3. else { 
  4.   return reject(err); 

三元判斷語句

  1.   [Symbol.iterator]: iterator ? iterator : defaultIterator 

優(yōu)于

  1.   [Symbol.iterator]: (!iterator) ? defaultIterator : iterator  

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

有時候我們只關(guān)心一個變量是否缺失,如果通過判斷true值的方式來命名,我們得用!操作符來否定它。這種情況下使用 "not" 前綴和取反操作符不如使用否定語句直接。

if (missingValue)優(yōu)于if (!hasValue)

if (anonymous)優(yōu)于if (!user)

if (!isEmpty(thing))優(yōu)于if (notDefined(thing))

函數(shù)調(diào)用時,避免用null以及undefined代替某一個參數(shù)

不要在函數(shù)調(diào)用時,傳入undefined或者null作為某個參數(shù)的值。如果某些參數(shù)可以缺失,更推薦傳入一個對象:

  1. const createEvent = ({ 
  2.   title = 'Untitled'
  3.   timeStamp = Date.now(), 
  4.   description = '' 
  5. }) => ({ title, description, timeStamp }); 
  6.  
  7. const birthdayParty = createEvent({ 
  8.   title: 'Birthday Party'
  9.   description: 'Best party ever!' 
  10. }); 

優(yōu)于

  1. const createEvent = ( 
  2.   title = 'Untitled'
  3.   timeStamp = Date.now(), 
  4.   description = '' 
  5. ) => ({ title, description, timeStamp }); 
  6.  
  7. const birthdayParty = createEvent( 
  8.   'Birthday Party'
  9.   undefined, // This was avoidable 
  10.   'Best party ever!'   
  11. ); 

不同的技術(shù)方案利用不同的代碼組織結(jié)構(gòu)來實現(xiàn)

迄今為止,應(yīng)用程序中未解決的問題很少。最終,我們都會一次又一次地做著同樣的事情。當(dāng)這樣的場景發(fā)生時,意味著代碼重構(gòu)的機(jī)會來啦。分辨出類似的部分,然后抽取出能夠支持每個不同部分的公共方法。這正是類庫以及框架為我們做的事情。

UI組件就是一個很好的例子。10 年前,使用 jQuery 寫出把界面更新、應(yīng)用邏輯和數(shù)據(jù)加載混在一起的代碼是再常見不過的。漸漸地,人們開始意識到我們可以將MVC應(yīng)用到客戶端的網(wǎng)頁上面,隨后,人們開始將model與UI更新邏輯分拆。

最終,web應(yīng)用廣泛采用組件化這一方案,這使得我們可以使用JSX或HTML模板來聲明式的對組件進(jìn)行建模。

最終,我們就能用完全相同的方式去表達(dá)所有組件的更新邏輯、生命周期,而不用再寫一堆命令式的代碼

對于熟悉組件的人,很容易看懂每個組件的原理:利用標(biāo)簽來表示UI元素,事件處理器用來觸發(fā)行為,以及用于添加回調(diào)的生命周期鉤子函數(shù),這些鉤子函數(shù)將在必要時運行。

當(dāng)我們對于類似的問題采用類似的模式解決時,熟悉這個解決模式的人很快就能理解代碼是用來做什么的。

結(jié)論:代碼應(yīng)該簡單而不是過于簡單化

盡管在2015,ES6已經(jīng)標(biāo)準(zhǔn)化,但在2017,很多開發(fā)者仍然拒絕使用ES6特性,例如箭頭函數(shù),隱式return,rest以及spread操作符等等。利用自己熟悉的方式編寫代碼其實是一個幌子,這個說法是錯誤的。只有不斷嘗試,才能夠漸漸熟悉,熟悉之后,你會發(fā)現(xiàn)簡潔的ES6特性明顯優(yōu)于ES5:與語法結(jié)構(gòu)偏重的ES5相比,簡潔的es6的代碼很簡單。

代碼應(yīng)該簡單,而不是過于簡單化。

簡潔的代碼有以下優(yōu)勢:

  • 更少的bug可能性
  • 更容易去debug

但也有如下弊端:

  • 修復(fù)bug的成本更高
  • 有可能引用更多的bug
  • 打斷了正常開發(fā)的流程

簡潔的代碼同樣:

  • 更易寫
  • 更易讀
  • 更好去維護(hù)

清楚自己的目標(biāo),不要毫無頭緒。毫無頭緒只會浪費時間以及精力。投入精力去訓(xùn)練,讓自己熟悉,去學(xué)習(xí)更好的編程方式,以及更有更有活力的代碼風(fēng)格。

代碼應(yīng)該簡單,而不是簡單化。 

責(zé)任編輯:龐桂玉 來源: 程序源
相關(guān)推薦

2017-01-12 14:55:50

JavaScript編程

2013-06-06 10:10:59

項目項目代碼代碼風(fēng)格

2020-07-26 00:40:48

JavaScript開發(fā)代碼

2011-03-15 13:45:49

JavaScript后續(xù)傳遞

2020-09-16 06:16:55

代碼編碼開發(fā)

2022-11-28 08:15:14

Go語言代碼

2022-12-05 09:32:29

Go 語言風(fēng)格規(guī)范

2021-05-06 11:04:55

GooglePython代碼

2024-06-03 08:55:27

團(tuán)隊代碼工具

2024-10-08 05:00:00

PEP 8編碼Python

2020-11-20 10:22:34

代碼規(guī)范設(shè)計

2012-07-30 13:15:18

代碼

2025-01-06 08:00:00

Python代碼編程

2009-07-01 14:31:01

JavaScript異

2023-08-14 14:04:14

JavaScript函數(shù)式編程

2009-02-01 14:34:26

PythonUnix管道風(fēng)格

2025-01-26 08:30:00

Python代碼編程

2021-02-19 23:55:15

PythonPythonic數(shù)據(jù)

2009-06-16 09:50:16

JS.Class 2.

2024-11-12 12:52:39

Python代碼函數(shù)
點贊
收藏

51CTO技術(shù)棧公眾號