Web DevCenter
oreilly.comSafari Books Online.Conferences.
MySQL Conference and Expo April 14-17, 2008, Santa Clara, CA

Sponsored Developer Resources

Web Columns
Adobe GoLive
Essential JavaScript
Megnut

Web Topics
All Articles
Browsers
ColdFusion
CSS
Database
Flash
Graphics
HTML/XHTML/DHTML
Scripting Languages
Tools
Weblogs

Atom 1.0 Feed RSS 1.0 Feed RSS 2.0 Feed

Learning Lab






Arctic Tern

Top Ten ColdFusion UDF Tips

by Rob Brooks-Bilson, author of Programming ColdFusion
02/22/2002

As the co-coordinator of the ColdFusion Common Function Library Project at CFLib.org, I receive a lot of emails asking for advice on user defined function (UDF) best practices in ColdFusion. One thing I've noticed over time is that developers tend to ask many of the same questions, so I've compiled ten of the most frequently asked-about topics into this article. I hope it will help developers who are getting started with user defined functions. If you are already comfortable writing UDFs in ColdFusion 5.0, you may still find a few tips here worth considering in your own development.

1. Choose your function names wisely

A name is a name is a name, right? Not when it comes to UDFs. There are several guidelines you should think about when deciding what to name your function, to avoid both errors and organizational problems.

  • You can't name a UDF with the same name as one of ColdFusion's built-in functions (also known as BIFs). Doing so will cause ColdFusion to throw an error.
  • UDF names must begin with a letter and can contain only letters, numbers, and underscores. This is the same rule that applies to variable names in ColdFusion.
  • UDF names cannot contain periods.
  • UDF names can't begin with "cf", "_cf", or "cf_".
  • UDF names must be unique. That is, each UDF available within a page request must have a unique name.
  • When deciding on a name for your UDF, consider the naming conventions used by ColdFusion's BIFs. For example, list functions all begin with the word "list": ListLen(), ListLast(), etc.

Following these conventions makes it easier to understand what a UDF does without necessarily having to see the code defining the function. It also makes organizing and finding UDFs much easier.

2. A little documentation goes a long way

Like all code, UDFs beg for documentation. Using a standardized documentation format for your UDFs makes it easy for you and other developers to know exactly what a UDF does without having to look at every line of code to figure out what's going on. It also makes writing the documentation much easier, as the format remains the same for each function you document.

One documentation method you might find useful is called UDFDoc. UDFDoc is a standardized documentation format that was adapted for UDFs by Raymond Camden and is modeled after the popular JavaDoc format. It consists of a number of comments that appear before a UDF is defined. Consider the following UDFDoc header created for a UDF called PadString():

/**
 * Pads a string with n characters.  Padding is from 
 * the left.
 * 
 * @param string         String you want to pad. 
 * @param char           Character to use as the padding. 
 * @param count          Number of characters to pad the 
 *                       string with. 
 * @return               Returns a string. 
 * @author Rob Brooks-Bilson (rbils@amkor.com) 
 * @version 1, August 16, 2001 
 */
function PadString(string, char, count)
{
  Var Padding = RepeatString(char, count);
  return Padding & string;
}

The first line of text gives a short description of what the function does. Each @param line describes the various parameters the UDF accepts as well as the data type they should be. @return tells you what output you should expect from the function. Author and version information are contained in the @author and @version lines, respectively. Note that the version is represented as the version number and the date of the last update.

In order to make documenting your UDFs using the UDFDoc format even easier, a custom tag called CF_UDFDoc has been made available for free. This tag can parse your CFML templates and generate HTML documentation containing the function name and any required parameters for all UDFs it finds.

3. Always Var your variables

One of the most common mistakes made by novice UDF coders is failing to declare local variables within their functions using the Var statement.

Since UDFs are either written inline or called as includes from your ColdFusion templates, they have access to all variable scopes that exist within your template. Because of the obvious potential for overwriting like-named variables inside and outside of the function, UDFs have a protected variable scope called the "function scope" that you can use to avoid conflicts.

The function scope differs from other variable scopes in ColdFusion in that there is no prefix used before function variables as there is for variables that exist in other scopes, like Session, Client, Application, etc. The function scope consists of named parameters passed to the function, the Arguments array, which contains the values of any optional parameters passed to the UDF, and variables created with the Var statement.

What all of this means is that any variables you use which are specific to your UDF need to be declared first using Var statements to avoid potential conflicts with like-named variables that exist outside of the UDF. Here's an example of a UDF that uses locally scoped variables:

