在鲁迅的名篇《孔乙己》中,有这样一个桥段,孔乙己
问作者是否知道茴香豆的“茴”字,有四种写法,用于讽刺孔乙己
这类落魄文人的迂腐。我们在coding时,为实现某一逻辑,也会存在各种各样的方案,而每种方案的背后,除了有各自的特点之外,也或多或少与给出这些方案的人的技能、经历、思考方式有关,本文试图以亲身经历的一个小故事,来探究编程领域内茴字N种写法背后的故事。
起因
一次代码Review的过程中,看到了类似下面的代码
1 | public OrderApiResponse getOrder(String orderId) { |
放眼望去,感觉没什么不对;仔细端详,又感觉哪里不对….为什么要用String
作为response的参数化类型呢?另外这个JSON.parseObject
也不是Spring Framework
内置的Jackson
啊,仔细一看原来是我天朝上国的fastjson,为什么要放弃内置的Json方案而再引入一个第三方的呢?
赶紧找来开发人员小G请教原委,
Order API Response的属性名是以大写字母开头的,
Jackson
默认不能处理,fastjson
默认可以处理,所以引入了fastjson
并以String
类型接收响应。
Hmmm,貌似很有道理的样子,但仔细推敲起来,至少还有其他问题,于是我回复道,
- 假设Order API返回10000个属性,我们只用10个,使用
String
全部接收响应,岂不是很浪费?性能是不是也不高?- 假设使用
fastjson
不可避免,那么是否有更好的解决方案呢?
开发人员G于是进入了冥思苦想状,而我的思绪也逐渐模糊,从fastjson
转向了RestTemplate。
第二种写法
无论使用Spring MVC来做为Server
端,还是使用RestTemplate来做为Clinet
端,Spring Framework
都是在使用HttpMessageConverter来处理http消息。
Spring Framework
内置了很多Http Message Converters,而MappingJackson2HttpMessageConverter就是内置使用Jackson
来处理Json的Converter,假设我们要使用fastjson
来替换Jackson
,最好的办法是直接在替换掉MappingJackson2HttpMessageConverter
,这样在使用RestTemplate
来请求API时,不需要手动处理response
的反序列化,本来,序列化方式也应该对客户端代码是透明的。那么问题来了,如何替换掉MappingJackson2HttpMessageConverter
呢?来,动手吧。
1 | public class OrderApiClient { |
Seems better now. 开发人员G对我的这个方案很满意,且慢,我还有问题呢!
- 既然Spring已经内置了
Jackson
来处理Json,我们为什么要画蛇添足地引入另外一种Json处理包呢?- 既然
fastjson
能做到,凭什么Jackson
做不到呢?
路漫漫其修远兮,吾将上下而求索。
第三种写法
在我苦苦求索不得要领之时,小R兴冲冲跑过来说道,
- Jackson有对应的
MapperFeature
来支持兼容大写字母开头的属性名;- 我们使用的是
Spring Boot
,可以直接使用properties
来设置该属性,不需要额外写代码;
综合小R的回复,我们只需要在application.properties
文件里,加上这么一句就好啦,
1 | spring.jackson.mapper.accept_case_insensitive_properties=true |
So Easy, 难道不是吗?可是敏而好学,不耻下问的我,又有新问题了,
- 如果兼容非Java Bean规范的属性名,想必要付出额外的性能代价吧?
- 在我们的应用中,有很多外部API调用的场景都使用到了
RestTemplate
,但并不是所有的这些场景都需要兼容首字母大写的属性名吧?- 配置在
application.properties
中的mapper_feature
应该是全局设置,对所有的RestTemplate
都起效吧,那岂不是说不需要兼容首字母大写的API调用在使用RestTemplate
时也需要承担性能下降的代价?
显然,还得继续求索啊。
第四种写法
为了不使其他使用RestTemplate
的API Client收到全局配置的影响,那么就不能在全局配置,配置应该发生在个体之上。在这个思路的只因下,小R在OrderApiClient
自己的RestTemplate
上下手了,
1 | public class OrderApiClient { |
很好,所有的问题都解决了,需求实现了,性能还不错,更没有污染全局,小R,V5!
然而,既然已经走到了这里,还能更进一步吗?
更进一步
正如上面所说,该做到的其实都已经做到了,无以复加了。可是患有强迫症的我,仍然觉得代码在结构和写法上仍然还有可调整的空间,
- 我们真的需要那个
RuntimeException
吗?- 基于目前的代码,所有的Converters里真的会没有找到
MappingJackson2HttpMessageConverter
?- 如果以上两个问题的答案是No,那么代码是否能写的更优雅呢?
这次不劳烦小G和小R动手了,我来亲手把它写出来吧,更进一步之后大概是这样子的,
1 | public class OrderApiClient { |
好了,终于没有然后了。
退而思之
让我们复盘一下整个故事的发展过程,
- 引入
fastjson
并单独使用JSON.parseObject
来处理非标准变量名; - 使用
fastjson
的FastJsonHttpMessageConverter
来替换自带的MappingJackson2HttpMessageConverter
; - 干掉
fastjson
,在application.properties
中配置全局的mapper_feature
来适应非标准变量名; - 直接修改
OrderApiClient
自己的RestTemplate
的mapper_feature
来适应非标准变量名,并避免污染全局; - 让代码变得更简洁、优雅。
从以上故事的发展过程,我们又可以总结出代码实现的不同等级,
- 实现了功能,但性能一般;
- 实现了功能,性能尚可,但引入了不必要的依赖;
- 实现了功能,性能尚可,但副作用较大,污染了全局;
- 实现了功能,性能不错,无污染,无副作用,代码略显冗余,或可读性差;
- 实现了功能,性能不错,无污染,无副作用,代码简洁优雅、易读易懂。
我们在做代码实现时,相信我们无一例外,都是奔着最高标准的第五点去的,没人从主观上就想写烂代码。但由于每个人的技能、经历、思考方式不同,写出的代码也自然不同,其质量也会大概率地分布在这五个等级内。我不能要求每个人写出的每行代码都是最高等级的,但我希望每行代码都能更趋近于最高标准。
为你们总结一下吧,小G,小R,新时代的我们,应该如何去写高质量的代码
- 首先要技术过硬,知其然亦要知其所以然;
- 要有全局观念和大局意识,从小处着手但从大处着眼;
- 多写亦要多想,学而不思则罔,思而不学则殆;
- 最后一点,也是最为重要的一点,对代码,一定要保留一份敬畏之心,一行代码写下去,即便不关乎个人生死,也会关乎项目存亡,不可不慎,不可不察。