Wednesday, 27 January 2016

ColdFusion: how not to document a function

G'day:
We've finally nailed down an Adobe ColdFusion Team member on the #documentation sub-channel of the CFML slack channel. So if you have issues with the documentation, post there, and direct it to +Jacob Royal (ooh: Google added that link for me automatically. Cool!).

Coincidentally this morning I read of a CFML newbie trying to write code, assisted by said documentation (ColdFusion parsing JSON). It's a reasonable question, and the root of the problem is they tried to use the online ColdFusion docs to learn how to do something. Worse: they tried to glean how the deserializeJson() function works from reading the code example. One cannot really fault a newbie for RTFMing, but this is often not a helpful thing to do when trying to learn CFML. Because the code examples on the Adobe ColdFusion documentation site are, generally, sh!t.

Here's the code example. How quickly can you spot the actual usage of deserializeJson():

<!--- Get the JSON Feed --->
<!--- JSON data is sometimes distributed as a JavaScript function.
The following REReplace functions strip the function wrapper. --->
<cfset theData=REReplace(cfhttp.FileContent,
"^\s*[[:word:]]*\s*\(\s*","")>
<cfset theData=REReplace(theData, "\s*\)\s*$", "")>
<!--- Test to make sure you have JSON data. --->
<cfif !IsJSON(theData)>
<h3>The URL you requested does not provide valid JSON</h3>
<cfdump var="#theData#">
<!--- If the data is in JSON format, deserialize it. --->
<cfelse>
<cfset cfData=DeserializeJSON(theData)>
<!--- Parse the resulting array or structure and display the data.
In this case, the data represents a ColdFusion query that has been
serialized by the SerializeJSON function into a JSON structure with
two arrays: an array column names, and an array of arrays,
where the outer array rows correspond to the query rows, and the
inner array entries correspond to the column fields in the row. --->
<!--- First, find the positions of the columns in the data array. --->
<cfset colList=ArrayToList(cfData.COLUMNS)>
<cfset cityIdx=ListFind(colList, "City")>
<cfset tempIdx=ListFind(colList, "Temp")>
<cfset fcstIdx=ListFind(colList, "Forecasts")>
<!--- Now iterate through the DATA array and display the data. --->
<cfoutput>
<cfloop index="i" from="1" to="#ArrayLen(cfData.DATA)#">
<h3>#cfData.DATA[i][cityIdx]#</h3>
Current Temperature: #cfData.DATA[i][tempIdx]#
<b>Forecasts</b>
<cfloop index="j" from="1" to="#ArrayLen(cfData.DATA[i][fcstIdx])#">
<b>Day #j#</b>
Outlook: #cfData.DATA[i][fcstIdx][j].WEATHER#
High: #cfData.DATA[i][fcstIdx][j].HIGH#
Low: #cfData.DATA[i][fcstIdx][j].LOW#
</cfloop>
</cfloop>
</cfoutput>
</cfif>

Note: I have not lost any formatting there... that's how it's presented in the docs.

What a f***ing mess. There's 43 lines of code there, and it's not until line 17 that deserializeJson() is actually used. Every single other line in there is a complete waste of time. What the hell has HTTP calls, treating the result like JSONP and mark-up got to do with deserialising JSON? Nothing.

Also note at no point do they actually show what the result of that bullsh!t might be.

The example should be this:

someJson = '{"stringValue":"a string", "arrayValue": ["a","b","c"], "booleanValue":true, "numericValue": 42}';
myStruct = deserializeJson(someJson);

writeDump(myStruct);

And simple show the output:


There should then be additional simple - and separate - examples showing the other options such as how to deserialise a previously serialised query, so that the deserialised object becomes a query again, not a struct of arrays of structs (or however the hell ColdFusion handles it).

And another example of using a custom deserialiser. Although even that is probably out of scope for this page, instead linking through to a broader section on custom serialisers.

None of this is rocket science. Even making the comparison to rocket science is like comparing... oh, I dunno... scribbling on a sheet of paper with a crayon to rocket science.

Almost all the docs for ColdFusion are like this. Crap.

cfdocs.org is better in this regard, but I think the example here does still let them down a bit:

person = deserializeJSON("{""company"":""Foundeo"",""name"":""Pete Freitag""}");
writeOutput( person.company );

It's nice and succinct which is good, but I think it unnecessarily muddies the water by using " as the string delimiter, as JSON itself requires that character as a delimiter, so the source string is - as a result - a bit confusing. For no good reason, given Pete could have instead simply used a single quote as a delimiter:

person = deserializeJSON('{"company":"Foundeo","name":"Pete Freitag"}');
writeOutput( person.company );

It's not much different, but I think it is a lot clearer. Always try to keep code examples on point. If somethign is unnecessary to the example: get rid.

One great thing about cfdocs.org is that it's community maintainable. So I expect by the time you come to read this, Dan Fredericks will have gone in and fixed this (that's me getting revenge on Dan for him volunteering me to write docs, yesterday ;-).

And one crap thing about the Adobe docs - ell: "another crap thing ~" - is that whilst they used ot be community maintainable, they removed that functionality recently. Cos they're daft.

Anyway, I'll jump on Jacob to get this sorted out, and hopefully encourage him to start weeding out the rest of the crap in there, and get the code examples more focused. I hasten to add that I doubt Jacob wrote those docs... they will predate his involvement in this. I don't blame him one bit for the lack of common sense employed on that site.

Righto.

--
Adam