Running with Code Like with scissors, only more dangerous

9Jan/121

Revealing Prototype Pattern: Pros and Cons

Posted by Rob Paveza

A short while ago, I wrote a post generally saying good things about the Revealing Prototype Pattern but mostly focused tearing down the other part that was presented with it, namely the way that variables were declared in a chain separated by the comma operator. This post will discuss some of the pros and cons of using this pattern, and give some examples about when you should or shouldn't use it.

Advantages

As Dan notes in his post, this features a significant improvement over straight prototype assignment (assignment of an object literal to a prototype), in that private visibility is supported. And because you're still assigning an object to the prototype, you are able to take advantage of prototypal inheritance. (Next time: How prototypal inheritance really works and how it can make your head explode).

Except for chaining his variable declarations, I have to admit Dan had me pretty well sold on the Revealing Prototype Pattern. It's very elegant; it provides good protection to its inner variables, and it uses a syntax we've been seeing more and more of ever since jQuery became a popular platform.

Unfortunately, it has some nasty drawbacks.

Disadvantages

To be fair, Dan lists some of the disadvantages about this pattern; however, he doesn't quite list them as such, and I think he was possibly unaware of some of their implications:

There's something interesting that happens with variables though, especially if you plan on creating more than one Calculator object in a page. Looking at the public functions you'll see that the 'this' keyword is used to access the currNumberCtl and eqCtl variables defined in the constructor. This works great since the caller of the public functions will be the Calculator object instance which of course has the two variables defined. However, when one of the public functions calls a private function such as setVal(), the context of 'this' changes and you'll no longer have access to the two variables.

The first time I read through that I glossed over the problems; I didn't quite understand the issue until I wrote some code. So let's do that - we'll implement the Java StringTokenizer class:

function StringTokenizer(srcString, delim)
{
    if (typeof srcString === 'undefined')
        throw new ReferenceError("Parameter 0 'srcString' is required.");
    if (typeof srcString !== 'string')
        srcString = srcString.toString();
    if (typeof delim !== 'string')
        delim = ' ';
    
    if (!(this instanceof StringTokenizer))    // enforce constructor usage
        return new StringTokenizer(srcString, delim);
        
    this.sourceString = srcString;
    this.delimiter = delim;
}
StringTokenizer.prototype = (function()
{
    var that = this;
    
    var _tokens = that.sourceString.split(that.delimiter);
    var _index = 0;
    
    var _countTokens = function() { return _tokens.length; };
    var _hasMoreTokens = function() { return _index < _tokens.length; };
    var _nextToken = function()
    {
        if (!_hasMoreTokens())
            return false;
        
        var next = _tokens[_index];
        _index += 1;
        return next;
    };
    var _reset = function() { _index = 0; };
    
    var resultPrototype = 
    {
        countTokens: _countTokens,
        hasMoreTokens: _hasMoreTokens,
        nextToken: _nextToken,
        reset: _reset
    };
    return resultPrototype;
})();

If you've ever written a jQuery plugin, you'll probably recognize what I did with the prototype assignment function; when writing jQuery plugins, it's common to close over the current instance of the jQuery object by assigning var that = $(this); so that you can write event-handler functions without losing access to the overall context. Unfortunately, what I did in this case is wrong; you may already see why.

var that = this;

In this context, this is a reference to the global object, not to the instance of the object - even though the prototype is being set. This is a generalization of what Dan said. Rewriting it to overcome it results in information leaking:

function StringTokenizer(srcString, delim)
{
    if (typeof srcString === 'undefined')
        throw new ReferenceError("Parameter 0 'srcString' is required.");
    if (typeof srcString !== 'string')
        srcString = srcString.toString();
    if (typeof delim !== 'string')
        delim = ' ';
    
    if (!(this instanceof StringTokenizer))    // enforce constructor usage
        return new StringTokenizer(srcString, delim);
        
    this.sourceString = srcString;
    this.delimiter = delim;
    this.tokens = srcString.split(delim);
    this.index = 0;
}
StringTokenizer.prototype = (function()
{
    var _countTokens = function() { return this.tokens.length; };
    var _hasMoreTokens = function() { return this.index < this.tokens.length; };
    var _nextToken = function()
    {
        if (!this.hasMoreTokens())
            return false;
        
        var next = this.tokens[this.index];
        this.index += 1;
        return next;
    };
    var _reset = function() { this.index = 0; };
    
    var resultPrototype = 
    {
        countTokens: _countTokens,
        hasMoreTokens: _hasMoreTokens,
        nextToken: _nextToken,
        reset: _reset
    };
    return resultPrototype;
})();

