Tuesday, 30 June 2009

Covering getters and setters? We're starting to focus on the wrong things

The project I've been assigned at work is a small one, but I've been keeping 100% coverage on the things that I care about - the things that I think are important. However, it sucks to have the metrics that will be looked at by management to be less than 100%. "It's okay, because that code isn't important!" Stupid getters and setters didn't have 100% coverage...

In my search for the One True Answer, I came across an interesting post that's the closest to my feelings on it. If I've gotten uncovered getters / setters, then I might not actually need that code. That was a big part of my solution today - deleting setters.

My whole issue with this is that now I'm taking time to think about code that I generate and for all intents and purposes has no effect on my project. Does it matter that a model object has an unused setter to a field? I don't think so. It can show that you don't have a test case covered, but if your only way to tell if you are missing an important use case is an uncovered getter / setter, you might have other problems.

What I wish they built into java was something along the lines of Ruby's attr_accessor keyword or the similar solution in just about every other language. There's even a feature request for it in java - but since it's over a decade old I'm not going to hold my breath.

The fact that there are so many discussions, work arounds, IDE tools, scripts, etc should indicate that it's a problem that should be solved on the language level. The fact that I was thinking about it today at work was a waste of time and money. Uncovered accessors effecting the perceived quality of an app I work on means 1) the metrics need improvement 2) I'm probably not the only one wasting time on this issue 3) we're discussing field accessors when we could be solving an actual business need.

It's stuff like this that makes me wonder how long it will be before enough people move to other solutions like groovy, ruby, phython, etc. before the people in control of java sit up and take notice and cut the cruft out. At that point it'll probably be too late. It's really hard to un-sink a ship. :-/

Monday, 29 June 2009

Data plan please, hold the voice

Looking at all the cool things the smart phones can do these days, I'm really starting to want one. The draw back is that the phones all seem to require you to have a voice plan first. Yes, I understand that it's a cell phone, but I don't want to use it as a cell phone. I want to use it as a mobile computer. What I want is a pay-as-you-go voice plan and a data plan.

I wouldn't mind paying for the services that I want, but the 100% mark up for something that I think I won't really use is holding me back. I think that in 5 years it will hard to get a cell phone that doesn't have a data plan available. Patience, my friend, patience...

Tuesday, 23 June 2009

Starving to death at a buffet

One thing that really pisses me off is when people don't take responsibility for their own learning. Yes, some places actually allocate money and time for you to bring up your skills - but I think that in no way makes this the employers responsibility. A training budget is a perk, not a scape goat.

For some jobs it is beyond an individuals ability to self-fund their own training - e.g. an astronaut or deep sea diver. For someone in IT, for the most part it just means having access to the internet or going to the library and borrowing a book (if you're too cheap to buy one). To buy one job specific book a year and read it is well within most people's budget.

When I hear someone say "I have not been sent on training so I don't know how to do that (on something that's happened over the last 10 years)" I just want to call bullshit. I picture that person going to a buffet where the restaurant staff are busting their asses to make sure that the actual buffet has a ton of food on it, the person sitting down at a table and then letting themselves starve to death because no one has brought them food. Life is a buffet, so get off your ass and help yourself. For IT professionals (or amateurs), the internet is the biggest smorgasbord of information and training that you can possibly come across, you just have to help yourself.

Next time you hear someone whining about their lack of knowledge because someone else has not sent them for training, just imagine how well that would go for other professions. Would you accept that as an excuse from your health care professional? Your accountant? Your lawyer?

If people are self sufficient enough to take care of themselves (wash, bath, dress, eat, ...), they should be able to do some reading on their own. Take responsibility for your life.

Saturday, 20 June 2009

Verifying proper HttpSession use for clusterable applications using eclipse, AJDT, AspectJ and maven

One question that I hear often at work is "how can we tell if a given application will work properly in a cluster?". The answer is always "test it", but honestly that's pretty hard to do really often. The tests usually rely on the app running in a cluster, session use, someone downing one of the servers as the functional tests run, etc. The really hard part, I think, is for an error to pop up that will be noticed. If session gets improperly synced across nodes, it might corrupt the data, but not in a way that's quickly noticed.



At work with our current clustering setup, objects put into session must implement java.io.Serializable, and the way you notify the server to sync your session is to call session.setAttribute(..). A hard to track down bug would be if you have a reference to an object, set it into session, and then use your reference to change the state of that object. What I set out to do is to create a fail-fast test where we can identify improper session use in the unit tests, before we even get close to deploying an app to a server.



Below is my first foray into the AOP java world using aspectj. I'm fully documenting this because, while there were many posts on aspectj, etc, I never found one that put it all together. I did originally start off trying to use the java annotations for aspectj, but after much struggle, I gave up and moved to using AJDT with great success.



The layout the solution consists of a corporate pom, a "auditor" library, and an example app that has unit tests. I won't actually show an example of the sample application because it simply is a bunch of tests, and has the corporate pom as it's parent. The code that runs the session tests has been put into a maven profile so that we can have one pom, but be able to mark applications as "clusterable" one by one. So, after each app updates to the given parent pom, to check if they are using session properly [1] they only have to run mvn clean test -Pclusterable.



One thing that I left as a TODO in the aspect is checking if the value being set into the session is actually serializable or not. I had actually written some code at work to do that, so I won't re-do it at home. ;-)



The algorithm


The high level idea of how the aspect works is that every time someone who implements HttpSession calls setAttribute(String,Object), we 1) can inspect the values being set to make sure that it's serializable 2) keep a reference to the session object for later 3) generate a hash of the attribute and store the hash in the session object [2]. After a test has successfully finished, we go over all the attributes in the session and check if the hash of each attribute matches the hash that we generated when setAttribute(..) was called. For the problems that we encounter, we throw a AssertionError - which is the same kind that junit throws.



