This question came in from a reader and it was something I really wanted my readers to weigh in on. Frankly I have no good answer here so I'm hoping others can chime in.
I'm in the design phase of an object oriented web app (in ColdFusion) and I am at the point where I'm laying out the error handling for this app. Most of my error handling up to this point involves modifying the onError method in App.cfc to output a global template that displays the error message and logs/emails any necessary information for the devs. However, lately I have been thinking about creating custom CFCs for errors specific to this part of the application. This app is part of a much larger app and the idea of having customized errors to contain module specific info/behavior sounds pretty appealing.
I'm wondering if you or anyone you know of has had experience with custom error CFCs and if you would recommend them or not. Also, if you would recommend them, is there a guide from Adobe I should follow when sub-classing the exception class? Can I even sub-class that class in ColdFusion?
Ok, so right off the bat - I do not believe ColdFusion allows you to "sub class" exceptions. You can use cfthrow to raise an exception. This can contain simple hard coded information like so:
<cfthrow message="some message" detail="some detail" type="my.custom.type">
And while I'm assuming ColdFusion creates a dynamic exception behind the scenes, I'm not sure I'd call this the same as something like this:
component extends="java.lang.exception"
My gut tells me that on one hand you are maybe making things a bit too complex, however, I'm not so certain it is always a bad idea. Given the fact that I can throw a custom type, how would I nicely manage that in a large application that may need to create a large number of custom errors? I could see - perhaps - creating a CFC to help abstract and formalize that within your application. This could be useful to ensure that the type attribute for your custom exceptions follow a specific format. So for example, when you throw and use dot notation, you can use a catch statement to match against a higher level version of type. That sounds horribly worded, but maybe an example will help:
<cftry>
<cfthrow message="foo" type="core.goo">
<cfcatch type="core.zoo">
zoo caught
</cfcatch>
<cfcatch type="core">
core caught
</cfcatch>
</cftry>
As you can see, my type is core.goo. When run, the second catch which looks for the more high level 'core' type will successfully match against the exception. Given a choice between more and less precise, ColdFusion will correctly match the more precise version:
<cftry>
<cfthrow message="foo" type="core.goo">
<cfcatch type="core.zoo">
zoo caught
</cfcatch>
<cfcatch type="core">
core caught
</cfcatch>
<cfcatch type="core.goo">
goo caught
</cfcatch>
</cftry>
In that version, core.goo, the 3rd cfcatch block, will execute. So - given that - I could imagine an application handling prefixing all thrown exceptions with a particular type. Perhaps even doing some validation to ensure no two CFCs share the same type.
So I'm really just talking out of my rear here. My main question is then - is my reader making this too complex? Have people had a need for something like this?
Archived Comments
I'm pretty sure catching all of the default exceptions would be more than enough info ...
Is there a framework involved? That really may solve some of those issues. I know with ColdBox for example has a nice trace of the error. Sub-classing may not really gain you much in that instance since its all event driven and you know what event failed.
Or you could look at something like Coldbox see how they trace and mimic that functionality, though seems overkill.
I've used custom exceptions in my Java apps before, but not that often in CF. Whenever I considered using a custom exception, it was usually because I wanted to bundle a few pieces of data/classes together into one cohesive structure/class so that my logic that would handle the exception didn't have to go grab information from multiple places; instead it was GIVEN the info/classes it needed to act on / interact with.
In many cases, I opted out of using a custom exception and created a more detailed "results" class/component that I returned from my function instead. Usually, when I handle exceptions I handle them fairly close to where they're thrown, but if you're having to handle non-trivial exceptions fairly distant from where they're being thrown (or even the same exception in multiple places throughout your app), then a custom exception may be the way to go. If you're not having to handle the 2 situations mentioned above, custom exceptions may be overkill.
Below is a little proof of concept I've been throwing around in my head for a while regarding how to handle custom exceptions in CF. It lets you throw custom/structured data with your exception that can be turned into a CFC when you catch the exception. I just have a CF7 instance spun up right now, you could definitely use other serialization techniques (like actually serializing a CFC instance and storing that in the extendedInfo attribute instead which would allow you to have nested components in your custom exception). But I think the example gets the point across on how you can throw and handle custom exceptions.
I'll have to play with this on CF8 tomorrow to see how much nicer it can be made.
<code>
<!--- /index.cfm --->
<cftry>
<cfset data = structNew() >
<cfset data.sku = "1234-ABC" >
<cfset data.qtyRequested = 100 >
<cfset data.qtyInStock = 50 >
<cfset throwCustomErr("test.InventoryException", data) >
<!--- <cfset renderOrderGood(orderCfc) --->
<cfcatch type="test.InventoryException" >
<cfset errCfc = getCustomErr(cfcatch) >
<cfdump var="#errCfc#" >
<!--- <cfset renderInventoryException(errCfc) > --->
</cfcatch>
</cftry>
<cffunction name="throwCustomErr" returntype="void" output="false" >
<cfargument name="type" type="string" required="true" >
<cfargument name="data" type="struct" required="true" >
<cfargument name="message" type="string" default="" >
<cfset var d = "" >
<cfset var msg = "" >
<cfif len(arguments.message) >
<cfset msg = arguments.message >
<cfelse>
<cfset msg = "Custom Exception: " & arguments.type >
</cfif>
<cfwddx action="cfml2wddx" input="#arguments.data#" output="d" >
<cfthrow message="#msg#" type="#arguments.type#" extendedInfo="#d#" >
</cffunction>
<cffunction name="getCustomErr" returntype="any" output="false" >
<cfargument name="catchData" type="any" required="true" >
<cfset var d = "" >
<cfwddx action="wddx2cfml" input="#arguments.catchData.extendedInfo#" output="d" >
<cfreturn createObject("component", cfcatch.type).init(argumentCollection = d) >
</cffunction>
<!--- /test/InventoryException.cfc --->
<cfcomponent>
<cffunction name="init" returntype="any" >
<cfargument name="sku" type="string" required="true" >
<cfargument name="qtyRequested" type="numeric" required="true" >
<cfargument name="qtyInStock" type="numeric" required="true" >
<cfset structAppend(this, arguments) >
<cfreturn this >
</cffunction>
<!--- getter/setters & other methods --->
</cfcomponent>
</code>