Defensive Programming With AOP
From NeoWiki
- OVal takes the legwork out of writing repetitive conditionals
Andrew Glover, President, Stelligent Incorporated
30 Jan 2007
- While defensive programming effectively guarantees the condition of a method's input, it becomes repetitive if it is pervasive across a series of methods. This month, Andrew Glover shows you an easier way to add reusable validation constraints to your code using the power of AOP, design by contract, and a handy library called OVal.
The major downside to developer testing is that the vast majority of tests exercise sunny-day scenarios. Defects rarely occur for these situations -- it's the edge cases that usually cause problems.
What's an edge case? It's the situation where, for instance, someone passes a null value to a method not coded to handle nulls. Many developers fail to test for such scenarios because they don't make much sense. But sense or no sense, these things happen, and then a NullPointerException is thrown and your whole program blows up.
This month, I suggest a multifaceted approach to dealing with the less predictable defects in your code. Find out what happens when you combine defensive programming, design by contract, and an easy-to-use generic validation framework called OVal.
Tip: Download OVal and AspectJ You need to download OVal and AspectJ to implement the programming solution described in this article. See Defensive_Programming_With_AOP#Resources to download these technologies now and follow along with the examples. |
Exposing the enemy
The code in Listing 1 builds a class hierarchy for a given Class object (omitting java.lang.Object because everything ultimately extends it). If you look carefully, however, you'll notice a potential defect waiting to be exposed because of assumptions in the method regarding object values.
Listing 1. A method without checks for null
public static Hierarchy buildHierarchy(Class clzz){ Hierarchy hier = new Hierarchy(); hier.setBaseClass(clzz); Class superclass = clzz.getSuperclass();
if(superclass != null && superclass.getName().equals("java.lang.Object")){ return hier; } else { while((clzz.getSuperclass() != null) && (!clzz.getSuperclass().getName().equals("java.lang.Object"))){ clzz = clzz.getSuperclass(); hier.addClass(clzz); } return hier; } }
Having just coded the method, I haven't yet noticed the defect, but because I'm a developer testing fanatic, I write a routine test using TestNG. What's more, I use TestNG's handy DataProvider feature, which allows me to create a generic test case and vary the parameters to it through another method. Running the test case defined in Listing 2 yields two passes! Everything is good to go, right?
Listing 2. A TestNG test verifying two values
import java.util.Vector; import static org.testng.Assert.assertEquals; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class BuildHierarchyTest { @DataProvider(name = "class-hierarchies") public Object[][] dataValues(){ return new Object[][]{ { Vector.class, new String[] {"java.util.AbstractList", "java.util.AbstractCollection"} }, { String.class, new String[] {} } }; }
@Test(dataProvider = "class-hierarchies"}) public void verifyHierarchies(Class clzz, String[] names) throws Exception{ Hierarchy hier = HierarchyBuilder.buildHierarchy(clzz); assertEquals(hier.getHierarchyClassNames(), names, "values were not equal"); } }
I still haven't spotted the defect, but something about the code is bothering me. What if someone inadvertently passes in a null value for the Class parameter? The call clzz.getSuperclass() in the fourth line of Listing 1 would throw a NullPointerException, wouldn't it?
Testing my theory is easy; I don't even have to start from scratch. I simply add {null, null} to the multidimensional Object array in the dataValues method of the original BuildHierarchyTest (in Listing 1) and run it again. Sure enough, I get the nasty NullPointerException shown in Figure 1:
Figure 1. The dreaded NullPointerException