Wednesday, March 31, 2010

10 Reasons Why Accessor Functions are Better than Global Variables

Novices often read that accessor functions, such as lineWidth() and setLineWidth(), are better than global variables, such as LINE_WIDTH. But they usually only see one or two reasons why, hardly enough to overcome the clear simplicity of using variables.

So let me try to put the nail in this particular coffin with 10 different reasons.

By global variables here, I include things like Lisp and C global variables, and things like public C++ data members and Java instance variables. By accessor functions, I mean things like Lisp and C functions, things like public C++ and Java getters and setters, and things like properties that some languages support that are "variable-like" in syntax but "function-like" in actual semantics.

1. Readability

To make it clear that some variable is global, most languages have some naming convention to make them stand out, e.g., asterisks as in *current-color* in Lisp, and upper-case as in MAX_DATA_LENGTH in C++ and Java. But such conventions clutter up code badly. [When an experienced Lisper sees code littered with starred variables, her first reaction is "Asterisks! The gall!".]

Accessor functions however are just regular functions and need no such special naming.

2. Read-only access

Often there is global information that the user should be able to access, but not change. By defining and making public only a reader function, you can prevent users from changing information that can not or should not be changed.

3. Write-only access

While less common, sometimes there is information that needs to be specified that should not be readable, by some users at least. changePassword(old, new) comes to mind, here.

4. Uniform access to variable-like data

There's more to a computer than CPU and memory. There are clocks, I/O ports, system host information, and so on. Accessors like currentTime() offer a uniform way to access such information, without worrying about whether such information is stored in a memory location or derived from some other source.

5. Scoped values

Consider output line width. When we set it to 50 characters, do we mean that to apply to all output, including output to files, or just to output to the screen or to some window on the screen? With accessors, it's easy to extend the API
to allow values to be scoped appropriately, e.g., setLineWidth(debugOut, 50), without creating a slew of specialized variables.

6. Safe assignment

Consider line width again. What happens if someone says (setq *line-width* nil) or setLineWidth(-10)? With variables, the chances are nothing happens until later when printing is attempted, at which point an error occurs. With an accessor function, bad values can be caught and prevented or replaced at assignment time.

7. Assignment with special values

Consider line width once more. With an accessor like set-line-width, we can extend the values it accepts to include things like named standardized values, such as (set-line-width :wide) or setLineWidth(WIDE), where "wide"
is not the actual value but a key to look up some standard default value.

8. Assignment by example

Consider date formats. There are many ways dates can be printed: full month names vs. abbreviated month names versus digital months, two-digit vs. four-digit years, month-day-year vs. day-month-year, hyphens vs slashes vs. spaces and commas, etc. A clever way of making this complex combination of choices simple to specify is to allow the user to given an example date, e.g.,
setDateFormat("2/31/95"). This isn't the actual value stored, but a
value that can be used to construct the actual format object.

9. Assignment by parts

Consider default pathnames, e.g., the default pathname for the compiled
binary form of a source code file, or an XSLT-transformed XML file.
Even though it makes sense to store this internally as one pathname, it makes more sense to allow the user to modify pieces of it without worrying about the other parts, e.g., (set-module-binary-pathname :directory "Lisp:").

10. Traceable access

Many development environments, especially for high-level languages, don't make it easy to trace when someone gets or sets the value of a global variable. This is no problem with accessor functions.

No comments:

Post a Comment