Validate method arguments
方法的第一行往往都是检查参数的合法性,其理念就是越早失败越好,这对于构造函数尤其重要。
对于private的方法, 跳过参数合法行检查策略是一个合理的策略, 因为private的方法只有被类自身调用,类作者应该能够保证调用private方法时参数的有效性。
如果有必要,可在private方法中可以用assert这个关键字来检查参数,用来保证类内一致性。(不推荐用assert来检查非private方法,
因为禁止assertion意味着在非private方法里的检查不再是强制的)
当检查一个参数时,如果检查失败(不符合要求),会抛出异常。 一般是下面几种unchecked exceptions:
IllegalArgumentException
NullPointerException
IllegalStateException
他们都是RuntimeException的子类.
Checked exceptions也可以抛出,可以参考 Model Object validation里面的主题. 许多程序员会在方法里对应的javadoc @throws里标明这些异常,
这样就对调用者明确的声明了方法的要求(或前提). 否则, 手工标注的这些异常应该避免。
如果每一个方法中的每一个参数对象都必须不为null以避免NullPointerException,那么在类的javadoc说明一下而不是在每一个方法中说明是可以接受的。
另一种方法是在overview.html中说明,所有的参数必须不为null,除非显式的说明.
例1
本类的构造函数先检查所有的参数,然后才做其他的事,如果检查失败,则抛出IllegalArgumentException.
public final class ElementaryParticle {
/**
@param aName has content.
@param aSpeed is in the range 0 (inclusive) to 1 (inclusive), and
is expressed as a fraction of the speed of light. (The photon is
an example of an elementary particle which travels at this speed.)
@exception IllegalArgumentException if a param does not comply.
*/
public ElementaryParticle (String aName, double aSpeed) {
if (!textHasContent(aName)) {
throw new IllegalArgumentException("Name has no content.");
}
if (aSpeed < 0.0 || aSpeed > 1.0) {
throw new IllegalArgumentException("Speed not in range [0..1]: " + aSpeed);
}
fName = aName;
fSpeed = aSpeed;
}
//..other methods elided
// PRIVATE
private String fName;
private double fSpeed;
/**
Returns true if aText is non-null and has visible content.
This is a test which is often performed, and should probably
be placed in a general utility class.
*/
private boolean textHasContent(String aText){
String EMPTY_STRING = "";
return (aText != null) && (!aText.trim().equals(EMPTY_STRING));
}
}
例2
一些检查是非常常见的:
检查一些对象是否为null
检查文本是否含有可见内容
检查一个数字是否在指定的范围
提供一个类来做这些检查是十分有用的。在本例中, 如果一个对应的bool检查失败,Args类会抛出IllegalArgumentException. 用Args类来检查参数会
提高可读性,尤其在有很多检查的时候.
package hirondelle.web4j.util;
import java.util.regex.*;
/**
Utility methods for common argument validations.
<P>Replaces <tt>if</tt> statements at the start of a method with
more compact method calls.
<P>Example use case.
<P>Instead of :
<PRE>
public void doThis(String aText){
if (!Util.textHasContent(aText)){
throw new IllegalArgumentException();
}
//..main body elided
}
</PRE>
<P>One may instead write :
<PRE>
public void doThis(String aText){
Args.checkForContent(aText);
//..main body elided
}
</PRE>
*/
public final class Args {
/**
If <code>aText</code> does not satisfy {@link Util#textHasContent}, then
throw an <code>IllegalArgumentException</code>.
<P>Most text used in an application is meaningful only if it has visible content.
*/
public static void checkForContent(String aText){
if( ! Util.textHasContent(aText) ){
throw new IllegalArgumentException("Text has no visible content");
}
}
/**
If {@link Util#isInRange} returns <code>false</code>, then
throw an <code>IllegalArgumentException</code>.
@param aLow is less than or equal to <code>aHigh</code>.
*/
public static void checkForRange(int aNumber, int aLow, int aHigh) {
if ( ! Util.isInRange(aNumber, aLow, aHigh) ) {
throw new IllegalArgumentException(aNumber + " not in range " + aLow + ".." + aHigh);
}
}
/**
If <tt>aNumber</tt> is less than <tt>1</tt>, then throw an
<tt>IllegalArgumentException</tt>.
*/
public static void checkForPositive(int aNumber) {
if (aNumber < 1) {
throw new IllegalArgumentException(aNumber + " is less than 1");
}
}
/**
If {@link Util#matches} returns <tt>false</tt>, then
throw an <code>IllegalArgumentException</code>.
*/
public static void checkForMatch(Pattern aPattern, String aText){
if (! Util.matches(aPattern, aText)){
throw new IllegalArgumentException(
"Text " + Util.quote(aText) + " does not match '" +aPattern.pattern()+ "'"
);
}
}
/**
If <code>aObject</code> is null, then throw a <code>NullPointerException</code>.
<P>Use cases :
<pre>
doSomething( Football aBall ){
//1. call some method on the argument :
//if aBall is null, then exception is automatically thrown, so
//there is no need for an explicit check for null.
aBall.inflate();
//2. assign to a corresponding field (common in constructors):
//if aBall is null, no exception is immediately thrown, so
//an explicit check for null may be useful here
Args.checkForNull( aBall );
fBall = aBall;
//3. pass on to some other method as parameter :
//it may or may not be appropriate to have an explicit check
//for null here, according the needs of the problem
Args.checkForNull( aBall ); //??
fReferee.verify( aBall );
}
</pre>
*/
public static void checkForNull(Object aObject) {
if (aObject == null) {
throw new NullPointerException();
}
}
// PRIVATE
private Args(){
//empty - prevent construction
}
}
方法的第一行往往都是检查参数的合法性,其理念就是越早失败越好,这对于构造函数尤其重要。
对于private的方法, 跳过参数合法行检查策略是一个合理的策略, 因为private的方法只有被类自身调用,类作者应该能够保证调用private方法时参数的有效性。
如果有必要,可在private方法中可以用assert这个关键字来检查参数,用来保证类内一致性。(不推荐用assert来检查非private方法,
因为禁止assertion意味着在非private方法里的检查不再是强制的)
当检查一个参数时,如果检查失败(不符合要求),会抛出异常。 一般是下面几种unchecked exceptions:
IllegalArgumentException
NullPointerException
IllegalStateException
他们都是RuntimeException的子类.
Checked exceptions也可以抛出,可以参考 Model Object validation里面的主题. 许多程序员会在方法里对应的javadoc @throws里标明这些异常,
这样就对调用者明确的声明了方法的要求(或前提). 否则, 手工标注的这些异常应该避免。
如果每一个方法中的每一个参数对象都必须不为null以避免NullPointerException,那么在类的javadoc说明一下而不是在每一个方法中说明是可以接受的。
另一种方法是在overview.html中说明,所有的参数必须不为null,除非显式的说明.
例1
本类的构造函数先检查所有的参数,然后才做其他的事,如果检查失败,则抛出IllegalArgumentException.
public final class ElementaryParticle {
/**
@param aName has content.
@param aSpeed is in the range 0 (inclusive) to 1 (inclusive), and
is expressed as a fraction of the speed of light. (The photon is
an example of an elementary particle which travels at this speed.)
@exception IllegalArgumentException if a param does not comply.
*/
public ElementaryParticle (String aName, double aSpeed) {
if (!textHasContent(aName)) {
throw new IllegalArgumentException("Name has no content.");
}
if (aSpeed < 0.0 || aSpeed > 1.0) {
throw new IllegalArgumentException("Speed not in range [0..1]: " + aSpeed);
}
fName = aName;
fSpeed = aSpeed;
}
//..other methods elided
// PRIVATE
private String fName;
private double fSpeed;
/**
Returns true if aText is non-null and has visible content.
This is a test which is often performed, and should probably
be placed in a general utility class.
*/
private boolean textHasContent(String aText){
String EMPTY_STRING = "";
return (aText != null) && (!aText.trim().equals(EMPTY_STRING));
}
}
例2
一些检查是非常常见的:
检查一些对象是否为null
检查文本是否含有可见内容
检查一个数字是否在指定的范围
提供一个类来做这些检查是十分有用的。在本例中, 如果一个对应的bool检查失败,Args类会抛出IllegalArgumentException. 用Args类来检查参数会
提高可读性,尤其在有很多检查的时候.
package hirondelle.web4j.util;
import java.util.regex.*;
/**
Utility methods for common argument validations.
<P>Replaces <tt>if</tt> statements at the start of a method with
more compact method calls.
<P>Example use case.
<P>Instead of :
<PRE>
public void doThis(String aText){
if (!Util.textHasContent(aText)){
throw new IllegalArgumentException();
}
//..main body elided
}
</PRE>
<P>One may instead write :
<PRE>
public void doThis(String aText){
Args.checkForContent(aText);
//..main body elided
}
</PRE>
*/
public final class Args {
/**
If <code>aText</code> does not satisfy {@link Util#textHasContent}, then
throw an <code>IllegalArgumentException</code>.
<P>Most text used in an application is meaningful only if it has visible content.
*/
public static void checkForContent(String aText){
if( ! Util.textHasContent(aText) ){
throw new IllegalArgumentException("Text has no visible content");
}
}
/**
If {@link Util#isInRange} returns <code>false</code>, then
throw an <code>IllegalArgumentException</code>.
@param aLow is less than or equal to <code>aHigh</code>.
*/
public static void checkForRange(int aNumber, int aLow, int aHigh) {
if ( ! Util.isInRange(aNumber, aLow, aHigh) ) {
throw new IllegalArgumentException(aNumber + " not in range " + aLow + ".." + aHigh);
}
}
/**
If <tt>aNumber</tt> is less than <tt>1</tt>, then throw an
<tt>IllegalArgumentException</tt>.
*/
public static void checkForPositive(int aNumber) {
if (aNumber < 1) {
throw new IllegalArgumentException(aNumber + " is less than 1");
}
}
/**
If {@link Util#matches} returns <tt>false</tt>, then
throw an <code>IllegalArgumentException</code>.
*/
public static void checkForMatch(Pattern aPattern, String aText){
if (! Util.matches(aPattern, aText)){
throw new IllegalArgumentException(
"Text " + Util.quote(aText) + " does not match '" +aPattern.pattern()+ "'"
);
}
}
/**
If <code>aObject</code> is null, then throw a <code>NullPointerException</code>.
<P>Use cases :
<pre>
doSomething( Football aBall ){
//1. call some method on the argument :
//if aBall is null, then exception is automatically thrown, so
//there is no need for an explicit check for null.
aBall.inflate();
//2. assign to a corresponding field (common in constructors):
//if aBall is null, no exception is immediately thrown, so
//an explicit check for null may be useful here
Args.checkForNull( aBall );
fBall = aBall;
//3. pass on to some other method as parameter :
//it may or may not be appropriate to have an explicit check
//for null here, according the needs of the problem
Args.checkForNull( aBall ); //??
fReferee.verify( aBall );
}
</pre>
*/
public static void checkForNull(Object aObject) {
if (aObject == null) {
throw new NullPointerException();
}
}
// PRIVATE
private Args(){
//empty - prevent construction
}
}