Remember the last time you installed a new operating system on your computer?
Everything was set to its defaults: colors, fonts, menu items, speaker volume,
and hundreds of other things. Over time you’ve gradually set all of those things
to match your own personal preferences, and now your computer works just the way
you like. Now imagine that every time you turned the computer on, it reverted to
its original settings. Not a very happy thought, is it? And yet I see
applications ignoring user preferences and going back to defaults all the time.
The quality that the operating system has (and that some applications lack) is
persistence. In this article, I’ll discuss the basics of persistence from
a developer’s point of view: what it is, when you should use it, and some of the
practical techniques for implementing it.
What is Persistence?
Persistence is the quality of persisting. In programming terms, persistence
refers to information which remains available even when it’s not being actively
used by your application. Like many other concepts, persistence isn’t a binary
choice; there are levels of persistence. For software, persistence flows
directly from the notion of variable lifetime. You can identify a scale of
information persistence, from least to most persistent:
- Local variables persist for the lifetime of a particular function.
- Object-level variables persist for the lifetime of the object.
- Global variables persist for the lifetime of the application.
- Stored information persists across multiple runs of the application.
- Permanent information persists across multiple installations of the
application.
For this article, I’m concerned only with stored and permanent information:
things that persist even after you shut down and restart the application. This
is the information that makes the application itself appear persistent to the
user.
Persistence and Preferences
Persistence is often tied to user preferences, but they are two distinct
concepts. User preferences allow the user of an application to dictate how the
application works. Most applications persist user preferences, but in some cases
you might want to implement a non-persistent user preference. For example, you
might allow a user to choose to run a document transformation application in a
test mode such that its user interface is functional but no changes are written
back to the document. In such a case, you probably wouldn’t want to persist the
preference for the next time that the application is run; such testing is likely
to be an occasional activity rather than a permanent user choice.
The classic mistake in user preferences is to have too many of them.
Sometimes programmers just can’t make a decision, and so they add another
checkbox to the user interface, usually on a cluttered Options dialog box. If
you’re expecting users to make hundreds of such decisions, you need to seriously
rethink your approach to user preferences. Possibly you can take some rare
settings, of interest to only 1% of your users, and migrate them to an
initialization file where the 99% won’t need to look at them on the user
interface. Better yet is to just figure out the best decision and make it for
the user. Flexibility in software is good, but it can easily be carried to
extremes.
Whether you allow the user to tweak a setting or not, you should always make
sure that the default, un-tweaked setting is appropriate for the majority of
your application’s users. If the application is run with the default setting for
everything, then it should still work sensibly.
What Should You Persist?
I’ve already mentioned user preferences as a category of information that
lends itself to persistence. Here are some other things that you should think
about persisting in your next application:
- Transparent preferences. Sometimes user actions result in a change to the
internal state of an application that it makes sense to persist. For example, if
you allow the user to customize the toolbars in your application, they probably
do so via drag-and-drop rather than through an Options dialog box. But even
though they don’t think of this as something to be saved, you should be smart
enough to save the changes behind the scenes. The size and location of your
application’s main window is another example of a transparent preference that
you might decide to persist. - User history. Sometimes it makes sense to give the program some memory
of what a user was doing when they last left the application. A weak form of
this sort of persistence is the most recently used (MRU) list of documents
you’ll find in many document-centric applications. A stronger form is to simply
open the last document that the user was working with automatically when they
launch the application in the future. - Logging with playback. Ordinarily, I don’t consider logging or tracing
of a program’s actions to be a part of its persistence layer. That’s because the
log is normally not meant to be read, only written to. But there’s a special
case when you can consider logging as a form of persistence: when you offer
playback. Imagine a complex graphics program that let you decide to suspend an
editing session in progress without saving the changes to the image. You might
log all keystrokes up to that point so that the user could return to where they
left off. - User limits. This is a case where permanent storage, even if the application
is uninstalled and reinstalled, can make sense. The classic example is limits on
shareware: if your application is designed to be used for thirty days without
payment, you don’t want the user to get another thirty days just by reinstalling
it. In this case, you’ll want to persist (and probably encrypt) the starting
date somewhere. Note, though, that not removing your information from the
computer at uninstallation time may violate the guidelines for various logo
programs.
The Problems of Programming Persistence
After you’ve determined the information that your application will persist,
there are two main implementation details to consider: where to persist
information, and how to structure the persistence layer in the application.
There are a wide variety of places that you can persist information: in the
Registry, in disk files (whether ones of your own design, or old INI files) or
in a database – and that’s just on Windows. There are several factors you need
to consider when determining where to persist user information:
- Is your application used by multiple users? Do you need to keep them from
“stepping on each other” by overwriting persisted information? - Is the information sensitive? Does it need to be encrypted to protect the
user’s privacy or security? - Is your application already using a data store (such as a database) for
other data storage? Can you leverage this to persist user information with
minimal code? - Is your application used by non-administrative users? Can these users open
the store you’ve chosen? - Is your application used by roaming users? Does your chosen data store roam
with these users so that they get their preferences wherever they log on?
Whatever you choose, I urge you to make use of any persistence features built
in to your operating system and coding environment. Don’t reinvent the wheel if
there are good facilities available. If you’re working with Microsoft .NET, for
example, take a look at the Isolated Storage feature, which automatically
handles security and roaming issues for you across all of the supported Windows
operating systems.
Finally, some advice on structuring your persistence layer:
- First, whatever you do, do create a persistence layer that presents
an abstract view of persistence to your application, and hides the
implementation details. This will allow you the freedom to change the underlying
storage medium without rewriting the rest of the application. - You’ll usually want to read the persistent information at startup and write
it out at shutdown. Be sure you take into account all code paths out of the
application; if something goes wrong, and the application needs to exit
abnormally, you should still persist information if possible. - Always assume that the data isn’t actually persisted when you’re reading it
in. That is, if the call to read the data fails for any reason, you should
supply good defaults to the application. - When you’re running in debug mode, add checks to make sure that persisted
information has reasonable values. This will help shake out bugs where you’re
saving the wrong information.
The Benefits of Proper Persistence
Why go through all this bother when you’re designing a new application? The
answer is simple: users expect applications to be intelligently
persistent. Properly implementing persistence translates into an easier to use
application with happier users. Ultimately that translates to either sales or
job security (or both!) for the developer. Look at it this way: having an
application without persistence is like going out to sleep every night at a
hotel. The furniture is always in the same place when you get there, whether you
like it or not. Wouldn’t you rather sleep at home, where you’ve moved the couch
and the bed to just the right spots, and they stay there when you’re away?
That’s what persistence can do for your applications: make the user feel right
at home.
About the Author
Mike Gunderloy is the author of over 20 books and numerous articles on
development topics, and the lead developer for Larkware. Check out his MCAD 70-305, MCAD
70-306, and MCAD 70-310 Training Guides from Que Publishing. When he’s not
writing code, Mike putters in the garden on his farm in eastern Washington
state.