Our corporate pom is below. Some important things to note are using the same version of aspectj everywhere, locking down the JSE version in the various plugins, and specifying the additional javaagent argument to surefire when using AOP.



<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId>ca.beernut.jim</groupId>
<artifactId>ParentPom</artifactId>
<version>1.0.1</version>
<packaging>pom</packaging>

<properties>
<aspectjVersion>1.6.4</aspectjVersion>
<jseVersion>1.5</jseVersion>
<surefireMemoryArgs>-Xmx512m -Xms128m</surefireMemoryArgs>
<auditorJarVersion>3.1.4</auditorJarVersion>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
<scope>provided</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.1</version>
<configuration>
<source>${jseVersion}</source>
<complianceLevel>${jseVersion}</complianceLevel>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspectjVersion}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${jseVersion}</source>
<target>${jseVersion}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.7</version>
<configuration>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
<ajdtVersion>none</ajdtVersion>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.4.3</version>
<configuration>
<forkMode>once</forkMode>
<argLine>${surefireMemoryArgs}</argLine>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<profiles>
<profile>
<id>clusterable</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-javaagent:"${settings.localRepository}org/aspectj/aspectjweaver/${aspectjVersion}/aspectjweaver-${aspectjVersion}.jar"\
${surefireMemoryArgs}</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<configuration>
<aspectLibraries>
<aspectLibrary>
<groupId>ca.beernut.jim.aspects</groupId>
<artifactId>ProjectAuditor</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspectjVersion}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</pluginManagement>
</build>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectjVersion}</version>
</dependency>
<dependency>
<groupId>ca.beernut.jim.aspects</groupId>
<artifactId>ProjectAuditor</artifactId>
<version>${auditorJarVersion}</version>
</dependency>
</dependencies>
</profile>
</profiles>
</project>


Our ProjectAuditor pom's is as follows:



<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ca.beernut.jim</groupId>
<artifactId>ParentPom</artifactId>
<version>1.0.0</version>
</parent>

<groupId>ca.beernut.jim.aspects</groupId>
<artifactId>ProjectAuditor</artifactId>
<version>3.1.4</version>
<packaging>jar</packaging>


<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectjVersion}</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.4</version>
</dependency>
<!-- This needs to be compile scope since we include it -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.4</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<configuration>
<!-- Override this because it is set to none in the parent -->
<ajdtVersion>1.5</ajdtVersion>
</configuration>
</plugin>
</plugins>
</build>

</project>


In ProjectAuditor, we had the 2 files: src/main/aspect/ca/beernut/jim/aspects/ClusterableHttpSessionAspect.aj



