View comments | RSS feed

ColdFusion MX Coding Guidelines - Good Practice

Release 3.2 (4/14/2005)

« Structure: Application, Component, Tag etc | Contents | Database Conventions »

Printable Version

Good Practice

This section provides some hints and tips that are considered "good practice".

Booleans

Booleans are always true or false, conditions are also true or false - do not test a boolean expression against a value, just test the expression:

<cfif boolExpr is "true">     <!--- BAD! --->
<cfif boolExpr is "false">    <!--- BAD! --->

<cfif boolExpr>               <!--- good --->
<cfif not boolExpr>           <!--- good --->

On the other hand, don't rely on implicit conversions from numeric types to boolean. Functions like compareNoCase() and listFind() should be explicitly tested against zero for clarity instead of relying on the "0 is false" conversion:

<cfif compareNoCase(x,y)>           <!--- BAD! --->

<cfif compareNoCase(x,y) neq 0>     <!--- good --->

cfswitch/cfcase

Instead of cascading cfelseif blocks, when you are branching on the value of a single expression, use cfswitch and cfcase instead:

<!--- bad: --->
<cfif expr eq "Value A">
    ...
<cfelseif expr eq "Value B">
    ...
<cfelse>
    ...
</cfif>

<!--- good: --->
<cfswitch expression="expr">
    <cfcase value="Value A">
        ...
    <cfcase>
    <cfcase value="Value B">
        ...
    </cfcase>
    <cfdefaultcase>
        ...
    </cfdefaultcase>
</cfswitch>

Components & cfargument

ColdFusion MX does not require that you use cfargument tags but they provide validation (type safety) and act as additional documentation - always provide a cfargument tag for each named argument your function expects and follow these rules:

<cfargument name="badArg"        type="string" required="false" default="foo" />   <!--- BAD! --->
<cfargument name="veryBadArg"    type="string" required="true"  default="silly" /> <!--- BAD! --->

<cfargument name="iAmRequired"   type="string" required="true" />                  <!--- good --->
<cfargument name="iAmOptional"   type="string" required="false" />                 <!--- good --->
<cfargument name="iHaveADefault" type="string"                  default="foo" />   <!--- good ---> 

Components & cfproperty

Be aware that the cfproperty tag is of limited use - it does only two things:

  1. It provides additional type validation for Web Service return types.
  2. It provides a way to add metadata to components.

In general, cfproperty should not be used - it does not provide type checking for instance data (in general) and it does not provide default values for instance data so its presence can be misleading rather than helpful.

If you really are returning a component type from a Web Service and want the public data members of that component to be type-checked at runtime, then cfproperty can be useful.

If you are creating a metadata-driven system, then cfproperty can be useful (although dynamic systems driven by component metadata tend not to scale well!).

Components & Constructors

All components should define a method called init() that initializes the instance, even if the body of the method is empty, i.e., the initialization doesn't do anything. This makes usage of components consistent since you are guaranteed to be able to call init() on any component instance, just as you can for Java objects created within ColdFusion. The method should have a return type that matches the component and it should return this so that the following construct is always legal:

<cfset obj = createObject("component","util.lib.thing").init() />

This matches the way that createObject().init() behaves for Java objects - init() returns the fully initialized Java object.

The util.lib.thing CFC would have a method that looks like:

<cffunction name="init" access="public" returntype="thing">
    ...
    <cfreturn this />
</cffunction>

If your component extends another component, your init() method should begin with a call to super.init() to initialize the base component.

Note: There is a notable exception to this guideline - when you are extending components in Mach II (listeners, plugins, event filters), instead of defining a method called init(), you define a method called configure() (which does not return anything - configure() has returnType="void"). This is because the framework itself uses init() for its own initialization and provides configure(), which it automatically calls for you once the framework has been initialized, so that you do not have to worry about any initialization arguments that the framework requires.

Cookies

Persistent cookies are intrusive - use them sparingly. Note that by default CFID / CFTOKEN are persistent cookies which is one of the reasons why we enable J2EE Session Variables (in the ColdFusion Administrator): jsessionid is in-memory so it's not as intrusive and it goes away when the browser closes (along with the user session, so it is more secure than using persistent CFID / CFTOKEN cookies for session management).

If your application requires cookies in order to work correctly, such as for session management or login, then handle the situation where a user has cookies disabled and do it gracefully - tell the user they need cookies enabled.

Modifying every link and form in your application to append the appropriate tokens (jsessionid or CFID / CFTOKEN) by using URLSessionFormat() might be an option but that opens up the security risk of users sharing links and inadvertently sharing session data (this is not an option for macromedia.com because the security risks are too high).

Custom Tags

Custom tags should be organized into related groups by using a meaningful directory tree under the Custom Tag Path (defined in the ColdFusion Administrator). This makes it easier to use cfimport to access specific groups of custom tags.

All custom tags should anticipate being executed in both start mode and end mode, i.e., they should be structured as follows:

<cfif thisTag.executionMode is "start">
    ...
<cfelse> <!--- thisTag.executionMode is "end" --->
    ...
</cfif>

If the custom tag is a simple one, i.e., it doesn't process a tag body, then the cfelse section should be empty (and cfelse can be omitted). Do not put spurious complaints in the cfelse section about being called twice!

Do not hardcode a result variable in a custom tag, e.g., caller.result. Instead, use either a result= or a returnVariable= attribute and return results by setting the specified variable, i.e., caller[attributes.returnVariable]. This is more flexible and also reduces coupling between a custom tag and its calling page.

« Structure: Application, Component, Tag etc | Contents | Database Conventions »

Comments


grantmr said on May 30, 2004 at 5:34 PM :
this first point about booleans - why is it bad practict to provide the expected value - i realise that it's redundant, but is there any danger is doing so?
SeanCorfield said on Jun 21, 2004 at 5:22 PM :
In a language that silently converts numeric values to boolean when the context requires it, you have the situation that zero converts to false but any non-zero value converts to true. However, while false converts to zero for a numeric comparison (allowing you to test eq false), true converts to 1 which will not be equal to an arbitrary numeric value.

That becomes a trap when you see things like if ( compare(s1,s2) ) and think you can do if ( compare(s1,s2) eq true ) - which only yields true when s1 is greater than s2 even tho' the first form yields true for s1 less than or greater than s2.

Therefore it is simply much safer to never test values equal to true or false and let the implied boolean context 'do the right thing'.
yitian said on Aug 4, 2004 at 7:16 PM :
May I know what is the best way to define global variables in CFCs?

Currently I have a global.cfc for each project to hold the global variables, and other CFCs will extend global.cfc if they need the variables. Your article recommends every contructor to have a init(). Does it imply for my global.cfc as well, since it doesn't contains any function, only variables.

My CFCs are Web services, and they don't store at the same place with other CFMs, so I suppose storing global variables in Application.cfm is not a good choice.

I'm having quite a number of projects on the server, so I suppose declaring global variables for each project in component.cfc is not very suitable also, since different projects use different sets of variables.
SeanCorfield said on Aug 6, 2004 at 6:54 PM :
Extending global.cfc is not good practice - CFC A should only extend CFC B if the logical relationship "A is-a B" holds true. Since global.cfc does not represent any sort of logical entity, the "is-a" relationship cannot hold.

You have several options for global variables depending on whether they represent truly static values (use a configuration file of some sort - either XML or a 'properties' file since CF can read both of those easily) or dynamic values.

For dynamic values, you can still use a CFC but it is just a 'service' CFC with methods to get the desired global value. Code that uses that CFC can either simply cfinvoke the method or, if you wish, construct an instance of the CFC and call multiple methods.

 

RSS feed | Send me an e-mail when comments are added to this page | Comment Report

Current page: http://livedocs.adobe.com/wtg/public/coding_standards/goodpractice.html