zoukankan      html  css  js  c++  java
  • 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年加入阿里巴巴,一直从事地图数据采集的相关工作。

    原文链接

    本文为云栖社区原创内容,未经允许不得转载。

  • 相关阅读:
    win10 uwp 商业游戏 1.1.5
    PHP ftp_exec() 函数
    PHP ftp_delete() 函数
    PHP ftp_connect() 函数
    PHP ftp_close() 函数
    PHP ftp_chmod() 函数
    grant 之后是否要跟着 flush privileges
    [TJOI2015]概率论
    win10 uwp 商业游戏 1.1.5
    Total Commander 显示文件包含文件名扩展
  • 原文地址:https://www.cnblogs.com/zhaowei121/p/11419568.html
Copyright © 2011-2022 走看看