Java 函数优雅之道
导读
随着软件项目代码的日积月累,系统维护成本变得越来越高,是所有软件团队面临的共同问题。持续地优化代码,提高代码的质量,是提升系统生命力的有效手段之一。软件系统思维有句话“Less coding, more thinking(少编码、多思考)”,也有这么一句俚语“Think more, code less(思考越多,编码越少)”。所以,我们在编码中多思考多总结,努力提升自己的编码水平,才能编写出更优雅、更高质、更高效的代码。
本文总结了一套与Java函数相关的编码规则,旨在给广大Java程序员一些编码建议,有助于大家编写出更优雅、更高质、更高效的代码。这套编码规则,通过在高德采集部门的实践,已经取得了不错的成效。
1.使用通用工具函数
案例1: 字符串比较
现象描述:
不完善的写法:
更完善的写法:
建议方案:
案例2: 判断列表为空
现象描述:
建议方案:
主要收益
- 函数式编程,业务代码减少,逻辑一目了然;
- 通用工具函数,逻辑考虑周全,出问题概率低。
2.拆分超大函数
当一个函数超过80行后,就属于超大函数,需要进行拆分。
案例1: 每一个代码块都可以封装为一个函数
每一个代码块必然有一个注释,用于解释这个代码块的功能。
如果代码块前方有一行注释,就是在提醒你——可以将这段代码替换成一个函数,而且可以在注释的基础上给这个函数命名。如果函数有一个描述恰当的名字,就不需要去看内部代码究竟是如何实现的。
现象描述:
建议方案:
案例2: 每一个循环体都可以封装为一个函数
现象描述:
建议方案:
案例3: 每一个条件体都可以封装为一个函数
现象描述:
建议方案:
主要收益
- 函数越短小精悍,功能就越单一,往往生命周期较长;
- 一个函数越长,就越不容易理解和维护,维护人员不敢轻易修改;
- 在过长函数中,往往含有难以发现的重复代码。
3.同一函数内代码块级别尽量一致
案例1: 代码块级别尽量保持一致
现象描述:
很明显,睡觉这块代码块,跟eat(吃饭)和code(编码)不在同一级别上,显得比较突兀。如果把写代码比作写文章,eat(吃饭)和code(编码)是段落大意,而睡觉这块代码块属于一个详细段落。而在liveDaily(每日生活)这个函数上,只需要写出主要流程(段落大意)即可。
建议方案:
主要收益
- 函数调用表明用途,函数实现表达逻辑,层次分明便于理解;
- 不用层次的代码块放在一个函数中,容易让人觉得代码头重脚轻。
4.封装相同功能代码为函数
案例1: 封装相同代码为函数
现象描述:
建议方案:
案例2: 封装相似代码为函数
封装相似代码为函数,差异性通过函数参数控制。
现象描述:
建议方案:
主要收益
- 封装公共函数,减少代码行数,提高代码质量;
- 封装公共函数,使业务代码更精炼,可读性可维护性更强。
5.封装获取参数值函数
案例1: 把获取参数值封装为函数
现象描述:
建议方案:
主要收益
- 把获取参数值从业务函数中独立,使业务逻辑更清晰;
- 封装的获取参数值为独立函数,可以在代码中重复使用。
6.通过接口参数化封装相同逻辑
案例1: 通过接口参数化封装相同逻辑
现象描述:
建议方案:
主要收益
- 把核心逻辑从各个业务函数中抽析,使业务代码更清晰更易维护;
- 避免重复性代码多次编写,精简重复函数越多收益越大。
7.减少函数代码层级
如果要使函数优美,建议函数代码层级在1-4之间,过多的缩进会让函数难以阅读。
案例1: 利用return提前返回函数
现象描述:
建议方案:
案例2: 利用continue提前结束循环
现象描述:
建议方案:
特殊说明:
其它方式:在循环体中,先调用案例1的函数getUserBalance(获取用户余额),再进行对余额进行累加。
在循环体中,建议最多使用一次continue。如果需要有使用多次continue的需求,建议把循环体封装为一个函数。
案例3: 利用条件表达式函数减少层级
请参考下一章的"案例2: 把复杂条件表达式封装为函数"
主要收益
- 代码层级减少,代码缩进减少;
- 模块划分清晰,方便阅读维护。
8.封装条件表达式函数
案例1: 把简单条件表达式封装为函数
现象描述:
建议方案:
案例2: 把复杂条件表达式封装为函数
现象描述:
建议方案:
以上代码也可以用采用流式(Stream)编程的过滤来实现。
主要收益
- 把条件表达式从业务函数中独立,使业务逻辑更清晰;
- 封装的条件表达式为独立函数,可以在代码中重复使用。
9.尽量避免不必要的空指针判断
本章只适用于项目内部代码,并且是自己了解的代码,才能够尽量避免不必要的空指针判断。对于第三方中间件和系统接口,必须做好空指针判断,以保证代码的健壮性。
案例1: 调用函数保证参数不为空,被调用函数尽量避免不必要的空指针判断
现象描述:
建议方案:
案例2: 被调用函数保证返回不为空,调用函数尽量避免不必要的空指针判断
现象描述:
建议方案:
案例3: 赋值逻辑保证列表数据项不为空,处理逻辑尽量避免不必要的空指针判断
现象描述:
建议方案:
案例4: MyBatis查询函数返回列表和数据项不为空,可以不用空指针判断
MyBatis是一款优秀的持久层框架,是在项目中使用的最广泛的数据库中间件之一。通过对MyBatis源码进行分析,查询函数返回的列表和数据项都不为空,在代码中可以不用进行空指针判断。
现象描述:
这种写法没有问题,只是过于保守了。
建议方案:
主要收益
- 避免不必要的空指针判断,精简业务代码处理逻辑,提高业务代码运行效率;
- 这些不必要的空指针判断,基本属于永远不执行的Death代码,删除有助于代码维护。
10.内部函数尽量使用基础类型
案例1: 内部函数参数尽量使用基础类型
现象描述:
建议方案:
案例2: 内部函数返回值尽量使用基础类型
现象描述:
建议方案:
此处只是举例说明这种现象,更好的方式是采用流式(Stream)编程。
主要收益
- 内部函数尽量使用基础类型,避免了隐式封装类型的打包和拆包;
- 内部函数参数使用基础类型,用语法上避免了内部函数的参数空指针判断;
- 内部函数返回值使用基础类型,用语法上避免了调用函数的返回值空指针判断。
11.尽量避免返回的数组和列表为null
案例1: 尽量避免返回的数组为null,引起不必要的空指针判断
现象描述:
建议方案:
案例2: 尽量避免返回的列表为null,引起不必要的空指针判断
现象描述:
建议方案:
主要收益
- 保证返回的数组和列表不为null, 避免调用函数的空指针判断。
12.封装函数传入参数
案例1: 当传入参数过多时,应封装为参数类
Java规范不允许函数参数太多,不便于维护也不便于扩展。
现象描述:
建议方案:
案例2: 当传入成组参数时,应封装为参数类
既然参数成组出现,就需要封装一个类去描述这种现象。
现象描述:
建议方案:
主要收益
- 封装过多函数参数为类,使函数更便于扩展和维护;
- 封装成组函数参数为类,使业务概念更明确更清晰。
13.尽量用函数替换匿名内部类的实现
Java匿名内部类的优缺点:
在匿名内部类(包括Lambda表达式)中可以直接访问外部类的成员,包括类的成员变量、函数的内部变量。正因为可以随意访问外部变量,所以会导致代码边界不清晰。
首先推荐用Lambda表达式简化匿名内部类,其次推荐用函数替换复杂的Lambda表达式的实现。
案例1: 尽量用函数替换匿名内部类(包括Lambda表达式)的实现
现象描述:
建议方案:
其实,还有一个更简单的办法。在调用函数sendWorkerSettleData(发送作业员结算数据)之前计算开始日期、结束日期,就直接可以用函数auditTaskDAO.statCheckerSettleData(beginDate, endDate)代替匿名内部类实现。
案例2: 拆分复杂匿名内部类实现接口为多个函数式接口
如果一个匿名内部类实现的接口几个函数间关联性不大,可以把这个接口拆分为几个函数式接口,便于使用Lambda表达式。
现象描述:
建议方案:
主要收益
- 定义函数并指定参数,明确规定了匿名内部类的代码边界;
- 利用Lambda表达式简化匿名内部类实现,使代码更简洁。
14.利用return精简不必要的代码
案例1: 删除不必要的if
现象描述:
建议方案:
案例2: 删除不必要的else
现象描述:
建议方案:
案例3: 删除不必要的变量
现象描述:
建议方案:
主要收益
- 精简不必要的代码,让代码看起来更清爽。
15.利用临时变量优化代码
在一些代码中,经常会看到a.getB().getC()...getN()的写法,姑且叫做“函数的级联调用”,代码健壮性和可读性太差。建议:杜绝函数的级联调用,利用临时变量进行拆分,并做好对象空指针检查。
案例1: 利用临时变量厘清逻辑
现象描述:
这是精简代码控的最爱,但是可读性实在太差。
建议方案:
这个方案,增加了代码行数,但是逻辑更清晰。
有时候,当代码的精简性和可读性发生冲突时,个人更偏向于保留代码的可读性。
案例2: 利用临时变量精简代码
现象描述:
这么写,大概是为了节约一个临时变量吧。
建议方案:
主要收益
- 利用临时变量厘清逻辑,显得业务逻辑更清晰;
- 利用临时变量精简代码,看变量名称即知其义,减少了大量无用代码;
- 如果获取函数比较复杂耗时,利用临时变量可以提高运行效率;
- 利用临时变量避免函数的级联调用,可有效预防空指针异常。
16.仅保留函数需要的参数
案例1: 删除多余的参数
现象描述:
其中,unused参数是无用参数。
建议方案:
案例2: 用属性取代对象
现象描述:
建议方案:
建议方案:
调用函数时,参数对象不需要专门构建,而函数使用其属性超过3个,可以不必使用该规则。
主要收益
- 仅保留函数需要的参数,明确了调用时需要赋值的参数,避免了调用时还要去构造些无用参数。
17.仅在需要时才定义变量
案例1: 仅在需要时才定义变量
一些具有VB、C++、PLSQL编程经验的人,习惯于把函数所需的变量定义在函数体最前面。
现象描述:
建议方案:
主要收益
- 变量在使用时定义,更容易让人理解变量的含义;
- 如果函数存在提前返回语句,避免了一些不必要的变量定义和初始化。
18.后记
"众人拾柴火焰高"。如果有更多更好的观点,亦或有更好的代码案例,欢迎大家进行补充说明。
笔者希望以此文抛砖引玉,如果最终形成一套完善的Java编码规范,善莫大焉。
本文作者:陈昌毅,花名常意,高德地图技术专家,2018年加入阿里巴巴,一直从事地图数据采集的相关工作。
本文为云栖社区原创内容,未经允许不得转载。
Java 函数优雅之道的更多相关文章
- 编码规范 | Java函数优雅之道(上)
导读 随着软件项目代码的日积月累,系统维护成本变得越来越高,是所有软件团队面临的共同问题.持续地优化代码,提高代码的质量,是提升系统生命力的有效手段之一.软件系统思维有句话“Less coding, ...
- 编码规范 | Java函数优雅之道(下)
上文背景 本文总结了一套与Java函数相关的编码规则,旨在给广大Java程序员一些编码建议,有助于大家编写出更优雅.更高质.更高效的代码. 内部函数参数尽量使用基础类型 案例一:内部函数参数尽量使用基 ...
- Java函数优雅之道
https://www.cnblogs.com/amap_tech/p/11320171.html 导读 随着软件项目代码的日积月累,系统维护成本变得越来越高,是所有软件团队面临的共同问题.持续地优化 ...
- Java并发编程75道面试题及答案
1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程(Daemon)和用户线程(User). 任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon( ...
- 怎样用Java 8优雅的开发业务
怎样用Java 8优雅的开发业务 目录 怎样用Java 8优雅的开发业务 函数式编程 流式编程 基本原理 案例 优雅的空处理 新的并发工具类CompletableFuture 单机批处理多线程执行模型 ...
- java函数参数默认值
java函数参数默认值 今天,需要设定java函数参数的默认值,发现按照其它语言中的方法行不通 java中似乎只能通过函数的重载来实现 函数参数默认代码
- JS调用Java函数--DWR框架
(1)dwr与ssh框架整合教程dwr框架介绍. DWR(Direct Web Remoting)是一个用于改善web页面与Java类交互的远程服务器端Ajax开源框架,可以帮助开发人员开发包含AJA ...
- Java函数参数传递方式详解
在阅读本文之前,根据自己的经验和理解,大家可以先思考并选择一下Java函数的参数传递方式: A. 是按值传递的? B. 按引用传递的? C. 部分按值部分按引用? 此处暂不宣布正确答案,我们通过一个简 ...
- JAVA函数的参数传递
JAVA开发过程中写函数都是不可避免的于是乎参数传递经常会困扰我们,特别是有C/C++功底的童鞋总会纠结于"java到底是值传递还是引用传递?" 先来一段代码(和程序员交流最好的果 ...
随机推荐
- Spring JdbcTemplate详解(9)
JdbcTemplate简介 Spring对数据库的操作在jdbc上面做了深层次的封装,使用spring的注入功能,可以把DataSource注册到JdbcTemplate之中. JdbcTempla ...
- Expression表达式 实现and、or搜索
用法: [HttpPost] public ActionResult GetBannerList(int pageIndex, int pageSize, string search) { Resul ...
- day2-元组、字典、文件操作
学习内容: 1. 元组操作 2. 字典操作 3. 文件操作 4. 深浅copy 1. 元组操作: 元组和列表非常相似,只不过元组不能在原处修改(它是不可变的),并且通常写成圆括号中的一系列项. # 元 ...
- 廖雪峰Java13网络编程-3其他-2RMI远程调用
1.RMI远程调用: Remote Method Invocation 目的:把一个接口方法暴露给远程 示例: 定义一个接口Clock,它有一个方法能够获取当前的时间,并编写一个实现类,来实现这个接口 ...
- Java驼峰和下划线互相转化
直接上代码 : package com.utils; public class ChangeChar { public static final char UNDERLINE = '_'; publi ...
- css过渡属性transition简单示例
2.transition 简单实例 demo1→在线预览源代码 效果 demo2→在线预览源代码 效果 demo3→在线预览源代码 效果
- 安装Tengine和Tengine说明
什么是Tengine 官方帮助文档:http://tengine.taobao.org/nginx_docs/cn/ Tengine的安装 新建tengine用户组 groupadd -r n ...
- 通过挂钩NtCreateSection监控可执行模块
通过挂钩 NtCreateSection 监控可执行模块 在 Win32 中,我们使用 CreateFileMapping 来创建映射文件对象,函数原型如下: HANDLE CreateFileMap ...
- Linux 的文件和目录管理类命令
文件和目录管理类的命令 目录: cd ~[username] ~:指定用户的家目录 cd -:回到上一次所在的目录 路径: 相对路径中: .:当前目录 ..:父目录 pwd: print workin ...
- Data too long for column
一篇文章的正文,需要很多字数,默认就是255,不够 @Lob @Basic @Type(type = "text") @Column(name = "detail&quo ...