floatToFraction UDF

Someone on CF-Talk just asked about converting floating point numbers back to decimals.  Always one to enjoy a little abstract thought on a random topic during my lunch, I threw together a solution:

function floatToFraction(num, maxDenom) {
var denom = "";
var numer = "";
var intPart = fix(num);
var sigFigs = "";
if (num EQ intPart) {
return num;
}
num = num – intPart;
sigFigs = len(num) – 2;
for (denom = 2; denom LTE maxDenom; denom = denom + 1) {
for (numer = 1; numer LT denom; numer = numer + 1) {
if (round(numer / denom * (10 ^ sigFigs)) / (10 ^ sigFigs) EQ num) {
if (intPart NEQ 0) {
return intPart & " " & numer & "/" & denom;
} else {
return numer & "/" & denom;
}
}
}
}
return intPart + num;
}

It's not perfect (feed it 0.7, for example), but it's a good start.  The tricky case is catching the irrational fractions (which is the source of the above mentioned imperfection), but a little clever rounding and it's easily solved for the majority of cases.  The maxDenom argument is for controlling the granularity of the fractions returned.  If, for example, you don't want to get any finder than eighths, you can pass 8 in as the maxDenom value and anything that would require a denominator of larger than eight will simply be returned as the floating point number passed in (e.g. floatToFraction(0.9, 8) will return 0.9, while floatToFraction(0.9, 10) will return 9/10).

ColdFusion and Java

No, this is not "that" post.

Since my post a while back about the new features of Java 5 (or Java 2 1.5 5.0 SE, or whatever the hell it's called), Sean Corfield and I have had a couple IM discussions about languages, development environments, developer mindset, tool support, frameworks, and various other things.

We were talking this evening and he pointed me to his post about Web Services, ColdSpring, and Reactor that I'd missed while I was ill last week.  I made the comment that I'm doing much the same thing, except with Spring, Hibernate, JUnit, and SpringMVC (to his ColdSpring, Reactor, cfcUnit, and Model-Glue:Unity).  He responded by saying, and I quote, "except in evil java rather than wonderful coldfusion ;)".  As you can surmise, we have differing opinions on some things, though at least from me, great respect for the other's viewpoint.  ;)

I happen to very much like compile-time typechecking, though largely for the improved tooling it allows (though I do enjoy JavaScript very much). I like strong IDE support.  I don't mind a little extra complexity for some additional flexibility, especially if I can ignore the complexity up front.

Sean likes his SmallTalk.  Later in that same conversation, he said "I never really liked the over-intrusive Java IDEs".   He'd also said something about how evil the EJB3 spec's use of in-code annotations for persistence was, even though they're fully overridable via external configuration/mapping files.

What's utterly fascinating to me is that we both get the job done (and it's almost the same job!), but approch it with such different mindsets.  It's not that we were trained differently, there's just some innate difference in the way we approach a given problem.  Something to remember next time one of those "religious" debates comes up on some list or another.  Almost always at least one wrong answer, but very rarely a single right one.

Should have signed up for Psychology, not Computer Science, eh?

Debuggers?

Over at Damon Cooper's blog, he's soliciting comments on the ultimate CF IDE.  What surprised me was the number of requests for a CF debugger.  CF Studio had one, way back in the day, and apparently people still miss it.

On a similar note, I've been doing a lot of Java development in Eclipse of late, and there's a heavy bent towards debugging in the JDT platform as well.  For example, F11 will debug the last launched app, while CTRL-F11 will just run it normally.  Seems like it should be just the opposite.

I've also gotten into a pissing contest with a New Atlanta representtive about the merits of debugging.  Paraphrasing here, but one comment was along the lines of "if you don't use a debugger, you're not a fully skilled developer". 

I personally haven't used a debugger since my CS 330 class down in Arizona, when we HAD to in order to complete an assignment.  I can say, with complete honesty, that I haven't missed them at all.  How's that, you say?  It's simple:

Unit testing and log4j, with an emphasis on the former.

