如何写出健壮的业务代码

原创 吴就业 117 0 2021-05-15

本文为博主原创文章,未经博主允许不得转载。

本文链接:https://wujiuye.com/article/458002020721455fb1d3f15eaf7c00ea

作者:吴就业
链接:https://wujiuye.com/article/458002020721455fb1d3f15eaf7c00ea
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。

本篇文章写于2021年05月15日,从公众号手工同步过来(博客搬家),本篇为原创文章。

我们一开始总会自信的觉得自己写出来的代码是个美女,只是写着写着越来越胖,最终写成了个200斤的胖子,自己见了都嫌弃……

很多人习惯性给方法堆代码,一个方法少则几百行多则上千行代码,好一点的只是代码堆的多,烂一点的还嵌套多层条件分支代码块,if里面还有if、if的if里面还有if……甚至于我们还习惯给自己找理由,看到方法里面的代码越来越长却又总是安慰自己:这次是因为没时间,下次一定改!

当你真的下定决心要重构它时,你发现这块逻辑你也看不懂了,至于是什么时候开始看不懂的你也不知道,此时你看啊看,看了后面忘记前面,一点点的拆分方法时发现这样拆好像影响后面的逻辑,后面有用到前面的一些变量……开始你心烦了,爆了句尼玛,转身出去抽根烟冷静冷静……结果重构又不了了之……

方法的拆分重构应该是开发过程中进行的,而不是需求做完之后,更不是有时间之后,没时间只是借口而已。

我对代码有洁癖,而且有强迫症,所以我对自己写的代码经常修整。

源于有朋友跟我说不懂怎么拆分方法代码合适,下面跟大家分享的是我自己总结的方法拆分原则,非标准,仅供参考:

1.针对重复出现的代码块

如果重复出现相同的一行非方法调用代码或者两行以上代码块,说明这些代码必然存在一个共同的名称描述它们,这就可以考虑抽离为独立方法。如果是非业务代码还应该考虑是否要抽离到工具类。

相同的一行非方法调用代码,如集合的判空,以前是这么写的:“collection!=null&&!collection.isEmpty()”,可以抽离为一个工具类方法:“CollectionUtils.isEmpty()”,当然,这些常用的工具类一些框架都已经帮我们封装好了,此处只是为了举例。

当一块代码在两个及以上方法中出现时,说明这个代码块存在共用性,也必然存在一个名称来描述它,这时就应该将该代码块抽离为独立方法。这个Idea工具也会有提示,只不过我们多数人都选择忽略它,但有强迫症的人一定忽略不了,比如我。

如果只是多个“属性”相同的类使用,如实现相同接口的类,那么就要考虑是否应该抽离到一个抽象类,提供模板方法,即模板方法模式。

2.针对条件分支以及循环体代码块

方法中出现条件分支代码块,且代码块超两行以上的,根据代码块是否能用一个名称来描述决定是否应该拆分为独立方法。对于嵌套条件分支语句同样可以继续拆。

循环语句中的代码块同理,如果能将循环代码块抽离为独立方法,那么通过Stream操作和lambda表达式就可以将代码改写为一行。

当一个方法拆出很多相似方法时,可以考虑是否应该用设计模式重写。本篇只介绍分法拆分,当然还有设计模式的使用,熟练的使用设计模式加上良好的方法拆分习惯就能写出漂亮整洁的代码。

以上两个方法拆分原则适用于大部分场景,但还是要结合实际情况考虑,理论并不适合生搬硬套。

案例一

图片

注意看,其中的getXxx方法是public的,也就是说会有别的地方调用它们,因此一个读缓存操作抽离为独立方法能够实现代码复用。其次,读操作封装在一个地方,其它地方不需要关心缓存的key是什么。

另外两个cacheXxx方法是private的,说明只有该类中使用,并且我知道也只有图中第一个方法调用到,那为啥还抽离为独立方法?set与get操作抽离为独立方法放在一块能够很清晰的看出是在哪写入的缓存,调用它的方法看起来也清晰。

