iT邦幫忙

2023 iThome 鐵人賽

DAY 7
0

今天的目標是跟大家分享「通過昨天測試的程式以及背後的觀念」,在開始看程式碼前我們先來了解一下背景知識吧~

如何做錯誤處理 ?

https://ithelp.ithome.com.tw/upload/images/20230922/20158615tliEoB2jFM.png

我們總是會遇到各種例外、錯誤、副作用。如何控制這些討人厭的副作用呢? 在接觸 functional programming 之前我可能會用這些方法

  • throw
  • try catch
  • if else

可是上面三種處理方式,尤其是以 Javascript 這個語言來說,都有缺點。使用 throw 語法很危險,因為 javascript 不強制你處理錯誤,甚至也不提示你 function 可能拋出錯誤,因此一個不小心忘了 try catch 就可能造成災難性後果。即使使用 try catch 或是 if else 也沒有比較好,因為可能讓程式碼變的很難讀。

錯誤處理就是一件事
在重構或程式碼可讀性的概念中,有個共同特性,就是函式(方法)應該只做一件事,避免函式中的程式碼陷入邏輯泥塊(Logical clump)。
--- 良葛格 重構錯誤處理程式

所以,我們到底該怎麼做錯誤處理呢?

軌道導向程式設計

軌道導向程式設計(Railway Oriented Programming) 提供了非常棒的錯誤處理解決方案。

https://ithelp.ithome.com.tw/upload/images/20230922/201586152TZImv2yNP.png
來源

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>
  • 想像每一小段鐵軌都是一個 function
  • 如同每一小段鐵軌都有兩條分岔,每個 function 都有一個輸入,兩個可能的輸出
  • 鐵軌可能向左走或向右走,如同英文雙關對應的 離開 或是 正確
  • LeftRight 都是帶有 tag 的,因此合起來就變成了一個 sum type - Either

有時候 Either 會換成 ResultLeft 換成 failRight 換成 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
    )

即使看不懂 flatMapmapRightmerge ,也可以觀察到看到 input 經過三個 validate function 處理後建立 ValidTextInput 的資料轉換流程,相對層層疊疊的 if else 是不是乾淨許多呢?

明天我們就一起來看看它們施展了甚麼魔法讓三個小鐵軌優雅的組合成一張大鐵軌。


最後來一首呼應標題的老歌

Yes


上一篇
D06 - 測試驅動開發
下一篇
D08 - 資料型別盒子
系列文
從 Next.js 開始的 Functional Programming30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言