CFML 101 - The importance of proper variable scoping

{ Posted By : Eric Cobb on September 22, 2010 }
1945 Views
Related Categories: CFML 101

One thing that always surprises me is the number of CFML developers who either don't use, or don't know about, proper variable scoping. I see it almost every day, whether it be at work, on side projects that I help out with, or even many of the open source CFML applications that I download and use. The lack of proper variable scoping seems to be rampant throughout the CFML world, most especially when it comes to the VARIABLES scope. I will admit that most developers seem to have a pretty good grasp on using the FORM and URL scopes, and required persistent scopes such as SESSION or APPLICATION, but that's about as far as it goes.

This bugs me. This really, really bugs me. I don't know why, but unscoped variables are like fingernails on a chalkboard to me sometimes. Maybe it's because I've inherited so many really (REALLY!) bad applications in my day and have experienced first hand the problems caused by unscoped variables. Maybe it's a touch of OCD with me knowing that variable scoping is the "recommended" and "proper" way of doing things, so that's how I have to do it. Or, maybe it's just some of the gamma rays from the alien satellites getting though my foil beanie. Whatever the reason, I felt compelled to blog about it.

Not too long ago I was talking with another developer and we got on the topic of unscoped variables. I made the comment that it was a bad practice, and his response was that it really wasn't that big of a deal. His thoughts were that it was easier not to bother scoping anything because ColdFusion can figure it out for you. I didn't know what to say. Here was a developer who had been using CFML for several years, and he had no real understanding of one of the most basic CFML principles there is.

