阿里Java架构师教你写代码-如何校验参数?

开发 前端
本篇教你写代码-如何校验参数?理解参数校验的意义和运用。

[[348391]]

 1 参数校验的意义

 

大多数方法会限制传递给它们的参数值。常见的比如,索引值非负,引用非空。作为优雅的开发者,应做到:

  1. 在Java Doc中清楚地记录这些限制,并在方法体开头校验
  2. 在错误发生后尽快找到。若不这样做,就不太可能检测到错误,而且即使检测到错误,确定其源头也很难

若一个无效参数被传递给一个方法,若该方法

  1. 校验参数,方法将迅速失败,并抛异常
  2. 未校验参数,可能会在方法执行过程中发生如下情形:
  • 莫名其妙的异常而失败
  • 正常返回,但会暗中计算错误结果
  • 正常返回,但会使某对象处于隐患状态,可能在未来某不确定时间在某不相关代码点报错。

总之,若不校验参数,可能会违反失败原子性。

对public、protected方法,要在方法说明使用 Javadoc 的 @throws 标签说明,若违反参数值限制时会抛出的异常。通常为 IllegalArgumentException、IndexOutOfBoundsException 或 NullPointerException。一旦在文档中记录了参数限制,并且记录违反这些限制将引发的异常,强加这些限制就很简单了。

看案例:

文档注释并没说「若 m 为空,mod 将抛NPE」,然而方法确实做了,只是作为调用 m.signum() 的副产物。该异常记录在外围 BigInterger 类级别的文档注释。类级别注释适用于类的所有public方法中的所有参数。可以避免在每个方法上分别记录每个 NullPointerException 而造成杂糅。
可与 @Nullable 或类似注解协作,指示某参数可能为 null,但这种做法并非标准,而且使用了多个注解。

2 最佳实践

 

Java 7 提供 Objects.requireNonNull 不再需手动执行空检查。

如果愿意,还可自定义异常详情。该方法返回其输入,所以使用一个值的同时可执行判空:

  1. // Java 内置的判空功能 
  2. this.strategy = Objects.requireNonNull(strategy, "strategy"); 

也可以忽略返回值并使用 Objects.requireNonNull 作为一个独立判空方法。

3 边界检查

 

在 Java 9 中,边界检查功能被添加到 java.util.Objects。该功能由三个方法组成:

checkFromIndexSize

checkFromToIndex

checkIndex

该套工具不如判空方法灵活。它不允许自定义异常详细信息,仅适用于 List 和数组索引,且不处理封闭范围(包含两个端点)。

4 断言

 

对于未暴露的方法,作为包开发者,你应该控制方法在何时能被调用,因此你可以并且也应该确保只传入有效参数值。因此,非public方法可使用断言检查入参:

 

从本质上说,这些断言是在声称被断言的条件为 true,而不管客户端如何调用。与普通校验不同的是:

 

  • 若断言失败,会抛 AssertionError
  • 若断言没有作用,本质上不存在成本,除非通过将 -ea或 -enableassertion标识传递给 java 命令来启用它们

 

 

静态工厂方法

 

尤其应检查那些尚未由方法调用,而是存起供日后使用的参数的有效性。例如静态工厂方法,它接受 int 数组并返回数组的 List 视图。若客户端传入 null,将抛 NullPointerException,因为该方法具有显式检查(调用 Objects.requireNonNull)。如果省略检查,该将返回对新创建的 List 实例的引用,该实例将在客户端试图使用它时抛出 NullPointerException。到那时,List 实例的起源很难确定,使调试变得复杂。
构造器就是一种特殊情况。务必检查构造器入参有效性,避免构造生成实例对象时,违背对象的不变性。

 

例外

 

在执行方法前,应显式检查参数,也有例外 - 有效性检查成本较高或不切实际,或检查在计算过程中隐式执行了。
例如,一个为对象 List 排序的方法,比如 Collections.sort(List)。List 中的所有对象必须相互比较。在对 List 排序的过程中,List 中的每个对象都会与列表中的其他对象进行比较。如果对象不能相互比较,将抛出 ClassCastException,这正是 sort 方法应该做的。因此,没有必要预先检查列表中的元素是否具有可比性。但不加区别地依赖隐式有效性检查可能导致失败原子性的丢失。

有时,计算任务会隐式地执行所需的有效性检查,但如果检查失败,则抛出错误的异常。即计算任务由于无效参数值所抛异常,与文档中记录的方法要抛出的异常不匹配。此时应该使用异常转换将计算任务抛出的异常转换为正确的异常。

5 总结

 

请勿从本文自以为对参数的限制永远都是好事。我们追求的是通用又实用的方法设计。若该方法可对它所接受的所有参数值进行合理的处理,那么对参数所加限制越少越好。

建议你每次编写方法前,考虑清楚参数存在哪些限制。在文档中记录这些限制并在方法主体的开头显式检查。养成这样的习惯!这一少量工作将在校验出现失败时给你一片春光!

参考

《阿里 Java 开发手册》

《重构》

《Effective Java》

 

责任编辑:姜华 来源: JavaEdge
相关推荐

2019-02-22 10:00:45

Java开发代码

2016-12-22 23:55:40

架构师代码技术

2020-10-26 11:41:47

kill代码

2020-11-03 09:10:18

JUC-Future

2021-02-01 07:40:55

架构师阿里技专家

2019-07-22 22:22:02

架构运维技术

2020-06-28 08:34:07

架构师阿里软件

2020-01-16 15:35:00

高并发架构服务器

2019-10-31 09:52:03

Android代码规范

2019-07-31 07:36:12

架构运维技术

2009-02-26 16:32:58

SaaS开发SaaS应用Open API

2019-08-22 10:54:05

分布式系统架构

2020-07-21 08:00:44

架构师BAT线程

2011-04-07 16:20:24

软件架构师架构师架构

2021-02-25 11:30:17

代码开发技术

2021-06-07 09:35:11

架构运维技术

2018-07-03 15:46:24

Java架构师源码

2012-08-04 16:02:00

架构师

2020-08-27 08:54:02

脚本架构师Linux

2017-09-16 18:29:00

代码数据库线程
点赞
收藏

51CTO技术栈公众号