<CFSCRIPT>
/**
 * Returns a list of all factors for a given 
 * positive integer.
 * 
 * @param integer   Any non negative integer greater 
 *                  than or equal to 1. 
 * @return          Returns a comma delimited list 
 *                  of values. 
 * @author Rob Brooks-Bilson (rbils@amkor.com) 
 * @version 1.1, September 6, 2001 
 */
function factor(integer)
{
  Var i=0; 
  Var Factors = "";
  for (i=1; i LTE integer/2; i=i+1) {
    if (Int(integer/i) EQ integer/i) {
      Factors = ListAppend(Factors, i);
    }
  }
  Return ListAppend(Factors, integer);
}
</CFSCRIPT>

In this example, two variables are initialized in the local function scope: i and Factors. i is given an initial value of 0 while Factors is set to blank (""). Pay particular attention to i. It is extremely important not to forget to initialize any variables used in a loop with a Var statement. This is something that is often overlooked, even by more experienced ColdFusion developers.

Here are some rules to keep in mind for using Var to initialize variables within a UDF:

  • Variables declared with Var must be defined at the top of the function, before any other CFScript statements, and take precedence over any other variable with the same name, regardless of the variable's scope.
  • Variables declared with Var follow the same naming rules as other variables. Additionally, they may not be compound variable names such as My.Var.Name.
  • Any valid expression can be used to initialize a variable. All of the following are valid Var statements:
    Var x=1;
    Var y="Hello";
    Var z=ArrayNew(1);
  • You must always supply an initial value or expression when declaring a variable with Var. This means you can't do things like Var x;

4. Hey! CFScript caused my switch/case statements to break

One issue I often see with developers starting their foray into CFScript and UDFs has to do with using switch/case statements to handle decision making. Many developers comfortable with the tag-based switch/case syntax used in CFML find that similarly constructed CFScript switch/case code provides unexpected results. For example, the following tag-based code returns the calendar quarter that the specified date occurs in as a string (1st, 2nd, 3rd, or 4th):

<CFSET TheDate="01/01/2002">

<!--- evaluate quarter --->
<CFSWITCH EXPRESSION="#Quarter(TheDate)#">
  <CFCASE VALUE="1">
    <CFSET Q="1st">
  </CFCASE>

  <CFCASE VALUE="2">
    <CFSET Q="2nd">
  </CFCASE>

  <CFCASE VALUE="3">
    <CFSET Q="3rd">
  </CFCASE>

  <CFDEFAULTCASE>
    <CFSET Q="4th">
  </CFDEFAULTCASE>    
</CFSWITCH>

<CFOUTPUT>
#MonthAsString(Month(TheDate))# is in the #Q# quarter of the year.
</CFOUTPUT>

If you run this example, you should see the code return the string:

January is in the 1st quarter of the year.

The same code written as a UDF might look something like this:

<CFSCRIPT>
function QuarterAsString(date){
  // assign the numeric quarter associated with
  // the passed in date
  Var theQuarter = Quarter(date);
  Var q=4;

 //evaluate the quarter and convert to string
  switch(theQuarter){
    case 1:
      q="1st";
    case 2:
      q="2nd";
    case 3:
      q="3rd";
    default:
      q="4th";
  }
  return q;
}
</CFSCRIPT>

<CFSET TheDate="01/01/2002">
<CFOUTPUT>
#MonthAsString(Month(TheDate))# is in 
the #QuarterAsString(TheDate)# quarter of 
the year.
</CFOUTPUT>

If you run this example, the first thing you'll notice is that the output is wrong. The program is trying to tell you that January is in the 4th quarter of the year, which is obviously wrong. What gives? After all, the code looks like it's identical to the tag-based code in the previous example.

The problem has to do with how ColdFusion's CFScript (and many other languages) deal with switch/case statements. In order to avoid falling through the first true case evaluation, and having the next case statement execute regardless of whether it's true or false, you need to have a break; statement after each case.

Adding a break; statement after each case is evaluated causes ColdFusion to exit the switch once a case evaluates as true. Try the following modified example. Note the presence of a break; statement at the end of each case statement.

<CFSCRIPT>
function QuarterAsString(date){
  // assign the numeric quarter associated with
  // the passed in date
  Var theQuarter = Quarter(date);
  Var q=4;

 //evaluate the quarter and convert to string
  switch(theQuarter){
    case 1:
      q="1st";
      break;
    case 2:
      q="2nd";
      break;
    case 3:
      q="3rd";
      break;
    default:
      q="4th";
  }
  return q;
}
</CFSCRIPT>

<CFSET TheDate="01/01/2002">
<CFOUTPUT>
#MonthAsString(Month(TheDate))# is in 
the #QuarterAsString(TheDate)# quarter of 
the year.
</CFOUTPUT>

Executing this example results in the output you would expect:

January is in the 1st quarter of the year.

Pages: 1, 2

Next Pagearrow