I’m using Big Sexy Poems as a ColdFusion playground for new application architecture ideas. One of the ideas that I’ve been considering is enabling “full null support” at the application level. Historically, dealing with null / undefined values in ColdFusion has been a little tricky. Ideally, ColdFusion would treat null / undefined values the way JavaScript does (which is seamlessly). But, I don’t know what “full null support” even means because I can’t find any documentation at all on the Adobe ColdFusion site. As such, I wanted to run a few tests to see how it might change my ColdFusion style.
The TL;DR is that I won’t be enabling full null support.
First, I created an Application.cfc ColdFusion framework file w…
I’m using Big Sexy Poems as a ColdFusion playground for new application architecture ideas. One of the ideas that I’ve been considering is enabling “full null support” at the application level. Historically, dealing with null / undefined values in ColdFusion has been a little tricky. Ideally, ColdFusion would treat null / undefined values the way JavaScript does (which is seamlessly). But, I don’t know what “full null support” even means because I can’t find any documentation at all on the Adobe ColdFusion site. As such, I wanted to run a few tests to see how it might change my ColdFusion style.
The TL;DR is that I won’t be enabling full null support.
First, I created an Application.cfc ColdFusion framework file with the enableNullSupport property set to true. And even though it isn’t entirely necessary, I left in all of the other core application settings that I traditionally provide such that this might represent a realistic application context.
component hint = "I define the application settings and event handlers." {
// Define the application settings.
this.name = "NullTesting";
this.applicationTimeout = createTimeSpan( 0, 0, 30, 0 );
// Disable session management.
this.sessionManagement = false;
this.setClientCookies = false;
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
// Turn on native handling of "null" values (whatever that means?!). This is the
// feature that we're exploring in this post.
this.enableNullSupport = true;
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
// As a security best practice, we DO NOT WANT to search for unscoped variables in any
// scope other than the core variables, local, and arguments scope. The CGI, FORM,
// URL, COOKIE, etc. should only ever be referenced explicitly.
this.searchImplicitScopes = false;
// Make sure that every struct key-case matches its original defining context. This
// way, we don't get any unexpected upper-casing of keys (a legacy CFML behavior).
this.serialization = {
preserveCaseForStructKey: true,
preserveCaseForQueryColumn: true
};
// Make sure that all arrays are passed by reference. Historically, arrays have been
// passed by value, which has no place in a modern language.
this.passArrayByReference = true;
// Stop ColdFusion from replacing "<script>" tags with "InvalidTag". This doesn't
// really help us out.
this.scriptProtect = "none";
// Block all file extensions by default. This will require each fileUpload() call to
// have an explicit set of allow-listed mime-types.
this.blockedExtForFileUpload = "*";
}
With enableNullSupport turned on for the entirety of this ColdFusion application, I then created a very simple test runner. In this test runner, each test is defined as a closure that is executed within a try/catch block. As the closures are executed, either the passing result or the failing error message is output to the screen.
For the collocation of code and outcome, I’m going to embed the results as comments in the following code along with some brief thoughts:
<cfscript>
test(() => {
// PASS: YES
// --
// Thoughts: The "null" keyword is a first-class citizen of the language when full
// null support is enabled. This makes sense.
return ( javaCast( "null", "" ) == null );
});
test(() => {
// PASS: YES
// --
// Thoughts: The "null" keyword is a first-class citizen of the language when full
// null support is enabled. As such, we better be able to test to see if the null
// value is, in fact, null.
return isNull( null );
});
test(() => {
// FAIL: Variable UNDEFINEDVARIABLE is undefined.
// --
// Thoughts: ColdFusion is drawing a stronger distinction between "null" and
// "undefined" when full null support is enabled. In a traditional ColdFusion app,
// this test would actually PASS. However, with full null support enabled, this
// test now fails. I DON'T LOVE THAT.
return isNull( undefinedVariable );
});
test(() => {
// FAIL: Element UNDEFINEDVARIABLE is undefined in a Java object of type class
// coldfusion.runtime.VariableScope.
// --
// Thoughts: This is really the same test as above, I just wanted to make sure
// that scoping the value wouldn't change the behavior.
return isNull( variables.undefinedVariable );
});
test(() => {
// FAIL: Variable UNDEFINEDVARIABLE is undefined.
// --
// Thoughts: This is really the same test as above, I just wanted to make sure
// that there wasn't a sneaky difference between isNull() and a direct null check.
return ( undefinedVariable == null );
});
test(() => {
// FAIL: Element keyToBeDeleted is undefined in VARIABLES.
// --
// Thoughts: More evidence that full null support creates a strong distinction
// between "null" and "undefined". When you delete a key from the struct, the key
// is no undefined, not null. As such, trying to reference it after deletion
// throws an error.
variables.keyToBeDeleted = "hello";
variables.delete( "keyToBeDeleted" );
return ( variables.keyToBeDeleted == null );
});
test(() => {
// PASS: YES
// --
// Thoughts: Again there's a strong distinction now between a null value and an
// undefined value. In this case, instead of deleting the struct key, we're
// setting it to null which is still "defined" and can therefore be referenced.
variables.nullVariablesKey = null;
return ( nullVariablesKey == null );
});
test(() => {
// PASS: YES
// --
// Thoughts: this is the same test as above, essentially
variables.nullVariablesKey = null;
return isNull( variables.nullVariablesKey );
});
test(() => {
// FAIL: Element NULLREQUESTKEY is undefined in a Java object of type class
// coldfusion.runtime.RequestScope.
// --
// Thoughts: WHAT THE HECK?! This is the exact same test as above, only using the
// `request` scope instead of the `variables` scope. There's no reason that the
// behavior of this feature should change depending on which scope I'm using. I'd
// say this a bug; but since I can't find any documentation on this feature, this
// might be a documentation issue.
request.nullRequestKey = null;
return isNull( request.nullRequestKey );
});
test(() => {
// FAIL: Element undefinedRequestKey is undefined in REQUEST.
// --
// Thoughts: This is the same as an earlier test; but now that we see (from the
// previous test) that the `request` scope is somewhat special, I just wanted to
// see how "undefined key" references would work (would they be different?).
return ( request.undefinedRequestKey == null );
});
test(() => {
// PASS: YES
// --
// Thoughts: The safe-navigation operator will short-circuit the evaluation of an
// an expression using an undefined key and will return null. Therefore our
// comparison of null to the resultant value is true.
return ( request?.safeNavKey == null );
});
test(() => {
// PASS: YES
// --
// Thoughts: This is the same as earlier tests, just with the `local` scope. I
// wanted to make sure that the local scope didn't do something tricky.
var nullLocalValue = javaCast( "null", "" );
return ( nullLocalValue == null );
});
test(() => {
// PASS: YES
// --
// Thoughts: The noop() function returns "void"; which appears to be the same as
// null when full null support is enabled.
return ( noop() == null );
});
test(() => {
// FAIL: The element at position 1 of dimension 1, of array variable "ELEMENTS"
// cannot be found.
// --
// Thoughts: Again this underscores the strong distinction between null and
// undefined values. I can't use null to test for undefined indices in an array.
var elements = [];
return ( elements[ 1 ] == null );
});
test(() => {
// PASS: NO
// --
// Thoughts: This doesn't throw an error and was more of a curiosity. The `cgi`
// scope has historically been safe to reference. All "undefined" key look-ups
// will result in an empty string. It seems that full null support does not change
// this behavior. But, should it?
return ( cgi.this_is_make_believe == null );
});
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
/**
* Light-weight test harness.
*/
private void function test( required function operation ) {
try {
writeOutput( "<p> PASS: #encodeForHtml( operation() )# </p>" );
} catch ( any error ) {
writeOutput( "<p> FAIL: #encodeForHtml( error.message )# </p>" );
}
}
/**
* I return void. But, what is "void" when full null support is enabled?
*/
private void function noop() {
// ....
}
</cfscript>
Thoughts On Full Null Support
Since I can’t find any documentation on how this feature works, I’m just doing my best here to feel along in the dark. After running these tests, I don’t see any value in enabling full null support. In fact, it seems that full null support actually make my ColdFusion application more complex not less complex. I was hoping that the code would be simplified; but, all this feature seems to do is create more edge-cases and caveats.
And, I didn’t even look at how SQL Query behaviors might have been impacted. Historically, ColdFusion has safely returned null query values as empty strings (a huge value add to data processing)! If those null values were suddenly to return as non-strings, that would be not great.
Consider the need to look at the value:
thing.property
If the existence of this property value is in question, traditional ColdFusion applications could simply check:
isNull( thing.property )
or,
thing.keyExists( "property" )
But with full null support enabled, it’s no longer that simple. When null and undefined are two different “things”, that isNull() check might throw an error or it might not. You’d literally have to check to see if the key exists first; and then, only if it exists, check to see if it’s null.
That’s just added complexity. This is a “hard pass” for me. But I’d love to hear other people’s experiences. Maybe there’s a huge value add that I’m not able to see in a simple set of tests.
Want to use code from this post? Check out the license.
I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
— Ben Nadel Managed hosting services provided by: