這兩天看了一份關于Monad
的PPT腊满,將使用Monad
比喻成了面向軌道編程断序,覺得寫的挺好的,周末特意寫篇文章記錄一下糜烹。首先我們看一段代碼违诗,這段代碼模擬了一個處理request
的業(yè)務邏輯:
// 模擬處理業(yè)務
fun executeRequest(request: Request) : String {
// 校驗身份
validateRequest(request)
// 處理業(yè)務
dealBiz(request)
// 存儲數(shù)據(jù)
saveToDB(request)
// 發(fā)送消息
sendMessage(request)
return "success"
}
看起來這些代碼很普通,但是在平時的業(yè)務代碼里運行起來通常會遇到各種異常疮蹦,也就難免我們需要很多的數(shù)據(jù)判斷來避免這些異常破壞我們的邏輯了诸迟。所以實際的代碼很可能會寫成這樣:
// 模擬處理業(yè)務
fun executeRequest(request: Request) : String {
// 校驗身份
val isValidate = validateRequest(request)
if( isValidate ) {
return "Request is not valid"
}
// 處理業(yè)務
dealBiz(request)
try {
// 存儲數(shù)據(jù)
saveToDB(request)
} catch (exception:Exception) {
return "Occur error when save results to DB"
}
// 發(fā)送消息
val isSendSuccess = sendMessage(request)
if (isSendSuccess == false) {
return "message send unsuccessfully."
}
return "success"
}
executeRequest()
函數(shù)在根據(jù)業(yè)務組裝自己的控制流,實際的業(yè)務代碼中有很多這種判斷,為了避免執(zhí)行到不應該被執(zhí)行到的代碼阵苇。但這種判斷越來越多壁公,代碼就越難維護了。有一個笑話稱這種代碼是“上帝代碼”绅项,除了自己和上帝沒有人能看懂紊册,過了一段時間之后,只有上帝能看懂了快耿。
那么如何讓這些代碼變得簡單易讀呢囊陡?先看一個例子,假設有兩個函數(shù)掀亥,他們的作用如下:
- 牛 --> 牛肉
- 牛肉 --> 牛肉干
可以把函數(shù)的處理想象成鐵軌撞反,就像下面這樣:
然后我們再把這個兩個函數(shù)合并一下:
函數(shù)總是的執(zhí)行總是兩種情況,成功或者失敗搪花。這個函數(shù)執(zhí)行的過程可能不會這么順利遏片,也許制作牛肉的過程就會有發(fā)生異常,也許制作牛肉干的過程會失敗撮竿。所以可以定義一個Result
做Monads
容器吮便,接收返回值,Result
定義的時候泛型可以指定兩個類型一個是正常返回類型(Result.Success
)幢踏,另外一個攜帶是Exception
的返回類型(Result.Failure
)线衫。如果函數(shù)正常執(zhí)行,就是用success()
函數(shù)處理惑折,如果中間有一個失敗了授账,則是用failure()
函數(shù)處理。
val result : Result<Boolean,Exception> = Result.of( 1 + 1 = 2 )
result.success {
// 處理正常業(yè)務邏輯
}
result.failure {
// 處理錯誤
}
這個時候惨驶,函數(shù)的執(zhí)行就像在兩條軌道上一樣白热,一旦函數(shù)出現(xiàn)錯誤,執(zhí)行函數(shù)的“火車”就可以駛向?qū)iT處理錯誤的軌道上一樣:
那么我們再回到之前的例子粗卜,利用Result我們可以先把業(yè)務函數(shù)(即validateRequest()
,dealBiz()
等等)全部設計成返回Result
屋确,組裝這些業(yè)務函數(shù)的方法就可以這樣寫了:
fun executeRequest(request: Request) : String {
val result = Result
.of(request)
.flatMap { validateRequest(request) }
.flatMap { dealBiz(request) }
.flatMap { saveToDB(request) }
.flatMap { sendMessage(request) }
result.fold(
success = { return "success" },
failure = { return it.message }
)
}
我們把異常定義在業(yè)務函數(shù)中,直接利用Exception的message拋出续扔,交由上層統(tǒng)一拋出攻臀。代碼看著清晰了很多。特別是每個flapMap代碼塊中都在處理各自的業(yè)務纱昧,然后也可以將結(jié)果傳遞給下一個代碼塊刨啸。這里解釋一下Result
中的map
函數(shù)和flatMap
函數(shù):
- map() : 將函數(shù)的結(jié)果計算完畢之后,轉(zhuǎn)換成一個新的
Result
對象返回识脆。 - flatMap() : 將函數(shù)的結(jié)果計算完畢之后直接返回设联,比如結(jié)果是true善已,那么直接返回一個布爾類型(Boolean)。
以上兩個函數(shù)如果遇到帶有異常的Result
离例,會直接將這個異常的``Result返回换团。而下面兩個函數(shù)是專門用來處理失敗的Result
的
- mapError() : 將函數(shù)新的異常捕獲之后,轉(zhuǎn)換成一個新的
Result
帶有新異常的對象返回宫蛆。 - flatMapError() : 將函數(shù)的結(jié)果獲得的新異常返回艘包。
同樣,如果遇到成功的Result
耀盗,那么函數(shù)會直接將這個成功的Result
返回想虎。之前也看過很多關于Monads的文章,雖然看著很厲害的樣子袍冷,但是一直不知道為什么要去使用Monads,其實就是為了讓代碼的業(yè)務邏輯和控制能分的更加清楚一些猫牡。之前讀過一篇文章說胡诗,編程范式的本質(zhì)是有效地分離Logic
,Control
和 Data
,即:
-
Logic
: 就是一般的業(yè)務代碼淌友,類似上面代碼中的dealBiz()
,sendMessage()
等等 -
Control
: 對業(yè)務邏輯的流程控制煌恢,比如遍歷數(shù)據(jù)、查找數(shù)據(jù)震庭、多線程瑰抵、并發(fā)、異步等等 -
Data
:函數(shù)和程序之間傳遞的這部分信息
所以面向“軌道”編程器联,就是設計了Result
這樣一套模型來分離了Logic
和Control
二汛。
Ps. 文中使用的是一個根據(jù)面向軌道設計Kotlin
庫:https://github.com/kittinunf/Result