开发小记:用函数式编程优化代码可读性,减少一半行数

用户头像
北京/设计爱好者/4年前/127浏览
开发小记:用函数式编程优化代码可读性,减少一半行数
用户头像
dasein58

开发小记:用函数式编程优化代码可读性,减少一半行数

  难忘阿里,4面技术5面HR附加笔试面,走的真艰难真心酸

  2022“闭关”跳槽季,啃透分布式三大技术:限流、缓存、通讯

  疫情期间“闭关修炼”,吃透这本Java核心知识,跳槽面试不心慌

  前言

  本文主要是记录一下用lambda 表达式优化代码的经历,篇幅不长,算是分享我觉得不错的一个小技巧。

  话不多说,直接进入正题。

  正文

  我们先来看这么一段代码:

  @Component

  public class ConfigCacheHelper {

  private final RedisHelper redisHelper;

  private final IChannelConfigMapper iChannelConfigMapper;

  @Autowired

  public ConfigCacheHelper(RedisHelper redisHelper, IChannelConfigMapper iChannelConfigMapper) {

  thisisHelper=redisHelper;

  this.iChannelConfigMapper=iChannelConfigMapper;

  }

  public AaaChannelConfig getAaaChannelConfig(String merchantId){

  if (StringUtils.isEmpty(merchantId)){

  throw new IllegalArgumentException("商户号不能为空");

  }

  Object obj=redisHelper.hget(RedisKey.CHANEL_CONFIG, RedisKey.AAA_CHANNEL);

  AaaChannelConfig config;

  if (obj==null){

  config=iChannelConfigMapper.selectAaaChannelConfig(merchantId);

  }

  else {

  Map  map=(Map )obj;

  config=map.get(merchantId);

  }

  return Objects.requireNonNull(config, "获取Aaa渠道配置为空");

  }

  public BbbChannelConfig getBbbChannelConfig(String merchantId){

  if (StringUtils.isEmpty(merchantId)){

  throw new IllegalArgumentException("商户号不能为空");

  }

  Object obj=redisHelper.hget(RedisKey.CHANEL_CONFIG, RedisKey.BBB_CHANNEL);

  BbbChannelConfig config;

  if (obj==null){

  config=iChannelConfigMapper.selectBbbChannelConfig(merchantId);

  }

  else {

  Map  map=(Map )obj;

  config=map.get(merchantId);

  }

  return Objects.requireNonNull(config, "获取Bbb渠道配置为空");

  }

  public CccChannelConfig getCccChannelConfig(String merchantId, String posId, String operatorId){

  if (StringUtils.isEmpty(merchantId)){

  throw new IllegalArgumentException("商户号不能为空");

  }

  Object obj=redisHelper.hget(RedisKey.CHANEL_CONFIG, RedisKeyC_CHANNEL);

  CccChannelConfig config;

  if (obj==null){

  config=iChannelConfigMapper.selectCccChannelConfig(merchantId, posId, operatorId);

  }

  else {

  Map  map=(Map )obj;

  config=map.get(String.format("%s_%s_%s", merchantId, posId, operatorId));

  }

  return Objects.requireNonNull(config, "获取Ccc渠道配置为空");

  }

  // ... 此处再省略N个渠道的config

  }

  俺是做支付的,这段代码的逻辑很简单,就是获取某个支付渠道的商户配置,缓存取不到就去数据库取。

  在IDEA乍一看,我倒没看出什么问题,代码检查插件也没有报什么warning,但是当我在这个类里面新增获取第N个渠道的方法的时候,我就感觉到了这块代码不是很优雅。

  总结出来两点:

  多余的StringUtils.isEmpty(merchantId)

  if (StringUtils.isEmpty(merchantId)){

  throw new IllegalArgumentException("商户号不能为空");

  }

  理由有二: 判断字符串为空应该是调用者的责任 古玩论坛的业务逻辑早就确保merchantId 不可能为空字符串,没必要再判断

  getXXXChannelConfig逻辑可以提取成如下

  public AaaChannelConfig getAaaChannelConfig(String merchantId){

  // if (StringUtils.isEmpty(merchantId)){

  // throw new IllegalArgumentException("商户号不能为空");

  // }

  // 1

  Object obj=redisHelper.hget(RedisKey.CHANEL_CONFIG, {渠道键值});

  AaaChannelConfig config;

  if (obj==null){

  // 2

  // selectBbbChannelConfig()

  // selectCccChannelConfig(merchantId, posId, operatorId)

  config=iChannelConfigMapper.{取某个渠道配置的方法}(...);

  }

  else {

  Map  map=(Map )obj;

  // 3

  // config=map.get(String.format("%s_%s_%s", merchantId, posId, operatorId));

  // config=map.get(merchantId);

  config=map.get({渠道配置Map的键值});

  }

  return Objects.requireNonNull(config, "获取Aaa渠道配置为空");

  }

  第一点很简单,不讲了。主要来讲下第二点,从上面的分析中,就可以抽取出3个变量:

  渠道键值去数据库中取配置的函数 - 配置的提供者渠道配置Map的键值

  这样子我们就可以把代码改造成下面这样:

  private  T getChannelConfig(String configKey, String configMapInnerKey,

  Supplier  daoSupplier) {

  Object obj=redisHelper.hget(RedisKey.CHANNELPROXY_HKEY, configKey);

  T config;

  if (obj==null){

  config=daoSupplier.get();

  } else {

  Map  map=(Map )obj;

  config=map.get(configMapInnerKey);

  }

  return Objects.requireNonNull(config, "获取渠道配置为空, 渠道值:" + configKey);

  }

  public AaaChannelConfig getAaaChannelConfig(String merchantId){

  return getChannelConfig(

  RedisKey.AAA_CHANNEL, merchantId,

  () -> iChannelConfigMapper.selectAaaChannelConfig(merchantId)

  );

  }

  public BbbChannelConfig getBbbChannelConfig(String merchantId){

  return getChannelConfig(

  RedisKey.BBB_CHANNEL, merchantId,

  () -> iChannelConfigMapper.selectBbbChannelConfig(merchantId)

  );

  }

  public CccChannelConfig getCccChannelConfig(String merchantId, String posId, String operatorId){

  return getChannelConfig(

  RedisKeyC_CHANNEL, String.format("%s_%s_%s", merchantId, posId, operatorId),

  () -> iChannelConfigMapper.selectCccChannelConfig(merchantId, posId, operatorId)

  );

  }

  这里简单提一下Supplier,这是java.utilction中提供的函数式接口,用来支持Java 中的函数式编程。从语义上理解就是“T的提供者”,比如在上文语境中就是对应渠道配置的提供者。类似的常用接口还有:

  

  优化结果

  这样优化之后,提升了代码的可读性,在实现相同功能的前提下,比原来减少了一半的代码量。(233 -> 137)

  如何抛出我想要的异常?

  这里想要再提一个场景,因为之前有碰到过,就是如何让你的Function抛出异常?

  还是拿上述代码为例,

  private  T getChannelConfig(String configKey, String configMapInnerKey,

  Supplier  daoSupplier) {

  Object obj=redisHelper.hget(RedisKey.CHANNELPROXY_HKEY, configKey);

  T config;

  if (obj==null){

  // 假设我想让这个方法抛出一个自定义的BizException 业务异常,怎么办?

  config=daoSupplier.get();

  } else {

  Map  map=(Map )obj;

  config=map.get(configMapInnerKey);

  }

  return Objects.requireNonNull(config, "获取渠道配置为空, 渠道值:" + configKey);

  }

  我刚开始想了蛮久的,后面发现这实际上是属于Java 基础方面的知识。

  我们传入的参数daoSuppier实际上相当于是一个函数式接口Supplier的匿名内部实现类而已(当然底层实现是不一样的,在某种意义上比匿名内部类好很多,无论是性能,可读性还是使用趋势)

  @FunctionalInterface

  public interface Supplier  {

  /**

  * Gets a result.

  *

  * @return a result

  */

  T get();

  }

  接口可以声明抛出某个异常,它的实现可以不抛出异常,反之呢,如果它的实现抛出了受检异常,这个接口就必须显式声明抛出这个异常。

  所以这种情况下,我们如果想我们的Supplier抛出我们想要的异常,那么就需要自己声明一个Functional Interface,

  public interface DaoSupplier  {

  /**

  * Gets a result.

  *

  * @return a result

  */

  T get() throws BizException;

  }

  再次改造后的代码:

  private  T getChannelConfig(String configKey, String configMapInnerKey,

  DaoSupplier  daoSupplier) throws BizException {

  Object obj=redisHelper.hget(RedisKey.CHANNELPROXY_HKEY, configKey);

  T config;

  if (obj==null){

  config=daoSupplier.get();

  } else {

  Map  map=(Map )obj;

  config=map.get(configMapInnerKey);

  }

  return Objects.requireNonNull(config, "获取渠道配置为空, 渠道值:" + configKey);

  }

  public AaaChannelConfig getAaaPayChannelConfig(String merchantId) throws BizException {

  return getChannelConfig(

  RedisKey.AAA_CHANNEL, merchantId,

  () -> Optional.ofNullable(iChannelConfigMapper.selectAliPayChannelConfig(appId))

  .orElseThrow(BizException::new)

  );

  }结语

  本文并没有引入很多的Java Lambda的原理性介绍、API介绍,因为本身就是个人的开发小记,偏重于实践,引入太多的知识性介绍反而偏离了本意。希望Java lambda 不熟悉的同学可以自己学习下相关资料。

0
阅读原文
|
举报
|
收藏
分享
相关推荐
评论
用户头像
评论你的想法~
表情
喜欢TA的作品吗?喜欢就快来夸夸TA吧!
推荐素材
人物网球运动、棒球运动动态插画
love玫瑰花
黑白荧光色实验性风格|个人作品集排版简历模板
乐陶创意体验工坊标志设计
小青龙IP吉祥物系列插画设计
像素兵马俑怪物动画序列帧
卡通兔子插画
香盒包装设计国风国潮通用香礼盒
炫光
玫瑰
汉堡插画
新能源腊八节海报
宠物纪念相册H5模板系列 · 温馨治愈系 · 三款主题
校园文化宣传栏
浙江省区位分析图
中秋佳节花好月圆
彩灯_花灯_春节彩灯_春节花灯_马年彩灯_马年花灯
中国风书法汉字字体设计其他
女生半身插画
人物运动系列插画
头发1
二十四节气图片之霜降
船坞民宿概方案
我要奋斗
国画水墨人物水墨画放牛娃画江南风景乡村墙绘素材
你可能喜欢
相关收藏夹
大家都在看
登录注册