根据前面介绍的几个原则,wxLogin方法其实还没有拆分完成,wxLogin中的if-else存在一个名称来描述它,即bindRoleIfNeed,因此还可以将if-else拆分为独立方法,方法名为bindRoleIfNeed,方法参数为accountId和role。(比较简单的情况下就没有必要使用设计模式了)

图片

现在wxLogin方法代码是不是很整洁、逻辑是不是很清晰,是不是像看文档一样,一眼就能看懂。而当我们想要了解细节时再一个方法一个方法看,并且每个方法的逻辑都很清晰。

如果不拆分wxLogin方法,画面就是下面这个样子的。

图片

案例二

图片

这是我们网关的一段代码,虽然是响应式编程,但并不妨碍我们理解它。

该方法实现的功能是获取响应body,解析为字符串,然后做一些替换,再将处理后的json再转为字节缓存。

由于Gateway针对返回参数过长的情况下会分段返回,因此响应body是一个List,我们需要将多个DataBuffer合并为一个,再转为字符串。

此案例中,将多个List转为字符串可以抽离为工具类方法,拆分后代码如下。(这里还隐藏了会导致响应结果出现中文乱码的bug)

图片

根据原则2,此方法中依然存在代码块:将多个List合并为一个,可以用joinDataBuffer去描述它,因此还应该将其抽离为独立方法。不过框架已经帮我们实现了,因此上面可以改写成如下。

图片

现在案例二的handleFlux方法可以简写为如下。

图片

你看,这不是更清晰可读了吗?

当然,并不是将一个方法拆分成多个就行了,方法可不是随便拆的。我去年就看到过一些反例,方法虽然是拆了,但所拆出来的方法并不能让逻辑清晰,也难以复用,这种就是失败的方法拆分。

最后

项目代码整洁=项目架构+设计模式+整洁的方法(函数);

良好的扩展性=项目架构+设计模式,要求高内聚低耦合;

高性能=算法+并发编程+对底层的理解(包括框架/中间件实现原理、操作系统底层一些知识(如零拷贝、虚拟内存映射等)、JVM、网络通信等等);

如果按优先级排序,我会这样排:

整洁易懂的代码 > 良好的扩展性 > 高性能。

前两项都包含了项目架构,这也是我喜欢推行DDD的原因。

对于写业务代码,如果鱼和熊掌不可兼得,那么我还是那句话,有时候我会为了扩展性以及代码的整洁易懂而选择牺牲一点性能,以及多花点时间。

是以空间换时间,还是以时间换空间,是需要看实际情况而定的。

代码是写给人看的,更多的时候是给自己看的,何不对自己好点。

#后端

声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。

文章推荐

写业务系统,更重要的是设计

写业务系统,我们应该更注重设计,好的设计能解决百分之八十的问题。

Spring Native与WebFlux一样注定昙花一现?

从Spring Native官方文档来看,我是承认它的优秀的,我也会继续关注它,或许将来在合适的项目中去使用它,至少从目前的了解来看,我还不会只为性能买单,一是对现有项目的改造成本略高,二是出于目前项目的成熟度考虑我们还缺少一些云原生组件的支持。

使用Redis实现积分排行榜,并支持同积分按时间排序

使用Redis实现实时更新的排行榜并不难,Redis提供的ZSet数据结构就很适合用于实现排行榜,但如何实现相同积分情况下再支持按时间排序呢?

在网关实现合并多个微服务Swagger接口文档的详细步骤

由于微服务的划分,使用Swagger生成的接口文档也随之拆散,前端同事不得不把每个微服务的接口文档保存为浏览器标签,方便快速切换。在引入网关之后我们想改善这个问题,统一多个微服务接口文档的入口,最好不需要将每个微服务暴露到外网,能够统一配置是否开启接口文档功能,也不需要为接口文档配置路由规则。

多人协作如何管理Git分支

关于Git分支管理,每个团队在不同阶段都有自己的管理策略,最近我们团队也争论过这个问题。

(a+b)*10,10是存在哪里的?是常量池么?

今天看到一个很有意思的提问:(a+b)*10,10是存放在哪里的?是常量池么?如果是常量池,在进行运算的时候,是通过指针来找到的吧?