Tuesday, 17 July 2012

Quick follow-up to CFPARAM posting

A coupla days ago I posted about how the default attribute of the <cfparam> tag worked.

Surprisingly - given it was a pretty bland topic - I did get some feedback from a colleague of mine (if nothing else, at least I know someone read it!).

Depsite being a better CF developer than I am, it hadn't occurred to him how the default thing worked, so it goes to show that even documented stuff escapes people who have had a lot of experience; they just haven't encountered everything in the language.

But he came up with another point for me to investigate: if <cfparam> behaves like that... is there a risk <cfargument> does too?  This would actually be something of a concern for us if it did.

This has given me another topic to investigate and report back on (thus staving off for another day the fear I have of running out of stuff to say... ;-)

So I knocked-together some test code, thus:

<!--- TestArgumentDefaults.cfc --->
    <cffunction name="getSomething">
        <cfargument name="whichOne" default="#defaultOne()#">
        <cfreturn "This is the one you asked for: #arguments.whichOne#">
    <cffunction name="defaultOne">
        <cfset sleep(5000)>
        <cfreturn "The default one">

<!--- testArgumentDefault.cfm --->
<cfset oTestArgumentDefaults = new TestArgumentDefaults()>

    <cftimer type="outline" label="Default">
    Just the default will be fine:<br />
    #oTestArgumentDefaults.getSomething()#<br />
    <hr />
    <cftimer type="outline" label="Specific">
        Get The specific one I want:<br />
        #oTestArgumentDefaults.getSomething("The specific one I want")#<br />

(Note that I am getting better at formatting the code so it's more readable...  I read some of the blogspot docs which explained how to do it...)

Anyway, the code.  This is simple stuff:
  • In the CFC I have a test method getSomething(), which has an argument which takes a runtime default of another helper method, defaultOne().  That helper method is terribly terribly complex, and takes five seconds to run.
  • in the CFM, we instantiate the CFC and perform two tests:
    • As a control, we call getSomething() without an argument, so the default needs to be set, and the slow defaultOne() method is called.  This is to demonstrate that defaultOne() - if called - does indeed take 5sec to run.
    • Then the actual thing we're testing: we call getSomething() but give it an argument.  The concern is that - if <cfargument> works similarly to <cfparam> - then we'll still see this performance hit as defaultOne() is called and the result discarded in favour or the passed-in value.
And the results are thus:

Default: 5002msJust the default will be fine:
This is the one you asked for: The default one

Specific: 0msGet The specific one I want:
This is the one you asked for: The specific one I want

 This is a relief.  But what's going on then? How come <cfargument> works differently from <cfparam>?

Cheekily, I set "Save Class Files" on in CFAdmin, ran the code, and then decompiled the saved class file for the method, and had a look.  Because the code is machine-generated it's a bit grim (especially to me who's not much of a Java developer), but here's a translation into pseudo-code of the relevant bit:

if getASpecificArgument(1) is null then
        argumentName    = "whichOne",
        argumentvalue    = callAMethod("defaultOne")

I guess this works differently because a <cfparam> tags is something that actually gets executed at runtime, whereas a <cfargument> isn't really executed, it's just defining how the function works.  So guidance for the compiler, rather than the runtime environment.  Or something like that.  Anyway, looking at how it works, It kinda makes sense to me.

All this comes as a relief to us, as we don't need to think about refactoring any code.  Cool.

Speaking of work... it's after 9am and I should be reverting to my day job.