Many checks you do with a debugger can be written as a unit test, which not only allows you to let the computer do most of the work, but it also lets you save the tests to be rerun later as part of a regression test suite.  Needless to say, this is a huge asset, as it can prevent the reoccurence of bugs, while an ad hoc debugging session is ethereal.

Unit tests aren't suitable for all situations, however, such as when you need to do some ad hoc examination of execution in order to pinpoint a bug.  This is where log4j (or whatever other logging package) comes in.  By allowing you put logging messages directly in you code and leave them there, you obviate the need to maintain breakpoints for your debugger.  Further, by allowing different loggers and log levels, you can very finely tune the output you'll get from your logging statements, something that's rather hard to do with breakpoints in a debugger.

Unit testing is something that hasn't really reached the level of acceptance in the CF community it deserves.  A large part of that, I think, is because the tooling isn't stellar, which is directly attributable to CF being a hard language to build good a unit testing framework.  The runtime nature of CF, while one of it's strongest assets as a RAD language, is also a weakness in this case.

Fortunately, with the CFLOG and CFTRACE tags, you can get some of the benefits of structured logging.  Or, if you're not afraid of a little Java, it's a snap to leverage log4j directly in your CF applications.

CAPTCHA – eeeewwwww

Dave Shuck posted about making CAPTCHA easier on your users this morning.  While I can see the point, CAPTCHA sucks.  I can't even tell you how many times I go to post something somewhere, see a CAPTCHA image and hit the Back button.  It's not that it's terribly difficult to deal with usually, but the the whole premise is flawed.  Though I can say I've had a couple occasions where it's taken several tries to get right, and I've got good eyes.

Dave makes a good point about capitalization, but my question is why the hell would you make a CAPTCHA case sensitive to begin with?  And it should NEVER contain 'l', 'L', '1', 'o', 'O', '0', or any other even remotely ambiguous characters.  And the characters should be friggin' HUGE so it's stupid easy to read for anything that has visual processing.  In other words, basic visual processing capabilities should be the ONLY requirement.

While I'll admit to having had some spam problems on my blog, I've solved them without CAPTCHA.  It's not terribly difficult.  A combination of form obfuscation and content filters has basically erased all comment spam with zero user impact.  And note also that I use MovableType under the hood, which is a very common blogging platform and therefore has a large number of spammers specifically targetting it because it's so widely used.

Eclipse/Ant Trick

Eclipse has very nice Ant integration, letting you rrun a build file from the context menu all over the place, either via a dialog for selecting the specific target, or using the last/default target.  What I didn't know until today, however, is that you can right click on a target in the Outline view and run that target directly, without having to go through the normal target selection dialog.

If you use Ant, definitely useful.  If not, reenforcement that it's worth a bit of digging to learn about your tools, so you can leverage them most effectively.

CFCDoc

I just got an email from another CF developer looking for the CFCDoc tool that Spike and I wrote a while back.  It's still available on Spike's site, but I've posted it here as well.

In a nutshell, it's a Javadoc-style tool for CFCs.  Unlike Javadoc, it doesn't generate a bunch of static HTML files, but instead builds pages dynamically.  That's good and bad, as you'd imagine, but mostly good.  Spike built the original and I've modified it heavily to add inheritance information, abstract/deprecated support, constructor (i.e. init()) support.

