今天的目標是跟大家分享「通過昨天測試的程式以及背後的觀念」,在開始看程式碼前我們先來了解一下背景知識吧~
我們總是會遇到各種例外、錯誤、副作用。如何控制這些討人厭的副作用呢? 在接觸 functional programming 之前我可能會用這些方法
可是上面三種處理方式,尤其是以 Javascript 這個語言來說,都有缺點。使用 throw 語法很危險,因為 javascript 不強制你處理錯誤,甚至也不提示你 function 可能拋出錯誤,因此一個不小心忘了 try catch 就可能造成災難性後果。即使使用 try catch 或是 if else 也沒有比較好,因為可能讓程式碼變的很難讀。
錯誤處理就是一件事
在重構或程式碼可讀性的概念中,有個共同特性,就是函式(方法)應該只做一件事,避免函式中的程式碼陷入邏輯泥塊(Logical clump)。
--- 良葛格 重構錯誤處理程式
所以,我們到底該怎麼做錯誤處理呢?
軌道導向程式設計(Railway Oriented Programming) 提供了非常棒的錯誤處理解決方案。
type Either<Error1, string> = Left<Error1> | Right<string>
type rail1 = (input:string)=> Either<Error1, string>
type rail2 = (input:string)=> Either<Error2, string>
type rail3 = (input:string)=> Either<Error3, string>
離開
或是 正確
Left
跟 Right
都是帶有 tag
的,因此合起來就變成了一個 sum type
- Either
有時候
Either
會換成Result
、Left
換成fail
、Right
換成success
,但不論叫甚麼名字,它們背後概念都是一樣的。
sum type
定義請參考 D04 - 設計資料模型
原始碼請參考 D06/unit-test
現在我們知道 Either
長甚麼樣了,而且很幸運的,effect
也有提供 Either
工具,所以我們就可以很簡單的用三個 function 來做長度上限、長度下限、字元類型的驗證。
import { Either } from 'effect'
const validateMinLen =
(minLen: number) =>
(input: string): Either.Either<InvalidTextInput, string> =>
input.length < minLen
? Either.left(
ofInvalid({
value: input,
reason: 'course name length should not be empty',
})
)
: Either.right(input)
const validateMaxLen =
(maxLen: number) =>
(input: string): Either.Either<InvalidTextInput, string> =>
input.length > maxLen
? Either.left(
ofInvalid({
value: input,
reason: 'course name length should not greater than 50',
})
)
: Either.right(input)
const validateWhiteList = (
input: string
): Either.Either<InvalidTextInput, string> =>
/^[A-Za-z0-9\s]*$/.test(input)
? Either.right(input)
: Either.left(
ofInvalid({
value: input,
reason: 'course name should only includes english, number, and space',
})
)
然後也可以很簡單的把它們串起來
export const textInputOf =
({ minLen, maxLen }: { minLen: number; maxLen: number }) =>
(input: string): InvalidTextInput | ValidTextInput =>
pipe(
input,
validateMinLen(minLen),
Either.flatMap(validateMaxLen(maxLen)),
Either.flatMap(validateWhiteList),
Either.mapRight(ofValid),
Either.merge
)
即使看不懂 flatMap
、mapRight
、merge
,也可以觀察到看到 input
經過三個 validate function
處理後建立 ValidTextInput
的資料轉換流程,相對層層疊疊的 if else 是不是乾淨許多呢?
明天我們就一起來看看它們施展了甚麼魔法讓三個小鐵軌優雅的組合成一張大鐵軌。
最後來一首呼應標題的老歌