Raymond Camden's Blog Rss

ColdFusion Newbie Contest - Entry 10

10

Posted in ColdFusion | Posted on 06-14-2007 | 2,785 views

This last entry in the ColdFusion Newbie Contest is here! It is an application created by Phillip Senn. As with the previous entry, I had difficulty setting up the code locally (due to my not having SQL Server at the time), so I'm going to point people to the online version: http://www.aacr9.com/index/camdenfamily/tamagotchi/

This entry is rather simple. As you can see - you are dropped into the creature maintenance immediately. A database is used for everything and I believe this means that if multiple readers hit the site, they will all be affecting the creature at the same time. So those of you who have easier access to SQL Server and want to try this application should download it instead. Anyway, as you can see, you simply feed and pet the creature, and that is it. In fact, I couldn't even kill off the creature. From a game play perspective, there isn't a lot here honestly, but there is some interesting code here for us to talk about. (Although the trick with making the image bigger as the creature ate was pretty funny.)

First off - Phillip made a huge mistake. Gigantic. He wrote a Word document describing what he did. That just makes me it easier for me to pick on him! (Just teasing Phill!) From the Word document we learn that Phillip only used stored procedures and views to interact with the data. Wow. I have to say I am impressed. It takes a lot of discipline to just use stored procs throughout an application. I will be 100% honest and admit I'm a database wimp and rarely use/write stored procs myself.

In his word document he goes on to explain why he doesn't use the Arguments scope inside of CFFUNCTIONs. His argument is based on the fact that since you can't prefix local var scope variables, and you can't mix the two, then there should be no confusion. Personally I don't agree with that. I think that when working in any slightly complex method, you are going to want to have a good handle on what is an argument and what is a local variable. So I may be being picky here, but I'd always use the argument scope.

Most impressive is the use of Application.cfc. I do not believe many of the entries did this. He caches many CFCs in the Session state making proper use of onSessionStart. I also liked this bit of code:

view plain print about
1<cfif NOT Len(cgi.HTTP_USER_AGENT)>
2    <cfreturn false> <!--- Bots --->
3</cfif>

I wonder how well this would work. I thought most bots actually had a user agent. It does bring up a good point. I know Michael Dinowitz has done some good work in blocking sessions for robots and has found this to be a big help for server performance. I don't know if Michael has written this up yet into a formal article, but if he hasn't, he should. (In his copious free time of course.)

In the last entry, one of the things I warned about was the use of hard coded numbers in the code. So for example, if "1" means happy, I'd abstract that so that you can do getHappyState instead. I see similar type issues in Phillips code, specifically in index.cfm where he handling updating the state of the creature.

Another issue - and let me be 100% clear this - this is not a fair complaint for Phillip. I am bringing it up as a warning to others but not to say that Phillip did anything bad here. I noticed a file named session.cfm. It simply did this:

view plain print about
1<cfdump var="#Session#">

While useful, and I've done the same thing myself, it is critical to remember to not push files like this to production (as has been done here). I've made this mistake myself, so I'm just reminding people.

Now let me dig into his CFCs. First off - Phillip points out that his CFCs all extend a common CFC, which is pretty cool to see. His common.cfc contains a base init, read, view, and delete method that his other CFCs can use. He uses an interesting CFC, Database.cfc, to handle abstracting his queries for him. This allows him to select data from other CFCs like so:

view plain print about
1<cfset qry=Variables.DatabaseObj.Select(
2TableName="Tamagotchi_CreatureStateView",
3Where="#Where#") /
>

I'll be hoenst and say I'm not quite sure I like that. Writing the SQL as he does in Database.cfc does mean he loses the ability to do cfqueryparam, and I'm not sure I'd give that up. But it is a very interesting usage. I'm going to give him extra brownie points though for using cfqueryparam everywhere he could though. Along with that I was also happy to see proper var scoping and output restriction.

Phillip pinged me directly a day or so ago to say he removed returnType to keep the code a bit simpler. I guess I can see that - but I tend to be pretty anal about always including my return type. I'd suggest he add that back in. Did you know that in ColdFusion 8 you can turn on duck typing in the ColdFusion Administrator? This means you go crazy with typing and still get the performance benefits on production.

So thats all I have to say about this entry, and that means we are done! My next post will gather up all ten entries and I encourage folks to start thinking about what entry was their favorite. Also please give feedback to Phillips code (and feel free to disagree with my notes as well).

