一個前端工程師看完《程式碼大全》後的二三總結

原始碼終結者發表於2019-03-04

題記: 書中自有黃金屋,書中自有顏如玉。 就好像 one piece ,等待著我們去發現,找到。不是所有的地方都有 one piece (書的每一章每一頁),也許在某處,有一段耐人尋味的話,等待著我們去發現。一些人可能錯過了,一些人可能找到了,這也許就是讀書的魅力吧。

寫在最前

讀了很多技術方面的書籍,這本 《程式碼大全》 值得我去為它寫一篇總結,這也是我在掘金寫的第一篇書籍讀後感,我非常提倡多去閱讀書籍。

image

因為,我個人覺得,每一個出書的人,在撰寫書的過程中,都投入了很多很多的時間和精力,他們會盡可能的把自己收穫的經驗、總結等都以儘可能的通俗易懂的方式在書中表達出來,這是一種本能的行為,就好像在遊戲中達成一個成就,出一本好書,可以說是每個出書人的初衷。

所以我給那些出書的人都點個贊吧,也許書中有很多地方我們覺得是照搬啥的,沒有營養啥的。但是以我的經驗來說,只要是公認的好書,基本上在某一章,甚至某一段,一定會帶給你柳暗花明或者說是茅塞頓開的驚喜感。就好比玩拳皇,一頓搖把加狂按,突然放出大招時的無以言表的驚喜和開心。。。嗯~~(從來沒按出來過)&¥&¥#。。。。。

開始我的表演

Just enough, 其他讚美的話就不多說了(讚美好書的詞語省略800字),下面準備步入正軌了。程式碼大全這本書,我很久以前就買了,當時還沒入圈?,看了一部分,也做了一些筆記,寫在了小本本上,自從入圈以來,就沒有看過了,最近幾天以前端工程師的身份重新完整的過了一遍,發現了新大陸,找到了幾處one piece。對溫故而知新的理念不能再贊同了,在此把我的一些總結和收穫分享出來,就像此書的一個讚譽那樣寫到:

每個程式設計師都改讀讀這本傑出的書籍。

趕快開始吧。。。

《程式碼大全》總共有944頁內容,很實在,裡面涉及到的知識點很多,主體程式碼是以Java和C++為主。程式碼形式其實都不重要,重要的是要表達的思想。對於前端工程師來說,我總結了以下幾個方面的知識,開始吧。

條件語句switch用對了麼

很多時候,我們在用switch語句的時候,對如何使用default,並沒有一個明確的原則,有時候default下,什麼都不執行,有時候default下,會去執行最後一種可能。還有有時候case的情況很多,我們並沒有在意排序問題。等等其他,其實這在程式設計中都是一種不好的行為。那麼來,怎樣才是正確編寫switch語句的姿勢呢?請看如下:

tip1——讓default幹它該乾的事情

原則上,default下,不要寫最後一種可能,因為default設計出來的目的,其實是為了捕捉錯誤的。所以業務中出現的所有情況都應該用case去捕捉,這也體現了使用case後面的label去說明這是哪種情況的作用。而default後面無法加label說明,也就失去了對可能情況的說明。舉個例子,在前端中,如果對請求返回的狀態碼進行判斷,可以寫成如下形式:

switch(code) {
  case: `1`:
    console.log(`成功`)
    break
  case: `0`:
    console.log(`失敗`)
    break
  default:
    dealError()
}
複製程式碼

從上面程式碼可以看出,default不要做任何有正常case的操作,它專門用來和檢測和處理錯誤,當然如果業務中把某些case都當成錯誤的話,也可以統一寫到default中。

tip2-讓case變的簡潔、有序和高效

1: 如果case的情況很多,那就要按照出現頻率去從上到下排列,這樣switch語句程式碼的整體效率會提高。

2: 如果case的一個情況,語句太多,那就果斷封裝成子程式來呼叫。避免在一個case下寫很多語句,讓case變得難以理解。

如何判斷程式碼的複雜度

大家可能會本能的想到大O方法啥的,對前端來說,大O那種模式不適用,大O是衡量演算法的複雜度。全全裡面提到了一個很有趣的判斷複雜度的方法,叫做 Tom McCabe 方法,該方法很簡單,通過計算函式中 決策點 的數量來衡量複雜度。下面是一種計算決策點(結合前端)的方法:

  1. 1開始,一直往下通過函式
  2. 一旦遇到if while for else或者帶有迴圈的高階函式,比如forEach map等就加1
  3. 給case語句中的每一種情況都加1

比如下面程式碼:

function fun(arr, n) {
  let len = arr.length
  for (let i = 0; i < len; i++) {
    if (arr[i] == n) {
        // todo...
    } else {
        // todo...
    }
  }
}
複製程式碼

按照Tom McCabe方法來計算複雜度,那麼這個fun函式的決策點數量是 3 。知道決策點數量後,怎麼知道其度量結果呢?這裡有一個數值區間判斷:

數量區間 度量結果
0-5 這個函式可能還不錯
6-10 得想辦法簡化這個函式了
10+ 把這個函式的某一部分拆分成另一個函式並呼叫他

從上面的判斷依據可以看出,我上面寫的程式碼,數量是3,可以稱為函式還不錯。我個人覺得這個判斷方法還是很科學和簡單的,可以作為平時寫程式碼時判斷函式需不需要重構的一個考慮點,畢竟一個函式,一眼掃去,決策點數量大致就知道是多少了,很好計算。

你心中的抽象和封裝以及模組是什麼?

怎麼說呢,其實有些時候,我們不知道,不代表我們不會,很多時候是這種情況:我真的看過這方面的知識或者我確實真的研究過這些,但是確實是忘了?。所以此刻,你看到這裡的時候,就贊贊收藏吧(嘻嘻)。

我覺得這種偏概念的東西,每個人都可以回答的不同,但是隻要你真正理解了其中的本質或者說是一種答案,其實這就夠了。這裡我說一下我自己對抽象和封裝以及模組的看法。

抽象在我眼裡,其實是把混亂變成有序,把零散變成整體。我們經常說,要把業務程式碼抽象成元件,做成元件化。其實就是把零散變成整體,把混亂變成有序的過程。舉個荔枝:

比如說我們專案中還沒有做元件化,彈窗這個功能,還是各種頁面裡寫自己的彈窗程式碼。現在我們需要重構,把這個零散和混亂的彈窗,抽象成一個整體有序的彈窗元件。那麼,我們來看看原來的彈窗程式碼,發現以下問題:

  1. 程式碼重複,同時零散分佈在各個頁面中。
  2. 程式碼混亂,沒有統一的入口,沒有統一的顯示邏輯。

如何解決呢,這個時候就需要對彈窗進行抽象了,如何抽象,一個辦法,就是解決掉重複和混亂,如果解決掉了,就可以表示抽象已經初見成效了。

這裡提一下ADT,這是面對物件的程式設計模式中的類的基礎,叫 Abstract Data Type ,也就是抽象資料型別,但是我不想用這個,這裡我借鑑DOMBOM形式,我把它叫做 ADM。 全稱:Abstract Data Model 也就是抽象資料模型。不知道可不可以這樣玩,但是我覺得問題不大,嘻嘻。沒有意見,那下面我繼續操作了。

我們需要把彈窗可能用到的資料全部收集起來,然後將這些資料歸屬於一個物件模型,為了形象比喻,我把它叫做彈窗人,擁有生命。這個彈窗人的實體是由這些資料組成的,OK,這樣就把之前零散的資料全部集合在一個實體上了,也就是把零散變成整體。請看下面這句話:

沒有封裝時,抽象往往很容易打破。

如果我只是抽象了,把零散變成整體了,但是卻沒有把混亂變成有序,那麼抽象就會被打破,就好比彈窗人擁有的資料,這裡我花式比喻一下。比如彈窗人擁有100萬,關鍵100萬還是公開的,結果悲劇了。

很多人都想向他借錢,如果彈窗人事先不指定好借錢的方式。那麼來就會很刺激了,朋友A微信問他借錢,朋友B支付寶向他收款,社會混子直接電話向他威脅借錢,更有牛逼的,直接強行把他綁架了,然後刀光劍影伺候。彈窗人心裡苦啊,難受。

終於彈窗人大悟了,事先指定了借錢的方式,只能先通過簡訊,其他借錢方式一律拒絕,並且要經過他的同意,同時堅決不公開自己的財產。從此以後,沒有人知道彈窗人有多少錢,還以為是個窮逼(彈窗人是程式設計師),就算想借錢,也只能通過簡訊告訴彈窗人我想借多少錢,然後同意後,才借給你。從此,彈窗人過上了幸福快樂的生活了。

上面是即興寫出來的栗子。忽然發現我還挺有想象力的。給自己 0110 0110 0110,其實你讀完大概就知道我想表達什麼思想了。這就是封裝的目的,不僅是前端領域封裝的目的,同時也是所有面對物件領域封裝的目的。如果有例外,,捂嘴,,沒有。。

從上面我們可以看出,抽象和封裝存在密不可分的聯絡。仔細去體味上面的故事,你會發現我們在讓混亂變成有序的的時候,其實是在讓可訪問性變得儘可能的低,也就是封裝本身的原則:

封裝的原則是讓可訪問性儘可能的低。

很多人雖然知道封裝是為了降低可訪問性,但是卻不知道為什麼,可能也知道為什麼吧?這裡中斷一下,跳個番外。

在貴(程式設計師)圈裡有個很好玩的現象,那就是我知道某一個知識的目的,但是卻說不清原理,或者說我也查過,試圖去了解這個原理,但是總是會有一種朦朦朧朧的不懂感圍繞著自己。然後心裡 ob :先就這樣吧,後面再說?。然後就不能透徹的理解。我咋知道這些的,因為我有時也有這種感覺,感覺是無法避免的。能做的就是在日後別忘了把當時朦朦朧朧的困惑解決掉。

中斷結束,繼續胡謅?,我有多少錢你不能知道,知道的話,我的錢就會變得不安全。你只能通過固定的渠道來訪問我,還不能直接訪問資料,需要經過我的同意,才能進行相應的操作。其實可訪問性這個已經解釋的很透徹了。好了,不解釋了。


那麼前端的模組化是什麼意識呢 ?

其實完全可以理解為元件化。模組化,顧名思義想表達的就是想要整體的意識,既然整體,那就要封裝,封裝前就需要進行抽象,然後又迴歸本質。那麼目前前端是怎樣實現模組化的呢?

JavaScript 是以物件導向為基礎的程式語言,至少有句話說的是:在JavaScript中,一切皆物件,其實可以這麼說,但是JavaScript有個很尷尬的一點是,沒有類,雖然現在有類,但也只是一種語法糖。這裡不考慮TypeScript。那它怎麼實現模組化呢?

實現程式語言中的類的抽象模型,也就是我上面說的ADM。在我看來,在不使用class語法糖的情況下,前端的模組化的實現原理就是通過使用JavaScript的私有變數特性和閉包特性來實現模組化。ES5的時候,JS是沒有塊級作用域的,只有全域性變數和私有變數,如果連私有變數也沒有的話,那JavaScrript也就GG了,所以我很好奇當初設計JS的時候,都採用了類C的語言設計風格,為什麼沒有實現塊級作用域呢,是不是當時覺得JS並不需要這麼多功能啊?。既然有私有變數,那麼就會有私有屬性和私有方法,畢竟本質上都是變數。具體程式碼我就不貼了,寫個虛擬碼吧:

function fun() {
    v1 是私有屬性
    f1 是私有方法
    // 返回一個物件
    return {
      v2 是共有屬性
      f2 是共有方法 {
          共有方法裡面可以訪問和修改 私有屬性和私有方法
          處理 v1
          處理 f1
      }    
    }
}
複製程式碼

如何編寫高質量的函式

全全(指程式碼大全,下同)裡寫的是如何編寫高質量的子程式,其實JS函式就是子程式(99%正確)。全全裡說的高質量的一些總結有些不適用JS。 這裡我融合一下自己的一些經驗和總結。請往下看:

有沒有想過為什麼要發明函式這個東西?

作為前端工程師,對於這個問題,還是10分重視的。頭偏向45度方向,在腦袋中想個10秒鐘,可能還會眨幾下眼睛,不要問我為什麼知道你的狀態,因為XX。其實大家都能說出一些自己的看法,不需要答案,但是我還是想給一個看起來逼格很高的answer。那就是:

函式是迄今為止發明出來的用於節約空間和提高效能的最重要的手段。 注意,沒有之一。那麼我們現在可以確定的是: 它是用來節約空間和提高效能的。 這樣的話,如果我們不去優化函式,寫出高質量的函式,豈不是失去了它存在的意義。現在面臨的最關鍵的問題就是:如何去優化函式,寫出高質量的函式?

