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 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