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

本文介绍了如何通过简化Scala代码,移除过度工程化的typeclass模式,采用纯函数和类型参数解耦,以自动配置构建工具为例,实现清晰易懂的程序逻辑。

【如何进行 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
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值