今天在群裡有人討論方老師的文章《我不是很懂 Node.js 社群的 DRY 文化》,我也看了一遍,槽點太多,不知道如何下筆。
方老師分析了幾個依賴最多的 npm 包,每個都只有不到百行程式碼。
比如 is-odd,每週下載 300 萬次,但是隻有核心 5 行程式碼。而且依賴了每週下載 1000 萬次的 is-number 庫。
得出了一個結論:
- 原來有這麼多 JS 程式設計師不會判斷奇數
- 只要 markdown 寫得漂亮,就能迷倒 JS 程式設計師
1 + `1`
的問題一直在困擾 JS 程式設計師,我要不要寫一個add()
庫解決這個問題呢
首先第一條:
原來有這麼多 JS 程式設計師不會判斷奇數。
其實不僅僅是 JS 程式設計師,大部分程式設計師都不會準確的判斷奇數。
你寫
const isOdd = x => x % 2 === 1;
這是小學的知識,除以 2,如果除不盡(有餘數)那麼就是奇數。正因為知識點很簡單,所以給人一種隨便一個程式設計師都會判斷的錯覺。
現在我們假設使用者傳入的引數一定是數字。
即便如此,這個函式依然不能正確判斷奇數。因為 -3 % 2
的結果是 -1
。
有人說那就這麼寫:
const isOdd = x => x % 2 !== 0;
隨便一個小數就被判斷為奇數了。更不用說浮點數中的妖怪 NaN
和 Infinity
了。
那麼是不是對 NaN
和 Infinity
直接返回 falst,然後把 -1
的判斷也加上去就行了:
const isOdd = x => x % 2 === 1 || x % 2 === -1;
也是圖樣
9007199254740991 % 2 === 1
9007199254740992 % 2 === 0
9007199254740993 % 2 === 0
9007199254740994 % 2 === 0
9007199254740995 % 2 === 0
// 後面的都是 0
為什麼從 9007199254740991
開始呢?因為這個值是 Number.MAX_SAFE_INTEGER
,是 2 ** 53 - 1
。
那回過頭來看看 is-odd 庫是怎麼實現的呢?
!!(~~i & 1)
~~i
用於把字串轉換為整數,和 1
進行按位與運算判斷最後一位是 1
還是 0
。
很遺憾,也有問題。? 因為在字串轉整數的時候精度就丟失了。
如果有誰想造輪子,可以寫一個 better-is-odd,可以把字串 `9007199254740995`
判斷為奇數,但是對於數字 9007199254740995
也是無能為力。等著 proposal-bigint 提案吧。
不僅僅是判斷奇數,單純的判斷一個字串是不是數字就可以難倒一大片 JS 程式設計師(其它語言程式設計師也一樣)。
is-number 庫核心程式碼不到 10 行。方老師只關注了庫的原始碼,但是我們如果看一看他的 test case,就決定要使用這個庫了。
作者為這 10 行程式碼寫了 108 行的測試用例,來保證這個函式的功能是正確的。
我在之前的文章百行程式碼,千行測試裡面曾寫過:
不要重複發明輪子。
很多大牛推薦我們“造輪子”,但是造輪子的目的是為了學習,而不是使用,尤其不要用在生產環境。
造個輪子很簡單,但是你非要把自己的輪子安在汽車上,開上路,那肯定是一個安全隱患。
有很多人會說,“既然自己可以寫一個,為什麼非要用別人的?” 還有人覺得,有些非常小的功能不需要使用別人的。
很多人還會藉此吐槽 leftpad 模組,但是平心而論,你自己能徒手這一個沒有 bug 且高效能的 leftpad 函式嗎?
前幾天我們專案組就遇到了一次,其實功能很簡單,一個頁面分享出去,並使用 url 攜帶引數。比如:
aaa.html?id=123456
看似很簡單的一個需求,但是真正自己寫一個卻不簡單。
- 查詢“=”字元,然後擷取後面的?
- split(“=”),然後去第二個
- ……
不到 10 行程式碼就寫完了。
第一次分享到微信是正常,把分享出去的頁面再次轉發分享,頁面錯誤。
因為微信會在 URL 後面新增一些額外的引數,同樣,不同的平臺都會有不同形式的新增引數方式,有的加
&
,有的加#
,不論加什麼都會導致解析的失敗。歸根結底是我們寫的解析函式有 bug,我們重新造了一個有 bug 的輪子。
解決方式就是:
npm i qs
麻雀雖小,五臟俱全。看看 github 原始碼,“百行程式碼,千行測試”。絕對比自己寫的程式碼靠譜。
我寫這篇文章不是為了推薦這個 qs 庫,而是告訴大家不要重複造輪子用在生產環境,平時大家多造輪子用來學習。
在回過頭來看看 is-number 庫,不僅僅有 100 多行的 test case,還有一個目錄 benchmark。這裡面的程式碼我沒有數,但是光看檔案數量就有 10 個以上。也就是說作者不僅僅保證了這個函式的執行結果沒有問題,更保證了這個函式的效能。
我們為什麼要使用這個庫,因為作者為了他的 10 行程式碼,寫了幾百行的其它程式碼來保證質量。
作者 9 天前還發布了新版,20 天前還優化了字串轉數字的效能。
再看看方老師說的第二條:
只要 markdown 寫得漂亮,就能迷倒 JS 程式設計師。
這些包的 markdown 程式碼遠遠多於 JS 程式碼,可能它們的 markdown 更值得我們學習
Redux 號稱百行程式碼,千行文件,一共就匯出了 5 個函式。
而且 markdown 寫的漂亮也是很有必要的,否則你不知道下面的程式碼到底輸出什麼
isOdd(` 12`)
isOdd(`一`)
isOdd(`①`)
isOdd(`Odd`)
第三條:
1 + `1`
的問題一直在困擾 JS 程式設計師,我要不要寫一個add()
庫解決這個問題呢
不能。
我是認真的!因為 npm 已經有一個 add
庫了,名字被別人佔用了,所以你只能叫別的名字了。
雖然是一個小眾的庫,但是每週也有近一萬的下載量。這個庫實現了 JavaScript 中的浮點數加法的 Rump-Ogita-Oishi 演算法。
比如有如下浮點數:
const nums = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7]
把這些數累加
nums.reduce((a,b) => a+b);
結果是:
15.299999999999999
而使用 Rump-Ogita-Oishi 演算法:
add(nums) === 15.3
再看看 benchmark (OS X 10.9.4, 2 GHz Core i7, 8GB DDR3 1600Mhz RAM):
add-precise x 1,400,712 ops/sec ±3.31% (89 runs sampled)
add-dumb x 24,268,034 ops/sec ±3.96% (80 runs sampled)
native x 94,957,251 ops/sec ±2.94% (85 runs sampled)
native is ~67.8 times faster than add-precise
最後再重申一般:Don`t Repeat Yourself。