Coding Standards

I admit, I am a code diva. I want to see and write clean, clear, prettified code. I firmly believe that all programmers (and teams) should have, and more importantly follow, a set of coding standards. A few of my personal CF quirks are:

  • Tabs not spaces for code indentation.
  • Functions are camel-cased, e.g., querySetCell and arrayToList.
  • Variables are camel-cased and descriptive, e.g., currentRecord and dateOfBirth.
  • Component names are capitalized, e.g., User and UserService.
  • SQL keywords are upper case, e.g., SELECT username FROM users.
  • Use implicit boolean function results, e.g., .
  • Use structKeyExists over isDefined whenever possible.
  • And so on.

The important concept behind coding standards is not necessarily the nitty-gritty details of what is good and what is bad, but the discipline it provides your coding efforts. Consistency in coding is more important than the (endless) debates about which code formatting template is more readable or which function is better. If you cannot read your own code because you never structure it the same way twice then you have only yourself to blame.

CFUnited 2008

This was my third CFUnited and once again I found it was worth every penny to be there. I focused most of my attention on sessions in the frameworks and advanced CF tracks, but also managed to go to a few Flex-related sessions.

ColdSpring

I recently started to use ColdSpring for its dependency-injection engine, so I attended Mark Drew's ColdSpring session on Wednesday and Chris Scott's ColdSpring 1337 session on Thursday. Both sessions gave me plenty to think about and explore. I need to find an application to experiment with the AOP capabilities of the framework.

I18N

I may soon get the chance to help design an internationalized application, so I attended Oguz Demirkapi's session on creating multi-lingual CF applications. I really learned a lot about the built-in localization abilities of CF and tips for truly working in Unicode.

Flex

The Flex-related sessions I went to really got me excited about using Flex for a project. Matt Woodward's Real World Flex and ColdFusion session made building a Flex front end for a CF app really look easy. Mike Nimer's session, Flex 3 for ColdFusion Developers, went into more technical details about integrating the latest Flex release with a CF backend. The key detail I took away from his talk was the 'properties in, events out' model of Flex. Thinking in events will take a bit getting used to.

Inside CF

The last session I attended, Elliott Sprehn's Internals of the Adobe ColdFusion Server, just blew my mind. Wow... I had to stop my notes and just let the information flow over me. He obviously is not satisfied with just accepting that things work, but wants to know the details of how. I left feeling a bit overwhelmed, but inspired to pay more attention to stack traces as well as a lot more curious about the underlying Java APIs that make up CF.

Why Scope? Variable Scoping in ColdFusion

Variable scoping, or lack of scoping, is a topic that comes up nearly every day on the various ColdFusion discussion lists and blogs I follow. An unscoped variable, in my opinion, is an immediate red flag during a code review and requires immediate correction or a detailed (and convincing) explaination on why it should remain unscoped.

Aside from readability issues, unscoped variables may cause interesting, and sometimes difficult to trace, bugs in your code. When CF processes a code template and reaches an unscoped variable, it looks in the following scopes in order:

  1. Function local (UDFs and CFCs)
  2. Thread local (new in CF 8)
  3. Arguments
  4. Variables (local scope)
  5. Thread (new in CF 8)
  6. CGI
  7. CFFile
  8. URL
  9. Form
  10. Cookie
  11. Client

The following example illustrates one of the many problems that unscoped variables can bring about.

view plain print about
1<cfparam name="customerID" value="100">
2
3<cfquery name="customer" datasource="example">
4    SELECT customerid, customername
5    FROM customers
6    WHERE customerid = <cfqueryparam value="#customerID#" cfsqltype="cf_sql_numeric">
7</cfquery>

We have a simple page that retrieves a database record for a customer based on their customerid. The cfparam makes sure a default value exists.

So, where is the customerID value coming from - a form post, a url parameter, a cookie? In this respect, the code is not self-documenting. It is also not a performant as it could be. ColdFusion will check each scope in order, starting with the function local scope, to find the customerID variable.

The code is also not as controlled as it could be. If, for example, the code is meant to be reached via a form post, someone could bypass the form entirely by adding customerid to the URL (&customerid=123). Since the URL scope is evaluated before the form scope, the URL value will take precedence over the form field value.

All variables, even local variables, should be scoped. No exceptions without a very good reason. It increases code readability and decreases inadvertant value changes due to scope precedence rules.

Retrieving Text from an ODT File

ODT files are an archive of XML files that describe and contain the content for a text document in the OpenDocument format. As I wrote earlier, you can use ColdFusion 8 and the CFZip tag to browse the contents of these files. Extracting the actual content of an ODT file, however, is more useful than simply looking.

[More]

Building a Date Table with ColdFusion

If you have ever worked with a data warehouse, or a complex, date-sensitive application, then you have probably come across a date dimension table. Simply put, a date dimension table is a lookup table that contains one record per date. Each record has information about that date, such as the day of the week, the quarter it falls in, and so on. Many DBAs come armed with a SQL script that creates and populates a date dimension table. If you are not a SQL adept, why not use the power of ColdFusion?

Each record in a date dimension table describes a date, so our table will have the following columns:

  • date - the full date
  • year - the date's year
  • month - the date's month number
  • month_desc - the month's name
  • day - the date's day
  • quarter - the date's quarter within the year
  • day_of_week - the date's day of week number
  • day_of_week_desc - the day of week's name
  • day_of_year - the date's day number within the year
  • week_of_year - the date's week number within the year

In SQL Server:

view plain print about
1CREATE TABLE days (
2    [days_id] int NOT NULL IDENTITY(1,1) PRIMARY KEY,
3    [date] smalldatetime NOT NULL,
4    [year] tinyint NOT NULL,
5    [month] tinyint NOT NULL,
6    [month_desc] varchar(10) NOT NULL,
7    [day] tinyint NOT NULL,
8    [quarter] tinyint NOT NULL,
9    [day_of_week] tinyint NOT NULL,
10    [day_of_week_desc] varchar(10) NOT NULL,
11    [day_of_year] tinyint NOT NULL,
12    [week_of_year] tinyint NOT NULL
13)

Now that the table is created, it needs data. Populating the data is simply a big loop using the many different CF functions for splitting and formatting dates. In this case, I will generate a script that can then be run in a database query tool, such as Query Analyzer:

view plain print about
1<cfset currentDate = createDate(2000, 01, 01)>
2<cfset targetDate = createDate(2008, 12, 31)>
3
4<cfloop condition="dateDiff('d', targetDate, currentDate) LTE 0">
5    <cfoutput>
6        INSERT INTO days (date, year, month, month_desc, day, quarter, day_of_week, day_of_week_desc, day_of_year, week_of_year)
7        VALUES (
8            '#createODBCDate(currentDate)#',
9            #year(currentDate)#,
10            #month(currentDate)#,
11            '#monthAsString(month(currentDate))#',
12            #day(currentDate)#,
13            #quarter(currentDate)#,
14            #dayOfWeek(currentDate)#,
15            '#dayOfWeekAsString(dayOfWeek(currentDate))#',
16            #dayOfYear(currentDate)#,
17            #week(currentDate)#
18        )
19    </cfoutput>
20    <cfset currentDate = dateAdd("d", 1, currentDate)>
21</cfloop>

In the above example, the date dimension table will contain one record for each day between January 1, 2000 and December 31, 2008. In the real world you will probably use a longer timespan, depending on your needs.

DateCompare Date/Time Gotcha

After years of date wrangling I really should know better, but ColdFusions's dateCompare function still managed to trip me up today. As its name implies, dateCompare compares two dates and returns a -1 if the first date is before the second date, 0 if they are equal, or 1 if the first is after the second. The gotcha lies in what a date is in ColdFusion. A date is not just the month, day, and year, but also the time.

This code snippet illustrates my head-slapping moment:

view plain print about
1<cfset currentDate = now()>
2<cfset targetDate = createDate(2008, 04, 30)>
3
4<cfloop condition="dateCompare(currentDate, targetDate) LTE 0">
5    <cfoutput>#dateFormat(currentDate, "mm/dd/yyyy")#<br /></cfoutput>
6    <cfset currentDate = dateAdd("d", 1, currentDate)>
7</cfloop>

I wanted a list of days between today, April 23, and the end of the month. However, I ended up missing a day, April 30. The output of the above snippet is:

04/23/2008
04/24/2008
04/25/2008
04/26/2008
04/27/2008
04/28/2008
04/29/2008

So where did that missing day go? Into the time portion of the date. DateCompare not only looks at the date portion of a date, but also the time portion.

view plain print about
1<!--- Returns 1 because the second date is midnight on April 23. --->
2<cfoutput>#dateCompare(now(), createDate(2008, 04, 23))#</cfoutput>

To accommodate the time element of a date, I changed the code to use the dateDiff function and examine only the day:

view plain print about
1<cfset currentDate = now()>
2<cfset targetDate = createDate(2008, 04, 30)>
3
4<cfloop condition="dateDiff('d', targetDate, currentDate) LTE 0">
5    <cfoutput>#dateFormat(currentDate, "mm/dd/yyyy")#<br /></cfoutput>
6    <cfset currentDate = dateAdd("d", 1, currentDate)>
7</cfloop>

