Monday 9 September 2013

Things I don't like about CFScript

G'day:
This is a lunch-time article so I gotta be quick.

Now don't let the title put you off... I love CFScript and that's what most of my code is written in. I think tags only have a place in files in which one is interacting with text, other than that, one's code should always be in script. However some of the ways things have been shoe-horned into script make me shudder slightly. Or a lot.

Firstly, this whole practice of simply lopping off the "<cf" and ">" from a tag and going "tada! CFscript!" is just shit. Tag code without the prefix and suffix is still tag code, not script code.

<cfparam name="foo" default="bar">

Becomes:

param name="foo" default="bar";

That's not script. That's a tag without its angly bits. And really doesn't fit with the rest with CFScript. Script solutions to tags should follow the existing style of the script code. Things that return values are implemented as function calls:

foo = param("foo", "bar");

Obviously there are more options there relating to type-checking, so I'd probably implement that as a struct of options:

foo = param("foo", "bar", {type="regex", pattern="[a-z]+"});

The exact minutiae of the implementation is beyond the scope of this article.

Stuff that has a block still follows the same format: an operation followed by parameters in parentheses, followed by the block statement:

for (i=1; i <=5; i++){
    // do stuff here
}

The for statement doesn't return a value, but if a block-type construct did, it should still be returned as the left-hand-side of an expression. Consider this:

savecontent variable="stuff" {
    // stuff here
}

That's awful. It should be:

stuff = savecontent {
    // stuff here
}

Possibly the most shit example of this syntax I've seen is Railo's approach to a query in CFScript. The details of how the SQL is captured aside, the general syntax is similar to savecontent's:

query name="record" datasource="dsn" maxrows=1 {
    // SQL here
}

No. It should be:

record = query(datasource="dsn", maxrows=1) {
    // SQL here
}

Whether or not the params take name/value pairs or just use normal built-in-function-syntax of ordered params, I dunno. A lot of tags have become such a mess over the years with every-single-option-possible-plus-the-kitchen-sink-as-well being tag attribute that passing ordered args might be prohibitive. Do you know how many attributes <cfquery> has? 16. Ridiculous. However four of these are for caching so could be one argument passed as a struct. A lot of them relate to how the connection to the DB is made, or the DB handles the data, so could be another single struct argument, not multiple simple arguments. So perhaps some reorganisation there could mitigate the haemorrhage of values one needs to pass to the tag. I imagine a lot of tags with endless seas of attributes could likewise be handled better.

NB: I dunno if the ColdFusion way of handling queries in CFScript is better or worse than Railo's. Both are a bit crap. All they need to give us is a single function which takes some attributes (and some parameters!), and runs a block of logic to capture some SQL. Or just take a string for the SQL, whatever.

Another thing is this notion of programmatically-meaningful comments that components and functions have. It's dire as well. Comments should never impact code execution. I'm all fine with comments as metadata (for hints, displaynames, etc), or to be parseable for documentation (like javadocs), but one can write actual executable code in comments when they're adjacent to component or function statements! Unbelievable. I get how there was a need to annotate these constructs, but not with comments. Some other annotation style should have been used. As my namesake points out in a comment below, there's a bug for this: 3037174. Please go vote for it.

CFML also needs to make up its mind when it comes to when "attribute" names work / are required, or when they don't. Consider these poorly-implemented tag to script ports:

Do we specify the attributes or not?

<cfabort showerror="message">

Becomes:

abort "message";

Sometimes it's both:

<cfinclude template="inc.cfm" runonce="true">

Has a mishmash of both specified attributes or unspecified ones:

include "inc.cfm" runonce=true;

Is it a statement?

<cfthrow message="message">

is implemented as:

throw "message";

Or a function?


<cfthrow type="AnException" message="message">

becomes:

throw("message", "AnException");

What even is its bloody name?

<cfdump var="#something#"> 

Isn't implemented as  dump() (which would make sense!), but is writeDump()!

writeDump(var=something);

TBH, if one catalogues the idiosyncrasies like that, the language is just becoming PHP.

I think both Adobe and Railo are neither putting much thought into their approach here, as well as focusing on what's easiest for them, not actually what would make sense, nor what would be best for the language. Guys: it's not a race. Don't just trot any old shit out: do the job properly!

Another thing Adobe have done is when they start with a tag like <cfimage> or <cfspreasheet>, their solution to providing functions for the functionality is to create a great morass of  contextless "procedural" functions, where the better solution would be to have a more OO approach. So instead of 50-odd separate functions, they could instead have provided one class "Image" which has a bunch of methods. CF's been an OO (-ish) language since CFMX6.0, but Adobe don't seem to have actually noticed. All the stuff they trot out is still procedural! Well except for the likes of Query.cfc, but that code and implementation is shocking, so is perhaps an example or an explanation as to why they don't produce more object oriented code.

Speaking of a more OO sort of solution (yes, this is all very rapid stream of concsciousness stuff, I know), Threading could be tidied up a bit along these lines too:

t1 = new Thread {
 // stuff here
}
t1.sleep()
t1.terminate()
Thread.join([t1,t2,etc])

This makes a lot more sense and looks much more in-keeping with the language than the tags-in-script approach they've given us so far.

I think Adobe are gonna try to finally fulfill a promise they floated back in CF9: that they'd give us full CFML functionality via CFScript in ColdFusion 11 (let's agree: that excludes anything that interacts with the UI!). As well as rounding out the language, I think they should step back and re-examine what they have already done, and see if they've done it right. Because in a lot of places I think they have not.

Also - going forward - almost all functionality should initially be implemented in a script-ready way. And only if it makes sense to have a tag version of it should there be a tag. And I'd go further and say that if the answer is "yes" that there should be a tag version... I question whether they should actually be implementing that feature at all. It'll probably be some sort of UI sugar thing, yet ultimately taste like vinegar.

Phew.

--
Adam