Wednesday, December 24, 2008

Refactoring can be easier in Groovy than in Java

I love Groovy for the productivity, but I am sceptical to maintenance and refactoring of large code bases in Groovy. Without type safety, you cannot detect refactoring errors at compile time, so you need very high coverage with unit tests.

But the other day, I experienced something that got me thinking: I have a few Java classes and some Groovy scripts that call the Java code something like this:
def var = javaObject.func()

I changed the return type of the Java method, and the Groovy code just kept on working. I didn't have to change it at all. Why? Because the Groovy code did not know the type I was returning. The dynamic nature of Groovy made refactoring easier, not harder!

How can type safety make refactoring harder? Because type safety leads to duplication of information. In Java, the code above would look like this:
String var = javaObject.func()

The return type "String" would be duplicated in calls to this method, in interfaces that specified this method, in subclasses, in variables, and so on. Not so in Groovy. The return type is declared only in one place.

I always wondered why we designed programming languages different than databases. In databases, we try to avoid duplication of information. Duplication is a maintenance nightmare. But in programming, we duplicate information all the time. Not only with return types, but with boilerplate code, design patterns and class, variable and method names. In databases, we define immutable primary keys instead of refering to rows by values that could be modified. But in programming languages, we refer to everything by name. So when we want to change a name, we need a complicated "refactoring" tool to replace this name everywhere it occurs.

I dream of a programming tool that let my type code like: "x = myObject.func1()" but under the hood it would create references to "myObject" and "func1" instead of resolving that at compile time. Then, there would be no "rename refactoring", just change the name and the reference would still be valid. The text should then be updated automatically.

Thursday, December 4, 2008

How to test Java Persistence API in 5 minutes

I am the author of HiberObjects and want to give an example of how useful it can be.

Yesterday, someone asked me a Java Persistence API (JPA) question: If an object is removed from a bidirectional relation, do both objects have to be saved to update the database correctly?

I thought I knew the answer, but better test it first. This is how I did it:

1) Design a class diagram with 2 persistent classes User and Membership and 1 unit test UserPersistentTest:



2) Initialize the database with some objects that I can test against: Right-click on the UserPersistentTest class and select Design Objects, then design objects like this:



3) Save the diagram. This will generate JPA classes, a unit test and a helper class. The unit test will initialize the database with objects like this:
   private void initObjects()
{
EntityManager entityManager = persistenceHelper.getEntityManager();
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
user = new User();
membership = new Membership();
user.setName("Lars");
membership.setX(100);
entityManager.persist(user);
entityManager.persist(membership);
user.getMembership().add(membership);
membership.getUser().add(user);
tx.commit();
entityManager.close();
}
4) Implement the test method:
   public void test1() throws Exception
{
EntityManager em = persistenceHelper.getEntityManager();
em.getTransaction().begin();
User user1 = (User) em.createQuery("from User").getSingleResult();
assertEquals(1, user1.getMembership().size());

Membership membership1 = (Membership) em.createQuery("from Membership").getSingleResult();
assertEquals(1, membership1.getUser().size());

assertSame(membership1, user1.getMembership().iterator().next());
assertSame(user1, membership1.getUser().iterator().next());

user1.removeMembership(membership1);
assertEquals(0, user1.getMembership().size());
assertEquals(0, membership1.getUser().size());
em.getTransaction().commit();

em.getTransaction().begin();
User user2 = (User) em.createQuery("from User").getSingleResult();
assertEquals(0, user2.getMembership().size());
Membership membership2 = (Membership) em.createQuery("from Membership").getSingleResult();
assertEquals(0, membership2.getUser().size());
em.getTransaction().commit();
}
5) Run as JUnit test:



This demonstrates the answer to the question: You don't need to save any of the objects explicitly if you change them in a transaction.