As some of you might know, I post all my ColdFusion code for the Skin Spider project. To help understand it, I was asked to post the methodology I use for programming. I have a whole document written that I try to follow. This is, however, a living, breathing, evolving document and I am sure that I don't follow all of it. In fact, I am only reading it now for the first time in months.
I have tried to put in notes where I loosely follow a guideline. Also, I have not spell checked this and some of the tabbing will most likely not come through. This is just for reference, don't take it to heart.
All variables must be scoped UNLESS they are created and used in one template for a short period. For example, indexing counters (ex. intIndex) and arithmatical intermediaries do not need to be scopes.
Any variable that is set in one template and used in one or more templates must be scoped in the REQUEST scope. This allows programmers to see a non-scoped variable and know that it is ONLY used in the current template and see to REQUEST-scoped variables and know that they were either set in a calling template or to be used in another template.
All variables that come in from the web must be scoped in either the URL scope, the FORM scope, or the ATTRIBUTES pseudo scope. All URL and FORM variables get stored in the ATTRIBUTES scope, however, the ATTRIBUTES scope should only be used when the source of the variables (URL vs. FORM) is either unknown or in unimportant. NOTE: Do not confuse this with the ATTRIBUTES scope within a custom tag, though this is a related idea.
Short hands for scoping (ex. <cfset App = APPLICATION.App />) should not be used as they cloud the readability of the code. While this may seem like a longer way to do things, it will be more maintainable.
Variable Naming Conventions
In general, all variable names should be descriptive.
All variables that come from web scopes (FORM, URL, ATTRIBUTES) should be all lower case and use underscores instead of spaces (ex. first_name, last_name, email).
All variables that are useing within pages and do not have scoping (ex. intIndex, flSubTotal) should be mixed case starting with a lowercase abbreviation that gives insight to the type of data held in the variables. These prefixes are in the following format:
- arr = Arrray ex. arrAddresses
- bln = Boolean ex. blnDeleteUser
- fl = Float/Decimal ex. flSubTotal
- int = Integer ex. intSize
- lst = List ex. lstContactIDs
- q = Query ex. qUsers
- str = String/Character ex. strMessage
- obj = Struct/Object ex. objWeeklySummary
Now that CFMX is built on Java, we have to have Java data naming convetions as well. While this list is not comprehensive, all Java data objects will start with "j" to signify Java. These prefixes are in the following format:
- jarr = Java Array
- jstr = Java String
- jsb = Java String Buffer
All variables that are keys of an object (not including the scopes mentioned above) should be mixed case (ex. User.FirstName, Cart.SubTotal). These variables are considered organized enough by the parent object to not need data type insight within the variable name itself. If you find that you are losing track of the items within a struct, perhaps you should re-evaluate how you are organizing your code.
NOTE: I do not follow this as strongly, especially for Java variables.
Methods that are part of objects should be considered keys of that object and follow the same naming convetions as above (ex. Lib.IsEmail(....)).
All scopes should be written in uppercase. These scopes include THIS, APPLICATION, SESSION, REQUEST, VARIABLES, URL, FORM, ENV (custom pseudo scope), ATTRIBUTES (both pseudo and built-in), ARGUMENTS, CGI, CALLER, LOCAL (pseudo var scope), and THISTAG. While these scopes are technically just structures, they are special structures that are part of the ColdFusion framework.
NOTE: I no longer upper case the manually built Attributes object as I have decided it is just a container in the request (REQUEST.Attributes). I do however still uppercase my custom tag attributes scope. Also, for use INSIDE of my custom tags, I tend to now camelcase the tag attributes: ATTIRUBTES.ColumnName.
Database Naming / Structuring Convetions
All the table names should be lowercase with underscores replaceing spaces in descriptive names (ex. blog_entry, user).
All table names should be singular (ex. user rather than user(S)).
All join table names should have the full name of each table being joined, followed by "_jn" to signify the join. The order of the tables within the join table name should be alphabetical (ex. address_contact_jn rather than contact_address_jn).
All column names should be lowercase with underscores replacing spaces in descriptive names (ex. first_name, date_joined). Column names should not use the table name as a prefix. For example, first_name in the user table should be "user.first_name", NOT "user.user_first_name".
All tables should have a unique key whose column is "id". This includes join tables.
For all data tables, the id column should be the primary key as well. For join tables, the primary key should be the combination of foreign keys (and any other column that is required to make the join unique).
NOTE: I don't really follow this rule anymore. Now, I really just make my ID the primary key no matter what the table is doing.
All date/time columns should start with "date_" (ex. date_started, date_ended, date_updated). This is done purely to have more consistency with less thinking (ex. Which to use?? "date_started", "event_start", "event_begins", etc.). Furthermore, all date fields should be "past tense" gramatically (ex. date_started rather than date_starts).
All data tables (non-join, non-reference tables) should have a date_updated and a date_created field of type date/time.
All boolean columns should be of type tinyint and should begin with a descriptive prefix such as:
- is_ ex. is_active
- allows_ ex. allows_updating
All columns should have default values defined even if the default value is (null).
No column should allow NULL values unless absolutely necessary (ex. date_last_logged_in will have NULL if the user has never logged in before).
The following is a list of common fields and their appropriate lengths:
- prefix VARCHAR( 20 )
- first_name VARCHAR( 30 )
- middle_name VARCHAR( 30 )
- last_name VARCHAR( 30 )
- suffix VARCHAR( 20 )
- email VARCHAR( 70 )
- phone VARCHAR( 30 )
- url VARCHAR( 500 )
- username VARCHAR( 30 )
- password VARCHAR( 20 )
NOTE: I usually forget that I even have this list of table values.
File Naming Convetions
No file name should have spaces in it. All would-be spaces should be replaced with underscores.
All file names accessible from a Url should be all lowercase with underscores replacing spaces (ex. contact_us.cfm, firm_overview.html, spacer.gif).
All custom tags should be lowercase with no underscores or spaces. (ex. formerrors.cfm, tree.cfm). You cannot have custom tags with underscores as those underscores will be reflected in the cfml tag usage, which is a naming violation.
All coldfusion components should be mixed case with no spaces or underscores (ex. FormErrors.cfc, ServiceFactory.cfc).
SQL / Query Coding Conventions
Basic query formatting should use all uppercase words for SQL directives and funcitons (ex. SELECT, WHERE, ISNULL, LOWER).
All column and table references made inside of a SQL statement should match the database names (as defined in the Database naming conventions).
All necessary column names should be present in the select statement. SELECT * should NEVER be used as this is slower for the SQL server as well as much less readable. It also creates problems with ColdFusion's Maintain Connection administration feature.
Select query formatting should be tabbed in the following format:
SELECT u.first_name, u.last_name, u.email FROM user u INNER JOIN user_type t ON u.user_type_id = t.id WHERE u.is_active = 1 AND ISNULL( ( SELECT MAX(foo) FROM bar ), 0 ) > 0 AND u.id IN ( SELECT id FROM cool_user ) AND ( u.is_super_user = 1 OR u.is_ghosting = 1 OR ISNULL( u.user_type_id, 0 ) = 14 )
As you can see from the above example, no tab lines up with its parent element or its opperator. That is why sub-parts of the OR statement are indented past the OR statment - to give a clear visual representation of the query structure.
Also note that IN directives have the "(" on the following line, where function calls have the "(" on the same line. The closing ")" lines up with either the opening "(" in the case of the IN directive or the method name.
Do not use DISTINCT unless necessary as this slows down the query.
Try to avoid using WHERE statements that perform function calls on columns as this slows down the query (ex. YEAR(date_started) = 2005). Instead, try to re-evaluate where things might be structured better (ex. year_started = 2005).
Do not put more than one item on a line (ex. SELECT first_name FROM user) as this makes reading the SQL statement harder.
Insert query formatting should be tabbed in the following format:
INSERT INTO user ( first_name, last_name ) VALUES ( <cfqueryparam cfsqltype="CF_SQL_VARCHAR" value="#FORM.first_name#" />, <cfqueryparam cfsqltype="CF_SQL_VARCHAR" value="#FORM.last_name#" /> )
The insert statement is formatted differently than the select statement because it is meant to emphasize the field information. The periphery information is left as is, but the field names and inserted values are tabbed out from the rest of the statement. Having the open and close brackets on different lines of the VALUES directive gives a nice amount of vertical space to make the "goings on" much more clear.
_NOTE: I now tend to but the VALUES parens on the same line: ) VALUES (. _
The update statement follows the select statement and should be in the following format:
UPDATE user SET first_name = <cfqueryparam value="#..#" cfsqltype="CF_SQL_VARCHAR" />, last_name = <cfqueryparam value="#..#" cfsqltype="CF_SQL_VARCHAR" /> WHERE id = <cfqueryparam value="#..#" cfsqltype="CF_SQL_INTEGER" />
While the where statement in this code is short, it follows the same exact formatting rules as the where statement in a select statement.
CFQueryParam should be used in any place that a dynamic variable is being set or compared. This helps the query caching and prevents sql insertion attacks. However, if a non-dynamic number is being passed, it can be set right in the SQL code as queryparam provides no performance or added security.
UPDATE user SET foo = <cfqueryparam value="#..#" cfsqltype="CF_SQL_BIT" /> WHERE is_active = 1
Notice that "1" is not dynamic and therefore, not param'ed.
In a CFQueryParam tag, always put the VALUE attribute first as it is the most important attribute. This should be followed by the CFSQLTYPE attribute, which might be followed by the NULL attribute.
DO NOT ever copy and paste the query statement as it has been formatted in MS SQL Enterprise Manager. Its formatting and naming convetions conflict with this methodology document.
All aggregate columns should be suffixed with the type of aggregate performed:
- SUM() : price_sum
- COUNT() : user_count
- AVG() : cost_average
CFML Code Structure Conventions
All ColdFusion tags should be in all lowercase (ex. <cffile />, <cfset />).
All tag attributes should be in all lowercase (ex, delimiter="|").
All self closing tags should end with XHTML style syntax "/>" (see above).
Tag attributes can be on the same line of the tag or on new lines. Readability is on a tag-by-tag basis and up to the user. The longer a tag gets, the more readable it is on multiple lines:
<cfmail to="...." from="...." subject="Hey, this is a really cool methodology document" cc="..." bcc="..." wraptext="72" type="HTML"> ... </cfmail>
If you decide to place the tag attributes on a new line, each attribute should go on its own line, tabbed in once from the open tag. NEVER indent using spaces.
All cfml tags MUST BE INDENTED from their parent tag (whether that parent tag is a CFML or an HTML tag. This increases the readability of the code by leaps and bounds and it allows for faster updating since it becomes very clear where tags start and stop. The exceptions to this rule are the HTML tag and the TABLE tag. Things are not indented from these as they are large wrappers and would add unneeded tabbing that does not add anything to the readability of a document.
<table> <tbody> <!--- Loop over the data rows. ---> <cfloop query="qRows"> <tr> <td> <cfset WriteOutput( qRows.data ) /><br /> </td> </tr> </cfloop> </tbody> </table>
All function calls must have a single space of padding and each argument must be sepparated by a comman and a space:
<cfset Foo( Arg1, Arg2 ) />
Parenthesis needed for logical grouping do not need padding, however it can be added if it increases readability:
<cfset intX = (intY + 2)
<!--- Or. --->
<cfset intX = ( intY + 2 ) />
I do not suggest the padding on the logical grouping as it will confuse people who are expecting padding on function calls only.
When checking for boolean conditionals, treat numbers as booleans as often as possible. Furthermore, try to never compare a boolean value to it's case unleass absolute required. For example, this is good:
<cfif Len( strText )>
And this is bad:
<cfif (Len( strText ) GT 0) />
Treating a value as a boolean increases the readability.
Always group compond statements in an CFIF statement. Notice that in the above, Len( strText ) GT 0 was grouped with parenthesis.
If you have a CFIF statement that has many sub conditionals, all conditionals should be grouped (as one) with each sub conditional on its own line:
<cfif ( qUser.RecordCount AND Len( qUser.ssn ) AND (qUser.date_started LTE Now()) )> <!--- Code. ---> </cfif>
While not required, try to use:
NOT Compare() rather than EQ.
Case-sensitive functions rather than case INsensitive functions.
StructKeyExists() rather than IsDefined().
Structure notation rather than Evaluate().
For all attributes that used to take YES/NO values, these should now only take TRUE/FALSE values ("true" and "false"). In fact, YES and NO should not be used anywhere for booleans unless absolutely required. Now that ColdFusion can handle TRUE/FALSE they should be used as often as is allowable.
CFML Commenting Conventions
Comment, Comment, Comment!
Commenting should be descriptive and used often, but not so much that it takes away from the code readability (this is not often the case). In Homesite and other editors, comments are generally in a lighter, less intrusive font color. This makes comments natural code buffers and increases the readability of the actual code.
All comments should end with a "." for consistency (ex. <!--- Do it. --->).
Short comments should be on one line but longer comments should be on multiple lines with the comment itself indented from the open/close structures.
<!--- This is a really long comment here. It demonstrates the need to have the comment broken up into multiple lines and then indented. Isn't that just peachy. --->
Comments within a cfscript block should use the // notation, NOT the /* */ notation as it is harder to read.
// This is a really long comment here. It demonstrates the need to have // the comment broken up into multiple lines within the cfscript block. // Isn't that just peachy.
All included templates (NOT top-level url-accessible templates) should have the following comments at the top of the file to document the file use and update history:
<!--- ---------------------------------------------------------------- ---- File: methodology.txt Author: Ben Nadel Section: N/A Desc: This file provides a methodology for consistent and successful application development in ColdFusion. Upate History: 11/15/2005 - Ben Nadel Requested By Ben Nadel I have update this document to add more conventions for CFC coding. 11/14/2005 - Ben Nadel I just started this document. ----- ------------------------------------------------------------//// --->
The Update history is important so that we do not repeat work that has already been tried, and to keep track of any client changes that may need to be referenced in the future (to protect your butt).
All custom tags should start with a comment similar to the includes comment; however, the custom tag should have some sort of sample code so that a new developer can see how to moste effectively harness the tag.
<!--- ---------------------------------------------------------------- ---- File: securepage.cfm Author: Ben Nadel Desc: This tag allows a page to be given a login without integrating it with the security of the application itself. This is good for single pages or mini-extranets that the client wants to post to. Sample Code: <!-- Import tag library. --> <cfimport taglib="../lib/" prefix="sys" /> <!-- Secure page. --> <sys:securepage username="ben" password="test"> .... Code to be secured goes here .... </sys:securepage> Update History: 11/16/2005 - Ben Nadel Added piggyback functionality to allow one secure page to use the login status of another template. 11/14/2005 - Ben Nadel Beta tag has been completed. ----- ------------------------------------------------------------//// --->
All components should start with a comment similar to the custom tag comment and should include the sample code and the update history.
<!--- ---------------------------------------------------------------- ---- File: FormErrors.cfc Author: Ben Nadel Desc: This component handles a form errors queue used when validating data send through a form submissions. Is is essentially a non-persisting message queue with a standard message queue interface. Sample Code: <!-- Create the CFC. --> <cfset FormErrors = CreateCFC("FormErrors").Init() /> <!-- Check a field. --> <cfif NOT Len(FORM.first_name)> <!-- Add an error message. --> <cfset FormErrors.Add("Please enter a first name") /> </cfif> <!-- Check for form errors. --> <cfif NOT FormErrors.Length()> .... </cfif> Update History: 11/14/2005 - Ben Nadel Form Errors has been completed. ----- ------------------------------------------------------------//// --->
In the code, sometimes its nice to put assertions so that a developer can look at the code and know that something is true at a given point. You can put this in with the ASSERT comment:
<!--- ASSERT: intUserID contains newly created or existing user id. --->
In the code, sometimes you want things to work, but want to come back and finish up later. You can note this with a TODO comment. This is nice not only because it shows a developer what needs to be done, but it is also organized in the Eclipse globally.
<!--- TODO: Add error checking for out-of-range values. --->
Related to a TODO comment, sometimes you want to comment that a bug needs to be fixed, but you have not had time to fix it. You can do this with a modified todo statement:
<!--- TODO: BUG: Crashes for negative numbers. --->
In the code, sometimes you comment things out due to client requests. It is important to explain why this has been done. This should be discussed using a note comment:
<!--- NOTE: Jennifer Shutes asked for this to be disabled on October 15, 2005. It might be put back in later. ---> <!--- .... Commented out code goes here .... --->
Component Structuring Conventions
ColdFusion components will follow the following general format:
<!--- File comment (see above sections) ---> <cfcomponent displayname="MyCo" extends="BaseComponent" output="false" hint="MyCo does something"> <cffunction name="Init" access="public" returntype="MyCo" output="false" hint="Returns an initialized MyCo instance."> <!--- Define arguments. ---> <cfargument name="DSN" type="struct" required="true" /> <cfscript> ... // Return THIS object. return( THIS ); </cfscript> </cffunction> </cfcomponent>
A component should never output data to the response, but it can return a string that is then used in the response.
All non-public data should be declared in the VARIABLES scope. All public data must be declared in the THIS scope.
A component should never refer to scopes outside of itself. Any external scope or object that is needed should be passed to it via the Init method and stored in the variables scope.
The pseudo contructor is used to set up default structures and constants within the object (ex. THIS.Sizes.LETTER = 1). Constants should be all upper case with underscores replacing spaces (ex. THIS.Sizes.A4_PAPER). Constants should always be publicly accessible.
The Init method should set up all default data of the component (except for the constants). It should always return a reference to itself (ex. return( THIS )) and should be the first method in the component.
All methods following the Init method should be in alphabetical order.
All instance-specific data should be stored in the private object, Instance (ex. VARIABLES.Instance = StructNew()). However, do not confuse instance data with private data. Instance data is only data that pertains specifically to the functionality of the component instance. So for example, for the FormErrors component, the Errors array would be instance data (ex. VARIABLES.Instance.Errors = ArrayNew( 1 )) as it is specific to the functionality of the FormErrors instance. On the other hand, the ServiceFactory reference would be just private data (ex. VARIABLES.ServiceFactory) because it is merely a helper object.
NOTE: The jury is still out in my mind about VARIABLES.Instance.
Thanks for posting this Ben. I've just reached a place where a document like this is needed for my company. So it seems a little plagiarism is in order. Do you have this in Word or some other more easily plagiarized form? I'll be glad to keep your name on it as the original source.
Plagiarize away! No need to mention me in your document. It's just coding standards - I wouldn't go so far as to say there is any intellectual property issue here :)
As far an easier to use document, the original was written as a TXT file. I then updated that slightly for the blog post. Here is a link to the TXT file that the blog entry came from:
Hope that helps. Standards are cool to have :)
Sweet. Thanks for that text file.
I'm just one man doing another man a solid (any one here like the movie Cable Guy??).
There's plenty of intellectual property here!
BTW, have you seen
Thanks for the posting such an excellent link! I have read that in the past many times, but it is good to let everyone know about it. Clearly you can see there are a lot of ideas that I found very useful (as I mimic them), but there are certainly a number that I disagree with.
My naming "cases" are very different from most of the world. I like my stuff to start with a first upper case letter. I mean come on, CF is case-insensitive. It's not Java. Heck, the Java methods aren't even case sensitive in CF. No reason to keep that headless camel case in CF. Just my opinion though.
Also, one of the other biggest things is perhaps my use of White Space as a coding element. To me, white space makes stuff readable (to an extent).
Lots of really good stuff in that docs though.
But seriously, how can you put intellectual property on standards? It's like trying to claim the "golden rule". It's not an invention, it's a way of life :D
Ohhh the things I want to complain about in here! But I will refrain myself! No really I have no complaints. ;) If anything... oohhhh the singular based database table names. Man we developers can be so picky sometimes. I have to admit though this is a great document, with a lot of detail. Some great standards and several of them I like and I currently implement. Thanks for sharing, in fact I think you should make this a seperate page on your site so its easier to find! Or rather make a special link to it, maybe from your CF snippets section. Just an idea but definitely worth of it as many will find it very useful, especially beginners.
I've never been big on the underscore thing but as I build larger more complex applications to keep the readability and also the descriptiveness that I love it will be easier. That doesn't only go for database field names but files as well as I currently don't like that. Just one of those bad habits I got to break. The one thing I do like a lot from this set of standards that I definitely will use is when identifying aggregated database columns. Always hard to come up with a good name that uses the name of the function and I like how you put it at the end. Some great stuff. Again, thanks for sharing.
Man this is one big post. I've read many different coding standards docs for CF, and this one looks VERY good.
Thanks Sami :)
Yeah, i used to use plurals for all my database tables as well as my query names:
DB :: users
Query :: qUsers
It was actually my boss that convinced me to stop doing this. Here was his reasoning:
1. When it comes to queries, plurals are not always created by adding an "s". For instance, let's say I had a table names "userstatus" what was my query name? qUserStatuses? qUserStatii? It was english that was making this complicated, not the programming.
2. While you might select multiple records (ex. qUser to get all users) when you loop over it, each loop is ONLY referring to one user: qUser.firstname. When you remember that you are evaluating each record on its own, singular makes more sense.
3. It just takes all thinking out of it.
I have to say, I too fought it for a while, but eventually I saw that he was right. Using the singular does take thinking out of it and makes the code a bit more readable.
you are such a lazy... why not add a button to print the post in PDF format... so we can read while travelling.
thanks for such a detail and nice post.
I am moving away from using reserved words like [user]. I need to come up with some commonly used tables that are reserved. For instance, instead of using "user", I might try to use "account". And instead of using "transaction", I might use something like "account_transaction".
Outside of the LOCAL in query of queries, I pretty much want to get away from using any reserved words in my SQL (as table or column names).
I don't agree with the ID problem. If every "main entity" table has an ID that is it's primary key, then that is very consistent. Remember also that the primary key of a table might be spread across multiple tables, so no naming convention can help you at that point.
Thanks for this great post. I'm not a Java coder but most of your post applies to PHP programming too... What's funny is that I was recently ask to blog about the methodology I use to develop web application. I've posted it on my blog and I though you might find it an interesting read too although it focuses less on the phases before coding.