Which outputs:

04/23/2008
04/24/2008
04/25/2008
04/26/2008
04/27/2008
04/28/2008
04/29/2008
04/30/2008

Problem solved, until next time. :)

Ranking Using Query of Queries

Many applications these days have some sort of standings table where you can see how you compare to other site members. Forums have them to show top posters, wikis have them to show top contributors, social networking sites have them to show who has the most connections. Google "SQL ranking" and you will get a long list of articles about how to build one of these tables using the database.

One common approach is the following query, which uses a self-join:

view plain print about
1SELECT a.name, a.sales, COUNT(a.sales) AS rank
2FROM sales a, sales b
3WHERE a.sales < b.sales OR (a.sales = b.sales AND a.name = b.name)
4GROUP BY a.name, a.sales
5ORDER BY rank

In the interest of Friday fun, I decided to take a different route and completely remove the database from the equation. I used query of queries to create the recordset for a standings table for the Sky Blue Pink Pencil Club. Members in SBPPC collect rare pencils that are colored sky blue pink and are very proud of their collections. But who are the top pencil collectors?

Let's start by building a recordset of names and the size of their pencil collection:

view plain print about
1<cfscript>
2/* Create a recordset containing each member of the SBPPC club and the size of their pencil collection. */
3names = listToArray("Jason,Jennifer,Sophia,Gary,Sue,Greg");
4pencils = listToArray("10,15,20,40,50,20");
5
6totals = queryNew("");
7queryAddColumn(totals, "name", names);
8queryAddColumn(totals, "pencils", pencils);
9
10/* Make a copy of the query for use in the QoQ below. */
11copy = duplicate(totals);
12
</cfscript>

Note the last line within the cfscript tags where we create a copy of the original query. Query of queries does not support table aliases, only column aliases. It does, however, support joining two query recordsets together, which is the behavior we want.

Now let's build the standings query. To determine rank in SQL, we do a self-join on the data and count the number of records ahead of the current record when listed in order.

view plain print about
1<cfquery name="standings" dbtype="query">
2    SELECT totals.name, totals.pencils, COUNT(totals.pencils) AS [position]
3    FROM totals, copy
4    WHERE totals.pencils < copy.pencils
5        OR (totals.name = copy.name AND totals.pencils = copy.pencils)
6    GROUP BY totals.name, totals.pencils
7    ORDER BY [position]
8</cfquery>

When output, we see that Sue leads the pack, Gary comes in second, and Greg and Sophia are tied for third.

NAME	PENCILS	POSITION
Sue	50	1
Gary	40	2
Greg	20	3
Sophia	20	3
Jenn	15	5
Jason	10	6

Overwrite Attribute in Fusebox

Note to self...the overwrite attribute in the Fusebox set verb does exactly that, it overwrites the existing value of the variable.

While working with a Fusebox application, I needed to pass the current fuseaction to the next one. So I crafted a URL like:

view plain print about
1<a href="#request.myself##xfa.delete#&return=#myfusebox.originalfuseaction#">Delete</a>

The delete fuseaction looks for the return value and relocates to that fuseaction. The only problem was, it didn't work. It relocated to the wrong fuseaction. In fact, it was the default return location (I'm reusing the same fuseaction for multiple views). Looking at the code:

view plain print about
1<fuseaction name="doDelete">
2    <set name="attributes.return" value="defaultList" overwrite="true" />
3    <do action="m.deleteRecord" />
4    <relocate url="#request.myself##myfusebox.thiscircuit#.#attributes.return#" addtoken="false" />
5</fuseaction>

I realized my problem. Instead of paraming the attributes.return value, I was setting it. Changing the overwrite attribute to "false" fixed it right up. Mea culpa.

ColdFusion 8.0.1 Updater Available

Yippee! The 8.0.1 updater for ColdFusion is now available and it contains official support for OS X Leopard. Looks like I'll have some install fun tonight.

Browsing an ODT File with CFZip

I use OpenOffice.org for my home office suite. The only Microsoft Office product that is on one of my systems is Access 2000, and only because I use it for a personal-use application I have not gotten around to upgrading.

OpenOffice.org uses the OpenDocument Format specification as its internal file format. ODF is an XML-based file format, so an OpenOffice.org document is a collection of XML files stored in a ZIP archive. What a perfect opportunity to get acquainted with the CFZip tag in ColdFusion 8!

[More]

Previous Entries / More Entries