阿拉丁和灯

Thoughts, stories and ideas.



利用函数式编程增加代码可读性一例


一般我们写代码,是按照顺序写的,也就是直接按照顺序把要执行的语句一条条写下来。我们要表达的意思隐藏在语句中。这样的坏处是语句的语义,或者要意图,表达得不是很明确。为了解决这个问题,常见的做法是应用重构的手段,把一段代码抽取出来变成一个方法,方法名表示了这段代码的意图。举个例子,对于下面的这段原始代码:

代码0

重构之后,可能变成这样的代码

可以看到,我们将原先放在大方法里的一段代码抽取出来,变成一个独立的小方法,然后在大方法里调用小方法。这样的好处是大方法里的代码更加简单,代码的意图也更加清晰。但是,这样做也有坏处,就是这个代码变成了类的独立方法,增加了类中的方法的数量。如果这段代码只在这一个地方用到,那么变成类的成员方法,意义就不是很大。

除了抽取独立方法之外,其实我们还有别的方法来优化代码的结构,使得代码的意图清晰明显。比如,利用Lambda表达式。

Lambda表达式其实本质上是匿名的类,匿名的函数式接口的实现类。在Java 8之前,Java也可以支持函数式编程,但是,用匿名类的方式实现函数式编程接口,语法上冗长,大家不喜欢用。Java 8提供了Lambda的语法,虽然功能不变,但是大大简化了语法形式,使得函数式编程变成一种可接受的,优雅的编程形式。

我们利用函数式编程和Lambda表达式来优化我们刚才的代码。第一个尝试是使用Supplier函数式接口,代码如下:

代码1

这个代码把待优化的那段代码放到了一个独立的Supplier的实现里面,用Lambda表达式,我们可以直接用Inline代码的方式,也是是在被调用的地方,直接在代码里面写实现,但是又是相对独立的,从代码结构上看,它被封在一个大括号里面,读者可以马上意识到这段代码是相对独立的,是完成一件相对独立的事情的,如果我要分析的事情跟这件事没有关系,我可以忽略这段代码,这样就增加了代码的可读性。

上面这个代码达到了分离代码,并增加可读性的要求,但是因为引入了一个supplier,在后面使用的时候需要意识到supplier的存在(time.setContent(supplier.get())这个语句),与我们的原始代码稍有不同,增加了认知负担。所以我们再看看有没有其他写法。

第二种写法:

代码2

上面这段代码,其实也是利用了Supplier接口,但是Supplier只是作为中间变量存在,并没有显式赋给某个变量,赋给变量value的是supplier的值。为了做到这点,我们定义了一个SupplierUtil,帮助获取Supplier,或者说Lambda表达式的值。其实如果JDK本身的Supplier类能够提供一个类似的静态方法,我们就不用去自己定义这个SupplierUtil类了。我认为JDK提供这个方法是合理的,目前的JDK,要拿到Supplier的值,只有调用Supplier的get方法,而要调用supplier get方法,必须要先显式声明Supplier,然后调用它的get方法(如同我们在代码1里面做的那样),没有办法直接写一个Lambda表达式,然后调用它的get方法。JDK并不提供我说的这个静态方法,不能不说有点遗憾。

如果我们硬要避免自己定义SupplierUtil类,办法其实也是有的,代码如下:

代码3

这个代码与代码2的不同在于第一行,利用了Optional.empty().orElseGet方法,来调用一个Supplier(也是一个Lambda表达式),并返回它的值。具体原理大家可以查询一下Optional的相关方法的文档,就会很清楚了。

虽然这种方式避免了自己声明SupplierUtil类,但是代价是代码更复杂了,哪个方案更好?仁者见仁智者见智。

上面的这些方案虽然不同,但是都是用同步的方式调用了content的生成代码。如果这段代码非常耗时,同步调用会等待它完成,然后才能继续往下执行,如果后面还有很多别的事情要做,那些事情并不依赖content的结果,本来我们可以在等待content代码执行完成的同时并行执行那些代码,但是用同步调用的方式就不行了,要用异步的方式。具体怎么做呢?看代码

以上探讨了利用函数式编程来对代码进行改造,增加代码可读性,或者并行执行以增加性能的方法。不同方案各有优缺点,作为读者的你,觉得哪个方案更好呢,欢迎探讨。



View or Post Comments