Saturday 19 July 2014

Oh dear: tags vs script in CFML

G'day:
I'm surprised this one didn't come up earlier (like back when I did this one: "/>"). But Marcus Fernstrom and I started talking about this last night on Twitter, and - despite "best" efforts - I've concluded 140-char-limits are just stupid for such things.

My personal opinion regarding CFML code is that tags are very clunky, and in modern code seldom have an appropriate place in one's code, compared to the clarity and comparative elegance of CFscript-based code.

Some people will already feel their undies bunching up at this point... get over yourselves. This is just my opinion. You're entitled to yours, I'm entitled to mine. This is my blog, so we're getting mine.

That's the most significant thing here: it's all opinion. If you rationalise to yourself why you think tags are better and stick with that, go yer hardest. However you should look at your rationalisations and check whether they hold much water before offering them.

Background

CFML's initial inception was simply to offer some dynamic enhancements to HTML. Back in 1995 most websites comprised a few individual, hand cranked HTML documents. A homepage.html; aboutus.html; products/widget.html, products/doohickey.html etc. It was bloody tedious for the "web master" to maintain. So along came Cold Fusion with its CFML (or it might have been DBML back then... by the time I started using ColdFusion it was one word, and five years later). CFML was specifically designed to notionally "extend" HTML by putting dynamic preprocessed elements into the mark-up. So instead of this:

<table border="1>
    <tr><td>My first product</td><td>some description</td><td>£1.00</td></tr>
    <tr><td>My second product</td><td>some description</td><td>£2.02</td></tr>
    <tr><td>My third product</td><td>some description</td><td>£3.33</td></tr>
    <!-- etc -->
</table>

One could simply have this:
<table border="1>
    <cfoutput query="products">
        <tr><td>#name#</td><td>#description#</td><td>#price#</td></tr>
    </cfoutput>
</table>

One could also do cool stuff like this:

<html>
<head>
<cfinclude template="sitewide/headBlock.cfm">
</head>
<body>
<cfinclude template="sitewide/header.cfm">
<table>
    <tr>
        <td><cfinclude template="section/leftNav.cfm"></td>
        <td><cfinclude template="section/mainContent.cfm"></td>
    </tr>
</table>
<cfinclude template="sitewide/footer.cfm">
</body>
</html>

So one could factor-out common code for headers, footers, etc.

And mainContent.cfm would be a sea of HTML / CFML intermingled together with a <cfquery> adjacent to the <table> tag it was providing the data for (or, hey, a <cftable>!), HTML muddled in with business logic, etc. This was the approach to building a web page on a web site: rip out static HTML and replace it - inline - with whatever CFML was needed to generate the replaced HTML. It was a shockingly naive approach to coding, but the web and CFML were young, and it was a helluva lot better than maintaining static HTML.

Some people started to realise it was nicer to stick all the specifically codey bits into separate files, and the specifically mark-up-y bits in another file too. That way separate files busied themselves with separate tasks. And once an intermediary level of files which existed to control which code files and which display files were needed for a given page, we started getting coherent MVC frameworks appear such as Fusebox and its ilk.

Frameworks, organised code and CFScript

