CFC Best Practices
This document is intended to be a concise summary of best practices everyone building CFCs should be aware of. For a more thorough review of best practices, be sure to check out the ColdFusion MX Coding Guidelines created by Sean Corfield.
- Always, always, always use "var" for local variables inside your methods, including ALL loop counters and temporary variables
- In general, only allow access to data associated with a CFC ("private data") via methods rather than through direct manipulation of "public" data members. Public data members break the separation of implementation from interface. Two primary benefits of maintaining encapsulation with methods are:
For example, these are good:
- You can keep the exposed API the same while changing how your CFC works internally without breaking any code that uses your CFC.
- Prevents the developer from getting in trouble by violating your internal assumptions about how the instance data works.
foo = myCFCInstance.getInterestingStuff()
and these are bad:
foo = myCFCInstance.interestingStuff
myCFCInstance.interestingStuff = foo.
- Never use "this" when you mean private data (see above) -- that's what the "variables" scope is for.
- Either ALWAYS or NEVER explicitly scope references to "arguments" -- but, do not mix and match scoping and not scoping arguments. It can cause unexpected name collisions with the methods local scope because when a method is called the arguments are copied into the local scope automatically by ColdFusion. This author recommends choosing ALWAYS over NEVER in this case.
- Always (with rare exceptions) use OUTPUT="false" in your CFFUNCTION and CFCOMPONENT tags. Do not output directly to the buffer inside a CFC method -- instead return a string. The main reason is that you don't want to break encapsulation. By outputting directly to the output stream you assume knowledge of the external environment of the CFC, whereas if you return a string you get the exact same behavior when you do simply "#myCFC.someHTMLGeneratingMethod()#" but you gain the advantage of not assuming that's how your method will be used (for instance, what if the method that returns the string is used inside of a big CFSCRIPT block where someone is building a string via concatenation?). The other reason is that if you are not even trying to output text you'll end up with unwanted white space if you don't use OUTPUT="false".
- Use the "hint" attribute on CFCOMPONENT/CFFUNCTION/CFARGUMENT in almost most cases, to aid documentation (particulary for public methods)
- Use the RETURNTYPE attribute of CFFUNCTION and the TYPE attribute of CFARGUMENT to aid documentation and for runtime type checking. And don't forget that "void" is the RETURNTYPE when not returning anything. NOTE: there is minor performance penalty to TYPE and RETURNTYPE, so omit if you are squeezing miliseconds (something that is almost never necessary).
- Do not directly refer to external variables (i.e.: session/application variables) inside a CFC. When in doubt, preserve encapsulation. The one exception is when building facades, especially for web services/flash remoting, in which case ALL references to shared scope variables should be encapsulated within the facade. This means, for instance, passing in the dsn instead of referring to application.dsn when doing database queries.
- In general, non-required arguments of a CFC method should have a DEFAULT specified.
- Use "EXTENDS" only when describing an "is-a" relationship, not for a "has-a" relationship or for code reuse. For a nice summary, visit http://cnx.rice.edu/content/m11709/latest/
OPTIONAL, BUT WELL WORN PRACTICES
- Always have an init() method that acts as your constructor and returns "this" (unless you are building a web services/flash remoting facade).
- For instance data, create a "virtual scope" inside the VARIABLES scope. For instance, in the first line of your init() method you might have:
<cfset variables.instance = structNew()>
(some people like to call it "variables.my" instead). You can then reference your instance data as "instance.foo". The benefit is that it separates your stateful instance data from the VARIABLES scope, which also contains references to all methods (public AND private) as well as a reference to "THIS" -- this becomes really handy if you need to do things like return a memento of your instance or do global operations on all instance data. It's also very handy if and when you need to clone a CFC, since you can move state data in one chunk rather than worrying about which keys in VARIABLES are your instance data and which are built-in keys. You would likely still use the VARIABLES scope for non-stateful instance data such as references to other CFC's your instance uses.
THINGS TO KEEP IN MIND
- Variable pass in and out of components by reference or by value based on the same rules as the rest of CFML. For instance, strings, arrays, numbers, and dates all pass by value, but structures, queries, and all other "complex" objects (including CFC instances) pass by reference.
- Duplicate() and CFWDDX do not work on CFC instances.
- When extending a component outside the base component's package, the sub-component does not inherit package permissions -- thus, you cannot call "package" methods on other CFCs in the package of the base component from the sub-component.
- You can have a method that has a RETURNTYPE or an argument that has a TYPE of a base component and return any component that extends that base component. For example, if methodA takes an argument "foo" of type "motorVehicle" and you pass "foo" as an instance of "car" (which extends "motorVehicle") then "methodA" will honor that "car" is a "motorVehicle" when doing type checking on the argument "foo".