Optimizing Your Code - Scope Cascading

While optimizing a client’s application today, I noticed something interesting that I wanted to share with you.

The client’s application is running on FW/1, so the examples here relate directly to that framework, but the conclusions and techniques apply to any application or code base.

A common issue with CFML code performance arises from Scope Cascading. Scope cascading is the CFML “feature” that allows you to refer to variables in one of several scopes without explicitly scoping them. For example, referring to the variable CGI.REMOTE_ADDR as REMOTE_ADDR, or to Form.email simply as email.

The problem with not scoping your variables is that Lucee, or any CFML engine for that matter, needs to search for them in the different scopes. So when you reference the variable susi, for example, Lucee would first check for its existence in the Variables scope, and if not found there, it would try the scopes CGI, URL, Form, Cookie, etc.

Psuedo-codingly speaking it works something like this (simplified for the sake of clarity):

function getUnscopedVariable(variableName) {
  for (var scope in [ Local, Variables, URL, Form, Cookie, CGI ])
    if (scope.keyExists( variableName ))
      return scope[ variableName ];
  throw(message="Variable #variableName# does not exist");
}

Inside Functions things are even worse, since now there are two more scope to search: the Local scope and the Arguments scope. And while the Local scope is the nearest (first searched) for unscoped variables, it is not the default scope to which new unscoped variables are written to. The default scope to which new variables are written is the Variables scope of the Page or Component (which can also introduce concurrency issues but that’s for a different post).

So back to FW/1 and our client’s application…

Apparently, all of the View templates in FW/1 are included from a function named internalView() of the component framework.one.cfc (org.corfield.framework.cfc in older versions). Because of that, your View templates live inside a function, and therefore any unscoped variable (probably all of them?) inside your View templates are subjected to the Scope Cascading issue mentioned above.

So if your View template contains for example:

<cfset susi = 1>              <!--- (1) !--->
<cfoutput>#susi#</cfoutput>   <!--- (2) !--->

Then at point (1) the variable susi is first searched for in Local scope, when not found there it cascades to the Arguments scope, and when not found there it cascades to the Variables scope and written to it (if it were found in the Local scope then the search would have stopped there).

And at point (2) the variable susi is again searched for in the Local scope first, when not found there it is searched for in the Arguments scope, and when not found there it cascades further to the Variables scope.

Now imagine that you have something like this in your View template:

<cfloop from="1" to="100" index="ii">
  <cfset writeOutput( susi++ )>   <!--- (3) !--->
</cfloop>

That produces a lot of cascading! And a lot of unnecessary code execution which slows down your application for no reason.

Fortunately, there is an easy fix for this issue: Scope your variables! So instead of the example above, write:

<cfset Local.susi = 1>        <!--- (3) !--->
<cfoutput>#susi#</cfoutput>   <!--- (4) !--->

Now there is no ambiguity as to where the variable susi should be stored in point (3), and since Local scope is the nearest scope inside the function, then point (4) is also executed efficiently without any cascading. Notice that because the Local scope is the nearest scope, there is no need to scope the variable at point (4).

Tip: Variables in the nearest scope do not need to be scoped since they are found in the first scope searched and therefore no cascading takes place.

You might be reluctant to prefix all of your variables with Local. or var (some find it tedious, ugly, or both), and if that’s the case – then Lucee has an even simpler solution for you: set the LocalMode to Modern.

Setting the LocalMode to Modern changes the default behavior of CFML, and writes newly introduced variables in a function to the Local scope, as opposed to the Variables scope. There are a few ways to do that:

  1. Set the “Local Scope Mode” to Modern for the whole Server or Web Context. That can be done in the respective Lucee Admin at the Server/Scope page.

  2. Set the “Local Scope Mode” to Modern for the Application at the Application.cfc by adding

this.localMode = "Modern";
  1. Set the “Local Scope Mode” to Modern for the function only. That can be done by adding the attribute localmode="modern" to the function’s definition. In the case of FW/1, that means editing the framework.one.cfc file and updating the line:
private string function internalView( string viewPath, struct args = { } ) {

to read:

private string function internalView( string viewPath, struct args = { } ) localmode="modern" {

Whichever method you use, once you set the LocalMode to Modern, any unscoped variable inside a function will be set to the Local scope, thus preventing the unnecessary cascading.

Tip: You can enable the “Implicit Variable Access” option in Lucee’s Admin’s Debugging Settings to easily find all of the unscoped variable accesses that should have been scoped.

4 Likes

p.s. You can also set localMode to true instead of the string “Modern”. Nowadays I never write “Modern”. I either add to the function definition localMode=true, or better yet, set it for the whole application in Application.cfc:

this.localMode = true;
1 Like

It would be nice to be able to set this per CFC

<cfcomponent localmode="true">

https://luceeserver.atlassian.net/browse/LDEV-2919

1 Like

We discussed that in the past but the context of a function can change so that’s not that simple, e.g. if you have the function defined in one component, but then another component sets a reference to it and it has different settings. It can cause unpredictable results which will add confusion and bugs.

1 Like

Even though it is technically unnecessary to scope the variable at line (4), within functions it is a best practice to do so imho.
There are many reasons for scoping even the “nearest scope” within functions:

  1. if line (4) is actually line (104), then you lost your visible reference to the scoped variable. Makes code unpleasant to read, and may introduce issues when refactoring.
  2. Inside a query output, unscoped variables get extra confusing.
  3. if you refactor line (3) later on, and then forget to scope the variable, or remove it altogether, the rest of the code might silently start using the variables. scope.
  1. By rigourously scoping the local. scope, code is more readable, debugs faster, plus can catch errors where scoping was forgotten earlier on in the code.

Using the this.localMode=true setting is great if you write all your code yourself. Unfortunately, there are many software packages and modules out there which don’t work correctly with this setting turned on.

1 Like