Web DevCenter    
 Published on Web DevCenter (http://www.oreillynet.com/javascript/)
 See this if you're having trouble printing code examples


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.

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.

Programming ColdFusion

Related Reading

Programming ColdFusion
By Rob Brooks-Bilson

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

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.

5. Make the most of optional parameters

The functionality of many UDFs can be improved by allowing developers to pass optional parameters to these functions. For example, any time you create a UDF that manipulates list data, you should allow an optional delimiter to be passed to the function since not all lists are delimited by commas. Consider the following UDF called ListLeft(). It accepts a delimited list of values and returns the n leftmost values in the list--in essence, it's a left() function for lists.

The function has two required parameters: the list (list) and the number of list elements to return (numElements). It also accepts a single optional parameter, allowing a developer to pass in a delimiter used to separate values in the list.

<CFSCRIPT>
function ListLeft(list, numElements){
  Var tempList="";
  Var i=0;
  Var delimiter=",";
  if (ArrayLen(arguments) gt 2){
    delimiter = arguments[3];
  }
  if (numElements gt ListLen(list, delimiter)){
    numElements=ListLen(list, delimiter);
  }
  for (i=1; i LTE numElements; i=i+1){
    tempList=ListAppend(tempList, 
    ListGetAt(list, i, delimiter), delimiter);
  }
  return tempList;
}
</CFSCRIPT>

<CFSET List="1|2|3|4|5|6|7|8|9|10">

<CFOUTPUT>
#ListLeft(List, 3, "|")#
</CFOUTPUT>

Because optional parameters are not named, you will have to write code within your UDF to handle them. A one-dimensional array called Arguments is automatically created whenever a UDF is called. This array contains all of the required (named) parameters passed to the UDF as well as any optional parameters. In the ListLeft() example we just covered, the Arguments array always contains a minimum of two elements: the first element containing the delimited list of values, the second element containing the number of list items to return. To determine if the optional delimiter was passed in, we only need to check the length (using ArrayLen()) of the Arguments array to see if it is greater than the two, named parameters we are expecting:

  if (ArrayLen(arguments) gt 2){
    delimiter = arguments[3];
  }

If there are more than two elements in the array, we take the third and use it as the delimiter. The statement

Var delimiter=","; 

makes the comma the default delimiter in case no third parameter is passed to the function. If for some reason the developer passes in more than three parameters, everything after the third element is ignored.

6. Be careful with recursion

In programming, recursion refers to code that calls itself (we hope) until a certain condition is met. User defined functions are capable of recursion, but it must be used carefully. In the current version of ColdFusion (5.0), recursion is supported to about 800 levels. Going beyond 800 levels is likely to cause a stack overflow and has the potential to destabilize the server, as recursive algorithms tend to be both processor and memory intensive in ColdFusion.

One example typically used to demonstrate recursion is a function used to calculate the factorial (n!) of a number. The factorial is calculated by taking a positive integer (n) and multiplying all of the positive integers between 1 and n. For example, 5!=120 (5x4x3x2x1=120). The UDF for recursively calculating the factorial of a number looks something like this:

function Factorial(integer){
  if (integer LE 1)
    return 1;
  else
    return integer * Factorial(integer-1);
}

A better way to write this function, at least as far as ColdFusion is concerned, is to use a while loop:

function Factorial(integer){
  Var TheFactorial=1;
  while (integer GT 0) {
    TheFactorial = TheFactorial*integer;
     integer = integer-1;
  }
  Return TheFactorial;
}

Writing the function using a while loop avoids the potential stack-overflow issue associated with using recursion to perform the calculation.

7. By value vs. by reference

Different data types are passed to UDFs in different ways. Strings, numbers, date/time values, and arrays are passed by value. This means that any values passed to the UDF are actually copies of the original value. Any changes made to the value inside the UDF do not result in changes to the original value in the calling template.

Queries, structures, and objects (e.g., COM, CORBA, and Java), on the other hand, are passed to UDFs by reference. In this case, the value passed to the UDF isn't actually the value, but rather a pointer to the original value outside of the function. Because of this, any change made to a value passed by reference results in a change to the original value back in the calling template. You can avoid having the original value changed by making a copy of the variable using the Duplicate() function and passing the copy to the UDF. This is because the Duplicate() function creates an exact copy of the original variable, as opposed to a pointer back to the original.

There are certain instances where you can actually pass a variable partially by value and partially by reference. For example, in cases where you pass an array of structures to a UDF, the array is passed by value while each structure in the array is actually a pointer to the original structure.

8. Keep your UDFs organized

One of the great things about UDFs is that you can have more than one in a single template. This gives you the ability to take all of the UDFs that you use in a particular application or Web site and keep them in a single template, commonly referred to as a library. Depending on the number of UDFs used by your application, you may want to create one or more libraries, grouping your UDFs by functionality. The more UDFs you have, the more it makes sense to create multiple UDF libraries.

Here's an example of a small UDF library with several UDFs for calculating the area of various geometrical shapes:

<CFSCRIPT>
function AreaCircle(radius){
  Return (Pi()*(radius^2));
}

function AreaEllipse(r1, r2){
  Return (pi() * r1 * r2);
}

function AreaParallelogram(base, height){
  Return (base * height);
}

function AreaRectangle(length,width){
  Return (length*width);
}
function AreaRhombus(diag1, diag2){
  Return (0.5 * (diag1 + diag2));
}
function AreaSquare(side){
  Return (side^2);
}
function AreaTrapezoid(base1, base2, height){
  Return (base1 + base2)/2 * height;
}
function AreaTriangle(base, height){
  Return (0.5 * base * height);
}
</CFSCRIPT>

To use any of these functions in any of your templates, all you have to do is CFINCLUDE the template at the beginning of the page that needs them. The overhead of doing a CFINCLUDE is rather small, so as long as your library doesn't have dozens of UDFs in it, you shouldn't need to worry about any performance implications.

Perhaps the biggest advantage to storing your UDFs in a library has to do with code reuse and ease of management. Using a library gives you a central point from which to manage your UDFs. If you need to make a change to a particular UDF, you need only change it in your UDF library file instead of having to track down each individual template that has the UDF coded inline.

If you find yourself frequently using CFINCLUDE to include the same UDF library over and over throughout your application, you have the option of placing the CFINCLUDE inside your application's Application.cfm template. Doing this makes all of the UDFs in your library available to all templates (except custom tags) in your application without having to place individual CFINCLUDE tags in every template.

9. A word about UDFs and custom tags

Because UDFs exist in the local variable's scope, they are not automatically available within your custom tags. So, if you define or include a UDF in a template that then calls a custom tag which tries to reference that UDF, ColdFusion will throw an error. There are two ways around this. The first is to code the UDF that the custom tag needs in the custom tag template. This makes the UDF available only to the custom tag, and not the template calling it. The second option you have is to copy the UDF to the request scope, thereby making it available to the custom tag and any other templates that are part of the original page request. To copy a UDF to the request scope, the syntax looks like this:

<CFSCRIPT>
function AreaCircle(radius)
{
  Return Pi()*(radius^2);
}

//Copy the UDF to the Request scope
Request.AreaCircle=AreaCircle;
</CFSCRIPT>

In your custom tag template, to call the AreaCircle() function in the Request scope, you would use the following code:

<CFOUTPUT>
The area of a circle with a radius 
of 3 is #Request.AreaCircle(3)#
</CFOUTPUT>

Although both methods of dealing with UDFs within custom tags work, I prefer the first method as it allows you to keep your custom tags portable and self-contained. This is especially true if you distribute your custom tags outside of your organization.

10. Don't reinvent the wheel

The next time you need a UDF for a particular task, you might consider checking to see if one with the desired functionality already exists. The Common Function Library Project is an open source repository of UDFs for ColdFusion that you can download and use in your applications free of charge. The site is run by me and Raymond Camden, a well known ColdFusion author and developer. The site maintains several libraries containing hundreds of functions for everything from string manipulation, statistics, and scientific calculations, to file operations and more. Because it's an open source project, the source code for each function is available unencrypted. In addition, each function comes with documentation as well as an example showing common usage.

The site also contains several tools for working with UDFs, including: the previously mentioned UDFDoc custom tag for auto-generating UDF documentation based on JavaDoc; a ColdFusion Studio (ColdFusion's IDE) extension for interacting with UDFs on the site from directly within Studio; and a UDF generator for automatically generating custom function libraries from the UDFs available on the site.

Because the site is a community project, submissions for new UDFs and improvements to existing UDFs from the ColdFusion community are always welcome. Each UDF submission is tested before being accepted or rejected, ensuring a high level of quality in the available code.


Rob Brooks-Bilson is a freelance writer and a senior technology manager at Amkor Technology. He is the author of O'Reilly's Programming ColdFusion MX, 2nd Edition (covering CF MX 6.1).


Return to the JavaScript and CSS DevCenter.

Copyright © 2009 O'Reilly Media, Inc.