package ca.beernut.jim.aspects;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpSession;

import org.apache.commons.lang.builder.HashCodeBuilder;

public final aspect ClusterableHttpSessionAspect {

/**
* Here is where we will keep track of all the session objects that were
* created. The key if the system's hashcode
*/
private ThreadLocal<Map<Integer, ClusterableHttpSession>> sessions = new ThreadLocal<Map<Integer, ClusterableHttpSession>>();

/** Default constructor that initalizes the thread local. */
public ClusterableHttpSessionAspect() {
sessions.set(new HashMap<Integer, ClusterableHttpSession>());
}

/**
* Create an interface type so that we can attach a member to it.
*/
declare parents : (HttpSession) implements ClusterableHttpSession;

private Map<String, Integer> ClusterableHttpSession.hashOnSet = new HashMap<String, Integer>();

public Map<String, Integer> ClusterableHttpSession.getHashOnSet() {
return hashOnSet;
}

public static interface ClusterableHttpSession {
}

/**
* Before anyone calls setAttribute, check that the pram is okay and keep
* track of it so later we can detect if it was changed outside of the set
* method.
*
* I could not get the method
* <code>after() returning (ClusterableHttpSession m): call(ClusterableHttpSession+.new(..))</code>
* to work for all mock sessions (see servletunit.HttpSessionSimulator), so
* I'm keeping track of the sessions in this method.
*/
before(ClusterableHttpSession m, String key, Object value) :
call(void HttpSession.setAttribute(..)) && target(m) && args(key,value) {

// TODO this is where you would put in your test to check if the "value"
// is serializable

// the value is serializable, so let's keep track of it's state
Map<Integer, ClusterableHttpSession> sessionList = sessions.get();

// let's keep track of this hash if we don't already have it
Integer sessionHash = Integer.valueOf(System.identityHashCode(m));
if (!sessionList.containsKey(sessionHash)) {
sessionList.put(sessionHash, m);
System.out.println("Added to thread local session: " + m);
}

Integer hash = hash(value);
m.getHashOnSet().put(key, hash);

System.out.println("For key: " + key + " and value " + value + " we have hash # of: " + hash);
}

pointcut afterUnitTest() : execution(void test*()) || @annotation(org.junit.Test);

/**
* After the unit test has finished, use the sessions that we have in thread
* local to check if the attribute has changed outside of the setAttribute
* call.
*
* This is just an "after returning" method because we only care about the
* state of the session if no exceptions were thrown.
*/
after() returning : afterUnitTest() {

Map<Integer, ClusterableHttpSession> sessionList = sessions.get();

System.out.println("After returning and sessions are : " + sessionList);

try {

// for each session, check the attributes that remain in the
// session against the ones that were set into it and make
// sure that the last matches

for (ClusterableHttpSession session : sessionList.values()) {
HttpSession httpSession = (HttpSession) session;
ClusterableHttpSession clusterableHttpSession = (ClusterableHttpSession) session;

for (Enumeration enumeration = httpSession.getAttributeNames(); enumeration.hasMoreElements();) {
String attributeName = (String) enumeration.nextElement();
Object value = httpSession.getAttribute(attributeName);

// we have to cast it here
Integer lastHash = clusterableHttpSession.getHashOnSet().get(attributeName);
Integer currentHash = hash(value);

if (!lastHash.equals(currentHash)) {
String message = "Object in session " + httpSession + " under key '" + attributeName
+ "' has hash of " + currentHash
+ ". However the prevous hash when setAttritbute was used was " + lastHash
+ ". Always call setAttribute on objects in session after changing their state.";
throw new AssertionError(message);
}
}
}
} finally {
clearThreadLocal();
}
}

/** If something was thrown, just clear the thread local for the next run. */
after() throwing : afterUnitTest() {
clearThreadLocal();
}

/**
* Clear out the thread local if the test returns properly or not
*/
private void clearThreadLocal() {

Map<Integer, ClusterableHttpSession> sessionList = sessions.get();

// clear out the thread local so we don't keep sessions from
// test to test
sessionList.clear();
}

/** A hash method to determine the state of the object. */
private Integer hash(Object obj) {
return Integer.valueOf(HashCodeBuilder.reflectionHashCode(obj));
}

}


