当前位置: 首页 > 科技观察

KenScambler介绍了Option以优雅地确保健壮性

时间:2023-03-12 09:52:20 科技观察

REA,他在演讲中总结了选择函数式编程的三个原因《2 Year of Real World FP at REA》:Modularity、Abstraction和Composability。函数式编程强调纯函数(PureFunction),这是模块化的重要基础,因为对于纯函数来说,可以从函数的输入中推断出函数的执行结果,而无需考虑调用的上下文。这就是Ken所说的:YoucantellwhatitdoeswithoutLookingatsurroundingcontext.Ken在演讲中给出了一个案例:defparseLocation(str:String):Location={valparts=str.split(",")valsecondStr=parts(1)valparts2=secondStr.split("")Location(parts(0),parts2(0),parts(1).toInt)}仔细阅读这段代码,你会发现这段代码并不健壮,可能会出现以下错误:strasinputmaybenullparts(0)andparts(1)可能导致索引越界parts2(0)可能导致索引越界parts(1)可能不是整数,调用toInt可能会导致类型转换异常。一段代码隐含的错误也可以广泛传播到系统中的其他地方,只要函数被调用。这种蔓延可能会对更多嵌套调用产生级联错误影响。例如:defdoSomethingElse():Unit={//...DootherstuffparseLocation("Melbourne,VIC30??00")}以及doSomethingElse()函数被其他函数调用,这些潜在的缺陷会分布到各个直接或间接的调用点。这意味着代码会从它调用的代码中继承错误和副作用,使得对代码的功能进行推理几乎是不可能的,更不用说代码模块化了。我们当然可以通过检查null来避免这些错误。但是,查看各种可能的空值分支需要我们进行各种条件判断。想象这样的代码会让人不寒而栗。引入Option类型很好地封装了这种可能性。根据Ken的说法:所有可能性都已提升到类型系统中。defparseLocation(str:String):Option[Location]={valparts=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}yieldLocation(locality,subdivision,postcode)}上面代码中split()函数返回的类型是Array[String],它本身没有optGet()函数。但是我们可以为Array[String]定义一个隐式转换:)elseNone}}optToInt方法可以用同样的方式处理。Ken的方案没有考虑到parseLocation函数的参数str可能为null值的可能性,所以调用str上的split方法还是有可能抛出空指针异常。因此,我们还可以修改parseLocation函数的定义:defparseLocation(optStr:Option[String]):Option[Location]显然,通过引入Option,我们可以避免前面分析中可能出现的错误,避免编写繁琐的if判断。这里的关键点是Option封装了两种可能性(None和Some)。它由Some和None两个代数类型组成,前者包含一个值,而后者包含一个不存在的值。实际上,Option是一个实现了flatMap和filter的MaybeMonad,因此可以在Scala中使用forcomprehension访问它。【本文为专栏作家“张艺”原创稿件,转载请联系原作者】点此阅读更多该作者好文