I guess I really shouldn't be surprised, though. We're told to scope our variables, but never really told in detail why we should do it, and when we don't do it ColdFusion just figures everything out for us anyway. Many developers treat ColdFusion's ability to track down and find their variables as a feature (i.e., I don't have to specify a scope and CF will still know what I mean), but it shouldn't be viewed that way. Rather, it should be looked at as more of a built in safeguard to try to keep bad code from throwing errors. That's right, it's there to try to figure out your screw ups, not magically interperate your lazy coding.

DISCLAIMER: A lot of what I'm about to say comes from 1) reading documentation and blog posts from those smarter than myself, and 2) my own personal understanding on how things work. You've been warned!

For those that don't know, "scoping" a variable means providing a prefix that designates where the variable belongs (I.E. "form", "url", "request"). To really understand the importance of variable scoping, we need to take a look at what ColdFusion has to do to process unscoped variables. You see, when you don't specify where your variables are, ColdFusion has to go on a hunt to try to find them. This hunt follows what we call the Order of Precedence. This is basically the order of the scopes that ColdFusion searches to try to find your variable.

In CF 8, the order of precedence is:

  1. Function local (UDFs and CFCs only)
  2. Thread local (inside threads only)
  3. Arguments (like when you are inside of a function)
  4. Variables
  5. Thread (as in thread.this or thread.that)
  6. CGI
  7. Cffile
  8. URL
  9. Form
  10. Cookie
  11. Client

This differs slightly from the CF9 order of precedence, which was changed to:

  1. Local (function-local, UDFs and CFCs only)
  2. Arguments
  3. Thread local (inside threads only)
  4. Query (not a true scope; variables in query loops)
  5. Thread
  6. Variables
  7. CGI
  8. Cffile
  9. URL
  10. Form
  11. Cookie
  12. Client

So, every time you reference a variable without specifying its scope, ColdFusion has to start at the top of that list and go through each one of those scopes until it finds your variable, or reaches the end of the list and throws an error. Not only does it have to search each of those scopes, but it's my understanding that it also has to take a look at every single variable inside each of those scopes. Take the following example.

<cfset myName = "Eric">
<cfoutput>#myName#</cfoutput>

Whenever you don't specify a scope when setting a variable, ColdFusion automatically puts it in the VARIABLES scope. As you can see from our list above, the VARIABLES scope comes in at number 6 in the CF 9 order of precedence. So that means that ColdFusion has to search 5 other scopes (and all of the variables in those scopes) before it even gets to the scope it needs. Not only that, but I don't think ColdFusion remembers where your variable is stored once it finds it (although, I may be wrong on that), so that would mean that it has to run through these same checks each and every time you reference that variable.

So, how many scope checks does ColdFusion have to go through to process the following everyday code sample?

<cfset myName = "Eric">

<cfif IsDefined("myName") and myName neq "">
<cfoutput>#myName#</cfoutput>
</cfif>

12 if you're on CF 8, and 18 if you're on CF 9. And that's not even counting the number of variables CF had to look at inside each of those scopes. While there's a good chance some of those scopes may be completely empty, it's still unnecessary processing that ColdFusion is having to go through. You could easily drop the number of checks to zero simply by properly scoping those variables like so:

<cfset variables.myName = "Eric">

<cfif IsDefined("variables.myName") and variables.myName neq "">
<cfoutput>#variables.myName#</cfoutput>
</cfif>

Now, if you take a look back at our order of precedence you'll see that FORM and URL scopes are way down on the list. Which means that every time you reference a form or url variable without prefixing it with a scope, ColdFusion has to run through all of the previous scopes (and all of the variables in those scopes) to try to find your variable. That's a lot of searching on ColdFusion's part. Also, if a variable with the same name exists in one of those other scopes, ColdFusion will just grab it and use it instead and not even bother looking in the FORM or URL scope for your variable. When searching through its Order of Precedence, ColdFusion will always choose the variables on a first-come, first-serve basis.

Using our previous example, let's say the "myName" variable is in the FORM scope instead of the VARIABLES scope.

<cfif IsDefined("myName") and myName neq "">
<cfoutput>#myName#</cfoutput>
</cfif>

The above example would run 27 scope checks on CF 8, and 30 scope checks on CF 9. Now, what if you've got another 15 form variables to process on that page, and you haven't specified a scope for any of them? It starts to add up quickly, doesn't it?

So as you can see, not scoping your variables can cause a lot of unnecessary overhead on ColdFusion's part. Allaire/Macromedia/Adobe have always recommended that you fully scope all variables (although for some reason they rarely do it correctly in the documentation). It's stated in the Fast Track To ColdFusion course, Adobe's Coding Best Practices for ColdFusion Performance, the ColdFusion curriculums, LiveDocs, and hopefully it's stated in the WACK books as well (I don't have a copy handy to look it up).

In the Coding Best Practices link mentioned above, Adobe says:

You can improve performance by always qualifying your variables with the proper scope. Wherever possible use fully scoped variables. A variable that has a scope prefix will be evaluated quicker than an unscoped variable.

LiveDocs states:

Because ColdFusion must search for variables when you do not specify the scope, you can improve performance by specifying the scope for all variables.

It doesn't get much clearer than that.

Now, in all honesty, despite the work ColdFusion has to do to process unscoped variables, it only takes a fraction of a second to do it. But is that really an excuse not to follow good coding practices? In my opinion, no, it's not. There's never a good excuse to write bad code. I can't help but wonder would other programming languages allow something this sloppy?

Am I borderline fanatical about scoping? Probably. Am I going overboard in my little rant here? Probably. Will properly scoping your variables make your CFML applications more efficient? Absolutely.


This post is part of a continuing series of CFML 101 articles. My intent is to produce a blog series aimed at the beginning CFML developer, one which helps to explain basic techniques and concepts to those new to the CFML world. The topics and examples covered in this series focus on the CFML programming language in general, not a specific application server. So whether you're using ColdFusion, Railo, or Blue Dragon (referred to as CF/R/BD) to run your CFML applications, these concepts still apply.

Comments
Travis's Gravatar Lack of scopes bothers me too but more for readibility. The few nano-seconds I save is just an added bonus.
# Posted By Travis | 9/22/10 10:21 AM
CD's Gravatar I thought I was anal-retentive for always scoping my variables. Thanks for confirming that I'm not. :-)
# Posted By CD | 9/22/10 12:15 PM
prims's Gravatar Yes, it is important to scope variables. Instead of doing isDefined it's always good to use structKeyExists. Unscoped variables will not be checked so it make sense not show in the precedence list.
# Posted By prims | 9/22/10 12:53 PM
prims's Gravatar In my earlier comment I was referring to session, request and application scopes.
# Posted By prims | 9/22/10 12:56 PM
Asher's Gravatar I won't deny I don't scope, but it's not to save time -- it's readability.

VARIABLES.x[1] = (-VARIABLES.b + sqrt(VARIABLES.b^2 - 4 * VARIABLES.a * VARIABLES.c )) / 2 * VARIABLES.a;

That's a win, is it?
# Posted By Asher | 9/22/10 6:28 PM
Eric Cobb's Gravatar @Asher - yes, to me that is much more readable, and informative. I instantly know where those variables are coming from. I don't have to wonder if "b" is in the url or "a" is in a form, I see the scope and know that they are all local variables and if I need to modify one I can easily find it in the same .cfm file. Plus, it allows for much more efficient processing by the ColdFusion server.

Do you consider it a win to intentionally write bad code just because it's easier to read?
# Posted By Eric Cobb | 9/23/10 8:02 AM
Asher's Gravatar "Bad code" is a judgment call, and you're making an assumption -- that is, that I don't scope FORM, URL, SESSION, or ARGUMENTS. I guess that's a fair assumption, but the only ones I don't normally scope are LOCAL and VARIABLES.

That right there is one of the two roots returned by the quadratic formula -- but I couldn't tell that from reading it, or not easily. All that screaming about VARIABLES obscures what the code DOES. I don't do write-only code; readability is hugely important.

Bad code that's easy to read isn't bad code. Good code you can't read isn't good code.
# Posted By Asher | 9/23/10 10:05 AM
Eric Cobb's Gravatar "Good code you can't read isn't good code." - That is an excellent point, and I agree 100% with that. But, "Bad code that's easy to read isn't bad code", I can't say I agree with that one. I've seen plenty of easy to read code that was horrible. Bad code that's easy to read is bad code that easier to fix. ;)