if (str === `hello world`) {
    console.log(`hello world`)
} else {
    console.log(`666`)
}
複製程式碼

上面是一段程式碼,如果在其他地方要用,就必須在用的地方把上面這段程式碼原封不動的重複編寫一遍,但是有了函式,就可以將其放到函式內,然後通過函式print(str)來呼叫,在其他地方只需要寫這一行就可以了,優點很明顯。

如何寫出高質量的函式。其實就這一點就可以寫一篇文章了,這裡我不詳細說明闡述了,隨便提幾點吧,高質量的具體實現以後再說?。

從娃娃抓起,起一個好名字很重要

不能輸出起跑線上,考慮的點有以下幾點:

  1. 名字要能清晰的描述此函式的目的
  2. 統一好書寫形式。比如統一使用下劃線、駝峰等命名形式
  3. 統一好單片語成形式,是名詞+動詞,還是動詞+名詞

優雅的傳遞和處理引數

  1. 引數較多,可以使用引數物件模式
  2. 引數不固定,比如動態引數,可以使用ES6的擴充套件符
  3. 使用ES6的引數預設值來處理引數預設取值問題

設計函式時的一些考慮

  1. 避免函式程式碼重複
  2. 縮小函式程式碼規模
  3. 增加函式魯棒性
  4. 註釋風格要統一
  5. 儘可能的保持pure特性,也就是冪等性,這樣可以最大限度的防止後期的各種莫名其妙的bug

關於跨度和生存時間

變數有一個自己的屬性,叫存活時間,在程式語言中,要儘可能的縮短變數的存活時間。這樣的話,如果我們用跨度和生存時間的概念來看全域性變數,就會發現為什麼要避免使用全域性變數,因為全域性變數的跨度和生存時間都很長。

彩蛋:JS 為什麼要避免全域性變數,其實一個主要原因是 JS 的程式碼執行,是由裡向外的,如果是全域性變數的話,那麼這個查詢的時間就比較長,所以在 JQuery 等原始碼中,直接將 window 當成引數傳到函式中,是有它的道理的,可以提高程式碼效能。

關於布林表示式的一個鮮為人知的點

看如下程式碼

let i = true 
if (i = true) {
  // todo...
}
複製程式碼

可以看到,如果你在業務程式碼中,不小心少寫=號,變成上面這段程式碼,那麼它是不會報錯的,但是要是寫成這種形式:

let i = true
if (true = i) {
  // todo...
}
複製程式碼

就會報錯了,因為如果表示式左側不是變數的話,直譯器會報錯。所以在JS程式設計中,可以把常量放在等號左側。這是一個很小眾的 tip,而且在前端來說,給專案加個 eslint 就可以完全避免這種錯誤了,如果沒有寫 eslint,可以考慮一下這個 tip,如果用了程式碼檢查工具,那就當下酒菜吧。

來點佛學的知識

如何修改bug、程式碼缺陷

這個。。。五彩臉?對於及時儲存原始碼等這種建議就不提了,畢竟 git 操作穩的一比,提一下其他方面的雞湯吧:

  1. 放鬆一下:嗯。。。。。。
  2. 修改程式碼時一定要有恰當的理由。理由要充分,有理有據。
  3. 一次只做一個改動

備註

  1. 設計模式這個就不總結了,東西很多,需要拿出來單獨說。
  2. 一些系統級的總結也不說了,和前端關係不大。
  3. 對於遞迴的注意事項也不說了,前端用到遞迴的情況不多。
  4. 對於虛擬碼程式設計,以後單獨提
  5. 對於編寫高質量的函式,總結的簡單,因為涉及到的東西很多,這裡我更想提的是最本質的一些知識,比如被創造出來的目的,有時候你寫了很多函式,都沒有想過這個問題,現在看到了,心中會有種豁然開朗的感覺吧。

文末的可愛宣告: 如果轉發或者引用,請貼上原連結。本文就好比一本書,可能有一些地方對於你來說是沒有營養,但是隻要有那麼一段,或者一句讓你感到驚喜的地方,我就很開心了。文章可能有一些錯誤,歡迎評論指出,也歡迎一起討論。文章可能寫的不夠好,還請多多包涵。多一點貢獻,多一分開心~

相關文章