【如何进行 Scala 代码设计 -- 大道至简】 -- 以自动配置构建工具的小工具为例 (三)

【如何进行 Scala 代码设计 -- 大道至简】 -- 以自动配置构建工具的小工具为例 (三)

前言

【如何进行 Scala 代码设计 – 类型划分定基调】 – 以自动配置构建工具的小工具为例 (一)
【如何进行 Scala 代码设计 – 接口粒度见功夫】 – 以自动配置构建工具的小工具为例 (二)

之前两篇代码设计的跟粑粑一样… 净整一些花里胡哨没用的东西, 搞得代码很复杂, 引入了 typeclass 模式也没有什么作用, 只是增加代码的复杂度而已, 增加代码的阅读难度.

在这里推荐大家看看 lihaoyi 博客的这篇文章 Scala设计原则之大道至简 . 代码中多引入一个类, 多引入一个接口都会增加程序员认知的负担, 如果没有必要就不要引入那么多东西, 切记不要 “过度工程化”.

这次我们要做简化, 去掉那些花里胡哨的东西, 实际上 typeclass 模式普通的编程根本就没有啥必要, 这次我们做减法 – 去除 typeclass 模式. 其次最近也在思考"函数式编程到底有什么好处之类"的问题(参考: 纯函数式编程演讲笔记), 而这个工具的代码, 也将尽量以纯函数的方式来写, 同时参考 《函数响应式领域建模》一书中的方法, 将各部分利用类型参数解耦.

程序流程

程序还是那样, 读取当前的配置, 根据选项修改配置, 保存配置; 除此之外, 还有一些配置是需要配置环境变量的, 这种配置就不适用于上面这种步骤.

SBT 相关配置的逻辑

首先看一下包结构, 下面以 SBTConfigTool 为核心, 聊一下整个程序结构
在这里插入图片描述
看一下 SBTConfigTool 的内容, 所有的公有方法都是对应需要的步骤. 其中的类型参数F[_] 是上下文参数, ME 是 MonadError, 因为在读取的过程中可能报错, 所以用一个 MonadError 来修饰.
在这里插入图片描述
使用的时候, 是直接 import SBTConfigTool._ , 然后调用其中的方法, 非常方便.

sbtJarLauncherReader – 读取配置

这里返回的是一个 Reader, 整个逻辑是解析出 sbt-launcher.jar 中 sbt/sbt.boot.properties 配置的内容, 当然这里面都是一些 Monad 的 flatMap 的东西, 而 MonadError 允许将异常封装成 F[_]
在这里插入图片描述
在这里插入图片描述
而写入配置也是类似代码, 通过直接声明静态方法在 SBTConfigTool 中, 我们去掉了 typeclass 模式的东西, 这样代码一目了然, 而不用满世界找隐式注入, 考虑各种类型的组合啥的.

配置转换部分

在这里我们定义一个接口,用来表示配置, 这个接口其实说实话, 也是可以去掉的. 因为我们实际上最终需要的是 Content[C] => Content[C] 的这个转换函数而已. 但是鉴于多种转换也确实存在共同的模式, 姑且提取这个接口吧
在这里插入图片描述
以镜像的配置为例:
在这里插入图片描述
整个实现, 都是纯函数, 没有副作用, 所以这里不需要什么上下文 F[_].

组合相关的逻辑

这里我用了一个 case class 来进行组合, 实际上也可以不用这个 case class,直接在用的时候手撸就行了
在这里插入图片描述
其中有很多类型参数, 注意到 F[_], Home, P, Value 这些都是类型参数, 是等到特定场景使用的时候, 才指定的. 比如说 对于 SBT 的配置, Value 就是 List[String], 上面我们实现的 Reader 和 Writer 是针对 List[String] 实现的, 而 F[_] 到时候可能就取 Either[Throwable,_] 或者是 IO[Either[Throwable,_]], 根据实际需要取, 而不用改变其他的代码, 非常灵活.

而程序的主逻辑, 也是利用 Monad 和 Functor 的特性进行组合的: reader.read 方法返回的是 Content[Value] , 利用其 map 方法可以应用 configs 中的函数, 这样我们就改变了其中的内容, 然后利用 flatMap 组合带有副作用的 writer.save, 一气呵成.

其中 combineFunctions 的作用是将 List[A => B] 转换成 A => B (利用 foldLeft)
在这里插入图片描述

组装程序

程序是根据配置文件或者是从标准输入读取参数, 然后执行对应的逻辑, 到这里我们已经可以确定一部分的函数参数了, 最后只留下一个上下文参数F[_].
在这里插入图片描述
假设我们已经有所有需要的配置参数了
在这里插入图片描述
然后又是一通组合组合组合
在这里插入图片描述
其实如果刨去 组合子 这些东西, 全部指定特定的类型, 比如 F[_] 就用 Eihter 或者 Future 直接代替, 也是可以的, 整个函数的逻辑同样也是很清晰.

总结

这一版算是比较满意的一版了, 逻辑很清晰, 代码量大大减少. 同时这一版也加入了互动式地从标准输入读取参数的功能, 还能根据环境变量自动查找home目录, 这才算是一个好用的工具.

在敲代码的过程中, 也体会到了一些东西:

  • 如非必要,勿增实体
    虽然 scala 有很多高级特性, 但是没有必要的情况下,不要使用; 我觉得对于 Java 来说, 是因为其"井井有条"的语法导致的简化不起来, 所以搞了一堆类的层级结构, 使得代码又臭又长. 用 scala 是为了摆脱束缚, 解放大脑, 而不是陷入另一种花里胡哨的陷阱.
  • 函数式编程是声明式的, 而不是命令式的
    这一点同样可以看看 lihaoyi 的博客: 什么是函数式编程

代码保存在 码云,重构后的代码是放在了scala3-refactor 的分支, 暂时还没有合并到 master.

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值