Unix Programming - Compactness and Orthogonality - Orthogonality
Orthogonality
is one of the most important properties that can help make even
complex designs
compact. In a purely
orthogonal design, operations do not have side effects; each action
(whether it's an API call, a macro invocation, or a language
operation) changes just one thing without affecting others. There is
one and only one way to change each property of whatever system you
are controlling.
Your monitor has orthogonal controls. You can change the
brightness independently of the contrast level, and (if the monitor
has one) the color balance control will be independent of both.
Imagine how much more difficult it would be to adjust a monitor on
which the brightness knob affected the color balance: you'd
have to compensate by tweaking the color balance every time after you
changed the brightness. Worse, imagine if the contrast control also
affected the color balance; then, you'd have to adjust both knobs
simultaneously in exactly the right way to change either contrast or
color balance alone while holding the other constant.
Far too many software designs are non-orthogonal. One common
class of design mistake, for example, occurs in code that reads and
parses data from one (source) format to another (target) format. A
designer who thinks of the source format as always being stored in a
disk file may write the conversion function to open and read from a
named file. Usually the input could just as well have been any file
handle. If the conversion routine were designed orthogonally,
e.g., without the side effect of opening a file, it could save work
later when the conversion has to be done on a data stream supplied
from standard input, a network socket, or any other source.
Doug McIlroy's advice to “Do one thing well”
is usually interpreted as being about simplicity. But it's also,
implicitly and at least as importantly, about orthogonality.
It's not a problem for a program to do one thing well and
other things as side effects, provided supporting those other things
doesn't raise the complexity of the program and its vulnerability
to bugs. In Chapter9 we'll
examine a program called ascii that prints
synonyms for the names of ASCII characters, including hex, octal, and
binary values; as a side effect, it can serve as a quick base
converter for numbers in the range 0–255. This second use is not an
orthogonality violation because the features that support it are all
necessary to the primary function; they do not make the program
more difficult to document or maintain.
The problems with non-orthogonality arise when side effects
complicate a programmer's or user's mental model, and beg to be
forgotten, with results ranging from inconvenient to dire. Even when
you do not forget the side effects, you're often forced to do extra
work to suppress them or work around them.
There is an excellent discussion of orthogonality and how to
achieve it in The Pragmatic Programmer [Hunt-Thomas]. As they point out, orthogonality reduces
test and development time, because it's easier to verify code that
neither causes side effects nor depends on side effects from other
code — there are fewer combinations to test. If it breaks,
orthogonal code is more easily replaced without disturbance to the
rest of the system. Finally, orthogonal code is easier to document
and reuse.
The concept of refactoring, which first
emerged as an explicit idea from the ‘Extreme Programming’
school, is closely related to orthogonality. To refactor code is to
change its structure and organization without changing its observable
behavior. Software engineers have been doing this since the birth of
the field, of course, but naming the practice and identifying a stock
set of refactoring techniques has helped concentrate peoples'
thinking in useful ways. Because these fit so well with the central
concerns of the Unix design tradition, Unix developers have quickly
coopted the terminology and ideas of refactoring.[43]
The basic Unix APIs were designed for orthogonality with
imperfect but considerable success. We take for granted being able to
open a file for write access without exclusive-locking it for write,
for example; not all operating systems are so graceful. Old-style
(System III)
signals were non-orthogonal, because signal receipt had the
side-effect of resetting the signal handler to the default
die-on-receipt. There are large non-orthogonal patches like the BSD
sockets API and
very
large ones like the X windowing system's
drawing libraries.
But on the whole the Unix API is a good example:
Otherwise it not only would not but
could
not
be so widely imitated by C
libraries on other operating systems. This is also a reason that the
Unix API repays study even if you are not a Unix programmer; it has
lessons about orthogonality to teach.
[an error occurred while processing this directive]
|