At about the same time Fusebox came out, CFScript was added to CFML (version 4.0, as far as I can tell: it's not mentioned in the Cold Fusion 3 docs). The raison d'etre of CFScript seems to have been a realisation - even at that time - that tags were all well and good, but not that suited for more "programmy" sort of code: branching logic, looping, calling lots of functions, and not really doing much with mark-up. So Allaire "got it" way back in 1997. Almost 20yrs later a lot of the CFML community still haven't.

However the implementation of CFScript was never approached in a professional thorough fashion, and - to be brutally honest - I don't think the language designers of CFML really knew what they were doing with the language once CFScript came along. Clearly there was the realisation that some code warrants intermingling with mark-up: go the tags! Yet some code does not: CFScript. So we got <cfstoredproc> rather than executeProc(), <cfldap> instead of queryLdap() (or something, I dunno). Clearly tags like this have no interconnection to mark-up, so it's just stupid that they were ever tags. Bear in mind that my suggested executeProc() there is not a CFScript construct; it's just a function. It would have worked equally well in tags:

<cfset result = executeProc("usp_dostuff", inputs, outputs)>

As it would in CFScript:

result = executeProc("usp_dostuff", inputs, outputs);

Poor language design basically consigned CFScript to second-class-citizen status of looping and branching and calling functions; but severely hamstrung by a lot of CFML's functionality inappropriately being implemented as tags.

So people had no choice. One had to use tags for common operations like querying, making HTTP calls, including sub files or modules etc. None of these operations ever should have been implemented as tags (or implemented in CFScript right from the outset), yet we were forced to write tags.

But this was OK because most people were still just slapping all their business and display logic together anyhow: so tags still made a kinda sense given they were right there with the HTML tags.

Language maturation

Along came CFMX 6.0, and CFCs. Previously one could factor out reusable codes using includes and modules, but now one could create objects too. And by the time 6.1 came out, they were usable.

Now a real separation of coding concerns was possible: CFC methods for one's business logic, CFMs for display code/logic (there's still gonna be looping logic 'n' stuff in the CFM files). But Macromedia did not revisit CFScript and back-fill it with the functionality and constructs to write the business logic without needing to use tags (which are clearly now out of place in such files). And - f*** me you morons - they implemented CFCs as tag-based. Honestly, that they ever implemented the <cfcomponent> and <cffunction> tags beggars belief. But it's a demonstration of the design-rot and condescension Macromedia applied to their development community (this continues to this day): "oh, the CFMLers aren't good enough to be able to get their brains around script-based language; they need it to be in tags so they can understand it". Patronising f***s.

I think CFC usage got off to a slow start - Macromedia might have been patronising but they were also right to a degree - so people will still mostly mishmashing their code and their mark-up, and there was not much traction from the few of us going "can you please stop implementing this functionality as tags only, and also port some functionality that already exists over into CFScript". This was met on the pre-releases with derision and the position that we'd all rather new tagsfunctionality, and moving stuff to CFScript was a waste of time.

But finally people became accustomed to separating out their business logic and their display logic, and the case for not being forced to write business logic in tags was acknowledged, and in CF9 one was finally able to relegate tags to view files, and write one's CFCs entirely in a situation-appropriate syntax. IE: not in tags. In script.

Adobe claimed to be aiming for 100% functionality support for CFScript in CF9 and CF10, and finally got around to it in CF11 (although they made a mess of it, but hey).

Now. I do not write all my code in CFScript. And I do not dislike tags. I think tags are cool. Used sparingly, and where they were originally intended to be used: when inter-operating with mark-up. So in your view files. I will always write tag code in my views... if I feel the urge to shift some code into a CFScript block, it's a sign that my view is doing too much work. Re-factor. I'm also interested in trying to revive custom tags as a concept, as I'd much prefer to use those than CFML tags: have more view-focused tags to do the specific task for that view, rather than use generic tags; which end up needing more code than appropriate for a view, sometimes.

"I just prefer tags"

Some people - like Marcus - still try to come up with rationalisations as to why they stick with tags. The only valid rationalisation I've heard is: "I just prefer tags". That's fine. I think you're career-limiting by having that attitude (and demonstrating a lack of comfort in your language of choice!), but it's up to you.

"Tags are clearer"

One I don't think is valid is "I find the code clearer". How is this clearer:

<cfcomponent extends="Base" output="false" hint="Does the stuff">

Than this:

/**
* @hint Does the stuff
*/
component extends="Base" {

The tag version is a mishmash of functional declaration, artifact of poor language implementation (needing to have the output="false") and documentation all conflated in one.

The script version is better organised, has less cruft, and is... innately... clearer. Don't confuse "I'm used to looking at tags so other syntax confuses me slightly", with the notion of "clarity". All things being equal, I don't see how a reasonable person can claim the tag code is clearer here.

What about this:

<cffunction name="firstXDayOfMonth" returntype="date" access="public" output="false" hint="Returns a date object of the first occurrence of a specified day in the given month and year.">
    <cfargument name="dayOfWeek" type="numeric" required="true">
    <cfargument name="month" type="numeric" required="true">
    <cfargument name="year" type="numeric" required="true">    

Compared to:
/**
* @hint Returns a date object of the first occurrence of a specified day in the given month and year.
*/
public date function firstXDayOfMonth(required numeric dayOfWeek, required numeric month, required numeric year)

I did not cherry-pick that code to demonstrate my point... it's the first function listed on the CFLib.org home page.

Also note that the script version is 224 chars as opposed to 375. One one hand: not much; on the other hand, the tag-version is 40% more. And that difference perpetuates throughout every single line of code. I am not arguing about more/less typing here; I'm suggesting there's 40% more text to wade through to read the code. Which is a waste of time.

At its very simplest. Which is clearer:

<cfset x = 1>

x = 1;

(in Railo one doesn't even need the semi-colon)

It's not the tag. The script code is clearer.

"I have this one example in which tags are clearer than CFScript, so tags are clearer"

What about this though:

<cfquery name="filteredNumbers" dbtype="query">
    SELECT    id,en,mi
    FROM    numbers
    WHERE    1=1
    <cfif NOT isNull(URL.min)>
        AND id >= <cfqueryparam value="#URL.min#" cfsqltype="CF_SQL_INTEGER">
    </cfif>
    <cfif NOT isNull(URL.max)>
        AND id <= <cfqueryparam value="#URL.max#" cfsqltype="CF_SQL_INTEGER">
    </cfif>
</cfquery>

Or:

sql = "
    SELECT    id,en,mi
    FROM    numbers
    WHERE    1=1
";
params = [];
if (!isNull(URL.min)){
    sql &= "AND id >= ?";
    arrayAppend(params, URL.min);
}
if (!isNull(URL.max)){
    sql &= "AND id <= ?";
    arrayAppend(params, URL.max);
}
filteredNumbers = queryExecute(sql, params, {dbtype="query"});

I'm gonna give that to the tags. Cool. Some coding constructs do work better in tags. This doesn't then mean everything should be done in tags. Just the stuff that works better. I make a point of saying this because I have actually had a number of people justifying using tags across the board because they can come up with one situation in which tags are clearly better.

That said though... I specifically contrived a situation in which tags would work better there. I think having the conditional logic in there to build the SQL string is a bit hairy to start with. If the SQL was static, it becomes this:

<cfquery name="filteredNumbers" dbtype="query">
    SELECT    id,en,mi
    FROM    numbers
</cfquery>


Or:
filteredNumbers = queryExecute("
    SELECT    id,en,mi
    FROM    numbers
    "
);

I think there's very little in that, and I dunno whether one has merit over the other.

"I'm just more comfortable with tags"

That is a very career-limiting statement. Don't tell anyone that even if it's true. And if it is true, you've just admitted to yourself that you are not comfortable with a very fundamental part of your language of choice. You should be actively doing something about that. Odds-on you won't always be looking at your own code, so you need to be comfortable with the whole language. Not just the basics.

"Back in older versions of CFML CFScript was not good, so I don't use it"

And that would be relevant next time you travel back in time to one of those situations again.

If you're still locked in the previous decade using CF8 or lower: fair enough. CFScript was hamstrung. And your current code will be better off being written in all tags. Gotcha. Still: hopefully you'll not be stuck in the past too long, and at some stage you'll join us in the present and you'll need to be comfortable with CFScript. Just bear that in mind.

"What does it matter?"

You need to have more pride in your work, sunshine. I'm not saying "CFScript = something to be proud of", but your code quality should always matter. So you should be considering what equates to good quality code. Draw your own conclusions, but don't hide behind false rationalisations.

"You're just elitist"

Well if your definition of "being elitist" is that I know when to use tags and when not to... yup: guilty as charged. What's your point?



To conclude, I really think you should do what you want with your CFML. But I find people who doggedly stick with tags really ought to evaluate what they're doing, and assess their rationalisations a bit.

I'm all ears if anyone has any different rationalisations. Maybe there's some valid ones out there.

One place though that I think tag-based code really ought to be actively avoided / reworked is in example code and tutorials. There's a lot of sample code out there teaching people to write code like it's 1996. I really wish people wouldn't perpetuate these practices on unsuspecting newbies.

--
Adam