作者:京东科技 文涛
前语
软件研制工程师俗称程序员常常对业界外的人自谦作码农,一来给自己不菲的收入找个不错的说辞(像农人伯伯那样辛勤耕耘挣来的血汗钱),二来也是自嘲这个行业的确辛苦,辛苦得没时间捯饬,乃至没有驼背、掉发加持都说不过去。不过时间久了,行外人还真就信任了程序员便是一帮没品尝,木讷的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
@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;
}
日志打印防止只打整个参数
研制常常为了省劲,直接将入参这样打印
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,该技能对事务功用完成没任何卵用,但会给单调的编程日子增加一点乐趣。
以下是官方文档的说明: docs.spring.io/spring-boot…
另外你还需要ASCII艺术字生成东西: 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");
}
}
未完,待续,欢迎谈论区弥补