也并不是所有的开源项目的代码可读性都很高,有些为了追求极致的性能损失了部分可读性,如果不知道学习哪个开源项目,那就学习spring-boot项目,下面截图是spring-boot项目中的代码,命名方式值得学习。
使用Optional
▐ 优雅判空
NullPointerException是Java程序员无法言语的痛,为了避免空指针异常,我们通常需要做非常多的防御性编程,if判空是最简单的方式,但是充斥大量着if判空的代码会淹没核心代码逻辑,导致可读性差。
下面举一个例子:
- Optional优化前:
public Long parseUmpActivityId(PlayApplyContext applyContext) {
if (applyContext == null || applyContext.getPlayDetailDO() == null
|| StringUtil.isBlank(applyContext.getPlayDetailDO().getDetail())) {
return null;
}
Map<String, String> playDetailMap = toPlayDetailMap(applyContext.getPlayDetailDO().getDetail());
if (playDetailMap == null) {
return null;
}
String umpActivityIdStr = playDetailMap.get(Constant.UMP_ACTIVITY_ID);
if (StringUtils.isBlank(umpActivityIdStr)) {
return null;
}
return Long.parseLong(umpActivityIdStr);
}
- Optional优化后:
public Long parseUmpActivityId(PlayApplyContext applyContext) {
return Optional.ofNullable(applyContext)
.map(PlayApplyContext::getPlayDetailDO)
.map(PlayDetailDO::getDetail)
.map(this::toPlayDetailMap)
.map(m -> m.get(Constant.UMP_ACTIVITY_ID))
.filter(StringUtils::isNotBlank)
.map(Long::parseLong)
.orElse(null);
}
▐ 分支判断
Optional的orElse具有分支判断的能力,可以在一些情况下代替if,提升代码的可读性,如下场景所示,经过三目运算符的优化依然可读性不强,Optional优化后才具有较高可读性。
- 优化前
…
Result result = apply(juItem);
if (result == null || !result.isSuccess()) {
if (result != null && result.getMsg() != null) {
return Result.buildErrorResult(result.getMsg());
}
return Result.buildErrorResult(“创建失败”);
}
…
- 三目运算符优化后:
…
Result result = apply(juItem);
if (result == null || !result.isSuccess()) {
return Result.buildErrorResult(
result != null && result.getMsg() != null ? result.getMsg() : “创建失败”);
}
…
- Optional优化后:
…
Result result = apply(juItem);
if (result == null || !result.isSuccess()) {
return Result.buildErrorResult(
Optional.ofNullable(result).map(Result::getMsg).orElse(“创建失败”));
}
…
▐ 陷阱
在使用Optional的orElse时候可能会误入陷阱,举一个具体的例子,如下所示的代码存在问题吗?
这段代码的作用是,当传入参数中activity不为空则取传入的activity,否则通过接口根据活动ID查询,避免了无谓的查询。
Result applyActivity(Params params) {
Activity activity = Optional.ofNullable(params)
.map(Params::getActivity)
.orElse(activityManager.findById(params.getActivityId()));
…
}
以上代码存在两个问题,第一,params.getActivityId()可能出现空指针异常,第二,activityManager.findById一定会被调用,无法达到预期的效果。
而这两个问题的根本原因都是因为orElse方法传入的是语句执行之后的结果。
所以在orElse方法中最好不要传入执行语句,而应该是默认值。
上面应该这种情况正确应该使用orElseGet,orElseGet传入的是函数。
▐ 正确换行
Optional方式编程很大程度提升了代码的可读性,写代码如行云流水一般,为了更好的阅读,需要采用正确的换行方式,最好是一行一条Optional语句,如下图所示,这样换行的好处就是,一行做一件事情,阅读流畅。
而且最重要的是IDEA在每条语句后面提示了返回结果的类型,这个提示不仅仅对阅读有帮助,对编写代码也有很大帮助。这个原则同样适用于Lambda表达式的编写。
当然,对于非常简单链式语句可以打破以上原则,比如context.setActivityId(Optional.ofNullable(activityId).orElse(0L));
使用Lambda
关于Lambda表达式编程的好处和用法想必大部分人都清楚,正确使用Lambda表达式可以很大程度提升代码的可读性,但是不正确使用Lambda表达式会给可读性带来更大的灾难。
▐ 拒绝匿名函数
如下函数的功能是根据活动信息获取活动中的所有报名记录,采用了普通的for循环编写,嵌套比较深,代码含义不是很明确,有优化的空间,接下来采用Lambda表达式进行优化。
private List obtainRecords(List campaignList) {
List recordList = Lists.newArrayList();
for (Campaign campaign : campaignList) {
if (campaign.getStartTime() != null && campaign.getStartTime().getTime() < System.currentTimeMillis()
&& campaign.getStatus() > 0) {
Params params = new Params();
params.setCampaignId(campaign.getId());
params.setStartTime(campaign.getStartTime());
params.setStatus(campaign.getStatus());
List originRecordList = campaignRecordFacade.query(params);
for (Record record : originRecordList) {
if ((record.getStatus() <= INIT && PLAY_TYPE.equals(record.getType()))
|| record.getStatus() == AUDIT_PASS) {
recordList.add(record);
}
}
}
}
return recordList;
}
采用Lambda表达式重新编写后如下所示,一定程度上提升了代码的可读性,是否还具有提升空间呢。
其中匿名函数占据了大部分代码逻辑,导致主流程不清晰,在使用Lambda表达式的时候应该尽量不要使用匿名函数。
private List obtainRecords(List campaignList) {
return campaignList.stream()
.filter(campaign -> campaign.getStartTime() != null
&& campaign.getStartTime().getTime() < System.currentTimeMillis() && campaign.getStatus() > 0)
.map(campaign -> {
Params params = new Params();
params.setCampaignId(campaign.getId());
params.setStartTime(campaign.getStartTime());
params.setStatus(campaign.getStatus());
return campaignRecordFacade.query(params);
})
.flatMap(Collection::stream)
.filter(record -> (record.getStatus() <= INIT && PLAY_TYPE.equals(record.getType()))
|| record.getStatus() == AUDIT_PASS)
.collect(Collectors.toList());
}
去除匿名函数优化后如下所示,主流程非常清晰,没有阅读障碍,函数名解释了所做的具体事情,通过阅读函数名而不是具体的代码去了解这块做了什么事情,具体阅读某个函数时,只需要保证代码逻辑符合函数名表达的含义。
private List obtainRecords(List campaignList) {
return campaignList.stream()
.filter(this::isValidAndAlreadyStarted)
.map(this::queryRecords)
.flatMap(Collection::stream)
.filter(this::isInitializedPlayOrAuditPass)
.collect(Collectors.toList());
}
private boolean isValidAndAlreadyStarted(Campaign campaign) {
return campaign.getStartTime() != null
&& campaign.getStartTime().getTime() < System.currentTimeMillis() && campaign.getStatus() > 0;
}
private List queryRecords(Campaign campaign) {
Params params = new Params();
params.setCampaignId(campaign.getId());
params.setStartTime(campaign.getStartTime());
params.setStatus(campaign.getStatus());
return campaignRecordFacade.query(params);
}
private boolean isInitializedPlayOrAuditPass(Record record) {
return (record.getStatus() <= INIT && PLAY_TYPE.equals(record.getType())) || record.getStatus() == AUDIT_PASS;
}
▐ 结合Optional使用
Lambda表达式结合Optional使用可以更加简洁,如下所示查询报名记录后获取报名记录的ID,不使用Optional的时候需要判空等其他操作,Optional让语句更加连贯。
这里需要注意一点,Collections.emptyList()返回的是一个不可变的内部类,不允许添加元素,如果返回的结果需要添加元素,需要使用Lists.newArrayList()。
Optional.ofNullable(playRecordReadService.query(query))
.orElse(Collections.emptyList())
.stream
.fileter(this::isValid)
.map(Record::getId)
.collect(Collectors.toList());
▐ 用好异常
Checked Exception是Lambda表达式的天敌,因为在Lambda表达式中必须捕获Checked Exception,这样会导致Lambda表达式特别累赘。
针对这种情况,在系统内部最好使用Runtime Exception,如果是外部接口申明了Checked Exception,那我们应该在基础设施层将外部接口封装一个facade,facade只抛出Runtime Exception。
有一种系统设计,提倡系统内部接口也使用Result作为返回结果,这种设计导致了很难流畅地使用Lambda表达式,因为你的代码里面会充斥着大量if (!result.isSuccess())的判断,如下代码所示,queryRecordsByCampaign是一个RPC接口,可以看到代码逻辑非常啰嗦,核心逻辑不明确。
Result<List> queryRecordsByCampaign(Campaign campaign) {
Result checkResult = checkCampaign(campaign);
if (!checkResult.isSuccess()) {
return Result.buildErrorResult(checkResult.getErrorMsg());
}
Result contextResult = buildContext(campaign);
if (!contextResult.isSuccess()) {
return Result.buildErrorResult(contextResult.getErrorMsg());
}
Result<List> queryResult = queryRecords(contextResult.getValue());
if (!queryResult.isSuccess()) {
return Result.buildErrorResult(queryResult.getErrorMsg());
}
if (CollectionUtils.isEmpty(queryResult.getValue())) {
return Result.buildSuccessResult(Lists.newArraysList());
}
List records = queryResult.getValue().stream()
.filter(this::isValid)
.map(this::compensateRecord)
.collect(Collectors.toList());
return Result.buildSuccessResult(records);
}
private Result checkCampaign(Campaign campaign) {
if (campaign == null) {
return Result.buildErrorResult(“活动不能为空”);
}
if (campaign.getId <= 0) {
return Result.buildErrorResult(“活动ID非法”);
}
return Result.buildSuccessResult();
}
另外一种系统设计,提倡系统内部使用Runtime Exception控制异常流程,RPC接口不抛任何异常,使用Result表示返回结果。
上面的代码经过这种思想修改后的代码如下所示,代码简洁明了,Optional与Lambda完美配合。
其中关于参数校验和断言可以参考apache工具包中的Validate设计适合自己应用的工具类,通过Validate做校验非常简洁,并且可以自定义ExceptionCode来区分错误类型。
但是,一定不要使用异常来控制正常流程。
Result<List> queryRecordsByCampaign(Campaign campaign) {
try {
checkCampaign(campaign);
List records = Optional.ofNullable(campaign)
.map(this::buildContext)
.map(this::queryRecords)
.orElse(Collections.emptyList())
.stream()
.filter(this::isValid)
.map(this::compensateRecord)
.collect(Collectors.toList());
return Result.buildSuccessResult(records);
} catch (Throwable t) {
log.error("an exception occurs ", t)
return Result.buildErrorResult(t.getMessage());
}
}
private void checkCampaign(Campaign campaign) {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
最后
很多程序员,整天沉浸在业务代码的 CRUD 中,业务中没有大量数据做并发,缺少实战经验,对并发仅仅停留在了解,做不到精通,所以总是与大厂擦肩而过。
我把私藏的这套并发体系的笔记和思维脑图分享出来,理论知识与项目实战的结合,我觉得只要你肯花时间用心学完这些,一定可以快速掌握并发编程。
不管是查缺补漏还是深度学习都能有非常不错的成效,需要的话记得帮忙点个赞支持一下
整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-RV2CyRjK-1712903883384)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-iPoBdQoN-1712903883384)]
最后
很多程序员,整天沉浸在业务代码的 CRUD 中,业务中没有大量数据做并发,缺少实战经验,对并发仅仅停留在了解,做不到精通,所以总是与大厂擦肩而过。
我把私藏的这套并发体系的笔记和思维脑图分享出来,理论知识与项目实战的结合,我觉得只要你肯花时间用心学完这些,一定可以快速掌握并发编程。
不管是查缺补漏还是深度学习都能有非常不错的成效,需要的话记得帮忙点个赞支持一下
整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-4wQh77ii-1712903883384)]