Showing posts with label coding. Show all posts
Showing posts with label coding. Show all posts

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.

Thursday, February 11, 2010

The Cardinal Rule of Functions

Functions should be:
  • short
  • easy to understand
  • well-named
  • reusable
  • maintainable
All these desirable properties follow from obeying the Cardinal Rule of Functions.
One Function to a Function
That is, the code implementing a function should have one and only one task to do. Example tasks are:
  • Calculate: using primitive operators, calculate a value; this includes arithmetic, string operations, list building, and so on
  • Read or write: get data from or send data to files or users
  • Parse or format: convert text into structured data or construct text representations of structured data
  • Combine: apply several other functions to a set of arguments
  • Dispatch: decide which of several other functions to apply to a set of arguments
  • Iterate: repeatedly apply another function to a list of argument values
Notice that the latter tasks correspond to classic control structures (composition of function calls, conditional branching, and iteration). A symptom that the rule has been broken is when several of these control structures appear in one function. Another symptom is when you have trouble keeping a function to less than a dozen lines of code, and/or its lines to less than 72 characters.
Another good sign that the rule has been broken is difficulty naming the function clearly, or using names like scan-and-compress-and-print.

Tuesday, February 9, 2010

Names (first in a series)

It's hard to over-emphasize the importance names bring to the maintainability of code. They're the first thing a maintainer sees, and they're often the last thing a novice code thinks about. Textbooks provide notoriously bad examples that students follow in the real code.

If you haven't already, spend some time at Roedy Green's How to write unmaintainable code. Note that the longest essay there is on naming.

Unfortunately, there is no formula for generating names, just some good heuristics, and some major "don't"'s, like names that are
  • cryptic, e.g., bltx

  • vague, e.g., data

  • verbose, e.g., first-item-to-delete

  • misleading, e.g., num for something that holds a
    list of numbers
Good names are:
  • short
  • plain English or conventional
  • accurate
A function name should
  • say what a function returns or does, e.g., get-test,
    never how it does it, e.g., search-recursively
  • be either a verb-object, e.g., get-test, or
    qualified-verb, e.g., string-sort
  • be semantic for application-specific functions, e.g.,
    remove-test-case
  • be generic for general utilities,
    e.g., reverse-sequence



Much more to come on this topic.



Addendum: Just to reinforce the importance of naming, see
page 5 of Where the Wild Bugs Are.