写点什么

码农如何提高自己的品味

  • 2023-02-08
    北京
  • 本文字数:4275 字

    阅读完需:约 14 分钟

码农如何提高自己的品味

作者:京东科技 文涛

前言

软件研发工程师俗称程序员经常对业界外的人自谦作码农,一来给自己不菲的收入找个不错的说辞(像农民伯伯那样辛勤耕耘挣来的血汗钱),二来也是自嘲这个行业确实辛苦,辛苦得没时间捯饬,甚至没有驼背、脱发加持都说不过去。不过时间久了,行外人还真就相信了程序员就是一帮没品味,木讷的 low 货,大部分的文艺作品中也都是这么表现程序员的。可是我今天要说一下我的感受,编程是个艺术活,程序员是最聪明的一群人,我们的品味也可以像艺术家一样。


言归正转,你是不是以为我今天要教你穿搭?不不不,这依然是一篇技术文章,想学穿搭女士学陈舒婷(《狂飙》中的大嫂),男士找陈舒婷那样的女朋友就好了。笔者今天教你怎样有“品味”的写代码。


以下几点可提升“品味”

说明:以下是笔者的经验之谈具有部分主观性,不赞同的欢迎拍砖,要想体系化提升编码功底建议读《XX 公司 Java 编码规范》、《Effective Java》、《代码整洁之道》。以下几点部分具有通用性,部分仅限于 java 语言,其它语言的同学绕过即可。

优雅防重

关于成体系的防重讲解,笔者之后打算写一篇文章介绍,今天只讲一种优雅的方式:


如果你的业务场景满足以下两个条件:


1 业务接口重复调用的概率不是很高


2 入参有明确业务主键如:订单 ID,商品 ID,文章 ID,运单 ID 等


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


如何监测到重复提交呢?MySQL 唯一索引 + org.springframework.dao.DuplicateKeyException


代码如下:


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

用好 lambda 表达式

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


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


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

用好卫语句

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


下面举例说明:


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


if (title.equals(newTitle)){  if (...) {    if (...) {      if (...) {
} }else{ } }else{ }}
复制代码


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


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

避免双重循环

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


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


1 将列表 1 进行 map 化


2 循环列表 2,从 map 中获取值


代码示例如下:


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);  }});
复制代码

@see @link 来设计 RPC 的 API

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


示例如下:


@Data@Builder@NoArgsConstructor@AllArgsConstructorpublic 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;}
复制代码

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

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


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


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


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


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


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

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

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


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


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 不同异常以便进行不同逻辑的处理,这样调用方特别清爽,不必做返回结果判断


代码示例如下:


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();}
复制代码

自定义 SpringBoot 的 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


效果如下:


   _ _                   _                     _                 _         (_|_)_ __   __ _    __| | ___  _ __   __ _  | |__   ___   ___ | |_ ___   | | | '_ \ / _` |  / _` |/ _ \| '_ \ / _` | | '_ \ / _ \ / _ \| __/ __|  | | | | | | (_| | | (_| | (_) | | | | (_| | | |_) | (_) | (_) | |_\__ \ _/ |_|_| |_|\__, |  \__,_|\___/|_| |_|\__, | |_.__/ \___/ \___/ \__|___/|__/         |___/                     |___/                             
复制代码

多用 Java 语法糖

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


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


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

利用链式编程

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


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


/**    链式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"); }}
复制代码


未完,待续,欢迎评论区补充

发布于: 刚刚阅读数: 5
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
码农如何提高自己的品味_Java_京东科技开发者_InfoQ写作社区