The code works correctly; but you can see that we have to make public all of the state variables we'll use in the constructor. (The alternatives are to either initialize the state variables in each function, where they would still be public; or to create an init function, which would still cause the variables to be public AND would require the user to know to call the init function before calling anything else).

Dan also indicated that you needed a workaround for private functions:

There are a few tricks that can be used to deal with this, but to work around the context change I simply pass “this” from the public functions into the private functions.

Personally, I prefer to try to avoid things one might call clever or tricky, because that's code for "so complex you can't understand it". But even in the case where you have a public function, you'll still get an error if you don't reference it via a public function call. This error is nonintuitive and could otherwise make you go on a bug hunt for a long time. Consider this change to the above code:

    var _hasMoreTokens = function() { return this.index < this.tokens.length; };
    var _nextToken = function()
    {
        if (!_hasMoreTokens())   // changed from:   if (!this.hasMoreTokens())
            return false;
        
        var next = this.tokens[this.index];
        this.index += 1;
        return next;
    };

Simply removing the 'this' reference in the caller is enough to cause 'this' to go out-of-scope in the _hasMoreTokens function. This is completely unintuitive behavior for developers who grew up in the classical inheritance model.

Alternatives

I wouldn't want to give you all of these options without giving you an alternative. The alternative I present here is one in which the entire object is populated in the constructor:

"use strict";
function StringTokenizer(srcString, delim)
{
    if (typeof srcString === 'undefined')
        throw new ReferenceError("Parameter 0 'srcString' is required.");
    if (typeof srcString !== 'string')
        srcString = srcString.toString();
    if (typeof delim !== 'string')
        delim = ' ';
    
    if (!(this instanceof StringTokenizer))    // enforce constructor usage
        return new StringTokenizer(srcString, delim);
        
    if (typeof Object.defineProperty !== 'undefined')
    {
        Object.defineProperty(this, 'sourceString', { value: srcString });
        Object.defineProperty(this, 'delimiter', { value: delim });
    }
    else
    {
        this.sourceString = srcString;
        this.delimiter = delim;
    }
    
    var _tokens = this.sourceString.split(this.delimiter);
    var _index = 0;
    
    var _countTokens = function() { return _tokens.length; };
    var _hasMoreTokens = function() { return _index < _tokens.length; };
    var _nextToken = function()
    {
        if (!_hasMoreTokens())
            return false;
        
        var next = _tokens[_index];
        _index += 1;
        return next;
    };
    var _reset = function() { _index = 0; };
    
    if (typeof Object.defineProperty !== 'undefined')
    {
        Object.defineProperty(this, 'countTokens', { value: _countTokens });
        Object.defineProperty(this, 'hasMoreTokens', { value: _hasMoreTokens });
        Object.defineProperty(this, 'nextToken', { value: _nextToken });
        Object.defineProperty(this, 'reset', { value: _reset });
    }
    else
    {
        this.countTokens = _countTokens;
        this.hasMoreTokens = _hasMoreTokens;
        this.nextToken = _nextToken;
        this.reset = _reset;
    }
}

The advantage of a structure like this one is that you always have access to this. (Note that this example is unnecessarily large because I've taken the additional step of protecting the properties with Object.defineProperty where it is supported). You always have access to private variables and you always have access to the state. The unfortunate side effect of this strategy is that it doesn't take advantage of prototypal inheritance (it's not that you can't do it with this strategy - more of that coming in the future) and that the entire private and public states (including functions) are closed-over, so you use more memory. Although, one may ask: is that really such a big deal in THIS sample?

Usage Considerations

The Revealing Prototype Pattern can be a good pattern to follow if you're less concerned with maintaining data integrity and state. You have to be careful with access non-public data and functions with it, but it's pretty elegant; and if you're working on a lot of objects, you have the opportunity to save on some memory usage by delegating the function definitions into the prototype rather than the specific object definition. It falls short, though, when trying to emulate classical data structures and enforce protection mechanisms. As such, it can require complicated or clever tricks to work around its shortcomings, which can ultimately lead to overly-complex or difficult-to-maintain code.

Like most patterns, your mileage may vary.

6Jan/120

Defining Variables in JavaScript

Posted by Rob Paveza

I've lately been reviewing different patterns and practices recently, and after reading his article about the Revealing Prototype Pattern, I wanted to take some time to analyze Dan Wahlin's approach to defining variables. It's hard to believe I found some common ground with Douglas Crockford, but as they say about broken clocks.... [Addendum: apparently, since JSlint says to 'combine with previous var statement,' I don't agree with Crockford.] Anyway, to begin, this post is inspired by Dan Wahlin's presentation of the Revealing Prototype Pattern; I noticed what I thought was a curious way for him to define his private variables, and looking back through his blog posts he discussed it in the original blog post of the series, Techniques, Strategies, and Patterns for Structuring JavaScript Code. For the most part, I like what Dan has to say, but I'm going to have to disagree when it comes to defining variables.

