在開發程式的時候,有時候我們會寫出一些連自己都不知道在寫什麼的程式,特別是如果自己都不知道自己在寫什麼的話,更不用說和你一起協作開發的夥伴了,因此下面將會帶大家了解Side-effects與非預期的Side-effects是什麼。

Pure function

Pure function就像他本身的意思一樣,表示乾淨的函數。你可以很清楚且直覺地知道這個function做什麼事情,通常Pure function的輸入和輸出的參數都會是一致的,並且可以預期輸出的結果會長什麼樣子。

舉例

以下的函數可以視為Pure function,用來產生身分證字好的function:

1
2
3
4
5
6
generateId = (id) => {
// 初次換發
const idStamp = '01';
const formattedId = `TW-${id}-${idStamp}`;
return formattedId;
}

但是以下並非是Pure function,你知道為什麼嗎?

1
2
3
4
5
6
generateId = (id) => {
// 換發時間
const idStamp = Math.random().toString();
const formattedId = `TW-${id}-${idStamp}`;
return formattedId;
}

關鍵在於你能不能輕鬆得知格式一定長什麼樣子
例如:

1
const myId = generateId(‘1000400321’);

如果你使用第一個function,你可以用想的馬上知道他會變成TW-1000400321-01
如果你是用第二個function,因為是使用亂入產生換發時間,所以你無法確定他最後輸出的模樣。
因此,第一個function可以視為一種Pure function。
但是,這不代表第二個function這樣寫就一定不好,只是如果讓程式能夠盡量Pure,會讓開發的效率提升。

試著讓你的function Pure

以上的例子可以歸類出一些特點:

  1. 相同的Input,通常會有相似的輸出
  2. 沒有Side effects

在開發程式的時候,盡量讓程式的Side effects降到最低,這個時候就來到重點啦~什麼是Side effects?

認識Side effects

讓我們先來看以下的程式,createUser產生一個使用者並且回傳他,是一個標準的Pure function:

1
2
3
4
createUser = (account, password) => {
const user = new User(account, password);
return user;
}

但是一旦我加入一行程式之後,一切就會大不同了:

1
2
3
4
5
createUser = (account, password) => {
const user = new User(account, password);
excuteSession(user);
return user;
}

一旦excuteSession被執行,這個動作很有可能會改變程式的狀態,甚至是整個專案。
excuteSession並非僅僅是在這個function執行動作而已,他很有可能會帶動整個程式巨量的改變,例如:改變外部的變數。
僅管這個function輸入和輸出都是一致,可預期輸出的值是什麼,但是因為會造成Side effects,因此不能視為一種Pure function。

Side effects好不好

以上面的例子來看,也許你會認為Side effects並不是一件好事?
但是並非如此,這樣看你所開發的專案或是程式是什麼而定。

Side effects無所不在

有很多Side effects的程式是必須執行的,例如:啟動session, 傳送http請求, 登入系統, 顯示訊息……
這些都是常見經典的Side effects,因為改變了使用者使用這個系統的狀態。
因此Side effects無所不在,甚至在你目前所持有的專案幾乎每個都有Side effects。
講到這裡看起來…Side effects好像也沒有想像中的那麼糟?
那麼…到底需要注意哪部分呢?

要注意的地方

Side effects比較不好的地方,其實是無法預期的Side effects
以createUser的例子為例,你其實很難從createUser看出excuteSession主要是在做什麼,例如:可能會存取資料庫。
一旦createUser的程式大起來,事實上很難理解執行了createUser,到底會改變什麼。
以下再來看一個經典的例子:

1
2
3
4
5
6
7
8
let lastGeneratedId = null;
generateId = (id) => {
// 初次換發
const idStamp = '01';
const formattedId = `TW-${id}-${idStamp}`;
lastGeneratedId = formattedId;
return formattedId;
}

從這個例子可以看出function並非單純的執行generateId,也會改寫外部的變數。

避免非預期的Side effects

要避免無法預期的Side effects其實不難,通常只要盡量保持你的程式clean code,注意與制定協作的naming convention,就能盡量預期Side effects會造成什麼變化。

我們來做個小測驗吧!
(一)多選題 100%

  1. 以下哪些function會發生Side effects?
    A.saveUser(…) B.isValid(…) C.showMessage(…) D.createUser(…)

答案是ACD,你100分了嗎?

處理Side effects

以上的重點為:盡量讓你的專案不要出現非預期的Side effects
如果你的專案必須或需要Side effects的話
應該盡量:

  1. 從命名展示出可能發生Side effects
  2. 將會發生Side effects的部分,移動到其他function或是其他片段

結論

clean code原則可以讓你在開發程式時,減少出錯的時機,你也會發現clean code並沒有絕對的好或是絕對得不好,只有建議怎麼樣會比較好。因為clean code理論上雖然可以做到,但實際開發會遇到種種狀況導致無法如預期上的那樣完美,例如:程式中出現無法馬上移除,且很重要的程式片段。