关于重构遗留代码的函数式处理
之前的文章中对于java函数式编码做了一些了解,对于函数式的流式处理相信各位看官已经有些了解,下面让我们来浅谈一下关于使用函数式处理对臃肿代码进行重构的一些分析过程,预估这篇帖子文字为主,约消耗3-5分钟时间,希望这篇帖子对各位看官在研发过程中能启到抛砖引玉的作用。
关于未函数式处理的代码存在的问题
在java函数式编程出现之前的时代,代码的编写思路大致为:
1.我要code达到这么业务目的;
2.为了达到业务目的,我需要做一些脚手架处理;
基于这两个方面,我们开始了开发任务,往往在编写过程中,随着时间线的拉长和开发任务的紧迫性,到最后我们的项目代码,往往会成为项目中的业务逻辑与公共处理方法,甚至架构层的处理代码,混为一团。成为"一团"状态的代码,我们没有办法评估它的合理性,因为毕竟它解决了业务问题,做到了价值交付,但是这种项目几乎不会给人带来愉快的维护与迭代体验,那么它的问题出在哪里?
首先,混乱的结构,让我们在做项目重构时会频繁的做业务与过程分离的抽提处理,过程总对于源代码的逻辑步骤会发生较大变动,需要对原业务流程了解十分细致的人来协助,但是往往这个人处于离职状态;
其次,即使我们选择惰性处理,源代码保持不动,我们在基础之上分层提炼,做部分的重构工作,对于后续代码编写的人会越写越差异,甚至人格越发分裂,项目的结构越发混乱,项目服务间的耦合程度也会递增;
最后,正经人谁想写这种结构的代码啊,你想吗?我反正不想 :)
如何解决业务+过程混合的问题?
让我们通过一段code demo来分析一下,场景是要求在一组专辑中,找出其中所有长度大于1分钟的曲目名称,较为多见的编写方式:
public Set<String> find(List<Album> albums) {
Set<String> trackNames = new HashSet<>();
for(Album album: albums) {
for(Track track: album.getTracks()){
if(track.getLen() > 60)
trackNames.add(track.getName());
}
}
}
阅读这段代码,第一眼我们较难理解它的编写目的,而且处理的逻辑过程都是硬性编写,读起来有一些晦涩。让我们来看另一种处理写法:
public Set<String> find(List<Album> albums) {
return albums.stream()
.flatMap(album-> album.getTracks())
.filter(track->track.getLen() >60).map(track-> trac.getName())
.collect(toSet());
}
阅读这段代码,可以很直观的了解我们的业务处理步骤,原本业务间需要的逻辑处理已经被"节省"掉。就想我们之前介绍函数式编程时有聊到,最安全的代码是编写之后没有副作用,这也正是流风格代码的优势。
与流风格代码的链式调用相比较,传统的代码风格存在的问题有:
·代码可读性差,样板代码重复而且较多的重复,将真正业务逻辑隐藏;
·效率差,每一步都要对流及早求值,产生较多中间集合,浪费存储;
·难以自动并行化处理;
使用不会产生副作用的函数式代码几乎不会产生副作用,没有副作用的函数不会改变程序或者外界的状态,也就是安全的代码,建议新接触的小伙伴采用参考建造者模式进行code,以适应习惯流式编程风格。
结语
今天的分享就写到这里,我是雷记,专注并乐于分享编程相关的兴趣点与看法,如果你有好的想法和问题,欢迎加入我们的讨论组一起研讨,祝各位国庆愉快:)