The Proposition

As Dan points out, this is the standard way of defining variables in JavaScript:

var eqCtl;
var currNumberCtl;
var operator;
var operatorSet = false;
var equalsPressed = false;
var lastNumber = null;

He advocates trading that for this:

var eqCtl,
    currNumberCtl,
    operator,
    operatorSet = false,
    equalsPressed = false,
    lastNumber = null;

It saves on 20 keystrokes, and he claims improved readability. Now, I disagree with Crockford's argument that, because JavaScript hoists variables to the top of the function, that you should always declare the variable there. I believe that, whenever possible, you should try to maximize locality of a variable. This is a principle discussed in Steve McConnell's Code Complete; the reasoning behind maximization of locality is that the human brain can only comprehend so much at once. (This is, of course, another argument in favor of many, simple, and small subroutines). By delaying the declaration of a variable until it needs to be used, we are able to better-comprehend the meaning of the variable and how its use affects and relates to the rest of the program. As such, I believe that one of the premises for moving these declarations into a combined var statement - specifically, to reflect the hoisting - is a poor rationale.

Let's carry on.

Similarities to Other Elements

In The Prototype Pattern, Dan demonstrates the use of a JavaScript object literal in assignment to the Calculator prototype, so that any object created using the Calculator constructor would inherit all of those properties:

Calculator.prototype = {
    add: function (x, y) {
        return x + y;
    },
    subtract: function (x, y) {
        return x - y;
    },
    multiply: function (x, y) {
        return x * y;
    },
    // ...
};

The important thing to note here is that we are simply defining an object literal; we are not writing procedural code, and that comma is not an operator! It is an important part of the JavaScript grammar, to be sure, but the comma here does not have the same semantic meaning as the comma we saw before. This subtle difference may lead to coding errors, in which someone who uses comma syntax with both will mistakenly believe they are declaring an object literal and use colons to separate the identifier from the value; or that they are declaring variables and use assignment syntax to separate the property from its value.

Comma Operator Considered Harmful

It surprises me to learn that JSlint advocates combining var declarations. Crockford's The Elements of JavaScript Style, Part 1 indicates that he isn't terribly fond of it either:

The comma operator was borrowed, like much of JavaScript's syntax, from C. The comma operator takes two values and returns the second one. Its presence in the language definition tends to mask certain coding errors, so compilers tend to be blind to some mistakes. It is best to avoid the comma operator, and use the semicolon statement separator instead.

Whichever way Crockford prefers it, I think what we need to remember is just because you CAN do something does not mean you SHOULD.

Let's consider Dan's full body of JavaScript from the Revealing Prototype Pattern. I'm going to shrink it a little bit, to emphasize the changes I'll make; and I'm removing any of his comments.

var Calculator = function (cn, eq) {
    this.currNumberCtl = cn;
    this.eqCtl = eq;
};

