The Philosophy Of Undocumented ColdFusion Features
Posted May 16, 2008 at 9:19 AM by Ben Nadel
I was just reading over on Dave Ferguson's Blog about his debate over whether or not to use some undocumented features of ColdFusion (specifically, using the underlying Java objects that power core ColdFusion data types). This is an interesting debate, and one that I have struggled with myself. As with all complicated things, I often find that my true feelings on a matter can be more easily found when I port the situation into a slightly different problem space to which I have no emotional attachment. This way, I can separate emotion from reason and see what my gut is telling me.
But first, what are the reasons one might have against tapping into the underlying Java objects of ColdFusion? The biggest concern raised is the issue of forward compatibility. If a feature is undocumented, there is no guarantee that it will be available in the next release of ColdFusion. This is one of those concerns that immediately sounds very persuasive. Oh my god! what if all my systems start to crash when I upgrade?!? It's going to cost gagillions of dollars! What will my client's think! It will be crazy - dogs and cats living together - total chaos, right?
Let me ask you this, though - have you ever upgraded a production server without first upgrading your development environment and testing all your sites locally? Could systems really ever crash in such a way that it actually causes your client's a lot of money or public shame? I believe that the development life cycle should prevent this from ever happening. Anything that could go wrong should be discovered long before it is even put a in a position to cause problems. So, let's get rid of that "chaotic fear factor;" no problems are just gonna pop up out of no where; hundreds of clients are not going to start frantically calling you all at the same time wondering why their sites are down.
Ok, so please continue reading knowing that what ever happens, it will be slow and it will be controlled and it will be calm.
Now that we are calm and relaxed, let's take our situation and port it over to a less emotionally charged problem domain such that maybe we can think more logically. Let's take a look at ColdFusion User Defined Functions (UDF). When you build a user defined function for ColdFusion, do you ever feel bad? Do you ever feel like you are doing something wrong? Absolutely not; the ability to create user defined functions is a wonderful and documented feature of ColdFusion that makes development much easier. But, just because it's documented, does that mean that it guards us against future compatibility issues? I can tell you first hand that it does not. When I upgraded from ColdFusion MX6 to MX7, dozens of my applications had UDFs whose name conflicted with a new, built-in ColdFusion function. Not only did I have to change the name of the function, I had to search through my code and change every reference to it.
Whether or not this has happened to you, this kind of issue is much more likely than you think. As a though experiment, let's take a look at the security functions that are new in ColdFusion 8:
These aren't far-out function names. These function names are very logically thought out and appropriate to the context in which they are used. In fact, IsUserLoggedIn() is such an obvious name that I would guarantee that there were many folks out there with UDFs of the same name whose ColdFusion applications are now not forward compatible. ColdFusion, in general, follows such a logical naming scheme that it almost creates a higher likelihood of you authoring UDFs that are NOT future proof. Imagine if you wanted to create a list function that would take a list and reverse it, what would you do? You'd see the trend in list naming and call it ListReverse(). The problem with this is that it fits so nicely in the traditional ColdFusion naming scheme that if ColdFusion ever added a function that did this, it would most definitely use this name, making your future ColdFusion application not compatible.
Now, let's take one foot out of ColdFusion and talk about solutions that work with third party applications. What if you build a solution in ColdFusion that works with Microsoft Word 2003. Whose to say that that solution will work with Microsoft Word 2007. In fact, from what I have heard, Microsoft Office 2007 got such an overhaul that many products that worked with Office 2003 no longer work with Office 2007. Does this mean that the original solution should not have been built? Do you think ColdFusion developers were sitting in their seats thinking to themselves, "I have an awesome ColdFusion idea that works with Office 2003, but it might not work with Office 2007 or other future iterations... I should probably scrap the whole idea because if anyone ever upgrades this will no longer be compatible."
When you take the context slightly out of ColdFusion, it sounds ridiculous, right? No one really thinks about third party compatibility issues, but is it any different? You're using features of a language that are not guaranteed to be forward compatible.
What if we leave the ColdFusion domain all together? Let's talk about laptops for a second. Many people go to and from work with their laptops; and, since laptop power supplies are so heavy, oftentimes, people will get a power supply to leave and work and one to leave at home. Now, laptop power supplies are not universal. In fact, they are almost comically NOT universal. It seems that almost every single new laptop that comes out has to use its very own power supply. Even though this is the case, do you think people worry about it? Do you think Joe Computer Guy ever thinks to himself, "Oh man, my life would be a lot better if I had a power supply at home and at work, but if I ever buy another laptop in my life, that power supply will probably not be compatible - I better just drag my power supply to and from work in case I need to get a new laptop soon."
Again, another totally crazy-ass thought, right?
I think you can look at dozens of different situations in your own life and realize that the fear of forward compatibility almost never comes into light when making decisions. So, what is it about undocumented features in ColdFusion that make people react to emotionally? Let's examine some potential thoughts:
1. Undocumented features might not be compatible in the future.
Yeah, true. But, like I pointed out above, incompatibility won't just happen because of your usage of underlying features. It could happen with emerging function conflicts or even out-of-your-control third party upgrade issues. How is that any different?
2. By using only documented features I am taking steps to ensure that my applications will be future compatible.
Yeah, I'm sure that's what people were thinking when they made an awesome UDF named IsUserLoggedIn(). Unfortunately, their effort to use only documented features did not work out so well for them. And, who's to say that future versions of ColdFusion will be backward compatible at all? I have heard that some people are pushing for a version that is not backward compatible. If a switch like that happened, all your effortts would be moot.
3. If compatibility changes, it will cost millions of dollars and months to fix all the code.
Really? How many undocumented features do you think you are using? Let's pick something really common - let's say you were using the underlying String.Length() method rather than Len( String ). This could be used on potentially every page of your application, right? Not a crazy thought. Even if this was in, oh, let's be generous and say 5,000 pages. I think this is way more pages than most any application has, but heck, let's play hard. Now, let's go crazy and say that an extended Find takes 10 minutes to execute and that each of those changes takes two minutes to switch over to a compatible format (which is extremely extremely generous). That means that making all of those changes would take 166 man-hours. If only one person did this, it would take 20 days. If three people worked on it, it would take 6 days.
For a 5,000 page application, which is unbelievably large, it would take a few people just a few days to fix the entire application. Is that such a nightmare? Really? Is that even such a huge cost? I don't think so. And, if you take a much more likely situation, let's say a 100 page application where the issue is on every page, it would take one person maybe 4 hours to fix. Four hours is nothing - I've spent more time debugging documented code, so don't even tell me about the cost of fixing undocumented features.
I know it is an oddly emotional subject, but I really just can't see what logical reasons people have against using undocumented features. If you are using something that makes your code better / faster, why shouldn't you do it? Especially, when the time/cost of future change is really not that significant, even on absurdly large projects.
You know what seems ironic to me? The fact that people who use "best practices" in programming seem to be the most adamant about NOT using undocumented featured. At first, this might make sense, but think about what these best practices are - cohesion, low coupling, encapsulating what changes. People who have very well organized code and a very high amount of code reuse would be the ones least hurt of issues of future incompatibility. A programmer who has to change a piece of functionality in one place is much better off no matter what than a person who needs to change a piece of functionality that is duplicated in 50 places, and yet, it is the less experienced, spaghetti coder who I think is more willing to take a chance. That just seems ironic and funny to me - the people who have the tools to absorb the hit are the ones least likely to try.
So anyway, that's my little brain dump on using undocumented features. I'm not saying you should run out and use as many as you can; I'm just saying that if you find one that you feel makes your programming better, use it without worrying - it's no more dangerous than anything else you do.
- Wanted: Full-Time ColdFusion Developer at Intoria Internet Architects
- Cold Fusion Senior Developer at Edge Information Management
- Back-End Web Developer-Information Technologist at Michigan State University
- ColdFusion Developer at Nonfat Media
- Mid-to-Senior Level Web Application Developer at SiteVision, Inc.
My biggest issue with this blog entry is that you minimize the impact of using an undocumented function. Your example of using foo.len() would be simple to fix. But what about more complex undocumented features like ending sessions or getting all sessions. You are not going to fix that with a simple replace. In this case you've used an entire 'feature' that is not really available. You could be looking at a serious amount of work to correct this issue.
I think the old "encapsulate what changes" adage applies here. I would tend to avoid using undocumented features unless they confer a major advantage.
As much as possible, I would recommend encapsulating their use so that you only need to change code in one place if the undocumented code no longer works.
So, for example, if you wanted to use string.length(), you could create a jlen() function and pass the string into that. Then, if string.length() quit working, you would just change the jlen() function once.
I guess that I am biased because the undocumented features that I use are generally utility features. Like, I might use String.RepalceAll() rather than REReplace() if I need the Java regular expression engine. Or, I might use the Array.AddAll() to append elements to an array if I have to do a lot of them.
So, in my experience, the undocumented features are all things that are more or less very easily convertible back to ColdFusion - they are just easier when using the underlying methods. I guess this does heavily influence the way I look at the kind of technique.
I have never used anything that really messes with Sessions or anything that is not very concentrated and finite.
I think you are totally right here, encapsulating the usage of undocumented features is probably the safest approach. If you are using undocumented features there clearly has to be some advantage whether it's performance or development time and taking the extra step to abstract it out should be the least you can do.
Something else to keep in mind. If you use an undocumented feature for a client, I think you are really doing them a disservice. Most clients would probably NOT be happy with going out of the box like that. If you do decide to do this - be sure to let your client know _why_ you are doing and why you feel it is necessary.
I tend to agree. I think the string length doesn't make so much sense, but its just a bad example.
In my experience, underlying feature use tends to be clustered. For example, let's say I am importing and cleaning a data file. I might use a lot of java regular expression stuff in the String object because of its speed and larger feature set.
This way, even if it does change, at least its is in clumps of functionality.
I think the "use case" here is really important to think about. Most people, I think, aren't going to be haphazardly scattering these features through their applications - they are going to be leveraged in algorithms where they make sense.
Of course, maybe I am just one of those people who needs to learn from his mistakes :) Perhaps I need to need to get burned before I become more cautious.
Another big disadvantage of undocumented features is it limits your portability between ColdFusion engines.
So if suddenly you decide you want to move your application over to Open BlueDragon for example, you are going to find that these features are very unlikely to work.
I don't think you can think about it as disservice - it's only a disservice if something bad comes of it. I think the same could be said of UDFs - do you have to tell your clients why you use certain UDFs? What if they become incompatible?
Again, I am speaking as someone who has NEVER been hurt by this, so I am perhaps not grounded; but, I think we are judging the chicken before the egg.
I'm not sure that is a valid reason. That's like saying all car engines should be made the same because that way they can be easily ported from one chassis to another. Is that really the goal of creating an engine? Is it portability? Or performance?
I totally agree that the decision rests on the use case. In other words, if the use case presents sufficient return on the risk, then there may be a valid reason there.
The example that comes to mind for me is use of the Java service factories. I built a pretty full-featured RDBMS management console entirely in CFMX when it came out, starting from the knowledge that I could leverage the underlying Java DatasourceFactory (or whatever it's called). That one bit of underlying architecture allowed me to query all DSNs on the server, to validate any requested DSN, to determine the DB software (Oracle, MSSQL, whatever), etc, yet the meat of the application was simply construction and execution of well designed CFQUERY statements (ALTER, MODIFY, DROP, ...). Sure, we could have implemented the ASP.NET management app for SQL Server or some other 3rd party web-based tool, but why break out of the CF mold when I didn't need to? In this case, there was a limited set of potential users (obviously not many people even within the company should have direct access to the production databases) and if the feature changed, the code effect could be accounted for in 1 or 2 CFCs which were the base objects for the low-level interactions.
Encapsulation reduced the risk and the use case closely defined the limited impact.
@Ben - I still say that a UDF is not the same as an undocumented feature. Yes, there is a chance that a UDF could stop working. Ditto for a tag (although Adobe almost never kills off tags), but an undocumented feature is something that a client could not get support for. A UDF breaking due to a name conflict IS something a client could get support for.
See my point there? Imagine you die and the client has to call up Adobe to figure out what's wrong.
One more note on the commonality of CF function names and their obviousness. I have a Model-Glue framework that included a built-in file management engine. One of the core file methods that I had was called FileWrite(), which essentiall encapsulated certain CFFILE ACTION="WRITE" functionality. Sure enough, CF 8.0 came along with a new method called ... FileWrite(). And all my CFSCRIPT references to my base method threw errors at instantiation because I wasn't sending the "correct" params for the built-in function.
Quick fix to change the method name in the CFC to something 'safe', like jfFileWrite() or something, but then a careful search through code to be sure that any existing use was referring back to the correct method. When I get a chance, of course, I will try to determine whether or not it makes sense to just use the new built-in function, but meanwhile your exact scenario has already bitten ... with a documented feature.
I don't think that you can assume that a development server would always get upgraded before a production server. HostMySite upgraded all my sites from CF7 to CF8 without even telling me. I didn't run into any issues, but I hadn't upgraded to CF8 at the time.
i really utilizing undocumented features just really boils down to what exactly the features does.
in ben's defense, utilized the undocumented java object functions is really never going to hurt you from what i can see. the plain fact of the matter is that cf is written on top of java, hence it will always have access to the underline java. in order for an undocumented feature like this to break, adobe would have to port cf to another language and i don't think that their planning on doing that anytime soon.
in ray's defense, yeah i can see where doing something like would totally screw you in the end. having the business logic of your application dependent of functionality that isn't guaranteed is stupid.
You make a good point with the support issue.
Also, as much as we don't want to minimize the risk of using undocumented features, also, don't minimize the risk of using documented features. As far as compatibility issues go, the only issues that I have experienced myself are UDF-related, not underlying method related.
Yeah, encapsulating the areas is definitely a good idea. And, I think something that should be done in general.
I don't want you guys to think I go around using every possible undocumented feature. That is not what I do, and certainly not what I am advocating. In fact, the amount of undocumented features I use is VERY SMALL. It usually only involves string manipulation and sometimes collection manipulation. And on top of that, these are usually concentrated in one or two areas (like what JFish does).
Please don't think that using undocumented code == bad programming in general. It's not like a gateway drug ... poor Ben, first he was using undocumented features... not he's programming in ASP classic... what a waste :)
Hmmm, interesting. I hadn't thought of that. I figured they would at least tell you, or at the very least, give you a chance to be ported over to a server that wasn't going to be upgraded.
I ask myself: Can I reasonably reach the same outcome using only official features? "Outcome" is whatever I deem important about what I'm trying to accomplish, might include user-facing features, performance considerations, code readability considerations, etc.
I try to make a decision about whether the ends justifies the means, and whether the level of risk is acceptable for the future, which includes examining exactly what the level of risk is.
If I were considering something like patching the execution stack in real time - this is a very high level of risk, and the sort of thing which would likely be broken by a regular updater. I would only consider this if there was realistically no other way to accomplish a critical feature (eg, maybe something funky I could do would triple application performance for a very high traffic site which is struggling today). In reality I would probably never actually consider doing that except in an experimental context (ie learning how internals work).
One thing I have done which relies on an undocumented feature is extract the CF function name from the "raw trace" portion of error stack traces. For example: cfApplication2ecfc80862666$funcONREQUEST.runFunction - you can see that the function name is ONREQUEST. I use this for debugging purposes and include it in my error reporting within a <cftry> block (in fact, I have a function dedicated to extracting a little bit of extra info from error reports which is heavily try/catched so that at worst I end up with a normal stack trace).
I established the benefit: extra debugging information. I established the fault risk level: very little because of extra try/catches. And I established the damage from a likely worst case scenario: I don't get bonus debug information and am left with traditional debug information (of course there's an unrealistic risk that somehow this brings the whole server down =P). The level of risk is justified by the benefit gained, and I cannot realistically accomplish this same feature any other way. So I rely on the undocumented feature.
I recommend whenever relying on undocumented features, put fault catching code in place and a "way out" for your code when your undocumented feature blows up.
Speaking of undocumented, am I the only one wishing these were documented features? http://www.zrinity.com/developers/mx/undocumentation/query.cfm - Make it so!
No way. If it's a shortcut that saves you as much as several lines of code then is it really worth the potential consequences? You'll be storing up problems for the future and if the project is for a client then that's just bad practice and unprofessional.
If your client upgrades their cfml engine in the future and they find out their application doesn't run because you use unsupported code (after spending half a day debugging and calling in consultants) then that will hurt your reputation.
If you've gone to the trouble to find or learn about undocumented features then surely it wouldn't have been any harder to find out how to achieve the same using official features?
That's my take on it. Good discussion - clearly a popular topic.
I think it is easy to say "never do it", but it is also important to recognize that there may be edge cases where undocumented features are the only viable solution.
It is important to encapsulate the use of undocumented functions and to wrap them in a try/catch that will return an informative and helpful error if they fail. Something along the lines of "Undocumented feature X referenced in file Y failed.".
It is essential that, should the feature fail, it is immediately obvious what happened and where.
This is an important point about UDFs. If a new BIF exists for your previously created UDF, then ColdFusion will give an error that is helpful in determining the problem.
Proper error messages are essential in debugging.
Using official features is not always possible. Take the string object, which is really where I play most of the time. ColdFusion does not allow you to differentiate between a Java string and ColdFusion string. In fact, if you try to create a Java string using CreateObject(), it will convert it to a ColdFusion string the second it gets s chance.
Therefore, If you wanted to use the String.ReplaceAll() feature of a Java string, you quite literally cannot do it without using undocumented features:
So, while I agree you shouldn't just use undocumented features for fun, it is a bad assumption that all things done with undocumented features can also be done with documented features.
"Using official features is not always possible."
I'm going to be the nit picky jerk here. This comment implies that you -must- use undocumented features. While it may be quicker too, I wouldn't say that you are ever FORCED to do so. It may be convenient to - but that's not the same as being forced to.
This is hairy point. Ok, yes, if you feel completely adverse to use Java strings regular expressions, you could technically go through and create a Java Pattern object and use a Java matcher (all via the documented CreateObject() route) and jump through a lot of hoops and eventually get the same result. But I think that these two approaches are so completely different that they are hardly the same thing.
This may seem silly, but I think the implications that ColdFusion doesn't see a difference between ColdFusion strings and Java strings are huge. In a way, it ensures that certain undocumented features will be forward compatible until the language takes a drastically different direction.
@Ray: I don't agree that it's always possible to accomplish a goal through the use of only supported features. Consider my example: knowing which method of a CFC (or which UDF) caused an error. The best you could do is argue that it's difficult to come up with a case where this was a mandatory feature, but it remains that there are some things which can only be accomplished via unsupported features.
Another example (this one is much easier to make a case for): Doing a quiet sleep for a specified duration before the introduction of cfthread in CF 8. Creating a java thread and having it sleep on your behalf is the closest you can come to this in CF 7 and earlier.
Yet another example: Some times it is exceedingly valuable to have a Struct whose key insertion order is preserved for looping over it in the same order as they keys were inserted. For example, if you have a chunk of code that manages a custom cache, when you're reaping expired entries from this cache, you can break out of the age-testing loop as soon as you encounter the first non-expired cache entry, and know you encountered all expired entries (assuming they have a fixed expiry age).
Createobject("java", "java.util.LinkedHashMap").init() will give you an object that as far as CF is concerned is a struct - it works in all functions that accept structs. It's an undocumented feature that LinkedHashMap is fully compatible with Struct, but there isn't realistically another way to accomplish this with supported functionality alone. However since Struct and LinkedHashMap are of the same lineage, the chances of this ever breaking in the future are pretty slim. Honestly the fact that Struct is not order-preserving has been a sore point for me with ColdFusion ever since the first day I typed the key sequence "<cf".
Sometimes the benefit simply outweighs the risk. It's up to each developer to evaluate the likelihood of their change being broken in the future, the damage that such a break would bring about, and make a risk/benefit analysis for themselves. The important thing is that such an analysis takes place.
I think there's a misconception of "undocumented" and "using java." Createobject("java", "java.util.LinkedHashMap").init() is hardly what I call undocumented. It is reaching into the bowels of coldfusion, which is currently java and utilizing it.
The link I gave above is an example of undocumented functions in ColdFusion.
@Eric - Todd said exactly what i was going to post. Using Java is not undocumented. It's very much documented.
@Ben, I get that the Java functions are really cool (by that I mean powerful and useful rather than cool like Paris Hilton and totally pointless) but if you use a CF UDF to achieve the same using just CFML then you won't have to use undocumented features to convert the string. Yes, more lines of code, maybe lots more, but it's not unachievable or even a mild showstopper. Would it impact the speed of your application? I doubt it - to any degree of significance.
99% of CF'ers have survived without touching Java functions or undocumented stuff and your brain would probably be one of the first to come up with a neat alternative CF UDF if someone set a challenge. Well, maybe I'd wager on Ray's brain first. ;-p
Using LinkedHashMap from ColdFusion as if it were a regular struct is documented?
Anyway, it's beside my point, my point is that you can't say there exist no problems which have no documented solution and instead must rely on undocumented functionality or else face significant performance-sapping, readibility-torpedoing, developer-wrist-slashing work-arounds.
Some times you encounter things whose undocumented solution is either so much more elegant, or else the only possible solution, that you have to go with it.
Using Java is not undocumented, but the fact that ColdFusion will look at a linked hash table and think it is a struct... I feel like that is the "undocumented" part of the feature. In order for you to know that, you have to know how ColdFusion handles structs behind the scenes.
It's possible that 99% of ColdFusion developers also don't love regular expressions. That doesn't mean that we shouldn't try to leverage powerful regular expressions if they are possible :)
@Ben: Any web developer who doesn't love regular expressions should consider either educating themselves or else a career change =)
@Eric, Documented in Java? I'm sure it is. Again, being a cfml developer, you have to walk the fine line. Most people don't walk that line, but for the advanced folks, you know who you are, you walk that line daily because you need something more than what CF-out-of-the-box is giving you.
Yes, I can do a query of queries to do a resort, but dammit, why can't Adobe give me a sort() function on my query objects? I'm not a total maroon.
"It's possible that 99% of ColdFusion developers also don't love regular expressions. That doesn't mean that we shouldn't try to leverage powerful regular expressions if they are possible :)"
Real men parse their strings with regular expressions!
@Ben, CF Severs Developers did their best to "mirror" to make stuff unobtrusive to normal cfml developers. Are they at fault that there are some die-hard passionate coders out there that want to monkey under the hood?
Ha ha ha ha ha :) I almost feel we should end this discussion on that note :)
Sorry, I am not sure what you are asking? I think ColdFusion is awesome and did a great job. It's the bee's knees. I think part of the reason it is so great is that is provides a layer above the nitty-gritty.
@Ben: It's not important. You said "Using Java is not undocumented, but the fact that ColdFusion will look at a linked hash table and think it is a struct... I feel like that is the "undocumented" part of the feature."
To the normal developer, they will see a struct for what it is, a struct. To the advanced developer or the want-to-monkey-under-the-hood developer, they're going to see whatever they want because Adobe is not going to be able to stop them from using reflection and being nosy and peeking at all the undocumented 'under-the-hood' features. :) I personally think the dev team what they did and figured they'd let the community sort out best practices. Adobe isn't going to say anything other than, "We don't recommend using undocumented stuff." They have to.
Do you really think that Adobe should be accountable for mentioning that linked hash table is going to be treated as a coldfusion struct? I don't. That's for the advanced developers / likes-to-dig-under-the-hood developers that quarrel on bennadel.com's website to sort out. Adobe will just sit back and watch (and, NOT COMMENT *mutters*).
Wai hallo thar missing . Sorry.
Do you really think that Adobe should be accountable for mentioning that linked hash table is going to be treated as a coldfusion struct? I don't. That's for the advanced developers / likes-to-dig-under-the-hood developers that quarrel on bennadel.com's website to sort out.
I think you just defined "Undocumented" for us =)
I agree, Adobe can't really comment on this stuff because, as Ray pointed out, they certainly don't want to have to support any of it.
But, at the same time, should that exclude it from thoughtful use in the application, especially if well encapsulated - I would say not, but it is up to everyone to decide.
Be smart kids. Wear a condom. Don't drink and drive or use undocumented features unless you know what you're doing.
Food for thought, if you're building a commercial application that you're going to allow the public to purchase and deploy and you use undocumented features, you better damn well have a disclosure in your contract / terms / cover-your-butt paperwork and a back-up plan.
And that's the way the cookie crumbles. :)
You had me at "cookies".
Another important consideration with undocumented features: one of the most important good programming practices is to assume that somebody besides you is going to end up maintaining the code at some point. Is the next CF developer going to know what's up with the undocumented feature you're using? This can obviously be mitigated by adding your own documentation, but it will probably take the next person more time to grok the code than if you stick to documented features.
Pretty cool stuff, just wondering how you find out about these features in the first place. Is it like when people find cheats for games and they just put loads of random codes in? :) Its true you should be concerned about the possibility of the feature being removed in a later version but realistically speaking i cant see any reason why they would remove it? Maybe if it was a security issue but i cant think of anything other than that.