Thursday, January 24, 2008

ColdFusion needs unit tests freakin' everywhere

By trade I am a Java developer -- I've done plenty of Java EE work as well as pure Java SE stuff. I'm a consultant in Pittsburgh. I won't mention my employer by name, or name any clients that aren't dead, but I've worked at a fair number of clients in the greater Pittsburgh area.

Back in 2000, at a long-dead website, I did about six months of really painful ColdFusion development. In the fall of this year I was offered the opportunity to do more CF consulting, at a local company, and I took the posting.

So, I've been doing CF again for a few months now. I miss so much stuff from Java development that I won't even list it all, although my previous CF post hits some of them.

Here's a new one: Today I lost half a day to the difference between <cfloop query="foo"> and <cfloop query="#foo#">. The error message I received informed me that I was trying to use a complex object where I needed a simple value. I didn't get a stack trace. I didn't get a line number.

And of course I didn't have a debugger. The only way to find out what's going on in ColdFusion is to layer your code with <cfdump> tags and other output-to-the-page wickedness.

So, anyway, what I'm finding is that the more CF development I do, the more I have to write unit tests for even ordinary model objects -- I need to make sure that freakin' getters and setters are behaving intelligently when faced with even slightly unusual circumstances.

Case in point: I have a method, setShippingCharge(), on a Customer CFC. When I designed Customer I knew that shippingCharge was numeric, and so I wrote it like this:

<cffunction name="setShipping" output="false" returntype="void">
 <cfargument name="shippingCharge" type="Numeric">
 <cfset shippingcharge = "arguments.shippingCharge"/>
 <cfreturn/>
</cffunction>

I didn't write any unit tests for this, because, y'know, it's a setter, and I'm a Java guy.

I ran some code using this method today, populating a Customer object from a database query. Well the
shipping_charge field in the database was nullable, and in this case, it was actually null. ColdFusion complained about this, because of a type mismatch. Null -- or, thanks to the retardation of ColdFusion, the empty string "" -- wasn't numeric!

So instead I had to rewrite the function like this:

<cffunction name="setShipping" output="false" returntype="void">
 <cfargument name="shippingCharge">
 <cfif>
  <cfset shippingcharge = "0"/>
  <cfelseif>
   <cfset shippingcharge = arguments.shippingCharge/>
  <cfelse>
   <cfthrow type="foo.IllegalArgumentException" message="this is supposed to be numeric">
 </cfif>
 <cfreturn/>
</cffunction>



I'm not sure I understand, at this point, why ColdFusion functions even allow you to declare their expected data type. If you've got to go through gyrations like the above for every method, what good is the 'type' attribute on <cfargument>?

I was going to turn this into a rant on dynamic languages but I guess I'll save that, except to say that gosh, a compiler could sure prevent a lot of this pain.

2 comments:

Anonymous said...

If you go into the administrator tool, you can change the exception information to include a stack trace and a line number. It's in there somewhere - there's a section for debug information or error messages.

James Kiley said...

Thank you -- I was unaware of that. I'm not the guy responsible for doing administration on the dev server, but I will talk to the admin about that.