Calculator.prototype = function () {
    var operator = null,
        operatorSet = false,
        equalsPressed = false,
        lastNumber = null,
        add = function (x, y) { return x + y; },
        subtract = function (x, y) { return x - y; },
        multiply = function (x, y) { return x * y; },
        // I'm going to do something evil here.
        divide = function (x, y) {
            if (y == 0) {
                alert("Can't divide by 0");
            }
            return x / y;
        },
        setVal = function (val, thisObj) { thisObj.currNumberCtl.innerHTML = val; },
        setEquation = function (val, thisObj) { thisObj.eqCtl.innerHTML = val; },
        clearNumbers = function () {
            lastNumber = null;
            equalsPressed = operatorSet = false;
            setVal('0',this);
            setEquation('',this);
        },
        setOperator = function (newOperator) {
            if (newOperator == '=') {
                equalsPressed = true;
                calculate(this);
                setEquation('',this);
                return;
            }
            if (!equalsPressed) calculate(this);
            equalsPressed = false;
            operator = newOperator;
            operatorSet = true;
            lastNumber = parseFloat(this.currNumberCtl.innerHTML);
            var eqText = (this.eqCtl.innerHTML == '') ?
                lastNumber + ' ' + operator + ' ' :
                this.eqCtl.innerHTML + ' ' + operator + ' ';
            setEquation(eqText,this);
        },
        numberClick = function (e) {
            var button = (e.target) ? e.target : e.srcElement;
            if (operatorSet == true || 
                this.currNumberCtl.innerHTML == '0') {
                setVal('', this);
                operatorSet = false;
            }
            setVal(this.currNumberCtl.innerHTML + button.innerHTML, this);
            setEquation(this.eqCtl.innerHTML + button.innerHTML, this);
        },
        calculate = function (thisObj) {
            if (!operator || lastNumber == null) return;
            var displayedNumber = parseFloat(thisObj.currNumberCtl.innerHTML),
                newVal = 0;
            switch (operator) {
                case '+':
                    newVal = add(lastNumber, displayedNumber);
                    break;
                case '-':
                    newVal = subtract(lastNumber, displayedNumber);
                    break;
                case '*':
                    newVal = multiply(lastNumber, displayedNumber);
                    break;
                case '/':
                    newVal = divide(lastNumber, displayedNumber);
                    break;
            }
            setVal(newVal, thisObj);
            lastNumber = newVal;
        };
    return {
        numberClick: numberClick,
        setOperator: setOperator,
        clearNumbers: clearNumbers
    };
} ();

Note my comment: "I'm going to do something evil here." Here goes:
console.log('Hello, world')

Do you see what happened here? Let me put it in context.

        subtract = function (x, y) { return x - y; },
        multiply = function (x, y) { return x * y; },
        console.log('Hello, world')
        divide = function (x, y) {
            if (y == 0) {
                alert("Can't divide by 0");
            }
            return x / y;
        },

