This post originated from an RSS feed registered with Java Buzz
by Adam Kruszewski.
Original Post: "Never accept null as a parameter and never return null."
Feed Title: Adam Kruszewski :: WebLog();
Feed URL: http://adam.kruszewski.name/blojsom/blog/adam.kruszewski/?flavor=rss2
Feed Description: Thoughts about linux, open source, programming, ...
We all know this principle and we all know how hard is code defensively to keep it.
But it is a little easier now ;) Today AJDT team released developement version 2005012444759 of their Eclipse plugin, providing latest AspectJ5 capabilities to compile and wave J2SE 5.0 code with support to pointcuts matching annotated elements (there is no parameter and package annotations support right now). Using annotations and one simple aspect we can check all calls
to chosen methods and throw IllegalArgumentsExceptions whenever null value is passed to them.
Here is how to do it:
First, create marker kind annotation (let's name it a "GoodCitizen") which we would use
to mark methods which calls should be "null parameter" free.
package sandbox.nullchecks;
import java.lang.annotation.*;
/**
* Indicates methods which should behave as "Good Citizen".
* In conjunction with GoodCitizenAspect all such methods
* should throw IllegalArgumentException when there is
* null value passed to them or they return null value.
*/
@Retention(RetentionPolicy.CLASS)
@Target( {ElementType.METHOD} )
@Documented
public @interface GoodCitizen {
}
Then create aspect which will do the hard work:
package sandbox.nullchecks;
public aspect GoodCitizenAspect {
// it will match any method call to annotated method with any signature.
pointcut goodCitizenMethodCall() : call (@sandbox.nullchecks.GoodCitizen * *(..));
before() : goodCitizenMethodCall() {
Object[] methodArgs = thisJoinPoint.getArgs();
for (int t = 0; t < methodArgs.length; t++) {
if ( null == methodArgs[t] ) {
throw (new IllegalArgumentException("Parameter " + (t + 1)
+ " of " + methodArgs.length + " in call to "
+ thisJoinPoint.getSignature()
+ " was NULL, but method is annotated as GoodCitizen."));
}
}
}
}
Now just try it! :)
package sandbox.nullchecks;
public class GoodCitizenExample1 {
@GoodCitizen
public void citizenTest(String str, int prim, Object obj) {
System.out.println(str+":"+prim+":"+obj);
}
public static void main(String[] args) {
GoodCitizenExample1 ex = new GoodCitizenExample1();
// it should yell about null as third parameter when executed.
ex.citizenTest("str", 9, null);
}
}
Running it produces this result:
Exception in thread "main" java.lang.IllegalArgumentException:
Parameter 3 of 3 in call to
void sandbox.nullchecks.GoodCitizenExample1.citizenTest(String,
int, Object) was NULL, but method is annotated as GoodCitizen.
at sandbox.nullchecks.aspects.GoodCitizenAspect.ajc$before
$sandbox_nullchecks_aspects_GoodCitizenAspect$1$38402fab(GoodCitizenAspect.aj:23)
at sandbox.nullchecks.GoodCitizenExample1.main(GoodCitizenExample1.java:13)
Also note we could check method calls to all methods enclosed within annotated classes (link to sample source code is available on bottom of this post) but it requires runtime checks and it would be rather inefficient.
As for "never return null" part I couldn't find a nice way of checking whenever method returned null or it's return type is void (after() returning(Object obj) advice threats void return type the same as when null is being returned). A little hackich way is to parse thisJointPoint.getSignature().toLongString() or
create two annotations -- one for parameters checks and another one for return value checks and use them when apropriate.
26-jan: update:
Matthew Webster pointed me that I could define another pointcut like this:
and then create after() returning advice which would catch all those non-void methods:
after () returning(Object retValue) : NonVoidGoodCitizenMethodCall() {
if (null == retValue) {
throw (new IllegalArgumentException("Method "
+ thisJoinPoint.getSignature()
+ " returned NULL but it is marked as GoodCitizen."));
}
}
Now it is a little easier to trace all those NullPointerExceptions out there. Happy hacking ;)
As always compressed Eclipse project is available to download. Eclipse 3.1M4 and latest developement snapshot of AJDT are required.