重构for推导式以移除Option值上的不安全.get调用
移除Scala for推导式中的Option.get不安全调用
问题背景
现有一段Scala代码,用for推导式结合数据库操作,但多处用了Option.get这种不安全调用——一旦Option是空值,直接会抛NoSuchElementException。需要重构其中的数据库查询片段,彻底删掉这些get调用。
原风险代码片段:
for { a <- ADao.get(123).transact(xa) // ConnectionIO[Option[A]] b <- BDao.get(a.get).transact(xa) // ConnectionIO[Option[B]] product <- CDao.get(b.get.productId.get).transact(xa) // ConnectionIO[Option[Product]] } yield product
重构方案
核心思路就是利用Option的flatMap特性,把Option的空值检查和数据库IO操作结合起来,每一步都先判断Option是否有值,再决定要不要执行后续查询,全程不碰get。
基础重构版本
用flatMap分分支处理空值情况,逻辑清晰:
EitherT.liftF { ADao.get(123).transact(xa).flatMap { case Some(a) => BDao.get(a).transact(xa).flatMap { case Some(b) => b.productId match { case Some(productId) => CDao.get(productId).transact(xa) case None => ConnectionIO.pure(None) // 没有productId时返回空Option } case None => ConnectionIO.pure(None) // 没有B数据时返回空Option } case None => ConnectionIO.pure(None) // 没有A数据时返回空Option } }
更简洁的嵌套for写法
利用Scala对Option和ConnectionIO的Monad支持,用嵌套for推导式让代码更规整:
EitherT.liftF { for { maybeA <- ADao.get(123).transact(xa) maybeB <- maybeA match { case Some(a) => BDao.get(a).transact(xa) case None => ConnectionIO.pure(None) } maybeProduct <- maybeB.flatMap(_.productId) match { case Some(productId) => CDao.get(productId).transact(xa) case None => ConnectionIO.pure(None) } } yield maybeProduct }
进阶优化:用OptionT简化嵌套
如果项目里用到了cats-effect或doobie的扩展库,可以用OptionT把ConnectionIO[Option[X]]这种嵌套类型抹平,代码会更简洁清爽:
import cats.data.OptionT EitherT.liftF { (for { a <- OptionT(ADao.get(123).transact(xa)) b <- OptionT(BDao.get(a).transact(xa)) productId <- OptionT.fromOption[ConnectionIO](b.productId) product <- OptionT(CDao.get(productId).transact(xa)) } yield product).value }
关键说明
- 所有
None情况都做了处理,彻底避免了get调用的风险 - 保持了原有的查询顺序:只有前一步拿到有效数据,才会执行后续的数据库查询
- 用
ConnectionIO.pure(None)统一空值场景的返回值,保证类型始终是ConnectionIO[Option[Product]]
内容的提问来源于stack exchange,提问作者Blankman
相关产品推荐
相关产品推荐

