引入Option優雅地保證健壯性
REA的Ken Scambler在其演講《2 Year of Real World FP at REA》中,總結了選擇函數式編程的三個原因:Modularity, Abstraction和Composability。
函數式編程強調純函數(Pure Function),這是模塊化的一個重要基礎,因為對于純函數而言,可以不用考慮調用的上下文,就可以根據函數的輸入推斷函數的執行結果。這也就是Ken所謂的:
You can tell what it does without Looking at surrounding context. |
Ken在演講中給出了一個案例:
- def parseLocation(str: String): Location = {
- val parts = str.split(",")
- val secondStr = parts(1)
- val parts2 = secondStr.split(" ")
- Location(parts(0), parts2(0), parts(1).toInt)}
仔細閱讀這段代碼,你會發現這段代碼是不健壯的,可能存在如下錯誤:
- 作為input的str可能為null
- parts(0)和parts(1)可能導致索引越界
- parts2(0)可能導致索引越界
- parts(1)未必是整數,調用toInt可能導致類型轉換異常
這段代碼隱含的錯誤還可能被廣泛地蔓延到系統的其他地方,只要該函數被調用。這種蔓延可能會因為更多嵌套的調用而產生級聯的錯誤效應。例如:
- def doSomethingElse(): Unit = {
- // ...Do other stuff
- parseLocation("Melbourne, VIC 3000")}
而doSomethingElse()函數又被其他函數調用,這些潛在的缺陷會分布到各個直接或間接的調用點。這意味著代碼會繼承它所調用代碼的錯誤以及副作用,使得對代碼功能的推理(reasoning)變得近乎不可能,更不用說代碼的模塊化(modularity)了。
我們當然可以通過對null進行檢測來避免出現這些錯誤。然而看看各種出現null值的可能分支,需要我們做各種條件判斷,想象這樣的代碼都讓人不寒而栗。引入Option類型就可以很好地封裝這種可能性。按照Ken的說法就是:
All possibilities have been elevated into the type system. |
- def parseLocation(str: String): Option[Location] = {
- val parts = str.split(",")
- for {
- locality <- parts.optGet(0)
- theRestStr <- parts.optGet(1)
- theRest = theRestStr.split(" ")
- subdivision <- theRest.optGet(0)
- postcodeStr <- theRest.optGet(1)
- postcode <- postcodeStr.optToInt
- } yield Location(locality, subdivision, postcode)}
以上代碼中,split()函數返回的類型為Array[String],該類型自身是沒有optGet()函數的。但是我們可以為Array[String]定義隱式轉換:
- implicit class StringArrayWrapper(array: Array[String]) {
- def optGet(index:Int): Option[String] = {
- if (array.length > index) Some(array(index)) else None
- }}
optToInt方法可以如法炮制。
Ken的解決方案并沒有考慮到parseLocation函數入參str存在null值的可能,故而在對str調用split方法時仍然有可能導致拋出空指針異常。因此進一步,我們還可以修改parseLocation函數的定義:
- def parseLocation(optStr: Option[String]): Option[Location]
顯然,通過引入Option,既規避了前面分析可能出現的錯誤,又能避免編寫繁瑣的if判斷。這里的關鍵點是Option對兩種可能性(None與Some)的封裝。它由兩個代數類型Some與None構成,前者包含了一個值,而后者則包含了一個不存在的值。事實上,Option是一個Maybe Monad,實現了flatMap與filter,因而在Scala中可以用for comprehension來訪問。
【本文為51CTO專欄作者“張逸”原創稿件,轉載請聯系原作者】