JavaScript semicolon insertion blew away the var when I inserted any valid statement or expression. In fact, I could have simply put 'Hello, world!' or 5 there, on its own line, and because the next line is a valid statement that stands on its own, JavaScript semicolon insertion blew away the var. As such, divide, setVal, setEquation, clearNumbers, setOperator, numberClick, and calculate were all just elevated to global scope, possibly blowing away existing variables and leaking a whole bunch of information with them. This could happen in any instance in which someone mistakenly types a semicolon (let's be honest - JavaScript is a semicolon-terminated language; it will happen somewhat frequently), or if they forget to put a comma at the end of a line.

As such, joining variable declarations together by using the comma operator is inherently an unsafe operation. You might think of it as a run-on sentence; it's not a good thing to do in English, so why would it be good to do in JavaScript or any other programming language?

And if that's not reason enough, here's another: declaring a variable is a statement. You are stating to the compiler, "I am declaring this variable to be a variable." Use the var statement to make a statement, and use the comma operator to indicate that there are operations. (Specifically, the one and only place I can think of in which a comma operator would be appropriate is if you need a single for-loop with multiple iterator variables, e.g., for (var i = 0, j = myArray.length - 1; i = 0; i++, j--). Of course, I don't want to say you should never use it, or else I'd be like Crockford with his dogmatic "I have never seen a piece of code that was not improved by refactoring it to remove the continue statement," which is patently silly.

But, beware the comma. He is correct in that it is easy to mark programming errors with commas. If you're going to declare a variable, do everyone a favor and declare it, using var, make it feel special by giving it its own line and declaration and semicolon. It will help in maintenance down the line.

2Jan/120

Facebook Security Concerns

Posted by Rob Paveza

I logged into Facebook today and I saw a new person under "People You May Know."  This person is someone I know from refereeing hockey here in Arizona.  It turns out that I only have one person on my friends list who I know happens to be an ice hockey referee, someone I know from not only refereeing but also playing hockey, more than 10 years ago.  When I clicked on Jeff's profile (the new person Facebook suggested), I saw he only had two friends - his is a very new profile!

Jeff and I haven't ever emailed back and forth directly.  I've sent a couple email blasts, and he probably has as well.  But that's the extent of it.

My best conclusion is that Jeff allowed the Facebook friend-finder application to have access to his email.  Because I'm on the same refereeing email distribution list as Jeff, I can only assume that Facebook has looked at his email and decided to inform me that I might know Jeff.

I do appreciate the flexibility of Facebook's find-a-friend tool.  But for it to be telling me that Jeff might be my friend based on data he provided seems to be a mild form of information leaking.

I'm only hoping that he provided his information to the Facebook friend finder tool.  I never did.  And if he didn't, well, now I'm concerned that Google and Facebook have been sharing that kind of information anyway...

30Dec/103

Net Neutrality: Are you sure it’s what you want?

Posted by Rob Paveza

Last week, the FCC, in concert with a priority of the Obama administration, passed a resolution stating that they would begin acting in promotion of “net neutrality,” an idea that all internet traffic should be considered equal.  But, are you sure that’s what you want?

A lot of techie geeks are in favor of this concept, and on its surface, at least, the principle sounds nice.  What would happen to the internet if Microsoft partnered with Verizon or Comcast to block Google from home or mobile internet users, so that they had to use Bing?  Or, even worse, consider what would happen if Cox blocked its customers from visiting Qwest.com here in the Phoenix area – they would be unable to switch services, at least via the internet.

Or would they?

The Historical Perspective

As we take a look at the idea of net neutrality, let’s take a look at the historical internet (circa early 90’s).  Prevalent offerings included America Online, now part of AOL Time Warner; The Microsoft Network, some features of which have now been folded into MSN.com and Windows Live; Prodigy, which was the first online services provider to offer its own web browser; and CompuServe, which was the first provider to allow users to send email to internet-based email addresses as early as 1991.

The internet was many orders of magnitude smaller, and yet net neutrality wasn’t even something that would have been considered.  MSN, AOL, and CompuServe all, in the early 90’s, proprietary: MSN featured channels, AOL offered its own channel-based content, and CompuServe had a substantial series of forums (promoting user-generated content).  CompuServe and MSN both offered world-wide web access starting in 1995, with CompuServe’s launch of Spry Mosaic and MSN’s offering of Internet Explorer 1; AOL also started offering limited internet access in 1995.  Ironically, the Wikipedia article about AOL notes that Prodigy “for several years allowed AOL advertising.”

In the early days of the internet, “net neutrality” as it is now termed wasn’t even a concern.  Why?  Businesses took huge risks and invested huge sums of money to provide any online services to its customers.  They were right in being able to be the content providers for their customers; and if customers didn’t like what their services provided, they (the customers) could figure out how to change services.

Today

I’ll start here: I don’t really know how much of a problem a lack of net neutrality exists in the United States.  Why?  It’s very simple.

Internet service providers have a collective interest in keeping the entire internet available to their subscribers.

Why does Verizon, for example, have an interest in keeping internet access to a direct competitor’s website, such as T-Mobile?  Well, there are two principles in play here:

  • Subscribers to Verizon would say, “If they’re blocking me from T-Mobile, what else are they blocking me from?”
  • Verizon isn’t an “exclusive” service; there is at least a reasonable chance that a Verizon customer has a competitor’s service for home or business use.  If Verizon doesn’t allow access to that service, the customer may decide that Verizon is an inadequate service provider.

In such a situation, it is the provider’s motivation of self-interest that promotes its desire for net neutrality.  The provider has a significant desire to retain customers for its continuous revenue.  One needs to look only as far as the 2005 scandal over the Sony BMG rootkit (software that was unknowingly installed on end-user computer systems) and the public outcry (and legal actions) that it caused Sony BMG to backpedal and eventually recall the affected product.  While this implies that a company may attempt to circumvent consumer “freedom,” it is important to note that the cost in doing so can easily become prohibitive. Particularly today with the rampant accessibility of microblogging and social media, companies need to be very careful how the treat their customers.

Businesses like NetZero paint a clear picture that some users are content to have a non-neutral internet service.  NetZero offered free internet service, provided that the user retained a permanent banner advertisement during a customer’s online connection.  The service has varied for cost, advertising methods, and speed, but the crux of the service remains the same; but with such a service, the user explicitly acknowledges that the service is not “neutral,” but is in fact sponsored by advertisers.

Why should ISPs not be allowed to be non-neutral, or even to offer different, neutral or non-neutral services to different market segments?  Consider a license for Microsoft Windows.  It has two primary market segments, namely, end-user and server.  Both of these segments are further segmented; Windows 7 is offered in variants such as “Home Basic,” “Home Premium,” “Professional,” “Enterprise,” and “Ultimate,” whereas the server version comes in version ranging from “Foundation,” “Web,” “Standard,” “Enterprise,” and “Datacenter.”  The core components of Windows are the same across all of the editions of the operating system; the specific feature set varies from one SKU to the next, because Microsoft can expand their market of potential customers by subsidizing the development of the lower-end licenses by charging more for the higher-end.  Similarly, ISPs may offer a basic “non-neutral” broadband package for an incredibly inexpensive $10/month, but your internet connection may not offer the “whole internet,” or may simply perform slowly when doing specific activities (like downloading illegal movies, or doing something legal like watching Hulu and Netflix) – how many customers could use this effectively, since they only use a very small number of sites?  Preferred providers could receive better performance on their sites for the provider’s customers because they help to subsidize those users.

In fact, Comcast and Cox both offer something akin to this already.  Looking at Cox’s product offerings for residential connectivity, we can see the note about “Download speeds with PowerBoost®” – an explanation of PowerBoost is offered via DSLReports.com:

PowerBoost is a patent-pending Comcast network technology that enables you to experience faster connection speeds while you are downloading and uploading large files to the Internet.  PowerBoost leverages an additional capacity that is already built into Comcast’s advanced network…. PowerBoost provides bursts for the first 20 MB downloading and 10 MB uploading of a file respectively on Comcast’s 6Mbps, 8Mbps, 12Mbps, 16Mbps, or *22Mbps High-Speed Internet services.

This is clearly a “non-neutral” use of the Comcast network; the modem must detect upload and download of files and distinguish between it and other traffic.  One would easily conclude that this is the gateway detecting HTTP or FTP uploads or downloads, and negotiating with the central switch to enable the burst bandwidth.  BitTorrent users would not see the benefit, nor would players of computer games (they would get the non-burst bandwidth speed).

In any case, it should be up to the users to regulate this behavior through market conditions – because that is true internet freedom.  Advocates of such “net neutrality” are often the users of BitTorrent, the popular, decentralized file sharing service, where the most commonly-distributed files include illegally-distributed movies, music, and software.  In essence, these folks have asked the FCC to help them to continue to infringe on copyright holders, and the FCC has now complied.

Tomorrow

Now that the FCC has chosen to support “net neutrality,” if the high Court doesn’t strike it down, what does the future hold for ISPs and consumers?

Well, we have the potential for an overall decrease in throughput for the general user.  When activities like videos can’t be prioritized below activities like email or web browsing, the higher-bandwidth activities will generally have their usage increase.

Scarier, though, is the role of the government as the “decider” of what is “neutral” and what isn’t.  It’s scary to consider that the government might one day decide to regulate the internet, similarly to what China is doing, because it prefers one kind of speech over another.

Net neutrality sounds nice on paper, but let users and ISPs decide whether it’s important to them.  If the options are between a company and the government making the decisions, I choose the people with only the business contract and not the guns to enforce the decision.

Of course, one day this opinion may not be “neutral” enough to be read online….

2Jan/080

Where Have All My Gigabytes Gone?

Posted by Rob

I don't know if this is a Windows Vista-specific trick, or maybe something related to Volume Shadow Copy (the service that supports System Restore), but there seems to be a major discrepancy on my hard drive.

In Windows Explorer, open the C drive, select all, then Alt+Enter to open the properties dialog.

Size: 128gb (137,787,853,411 bytes)
Size on disk: 129gb (138,600,678,813 bytes) (Note: I don't know how there's an odd number of bytes, except that apparently NTFS uses some type of odd clustering strategy - to my knowledge, the "size on disk" field should be a multiple of 4,096, the default NTFS partition cluster size for this size disk).

Now I navigate up to My Computer, right-click on the C drive and choose properties.
Used space: 267gb (287,193,178,112 bytes)
Free space: 664gb (713,020,395,520 bytes)

(This drive is a RAID-0 stripe of two Maxtor 500gb SATA drives)

Now if I set it to display protected OS files and folders, I can come up with a few new items:
4gb in hiberfil.sys (Hibernation support)
4gb in pagefile.sys (Virtual memory swap file)
132gb in \System Volume Information (System Restore)

Looks like I've discovered the reason for the space usage - System Restore.  Ugh.  Hard to believe I've had this installation of Windows active for about, what, not even a week?  And System Restore has managed to take up more space than the files I've installed.  The good news is, with it set at 15%, I'll probably only get another 7gb taken.  Still....  It's just incredible to me that it's filled so fast.