如何提升Java代码的可读性

也并不是所有的开源项目的代码可读性都很高,有些为了追求极致的性能损失了部分可读性,如果不知道学习哪个开源项目,那就学习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开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

最后

很多程序员,整天沉浸在业务代码的 CRUD 中,业务中没有大量数据做并发,缺少实战经验,对并发仅仅停留在了解,做不到精通,所以总是与大厂擦肩而过。

我把私藏的这套并发体系的笔记和思维脑图分享出来,理论知识与项目实战的结合,我觉得只要你肯花时间用心学完这些,一定可以快速掌握并发编程。

不管是查缺补漏还是深度学习都能有非常不错的成效,需要的话记得帮忙点个赞支持一下

整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

[外链图片转存中…(img-RV2CyRjK-1712903883384)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-iPoBdQoN-1712903883384)]

最后

很多程序员,整天沉浸在业务代码的 CRUD 中,业务中没有大量数据做并发,缺少实战经验,对并发仅仅停留在了解,做不到精通,所以总是与大厂擦肩而过。

我把私藏的这套并发体系的笔记和思维脑图分享出来,理论知识与项目实战的结合,我觉得只要你肯花时间用心学完这些,一定可以快速掌握并发编程。

不管是查缺补漏还是深度学习都能有非常不错的成效,需要的话记得帮忙点个赞支持一下

整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-4wQh77ii-1712903883384)]

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值