前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java程序员如何优雅编程

Java程序员如何优雅编程

作者头像
京东技术
发布2023-09-21 17:43:37
1290
发布2023-09-21 17:43:37
举报
文章被收录于专栏:京东技术京东技术

Tech 导读 本文结合作者经验提出了一些编程的建议,这些建议旨在告诉读者如何更好的构造代码以便于它们能更好的工作,也便于将来对代码进行修改和改善的时候有一个参考。甚至,程序也会因此变得更加令人愉悦,更加优雅。

01

前言

在今年的敏捷团队建设中,我通过Suite执行器实现了一键自动化单元测试。Juint除了Suite执行器还有哪些执行器呢?由此我的Runner探索之旅开始了!

编程不仅是一项单纯的技能,更是一个充满创造力的活动,能够使用代码与人进行双向互动,是一门真正的艺术。编码风格是编写优雅代码不可或缺的一环,好的编码风格有助于降低团队沟通成本。接下来让我们进步一探讨如何优雅编程。

02

优雅编程方式

理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕

2.1 优雅防重

如果业务体系满足以下两个条件:

  1. 业务接口重复调用的概率不是很高。
  2. 入参有明确业务主键如:订单ID,商品ID,文章ID,运单ID等。

在这种场景下,非常适合乐观防重,思路就是代码处理不主动做防重,只在监测到重复提交后做相应处理。如何监测到重复提交呢?

MySQL唯一索引 + org.spring framework. dao. Duplicate Key Exception。

代码如下:

代码语言:javascript
复制
public int createContent(ContentOverviewEntity contentEntity) {
  try{
    return contentOverviewRepository.createContent(contentEntity);
  }catch (DuplicateKeyException dke){
    log.warn("repeat content:{}",contentEntity.toString());
  }
  return 0;
}

2.2 用好Stream

初级程序员向中级进阶的必经之路就是攻克Stream。Stream和面向对象编程是两个编程理念,《架构整洁之道》里曾提到有三种编程范式,结构化编程(面向过程编程)、面向对象编程、函数式编程。初次接触Stream肯定特别不适应,但如果熟悉以后将打开一个编程方式的新思路。本文Stream,只讲如下例子:

比如,如果想把一个二维表数据进行分组,可采用以下一行代码实现:

代码语言:javascript
复制
List<ActionAggregation> actAggs = ....
Map<String, List<ActionAggregation>> collect = 
    actAggs.stream()
    .collect(Collectors.groupingBy(ActionAggregation :: containWoNosStr,LinkedHashMap::new,Collectors.toList()));

2.3 用好卫语

各个大场的JAVA编程规范里基本都有这条建议,但真正用好它的不多,卫语句对提升代码的可维护性有着很大的作用,想像一下,在一个10层if 缩进的接口里找代码逻辑是一件多么痛苦的事情。笔者曾在一个微服务里的一个核心接口看到了这种代码,该接口被过多的人接手导致了这样的局面。系统接手人过多以后,代码腐化的速度超出想像。

下面举例说明:

没有用卫语句的代码,很多层缩进:

代码语言:javascript
复制
if (title.equals(newTitle)){
  if (...) {
    if (...) {
      if (...) {

      }
    }else{

    }
  }else{

  }
}

使用了卫语句的代码,缩进很少:

代码语言:javascript
复制
if (!title.equals(newTitle)) {
  return xxx;
}
if (...) {
  return xxx;
}else{
  return yyy;
}
if (...) {
  return zzz;
}

2.4 避免双重循环

简单说双重循环会将代码逻辑的时间复杂度扩大至O(n^2)。

如果有按key匹配两个列表的场景建议使用以下方式:

  1. 将列表1 进行map化。
  2. 循环列表2,从map中获取值。

代码示例如下:

代码语言:javascript
复制
List<WorkOrderChain> allPre = ...
List<WorkOrderChain> chains = ...
Map<String, WorkOrderChain> preMap = allPre.stream().collect(Collectors.toMap(WorkOrderChain::getWoNext, item -> item,(v1, v2)->v1));
chains.forEach(item->{
  WorkOrderChain preWo = preMap.get(item.getWoNo());
  if (preWo!=null){
    item.setIsHead(1);
  }else{
    item.setIsHead(0);
  }
});

2.5 用@see @link来设计RPC的API

程序员们还经常自嘲的几个词有:API工程师,中间件装配工等,既然平时写API写的比较多,那种就把它写到极致@see @link的作用是让使用方可以方便的链接到枚举类型的对象上,方便阅读。

示例如下:

代码语言:javascript
复制
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ContentProcessDto implements Serializable {
    /**
     * 内容ID
     */
    private String contentId;
    /**
     * @see com.jd.jr.community.common.enums.ContentTypeEnum
     */
    private Integer contentType;
    /**
     * @see com.jd.jr.community.common.enums.ContentQualityGradeEnum
     */
    private Integer qualityGrade;
}

2.6 日志打印避免只打整个参数

研发经常为了省事,直接将入参这样打印:

代码语言:javascript
复制
log.info("operateRelationParam:{}", JSONObject.toJSONString(request));

该日志进了日志系统后,研发在搜索日志的时候,很难根据业务主键排查问题

如果改进成以下方式,便可方便的进行日志搜索。

代码语言:javascript
复制
log.info("operateRelationParam,id:{},req:{}", request.getId(),JSONObject.toJSONString(request));

如上:只需要全词匹配“operateRelationParam,id:111”,即可找到业务主键111的业务日志。

2.7用异常捕捉替代方法参数传递

程序员经常面对的一种情况是:从子方法中获取返回的值来标识程序接下来的走向,这种方式笔者认为不够优雅。