It all boils down to a personal preference as to whether or not it's "good" or "bad" or easier to read or not. But, I can't help but wonder, if all CFML developers had been taught from day 1 to always prefix with the variables scope (just like form, url, session, etc...) and that was the accepted standard way of doing things, if it would still appear as "unreadable"?

I think one of the reasons it appears to look so odd is because most developers don't use it, so when you do see it it tends to stand out. While reading properly scoped form or url variables is perfectly fine because it's a widely used and accepted practice.

I think maybe it really comes down to a matter of perception. The word "variables" looks odd tacked on the front of your variable, but "arguments" or "application" look fine even though they have just as many, or more, letters than the world "variables".
# Posted By Eric Cobb | 9/23/10 10:35 AM
Gus's Gravatar FYI.
I did a quick test, and the non-scoped version consistently out performs the scoped version!

I don't know if this blog will take the code format, but here it is:
<cftimer label="Non Scoped" type="outline">
   <cfset myName = structNew()>
   <cfloop from='1' to='10000' index="i">
      <cfset myName[i] = "Eric#i#">
      <cfif structKeyExists(myName,i) and myName[i] neq "">
         <cfoutput>#myName[i]# </cfoutput>
      </cfif>
   </cfloop>
</cftimer>
<br >
<cftimer label="Scoped" type="outline">
   <cfset variables.myName = structNew()>
   <cfloop from='1' to='10000' index="variables.j">
      <cfset variables.myName[variables.j] = "Eric#variables.j#">
      <cfif structKeyExists(variables.myName,variables.j) and variables.myName[variables.j] neq "">
         <cfoutput>#variables.myName[variables.j]# </cfoutput>
      </cfif>
   </cfloop>
</cftimer>

cftimer must be enabled to see the results.
# Posted By Gus | 9/23/10 10:49 AM
Asher's Gravatar "I think maybe it really comes down to a matter of perception. The word "variables" looks odd tacked on the front of your variable, but "arguments" or "application" look fine even though they have just as many, or more, letters than the world "variables"."

Yeah, and it's the best argument for changing the scoping rules I can think of. ARGUMENTS and APPLICATION and FORM are *foreign*. They are *other*. I don't have a problem referring to them explicitly, and it frankly baffles me that they're *allowed* to pollute the local namespace. If I want to talk about a URL variable I'll bleeding well tell you to go look in the URL scope.

I'd settle for a compromise, you know. If I have to preface a local variable with a single symbol, like $ or something, I could go with that. I survived doing it in BASIC, after all. But all this "VARIABLES." business savages clarity -- a line with three references gets thirty characters longer!
# Posted By Asher | 9/23/10 5:07 PM
Travis's Gravatar My big thing with scoping variables is knowing what variable is being used. I've come across to many instances where I see something like this.
<cfquery name = "qSomeQuery">
SELECT someValue
FROM someTable
</cfquery>
<cfset someValue = "x">
<cfset form.someValue = "y">
<cfset url.someValue = "z">
<cfoutput query = "qSomeQuery">#someValue#</cfotuput>
To me it is not clear what the programmer intended and now i have to maintain it. I try to write code so anyone who follows knows what my intentions are.
# Posted By Travis | 9/26/10 8:47 AM
Aeros's Gravatar One thing that I have wondered about on this but never really thought about asking: if you use a variable that is in the [variables] scope but don't scope it, does it actually run through all the scopes or just until it reaches the [variables] scope? We know that if a variable is not scoped CF has to run through the oder but I wasn't sure if it stoped sorting when it found it (6 counts when using CF9) or if it keeps runnign through all of them.

Thanks
# Posted By Aeros | 5/26/11 3:55 PM
Eric Cobb's Gravatar @Aeros - Once CF finds the variable, it stops searching. It uses the variables on a "first come, first serve" basis, so if you have url.firstName and variables.firstName and try to output just #firstName#, then CF will always choose variables.firstName because it will be the first match it finds.
# Posted By Eric Cobb | 5/26/11 10:51 PM