Download attached file

Comments

[Add Comment] [Subscribe to Comments]

Getting this error on the online demo:

Variable HAPPYSTATE is undefined.

The error occurred in C:\Websites\73172bkj\Index\CamdenFamily\Tamagotchi\Index.cfm: line 96

94 :          </cfloop>
95 :          </ul>
96 :          <cfif HappyState GT 0>
97 :             <cfset CreatureStateQry = Session.CreatureStateObj.View2(Session.CreatureID,session.HungerStatID)>
98 :             <cfif CreatureStateQry.CreatureState LE CreatureStateQry.MaxState>
Yep, I pinged him. For now I'd just download. Even if you don't have time to setup the app, I'd look at the code. The more I think about it the more impressed I get.
will do
Well THAT was embarrassing!
I assumed there would only be one creature, for the sake of not having to write a login script.
So when someone (naturally) added their own creature, it threw an error.
Sorry 'bout that.
I think I should take the time to make it multiplayer and send the zip to Ray.
Something that never really used to be a problem but is these days is the cfif bit of code that you like Ray (the first code snippet you show above). It makes the assumption that cgi.HTTP_USER_AGENT exists, and these days that's not always the case. Legitimate users running certain versions of, for example, Norton have the ability to suppress reporting of HTTP_USER_AGENT. I can't even count the number of "Error resolving parameter HTTP_USER_AGENT" I've seen in error logs lately. People, possibly potential customers, are unable to use sites because the developer(s) made the *assumption* that something, that cgi var in this case, would exist when it does not (nor does it say in any spec that it has to). There's no easy answer to this, plenty of bots don't report it but now legit users aren't reporting it to the web server either. One can avoid a CF error by cfparam'ing cgi.HTTP_USER_AGENT to "", but the user still wouldn't be allowed to use the site given the code shown.

I bring this up because I had to fix a customer's site on Monday that had this exact problem - a customer of one of our hosting customers tried to do some work shopping from home on Sunday night (it's a persistent, multiple cart system for schools to order and re-order supplies) and was met with:

An error occurred while evaluating the expression:

#HTTP_USER_AGENT#


Error near line 232, column 7.
--------------------------------------------------------------------------------

Error resolving parameter HTTP_USER_AGENT

because the original developer of the code had made that assumption. FWIW I also see this problem with cgi.HTTP_REFERER on occasion too, and I'm sure there are others. Just something to keep in mind when writing code, you don't want to be unintentionally losing customers...

btw - Ray, this must be on the new server now? Your site is orders of magnitude faster for me today. Or is it just that it's not running that slow Kontera stuff right now? Anyway, I like it!
This is the first new box. I am going to be moving again to an even faster box. In the past my slow downs were: old box + lots of JS code here. I have a new box now - and CF8 - and I still have lots of JS, but the server side stuff screams now. And will only get faster on my new box.
With the issue of hardcoding, I wrote the original "game" as a showpiece for database normalization.
But after seeing how tortured the cf code was in order to determine what action affected what state, I decided to simplify the ColdFusion part and hardcode some of it.
I think I had in the back of my mind an article about how we sometimes make things overly complex by softcoding everything.

Well, anyway....
Thanks Ray, for the opportunity and the review.
I am going to try and implement his cfc techniques. I too tend to just write on the fly and when it is time to refine data calls, I have to run through and find all instances of the queries.

Hats off to Phil.
Nice work Phillip. In true programmer fashion, you put some solid work in on the code side, and made the interface very simple. I liked his usage of cfc's and will be experimenting with some of his code on my projects in the near future.
CFC usage - Good use of CFCs, including the use of an error message (database.cfc)

Style - I liked the use of capitalizations in the variable names, indentions, CFCs, queryparams, etc. I also liked how you separated the javascript into a separate file for ConfirmDelete.js. I liked your use of cftry/catch.

Database - great use of stored procedures. It allows for extra security and separates your code from the database a bit more than most people tend to do. The down side to this is that your relatively simple application is now bonded to MS SQL. Of course, changing to another database would be a bit harder with stored procedures instead of simple inserts/updates. Having the majority of changes in a few files is nice though.

Select * - it would be preferable if you wrote out the column names in a select instead of using a *.

Good job!

--note, I haven't examined the other 9 entries yet--

[Add Comment] [Subscribe to Comments]