Here is the second file with a special location and name: src/main/resources/META-INF/aop.xml



<aspectj>
<aspects>
<aspect name="ca.beernut.jim.aspects.ClusterableHttpSessionAspect" />
</aspects>
<weaver options="-verbose -showWeaveInfo -Xset:weaveJavaxPackages=true">
</weaver>
</aspectj>


Notes:

[1] With this code we're only checking code that gets run by their unit tests.

[2] See Inter-type Declarations



Friday, 12 June 2009

Junit Interceptors

Sometimes you come across a problem to which you find a solution only to realize that it's not been released, and even if it was, it's blocked by another dependency's conflict. D'oh.

Here I thought that I was being clever... However, I have to be careful with that. :-/

Update: I think that I'm going to ditch the whole junit runner / listener idea. I think that I'll be able to do what I want a lot easier by just using aspectj for some pure AOP goodness. Time to start reading...

Wednesday, 10 June 2009

Surprised at good ideas

When I'm at work in discussions, sometimes I voice Good Ideas - and people act all surprised. Honestly I can't blame them, they surprise me as well. Sometimes they flow, sometimes they evolve, but most of the time they just stumble out of my brain like a teenager out of bed at 6 am. I wish that I could help them come more often, but I'll take what I can get.

Monday, 8 June 2009

Banned words

There are a couple of words that, when used, usually piss me off because of the tone that seems to come with them. I call this class of words the "jerk words" because they seem dismissive and arrogant. Words like "obviously" and "clearly". It's clichÈ, but what's obvious to one person is not to another. I don't believe that it's from lack of intelligence (in all cases), but more often from the different backgrounds and experiences that people have had in their lives. Obviously people like to use big words, but clearly it makes them sound like an ass.

The funniest thing: if it was actually clear to everyone involved, why the hell is anyone explaining it? The very act of explaining things using the jerk words means that the issue was not obvious.

*deep breath out*

You'll look sweet upon the seat...

Laura and I have often talked about taking a bicycle built for two (b42) for a spin. As a bit of a surprise, we rented a "KHS Alite tandem" and took it for a spin down to the arboretum for a picnic lunch. I've learned a couple of things:

  1. a b42 will either strengthen a relationship, or end it

  2. it's much harder than it looks

  3. they don't turn so well... hard to avoid people along the busy canal

  4. when the road is open, you can really motor along

  5. communication is key - I felt like I was on a ship from the old movies "engine ahead full!", "hard to port!!!", "tourist, dead ahead!"

  6. I glad we rented it and I had a lot of fun, but I don't think that I'll be looking to invest in one



Sunday, 7 June 2009

Fast turn around

I guess that it depends on your customers what support you provide. I emailed 2 different companies that sell medical software to doctors for their mobile devices (palm, iphone, crackberries, etc) and within two hours both companies got back to me with a good and useful answer. On a Sunday. I don't have an account with them, I was just making an inquiry. Pretty cool.

Now that I am thinking about it, if I was a company making software for mobile devices I would have my customer service / help lines all working with mobile devices so that I could schedule people to be on call on the weekends. It makes sense.

Friday, 5 June 2009

Up in 3D

Tonight we saw the new Pixar movie Up - in 3D. I think that the last time I saw a 3D film in a theater was when I was 6 in Disney World. I thought that it would be the old red / blue glasses, but it wasn't. In the theater I guessed that the glasses used some sort polarization, but not the kind I thought that it was. Laura thought that I looked like Stevie Wonder twisting my head and looking at my watch trying to see where the watch face turns black, but it didn't happen. Apparently it's using some kind of fancy new 3D system. Pretty cool.

We really enjoyed the movie. I won't say too much other than it's funny, sad, and silly - often switching between all 3 in quick succession. I'm not sure what my favourite Pixar film is: The Incredibles or Up.

Monday, 1 June 2009

The little things

It's the little things that make you feel really lucky. Biking home today in 9C rainy weather can really makes you feel lucky:

  • I hit every green light

  • It wasn't raining too hard

  • There wasn't much wind

  • It wasn't hailing

  • Traffic was light

  • I've got a warm house

  • There is lasagna to eat (as soon as I bake it)

Reading this you probably think that I am nuts. You may be right. Either way I just feel really lucky right now.