举例:以下代码paramCheck和deleteContent方法,返回了这两个方法的执行结果,调用方通过返回结果判断程序走向。

代码语言:javascript
复制
public RpcResult<String> deleteContent(ContentOptDto contentOptDto) {
    log.info("deleteContentParam:{}", contentOptDto.toString());
    try{
        RpcResult<?> paramCheckRet = this.paramCheck(contentOptDto);
        if (paramCheckRet.isSgmFail()){
            return RpcResult.getSgmFail("非法参数:"+paramCheckRet.getMsg());
        }
        ContentOverviewEntity contentEntity = DozerMapperUtil.map(contentOptDto,ContentOverviewEntity.class);
        RpcResult<?> delRet = contentEventHandleAbility.deleteContent(contentEntity);
        if (delRet.isSgmFail()){
            return RpcResult.getSgmFail("业务处理异常:"+delRet.getMsg());
        }
    }catch (Exception e){
        log.error("deleteContent exception:",e);
        return RpcResult.getSgmFail("内部处理错误");
    }
    return RpcResult.getSgmSuccess();
    }

可以通过自定义异常的方式解决:子方法抛出不同的异常,调用方catch不同异常以便进行不同逻辑的处理,这样调用方特别清爽,不必做返回结果判断。

代码示例如下:

代码语言:javascript
复制
public RpcResult<String> deleteContent(ContentOptDto contentOptDto) {
  log.info("deleteContentParam:{}", contentOptDto.toString());
  try{
      this.paramCheck(contentOptDto);
    ContentOverviewEntity contentEntity = DozerMapperUtil.map(contentOptDto,ContentOverviewEntity.class);
    contentEventHandleAbility.deleteContent(contentEntity);    
  }catch(IllegalStateException pe){
    log.error("deleteContentParam error:"+pe.getMessage(),pe);
    return RpcResult.getSgmFail("非法参数:"+pe.getMessage());
  }catch(BusinessException be){
    log.error("deleteContentBusiness error:"+be.getMessage(),be);
    return RpcResult.getSgmFail("业务处理异常:"+be.getMessage());
  }catch (Exception e){
    log.error("deleteContent exception:",e);
    return RpcResult.getSgmFail("内部处理错误");
  }
  return RpcResult.getSgmSuccess();
}

2.8 自定义Spring Boot 的 Banner

别再让Spring Boot启动banner千篇一律,spring 支持自定义banner,该技能对业务功能实现没任何卵用,但会给枯燥的编程生活添加一点乐趣。

以下是官方文档的说明:

https: // docs .spring. io/ spring -boot /docs /1.3.8. RELEASE /reference /htmlsingle /# boot -features - banner

另外还需要ASCII艺术字生成工具:https://tools.kalvinbg.cn/txt/ascii

效果如下:

图1 ASCII艺术字效果图

2.9 多用JAVA语法糖

编程语言中java的语法是相对繁琐的,用过golang的或scala的人感觉特别明显。java提供了10多种语法糖,写代码常使用语法糖,给人一种 “这哥们java用得通透” 的感觉。

举例:try-with-resource语法,当一个外部资源的句柄对象实现了AutoCloseable接口,JDK7中便可以利用try-with-resource语法更优雅的关闭资源,消除板式代码。

代码语言:javascript
复制
try (FileInputStream inputStream = new FileInputStream(new File("test"))) {
    System.out.println(inputStream.read());
} catch (IOException e) {
    throw new RuntimeException(e.getMessage(), e);
}

2.10 利用链式编程

链式编程,也叫级联式编程,调用对象的函数时返回一个this对象指向对象本身,达到链式效果,可以级联调用。链式编程的优点是:编程性强、可读性强、代码简洁。

举例:假如觉得官方提供的容器不够方便,可以自定义,代码如下,但更建议使用开源的经过验证的类库如guava包中的工具类:

代码语言:javascript
复制
/**
    链式map
 */
public class ChainMap<K,V> {
    private Map<K,V> innerMap = new HashMap<>();
    public V get(K key) {
        return innerMap.get(key);
    }

    public ChainMap<K,V> chainPut(K key, V value) {
        innerMap.put(key, value);
        return this;
    }

    public static void main(String[] args) {
        ChainMap<String,Object> chainMap = new ChainMap<>();
        chainMap.chainPut("a","1")
                .chainPut("b","2")
                .chainPut("c","3");
    }
}

2.11 优雅暂停线程

Thread.sleep(long timeout)暂停线程时必须捕获或向上层抛出InterruptedException,同时如果被中断,则满足不了预期的timeout时间,Guava包中有个方法:com. google .common. util. concurrent. Uninterruptibles #sleep Uninterruptibly,可以优雅的让线程暂停,不仅不用关心异常,同时如果被中断,还会补偿,直到time out时间耗尽。

2.12 适时使用元组(tuple)对象

接口返回值往往是成对的,或者是成三的,此时有几种做法:

1、通过map格式返回,

2、自定义新对象将返回的元素包装起来,此两种做法都能解决问题,

但笔者推荐另一种方法:使用元组对象apache common包里内置了二元组和三元组可供使用。

代码语言:javascript
复制
org.apache.commons.lang3.tuple.Pair
org.apache.commons.lang3.tuple.Triple

当需要4元组、5元组的时候,可以去自定义新对象。元组维度太多也不利于代码可维护性。(scala语言内置了1-20个维度的元组供研发使用)。 05 总结

本文立足于编码规范之上,从研发角度探讨如何优雅编程,罗列一些策略,如卫语句使用、注解设计API、异常捕获特殊用法、链式编程等,基于这些策略可以使代码更加优雅易维护。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-04-21 12:00:00,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 京东技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档