One significant differentiator between it and the built-in CFCExplorer tool is that CFCDoc doesn't instantiate your CFCs (which runs the psuedoconstructor) and can have some nasty side effects.  Instead of using the getMetaData() function (which is what requires the instantiation), CFCDoc just reads the raw CFML off the filesystem and parses it to extract the needed data.  I can't say for certain (because I've never tried), but that also it should run exactly the same on your CFML engine of choice, not just CFMX.

To get started, download the archive, extract it, add a path (absolute or relative) to your CFC directory in the config.xml file (there are examples), and then fire up index.cfm in your browser.  And, no, I don't get paid by the PayPal links that are in there.

Sudoku Anyone?

For my birthday last month, Heather (my wife) got me a book of Sudoku puzzles.  If you like puzzles and haven't tried Sudoku, highly recommended.  Simple and fun.

However, being the good computer geek that I am, I quickly decided that it'd be a heck of a lot easier to let the computer solve them for me.  Note that this is not cheating.  Since the computer can't do anything I don't tell it to do, I'm still solving the puzzles; the computer must exhibit a subset of my Sudoku prowess.  However, it does have far better "bookkeeping" abilities, which is a marked advantage.

I built it as a simple JUnit-driven Java 5 application.  It's pretty basic: 20 types, plus a few nested types.  It leverages the Strategy pattern for implementing strategies, so adding a new strategy is as simple as implementing the Strategy interface (probably by extending AbstractStrategy) and registering the implementing class as a known strategy.

What was particularly interesting, however, was that the program was able to solve one- through four-star puzzles (five stars is the most difficult) with just two simple strategies:

  1. Checking to see if any given unit (a row, column, or block) has only one cell that can contain a given number, which means that cell must contain the number in question.
  2. Checking to see if all cells in a given unit that can contain a given number are also within a second unit, which means the rest of the second unit can't contain the number in question.

I've no idea how difficult the puzzles are in the overal universe of Sudoku, but it was surprising to me that those two simple strategies were so effective.

I should mention that the rule of the game (each number is present exactly once in each block) is enforced by the system itself, and was usually enough to solve the one-star puzzles.  So you might consider the rules themselves to be strategy zero as they are effective in their own right.

AJAX Anti-Example

US Airways has a AJAXy interface for their homepage, but don't follow their example.  Try, for example, searching for the status of flight 8022 (from Philidelphia, PA to Portland, OR) today.  That happens to be the flight my sister and her family are on at this very moment.

You might try entering the flight number (knowing that the flight number uniquely identifies the flight).  Or you might try entering the departing city (hoping to get a list of candidate flights).  Neither works.  You have to enter both.

Once get both pieces of info in, submit the form.  You might try pressing enter, like I did.  After all, you had to type the info in, so your hand is already on the keyboard.  Denied.  You have to either use the mouse to click the Retrieve button, or tab through to it and press enter.

Ok, so now we get results.  Oh, it hasn't left yet, even though it's a couple minutes past the departure time.  Come back in 15 minutes and hit refresh.  Blank form – no results.  What?  Type in the info again and click on the Search button and see that the flight has now left, 13 minutes late.

A little digging and it seems that they don't use any forms or page parameters.  When you "submit" the form, it gathers data from the form fields, sends it to the server with JS Remoting (which sets some session variables), and then does a location='...' to the destination page where the session variables are read and cleared, and the page rendered.

If you're building AJAXy UIs make a note that you're still operating in an HTTP/HTML environment, so follow the rules.  Use forms for forms.  Use page parameters for generating dynamic pages.  Consider common use cases (like refreshing your flight status page) and their implications to your application.  Don't require superfluous data.  And one last nit-picky thing: don't leave half of your homepage blank specifically for rendering error messages in.

A couple things that they did do a good job on: the autocompletion dropdown for departure airport and tabindexing all their form fields.  But in general, very low marks for usability. 

CFEclipse 1.3 Beta

So I went to download the CFEclipse 1.3 beta today, and much to my chagrin, it requires Eclipse 3.2, which isn't even an official Eclipse release yet.  I really wanted the dynamic autocompletion stuff, but having to rebuilt my entire Eclipse installation is hardly worth it.  Maybe once it goes gold, but not for a beta.  I believe that also makes CFEclipse 1.3 incompatible with Flex2, so if you want to run both, you have to use separate Eclipse installs (which means duplicating all your projects, all your configuration and settings, and being unable to easily work on CF/Flex projects).

I'm a huge fan of CFEclipse, but this this seems like a really silly decision that's going to strand a lot of people on version 1.2 for quite a while.

Mike's 6:45am Phone Call

If your name is Mike, you called me at 6:45 am Pacific time and said something about queries of queries and then gave me your phone number, please call me back.  In my sleepy stupor failed to write down the number correctly, and calling it just says "the call cannot be completed as dialed".