在先前的章節中,我們花了很多的篇幅來介紹純函式與副作用(Side Effects)的定義,這對我們來了解 Impure Function 會非常有幫助!
那什麼是 Impure Function 呢?
與純函式不同,Impure Function 會在執行的過程中產生副作用,或是我們在函式中使用了 Mutable Data(例如帶有自己的狀態),帶來預期之外值的改變。
舉先前計算水果庫存的範例來說:
const appleAmount = 10;
const orangeAmount = 20
let appleStockAmount;
let orangeStockAmount;
function calcFruitStockAmount () {
appleStockAmount = appleAmount * 0.9;
orangeStockAmount = orangeAmount * 0.9;
}
calcFruitStockAmount();
// appleStockAmount -> 9
// orangeStockAmount -> 18
當我們在執行 calcFruitStockAmount
的同時,不論今天我們怎去執行這個韓式,appleStockAmount
與 orangeStockAmount
變數一定會被同時改變,即便我們不一定同時要計算兩者的庫存量,這就是個典型的 Impure Function。
經過先前的討論,我們也會發現,如果想要避免副作用的產生,我們就必須得搭配 return
關鍵字,並且將函式看作一種「值」。
在我剛開始學習寫程式的時候,我其實搞不是很懂所謂「函式是用來解決重複性問題」的定義,
以及我要怎麼判斷何處是被重複的,哪邊又該獨立出來。
於是我常常會把一整個流程寫進同一個函式中,導致我在無意識的狀況下幫自己寫了不少 Bug,舉例來說:
async function getData() {
const a = await fetchA();
const b = await fetchB();
}
// fetchA 與 fetchB 為 HTTP request 的簡化函式
某個函式是專門處理 A API 的 fetchA()
函式,用來取得 a
資料,但因為可能某個流程中同時會用到這兩隻 API ,於是我為了偷懶,同時在 getData
這個函式中又呼叫了 B API 。
導致未來有元件要獨自呼叫 A API 時,或是獨立呼叫 B API 時,我依然要額外將 fetchA()
及 fetchB()
各自分離出去,不然就會在不需要 B API 的狀況下呼叫它了。
在還沒有了解 Pure Function 設計理念前,我很常寫出一些 Impure 的程式碼,導致在開發的過程中,要不斷地回頭修正與翻新共用元件,這聽起來很不理想吧?
每當討論到 Impure Function 時,總會覺得撰寫出 Impure 的函式好像很不應該、是一種負面的意思,但其實我們可以把 Impure Function 當成一種行程( Procedure),一種被觸發後會完成指定任務的程式碼片段,這樣的程式撰寫模式所產出的 Output 並不會跟 Input 有所關聯。
還記得在聊 JavaScript 核心概念時,我們曾經特別聊過的表達式與陳述式的概念嗎?
其實 Pure 與 Impure 的差異,就如同表達式與陳述式的差異,我們很難在程式碼中完全不使用或是避開陳述式,而是在我們所需要的地方使用「適合」的工具。
因此 Impure Function 也是一種撰寫程式碼的風格,只是這樣的風格在原生的 JavaScript 並不好維護與控制其預期的產出。
那我們要將所有的函式都修正成純函式嗎?
除非我們想要將所有的程式碼、套件引入 Pure 的概念,不然依照網頁開發歷史悠久的包袱來說,很多套件都還是採用 OOP ,光是撰寫風格就很不同了,如果我們真的有必要引入這類型的套件,卻在擔心自己的程式碼不夠「Pure」,就不太必要。
雖然這系列的內容是在聊 FP ,也提倡大家可以使用 FP 及純函式,但我認為知其然而知其所以然會比「瞎用」來的重要得多。
理解程式碼為什麼要避開 Impure Function ,我們才能撰寫出好的純函式,因此我認為了解 Impure Function 的特性是非常重要的。
如果這幾章節消化下來,還是覺得 Pure 與 Impure 的概念有些空泛,這時候我們是時候來看看一些設計理論了!
在下一個章節中,我們會透過 Pure 與 Impure 的概念來看看所謂的宣告式程式設計與命令式程式設計,這些常常在程式書籍中出現,但我們卻不知道它們在幹嘛的「酷東西」,那麼我們就下一章節見啦!
TechPedia:What Does Procedure Mean?