G'day:
I faffed around on the computer - with ColdFusion and Railo and TestBox - all day today, and have concluded I have nothing interesting to show for it. Still... this is a log of what I've been up to with CFML, so you're gonna hear all about it anyhow. And what's "better"... I'm gonna spin it out to be two articles. I am the Peter Jackson of CFML blogs!
OK, so I got wind of something to investigate the other day, and to do the metrics, I needed to time some stuff. Normally I'd just use a getTickCount() before and after the code, but I thought I might need something more comprehensive this time, so I figured a wee stopwatch UDF was in order. I've also just installed TestBox and am in the process of seeing if I can migrate away from MXUnit, so decided to do another TDD exercise with it. Note: there is no great exposition in this article, or really much new about TDD etc. It's simply what I did today.
Note: I abandoned TesxtBox's MXUnit compatibility mode today, because of a show-stopper bug: "ADDASSERTDECORATOR not implemented". This is the function that allows the importing of custom assertions, which I am using here. On the basis of that, I decided to just go with TestBox syntax instead.
Let's have a quick look at the function, and the tests.
//makeStopwatch.cfm
struct function makeStopwatch(){
var timeline = [];
var lap = function(string message=""){
var ticksNow = getTickCount();
var lapCount = arrayLen(timeline);
var lap = {
currentClock = ticksNow,
lapDuration = lapCount > 0 ? ticksNow - timeLine[lapCount].currentClock : 0,
totalDuration = lapCount > 0 ? ticksNow - timeLine[1].currentClock : 0,
message = message
};
arrayAppend(timeline, lap);
return lap;
};
return {
start = function(string message="start"){
return lap(message);
},
lap = function(string message="lap"){
return lap(message);
},
stop = function(string message="stop"){
return lap(message);
},
getTimeline = function(){
return timeLine;
}
};
};
Not much interesting here. I enjoyed finding another reason / excuse to use function expressions and a wee bit of closure around the timeline which "sticks" with the start() / lap() / stop() / getTimeline() functions.
What this function does is to log a struct at start, each lap, and another on stop. Here's it in action:
// useStopwatch.cfm
include "makeStopwatch.cfm";
stopwatch = makeStopwatch();
stopwatch.start("Begin timing");
sleep(500);
stopwatch.lap();
sleep(1500);
secondLap = stopwatch.lap("after another 1500ms");
writeDump(var=secondLap, label="secondLap");
sleep(2000);
stopwatch.start("Stop timing");
writeDump(var=stopWatch.getTimeline());
Output:
secondLap - struct | |
---|---|
CURRENTCLOCK | 1387658080067 |
LAPDURATION | 1500 |
MESSAGE | after another 1500ms |
TOTALDURATION | 2000 |
array | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
1 |
| ||||||||||
2 |
| ||||||||||
3 |
| ||||||||||
4 |
|
This is the bumpf I usually need when I'm timing stuff. Well: some or all of it. It'll be quite handy, I reckon.
One interesting facet here is that initially I thought I'd need three separate functions for the start() / lap() / stop() functions, I was doing TDD (seriously, I knew you'd wonder, so everything I write on this blog uses full TDD now), and having knocked out the first few tests to verify the method signatures for the returned functions, it occurred to me that stop() was actually redundant. It doesn't do anything that lap() wouldn't already do. I mean all this "stopwatch" does is take time marks... there's no clock starting or stopping really (getTickCount() keeps on ticking, we simply start paying attention and then stop paying attention at some point).
So, anyway, I decided before I starting messing around with a redesign, I'd get the test coverage done, get it working, and then refactor. This is something one of my colleagues (Brian, I mean you) has been drumming into me recently: don't get halfway through something, decide to do it differently, start again, refactor, waste time, and not have anything to show for it if I get interrupted (or, in a work situation, we get to the end of the sprint and need to release). So I banged out the rest of the tests, got everything working, and looked at my code some more.
Here are the tests: