David Ungar
Sun Microsystems Labs
Mountain View, CA, USA
Henry Lieberman
Media Laboratory
Massachusetts Institute of Technology
Cambridge, MA, USA
Christopher Fry
PowerScout Corp.
Boston, MA, USA
Introduction
A good user interface brings you face-to-face
with whatever is being manipulated and experienced. For example,
the steering in a sports car, especially with a mid-engine layout,
lets you feel the road surface so you can tell when the road is
slippery, just by the feeling of the steering. A good tool, like
a high-quality wrench becomes a part of your hand, so you feel
that you are turning the bolt yourself. The machinery disappears,
and you feel connected to the object in question. A programming
environment can also convey an experience of immediacy, drawing
the programmer in closer to his program. When that happens, debugging
becomes easier. This principle of immediacy can serve as a guide,
keeping programming environment builders on the path to productive
environments.
Like your favorite pizza, a programming environment
can be described as a layered yet synergistic stack of ingredients:
The principle of immediacy can be brought
to bear on each of these layers.
For example, in the user interface, it will
help if each chunk of information (e.g. each procedure) shows
up as a visually distinguished and clearly legible unit. Here,
at the low level of visual perception, designers frequently employ
cues that mimic the real visual world in order to exploit a user's
preexisting perceptual abilities. That is why, for example, the
Self user interface [1] renders each object as a slab with stereotypical
stage lighting, from above and left. Careful attention to perceptual
legibility can lighten the burden on the programmer, so that more
attention can be focused on finding bugs and less attention on
finding objects on the screen. The user interface must allow programmers
to manipulate programs as well as see them. Here again, immediacy
can be achieved by employing affordances that match user's prior
experiences, or that reduce the need for the user to change focus.
A bad user interface draws so much attention to itself that it
distracts from debugging, and a good one lets a tired programmer
debug much later into the night (if need be!).
But the UI is only the lowest-level of the
system. The programming environment provides the lenses and levers
that let the programmer inspect, manipulate, and debug the program.
But if the lenses and levers feel like lenses and levers, the
programmer is distanced from the program, has to mentally work
harder to keep track of it, and is encumbered in attempts to fix
it.
The principle of immediacy leads us to understand
that the environment must make it effortless to examine a program,
with all of its connections. If the programming environment you
use cannot rapidly and effortlessly show you all the callers of
a function, or all the definitions of a message, or all the places
that can change a particular variable, it is distancing you from
the logical structure of your program, and making it that much
harder for you to debug it.
And when debugging, what goes for a static
program also goes for a (running or suspended) process. When you
find an argument that has an erroneous value, the environment
should be able to take you to where the value was ultimately passed
in, even if it is ten stack frames up (although I know of none
that can do this today). Immediacy says that the important relationships
must be made manifest.
Immediacy and debugging
We'll illustrate how the principle of immediacy applies to debugging environments by describing ZStep 95 [3, 4], a program debugging environment based on the principle of immediacy. Debugging is often a search that starts from observed effects of running a program and proceeds to deduce the possible causes in the underlying program code. So ZStep 95 strives to facilitate debugging by bringing the relationship between cause and effect closer to the programmer.
The separation between cause and effect can
occur in at least three dimensions: time, space and semantics,
thus designers can strive for three kinds of immediacy that are
important for debugging: temporal immediacy, spatial immediacy
and semantic immediacy.
Temporal immediacy
Human beings recognize causality without concious
effort only when the time between causally related events is
kept to a minimum. If there is a delay between a change in a
steering wheel and the response of a car, we describe the steering
as "mushy" and it destroys the feeling of immediacy
of control of the car. In programming, delay between an effect
and observing related events or data in the program puts a strain
on the programmer's short term memory to hold all the relevant
information in their head while waiting for the programming environment
to "catch up". In Eisenstadt's paper in this issue
[2], we see that a full 15% of bugs are directly attributable
to temporal distance between cause and effect, the largest single
source of error in his survey.
In debugging, it is important to reason backwards
from effects to causes, so temporal immediacy backward in time
is just as important, if not more important, than temporal immediacy
in the forward direction. ZStep 95 is built upon the notion of
reversibility. It keeps a history of the computation and
can be run either forward or backward using "video recorder"
controls. The history includes expressions, values, and the graphic
output of the program. Important also is fine control over the
level of detail displayed. It is not only immediacy between temporally
adjacent events that is important, but immediacy between temporally
relevant events. We provide both "fast" and "slow"
stepping speeds in both directions, manual and automatic control.
By allowing the programmer to choose speed and direction, ZStep
makes it easier for him to find a mode where the temporal distance
between relevant causal connectionns occurs on the appropriate
scale for preconcious perception.
We also provide a novel "Graphic Step
Forward" and "Graphic Step Backward" that works
in terms of events where something is drawn on the screen, rather
than in terms of expressions in the code. This allows you to step
the behavior of the program rather than step the code, and supports
temporal immediacy between graphic events and their causes in
the code.
Whether you choose to step expression-by-expression,
graphic event by graphic event, forward or backward, all views
of the program -- code, value, stack, and graphics are kept in
sync and present a consistent view of the program execution. Self
also stives to keep the information presented to the user consistent
with the dynamic state of the system.
The principal of immediacy explains why these
and other designers made this choice: by providing automatic display
updates, the link between the system's state and the display state
is removed from the programmer's concious attention and becomes
immediate and unconcious; the programmer unconciously comes to
identify one with the other, reducing the cognive burden of the
debugging task.
Spatial Immediacy
Spatial immediacy means that the physical
distance between causally related events should be kept to a minimum.
The reason is the same as for temporal immediacy -- events that
are widely separated by space on the screen force the user to
devote more concious effort to link them, forcing the user to
shift attention and putting a strain on he user's short-term memory.
Many steppers show the value of the current expression in a fixed
window off to the side of the window containing the program code
display. This causes a "ping-pong" effect as the user's
attention bounces from the code to the values and back again.
And the existence of a separate, constant area that seqentially
reflects the values of different expressions adds cognitive distance
as well as spatial by forcing the user to deal with the changing
link between the value display region and the expression being
displayed.
ZStep 95 takes the approach of showing a floating
window that follows the point of program execution. The code is
displayed in an editor buffer, and as the program runs, either
forward or backward, fast or slow, the expression currently being
evaluated is highlighted, and, right next to it, a floating
window shows the code's value, if it has been evaluated.
The user's attention never need wander to
see the value of the current expression. The user need never devote
precious short-term memory to the task of remembering which expression's
value is being shown. If the user is working backwards in the
history, the notation "Will be:" along with the value
is displayed, to indicate that the value lies further along in
the history. "Working on it..." is shown if the value
is only partially computed, so that the event is situated in the
temporal event stream.
Spatial immediacy is important because it
maintains the visual context. Seeing two related pieces of information
next to each other fosters making semantic connections in the
mind between those two pieces of information. If they are spatially
separated, "out of sight" becomes "out of mind".
In ZStep 95, a click on the floating window containing the value
brings up a data inspector. The code, the resulting value, and
an inspector on that value are all spatially co-located, reinforcing
the feeling of immediacy.
If an error occurs, the error message is shown
in the floating window, again, spatially located near where the
error is manifest, so both the cause and effect can be seen at
once. Another click on the error message brings up documentation
about the error, and another click still evokes documentation
for related functions.
Semantic Immediacy
Semantic immediacy means that information
that the conceptual distance between pieces of information that
are semantically related is kept to a minimum. In interactive
interfaces, the conceptual distance between two pieces of information
is often represented by how many user interface operations, such
as mouse clicks, it takes you to get from one to the other. We
have already seen some examples of semantic immediacy, between
code expressions and their values, and between error messages
and documentation. There are others.
Because ZStep 95 keeps a history of all the
graphic events in the program, we can associate each graphic object
drawn on the screen with the event that drew it. This makes it
possible to click on a graphic object, and go immediately to the
place in the execution where that object was drawn. Not only do
we see the source code that drew the object, but the entire state
of the stepper is backed up to the point in time where that object
was drawn, including the appearance of the screen at the time,
the values of expressions, the stack. Similarly, we can point
to an expression and jump the stepper immediately to that point
in time, keeping all views consistent, including the graphics.
To summarize, the process of debugging a program
involves understanding the relationships between the following
objects:
An expression in the source code [not "lines of code"!]
The value of that expression at a given point in time
The graphic output, if any, of that expression
Expressions that are executed before or after that expression
Other expressions or values that are semantically related
[for example, the stack, inspecting data
values, etc.]
ZStep's interface is designed to allow you
to go from any one of these objects to any one of the others with
practially no concious effort. It never takes more than one mouse
click. That's semantic immediacy.
Immediacy and programming language design
An environment creates the experience of immediacy
by allowing the programmer to make any change at any time, and
by taking less than half a second to do so, which keeps response
time below the level of perceivable delay. That way, the program
feels right in your hands. I once attended a panel session representing
three great programming environments at PARC: InterLisp, Smalltalk,
and Cedar. The Cedar panelist boasted that, since multitasking
had been added to Cedar, he now had the luxury of reading his
e-mail while waiting for compiles. But the Smalltalk user had
the greater luxury of having any change take effect in the time
it took to press the accept button and see the resulting screen
flash. He or she could then go on and continue debugging without
loss of continuity. Just as immediacy applies to both stimulus
and response sides of the UI, it also applies-at a higher level
of abstraction-to both browsing and changing code and data in
the programming environment.
Like Smalltalk, Self, APL, and most Lisps
let the programmer breakpoint a program, and insert extra code
to print information, or to check conditions, or even to attempt
a bug fix-while the program is suspended. The program can be continued
without restarting, the change takes effect immediately, runs
at full speed, can be browsed the same way as any other part of
the program. Few other environments-especially those in wide use-can
boast this level of manipulative immediacy. Many C++ systems take
minutes to change class definitions in a large program, forcing
the programmer to waste mental effort devising workarounds, or
leading to either boredom (leaving the flow state) or distraction
(reading e-mail, etc.). Immediacy at the programming-environment
manipulation level is crucial for debugging productivity, and
underlies many of the good results reported on in the rest of
this issue.
Finally, programming language design is often
viewed as high art, mystical religion, or as putting together
a menu for a restaurant (this is the principle of orthogonality--
the diner can choose sauce independently of meat to give everyone
something that they like). One can also appraise the design of
a language by returning to the principle of immediacy. In order
for a language to bring programs closer to the user, it must be
built around a small number of concepts. For example, APL embodies
linear algebra with its arrays, functions, and functions-on-functions
(a.k.a operators). As a result, to an engineer already familiar
with linear algebra, there is hardly any learning curve at all!
Just as you can write any matrix equation on a piece of paper,
you can write almost any APL expression, unlike many languages
that impose extra constraints. For example, APL though typesafe,
has no static type checking. Although the absence of static checking
requires the programmer to find and fix some bugs at runtime (the
traditional rationale for static typing), its presence compels
the programmer to reason much more indirectly about his program.
Like a scratch on a skipping CD (or LP), static semantics such
as type checking ram home the disturbing fact that the program
does not execute as written; there is a translation and checking
stage, which "executes" the program in its own way,
so that what finally runs is less directly related to what the
programmer can write or see.
As another example, macros are a popular variety
of static translation on programs. They often help the programmer
make up for a lack in the language design itself. But what happens
when there is a bug in the macro? The programmer may wind up staring
at "preprocessor output" in order to understand some
particularly abstruse and unexpected behavior of the program.
In effect the poor programmer has to debug two programs, the one
he wrote, and the one that eventually ran. The principle of immediacy
dictates that the programmer need not worry about static invariants
unless he or she wants to, but many existing languages
force doing this all the time.
By bringing the programmer closer to the program,
an experience of immediacy helps the programmer understand, change,
and ultimately debug. Attaining a reasonable level of immediacy
is a precondition for effective debugging and can serve as a useful
guide to the design of all aspects of programming environments.
References
[1] Bay-Wei Chang, Objective reality for self:
concreteness and animation in the Seity user interface. Ph.D.
Dissertation, Electrical Engineering Dept., Stanford University,
1996.
[2] Marc Eisenstadt, "My Hairiest Bug
War Stories", Communcations of the ACM, April 1997.
[3] Henry Lieberman and Christopher Fry, Bridging
the Gap Between Code and Behavior in Programming, ACM Conference
on Computers and Human Interface, CHI '95, Denver, Colorado, April
1995.
[4] Henry Lieberman and Christopher Fry, ZStep
95, A Reversible, Animated Source Code Stepper, in Software
Visualization: Programming as a Multimedia Experience, John
Stasko, John Domingue, Marc Brown, and Blaine Price, eds., MIT
Press, Cambridge, MA, 1997.
David Ungar
is a Senior Staff Scientist at Sun Microsystems Laboratories.
Author's present address: Sun Microsystems Laboratories, 2550
Garcia Ave, MTV29-117, Mountain View, CA 94043; e-mail: David.Ungar@sun.com
Henry Lieberman
is a Research Scientist at the Media Laboratory of the Massachusetts
Institute of Technology. His interests lie at the intersection
of artificial intelligence and human interface concerns, and among
his topics of interest are intelligent agents for the Web, software
visualization, visual programming and debugging, machine learning,
and programming by example. Author's present address: Media Laboratory,
MIT, 20 Ames St., Room 305 A, Cambridge, MA 02139 USA. E-Mail:
lieber@media.mit.edu
Christopher Fry is the Chief Technical Officer of PowerScout
Corporation in Boston. His current primary interest is the visualization
of complex data structures as diverse as the World Wide Web or
intermediate values within a program development environment.
Over the past couple of decades, Fry has hacked at MIT and numerous
MIT spin-offs. Author's Present Address: PowerScout Corp., 21
Crescent Rd., Lexington, MA 02173 USA. Email: cfry@shore.net