Applying design principles to software, Part III

Software design can be beautiful. It can bring joy to your work. Design is about usability. A beautiful object inspires good use. Good design teaches the user to use the object properly. A well designed library will bring joy to its users. It can instil a passion to learn more and improve skills. I love good design.
This is the third article in a series in which I explore how to apply principles of design to writing software libraries. The ideas in these articles were inspired while reading The Design of Everyday Things, a wonderful book by Donald Norman. There is one principal insight I am trying to explore: that the principles of design presented in the book can help us make software libraries that are intuitive to use.
In the last two articles, I explored the ideas of the Conceptual Model and Feedback. The Conceptual Model of the user is his/her understanding of the system. It’s what clues the user into how the system works. He/she builds the model from the System Image and through active experimentation, fueled by feedback. Feedback is when the library gives the user information about the effect of his/her actions.
In this article, I want to discuss the principle of constraints. Constraints limit actions. They can keep the user from breaking the system. Constraints can also guide the user to learn how to do what he/she needs to do. Constraints serve two purposes: preventing errors and helping the user figure out what actions he/she wants to take. Well designed constraints can vastly improve the usability of a library.
Constraints
Half of the ease of use of an object is in the object itself preventing errors, or at least making them undoable. If the object didn’t constrain them, the user would have to constrain him/herself from taking inappropriate actions.

Yes — constraints are a good thing. Well conceived constraints can give the user the freedom to explore. Preventing horrible errors makes it safe for learning by experimentation. It also gives the user confidence that he/she doesn’t have to worry much about unexpected errors happening. Constraints free the user from having to keep track of every possible thing that could go wrong. The system sets up boundaries within which the user can play and learn.
The other half of the ease of use is in helping the user decide how to do what they want to do. It is easier to choose between a small list of options than a large list. By using constraints, you can help whittle down the list of possible actions to just a few — freeing the mind of the user to think of other things. Constraints help the user figure out how to do what he/she wants.
In the design of physical objects, there are three kinds of constraints:
1. Physical constraints
Physical constraints use the physical properties of the object to limit the possible actions. These constraints are the strongest constraints. They absolutely limit the actions, and they are out of the control of the user.
Example:
Square pegs won’t fit in round holes.
Physical constraints might not literally exist in software, but there is an analogue in software. Compiler constraints limit actions before runtime. Lisp checks the number of arguments at compile time. Some languages check the types of the arguments at compile time. These constraints absolutely limit the possible actions the user can take.
Another type of physical constraint is runtime constraints — such as dynamic type checking — that limit actions, though you won’t know until you run your program. Runtime assertions can limit the actions allowed based on the values or types of the arguments, or based on the state of the system.
We’ll keep metaphorically calling these constraints “physical”. Physical constraints should be used in any case where an action should absolutely be avoided.

2. Cultural constraints
Cultural constraints use the cultural meaning attributed to objects to limit the possible actions. Cultural constraints are not as strong as physical constraints, but they are still important. While they don’t stop the user from performing an action, they do guide the user in deciding whether it is appropriate.
Example:
Green means go, red means stop. Coloring buttons this way makes it clear which to push when.
It’s possible to use the meanings embedded in function names to communicate more than just what the function does. It can also communicate when to use it, how to use it, and any context that surrounds it. For instance, the ABORT method on a transaction object implies that no more operations can be performed on it. The word “abort” connotes death, an absolute end. It would be difficult to imagine an operation that an aborted transaction could perform that wasn’t RESTART, RESURRECT, etc. Cultural constraints help the user filter out impossible or inappropriate actions.

Cultural constraints can be used instead of physical constraints or in addition to them. The cultural constraint helps the user figure out in which cases the action should be used. Well-named functions can make it intuitive how to use the library.
3. Logical constraints
Logical constraints limit the actions to what makes sense. Logical constraints are about as strong as cultural constraints. They don’t stop the user, but they can guide the user — especially when the user doesn’t know what to do next.
Example:
A logical constraint that we’re all familiar with is the process of elimination. If there’s only one choice left, it must be the right one.
The thing about programming is that it already relies on logic. Logical constraints form the basis of the whole enterprise. So it’s your job to make those constraints evident and clear. If something doesn’t make perfect sense, explain why — in the doc string or the error messages. The more your system makes sense to the user, the more natural it will feel.

Yes, you’re right. So we’ll need to deal with that.
Preventing and Fixing Errors
Obviously, limiting the number of errors the user makes will make the system feel easier to use.
Let’s talk about two kinds of errors: program errors and usage errors.
Program errors are problems in the program: syntax errors, type errors, wrong number of arguments, etc. Some of these are checked by the compiler, some are checked at runtime. Program errors are usually absolutely clear and can be caught. When a program error occurs, it means the action the user tried to perform did not occur.
Usage errors are when you accidentally do the wrong thing. You delete the wrong file or you store the answer in the wrong variable. Something wrong happens in usage errors, whereas usually nothing happens with program errors. Usage errors can be destructive — something happened, but not what the user wanted.
Program errors are easier to constrain. Syntax is checked by the compiler. Many type errors are checked at runtime, etc. One of your jobs, as a library designer, is to set up those constraints. These can usually be done with a physical constraint to absolutely prevent the action.
Usage errors are harder to constrain. They happen when the user does something he/she could do, but by his/her own reconning shouldn’t do. Imagine the usage error of setting your clock an hour too slow. It’s only a usage error because you don’t want to be late for work. If you were moving to a different time zone, on the other hand, it wouldn’t be an error. The clock can’t know what the user is up to, so it shouldn’t constrain. But that leaves room for errors. If these can be prevented, they are prevented through use of logical and cultural constraints. However, sometimes they are unpreventable. In these cases, the only recourse is to allow the action to be undone.
But let’s look first a how we can limit the damage caused by usage errors as much as possible. One way to limit usage errors is to constrain them with cultural constraints or logical constraints.
Let’s look again at the cultural constraint we talked about before. The ABORT method implies that no more operations can be performed on the transaction object. And let’s assume that it’s still possible to call methods on the transaction even after the ABORT. As a cultural constraint, it does its job pretty well of preventing an error.
Another way to reduce the number of usage errors is to turn them into program errors. Then the system can deal with it by setting up a physical constraint.
Even though the ABORT method is well-named, nothing would stop the user from accidentally calling a subsequent method, causing an error. But if we instead throw an exception when a subsequent method is called, we prevent an error from happening: we’ve created a physical constraint. The cultural constraint is still there — it still guides the user to choose correct actions — but there’s a safety net now.
You can do this to a lot of your functions — throw exceptions as soon as you know they will result in errors. Those exceptions can inform the client code (with appropriate restarts) and the programmer (with informative error messages). We’ll talk about that later.
Finally, errors will happen anyway, no matter how easy it is to use. What you can do in this case is to make them visible and correctable. Provide query functions, as in the last article, to discover the result of the actions. Also provide functions that are complementary to each other. One does, the other undoes, and vice versa. And make it clear that they can be used that way.
For instance, the macros PUSH and POP are complementary. If you POP something, and then decide you shouldn’t have, you can then PUSH it back on, and the state has returned to where it was. No destruction — the error is fixed.
Learning from errors

Program errors are inevitable — no one writes perfect code. But fortunately, they can be learning experiences. The library can teach the user how to fix the error.In the last article we talked about feedback. There’s an important kind of feedback that I didn’t describe in much depth — exception messages. In addition to constraining behavior, exceptions also communicate to the user through their type and through the message they contain. This makes exceptions part of the System Image, and therefore a good way to teach the user how to use the system — how to fix the error and how to prevent it later.
If the error is a result of the user’s misunderstanding, you can explain how the user can fix his/her conceptual model in the message. Take, for example, if the user enters this at the REPL:
CL-USER> (+ "Hello" 8 )
It would be easy to tell the user “Argument of wrong type”.
With that message, the user knows that one of the arguments is of the wrong type, but not which one. We can do better — and the message is usually not this bad.
When I type the above code at my REPL, SBCL throws a TYPE-ERROR condition, with the message The value "Hello" is not of type NUMBER.
Well, putting the type of the condition together with the message, the user can deduce that the function wants a NUMBER, and “Hello” is not one of them. But notice that it does require a bit of a leap. Imagine if you were new to Lisp and didn’t think to look at the type of the condition, only at the message. What would you think? I know I would think “Of course “Hello” is not of type NUMBER, it’s a string. Tell me something I don’t know.” It told me something obvious — it’s nearly a truism that “Hello” is not a number. I have to put the pieces together to figure out why it’s telling me this.
We can do even better than SBCL’s default message. Yes, that message does communicate a lot, but there is a lot more that could be communicated, and in a clearer fashion. What if the function gave this message:
"+ represents the mathematical operation of addition, and therefore only accepts arguments of type NUMBER. The value "Hello" is of type STRING. Perhaps you meant to concatenate two strings. In that case, read the documentation for CONCATENATE by entering (documentation 'concatenate 'function) at the REPL. Perhaps you wanted to add the length of the string to a number. The function LENGTH returns the length of a string."
What does this give me? While it’s still not perfect, I would much rather see this error than the previous one. It tries to teach me when to use + and explain why a STRING value is inappropriate. It also suggests another function that I might have meant instead. It is broadening my understanding of the system. Errors are a great opportunity to teach the user.
Conclusion
Remember, you want to set up boundaries within which the user can play and explore. So what can you do to do that?
Ask yourself these questions:
Does the library limit wrong actions, either through compile-time or runtime checks?
Check the arguments and throw exceptions with helpful messages. Check the type and value of arguments to functions at runtime, as soon as it is clear that they will cause an error.
Does the library let you undo mistakes?
Provide query functions for the state, as described in the feedback article. Also, for every function, provide another function that is the complementary action like PUSH and POP so that when the user determines that the action was an error, he/she can undo it.
Do the names of my functions and macros and their arguments imply how and when they should be used?
Think about the meanings of the names of your functions. The subtle meanings can communicate a lot to the user.

If you’ve been following the articles, you’ve learned some powerful concepts: Conceptual Models, Feedback, and Constraints. You’ve also learned about keeping knowledge out of the head of the user, and developing a good system image. These concepts can help you define the experience of the users of your library. That concludes the design principles article. Perhaps in the future I will expound more on the process of design. Thanks for reading. I hope to hear all about how you’ve used these principles in your own software.
Popularity: 4% [?]
Filed under Programming Practices | Comments (2)Applying design principles to software, Part II

In the last part of this series, I introduced the idea of Conceptual Models. I explained how the designer’s model is communicated to the user only through the System Image. Well, I lied. While the System Image is the first thing the user sees, and it is very important in helping the user develop a good conceptual model, I simplified the case a bit. I displayed a diagram that showed a basic one-way communication between the system image and the user. This is actually not true. The beauty of using a library or any system is that the user is constantly revising his/her conceptual model based on information about the effect of each action. This process is called feedback.
Here’s a diagram:

Feedback
The principle of feedback allows the user to immediately know the effect of his/her actions. A device or module that does not provide feedback is very hard to use. You have to guess whether you actually pressed the button hard enough, or if your command went through. Immediate feedback is important. We need feedback to understand what state the system is in in order to use it.
I’ll discuss two important reasons for giving good feedback. First, feedback allows the user to evaluate and revise his/her conceptual model. This reason is important primarily to the programmer, since the programmer can learn from experience. Second, when there is good feedback, the current state of the system is evident. The user can easily determine the effect of his/her actions without guesswork or feats of memory. This aspect of feedback is important for both the programmer and his/her client code.
Revising the Conceptual Model

While at the REPL, the user continuously uses feedback to construct an accurate conceptual model. Each function call potentially changes the state of the system and queries the state of the system. The user tries to predict what the function does, then calls the function and analyzes the outcome, comparing it to his/her expectation. If the expectation is right, confidence goes up. If it’s wrong, he/she’ll need to revise the conceptual model.
Feedback is imperative to learning how a system works — only the simplest of systems can be taught with documentation, comments, and examples alone. Proper feedback facilities should be considered as part of the system image, as should any other part that communicates to the user. In learning a system, you start with the static comments and documentation, and polish off your understanding with your own experimentation.
Gulf of Evaluation
Imagine, now, if the system did not provide feedback. How does the user know if his/her actions worked? How does he/she know if they performed as expected? The short answer is that the user doesn’t know. The long answer is that he/she will have to guess, keeping track of it in his/her head. But if the user is wrong, well, the user is wrong, but doesn’t know it. And that can be dangerous.
The difficulty a user has in determining the actual effect of his/her actions is called the Gulf of Evaluation.

Don’t make your users climb down there. They could get hurt.
A good feedback system will have a small Gulf of Evaluation — the effects of one’s actions are clear and immediate. The size of the Gulf of Evaluation is proportional to the effort required to get and interpret feedback from the system. A corollary is that the ease of use and ease of learning a system is indirectly proportional to the size of the Gulf of Evaluation. Decreasing the Gulf of Execution is an important part of making the library feel intuitive.
Let us, for the moment, assume that the user is exactly right about how the system works. Why would the user want feedback, in that case? Well, if the system won’t tell the user what state it’s in, then the user is going to have to keep track of it in his/her head. And that will tire out the user’s poor brain.

This is also exactly the reason why feedback is important to client code as well. Without programatic feedback for your client code to determine the state of the system, the client code will have to keep track of the state of the system itself. In fact, in order to function properly, the system will have to mimic the system exactly — which is equivalent to rewriting the system. So then why would anyone want to use the library if they just have to rewrite it?
This idea of where knowledge of the system is kept — in the world or in the head — is crucial. It has everything to do with decoupling. It deserves a lot of attention, so I’ll discuss it further in a later article.
What to do?
To return to the issue of feedback, what can we do to improve the design of our system?

Well, one thing we can do is to make functions return a value that indicates success or failure. In Lisp, NIL represents the empty list and the Boolean value false. This certainly doesn’t make sense in all cases. The authors of the function GETHASH recognized this. GETHASH returns two values: the value associated with the key (or nil if it isn’t found) and t or nil to indicate whether or not the key was associated. Why is this important? Why doesn’t the first value suffice?
Well, let’s imagine that GETHASH only returns the first value it now returns, namely the value associated with the given key. If we associate the value NIL with the key X, if we call (GETHASH ‘X), we get the value NIL. And that is exactly the same answer we would have gotten with (GETHASH ‘Y), even though Y is not associated with any value in the hash table.
So the key is to distinguish the two meanings with a second return value. The question the designer must ask him/herself is: “Are there any ambiguous values that I could disambiguate?” If the answer is yes, find a way to disambiguate them. Any impediments to learning the state of the system completely and unambiguously will slow down the learning of the system — your library will seem confusing and difficult to use.
The designer should also add query functions to the system that allow the user to determine the state. This isn’t to say that the internal implementation of the library should be exposed. But providing feedback functions gives you one more way to communicate an accurate conceptual model. The returned values of the query functions should correspond to the states and ideas that you as the designer want to communicate to the user.
One last thing you can do is to document your system in terms of the query functions. In your documentation, describe the output of these query functions after performing operations. For example
;; If you call (ENQUEUE QUEUE 10) then (= 10 (LAST-IN-QUEUE QUEUE)) will return T
This will not only teach the user how to use the parts of the system (like the function ENQUEUE). It will also teach how to use the query functions to learn more about how the system works.
I hope you enjoyed this article. You’ve learned about Conceptual Models and Feedback, which let you teach the user how to use the system effectively. The next article will teach you to build your system to empower the user to avoid and fix his own errors.
Popularity: 4% [?]
Filed under Programming Practices | Comment (1)Applying design principles to software, Part I
Sometimes I find a well-written library with a great API. It’s well documented, easily understood, and it seems to do what it “should” do. It’s almost as if the author of the library anticipated my usage. Those libraries are a pleasure for me to use. I tend to call them well-designed.
I’ve been reading a fascinating book on design recently, called The Design of Everyday Things (DOET). It discusses good and bad design of the objects we use every day, from the doorknob to stovetops. The book presents principles of good design that, if followed, can make products easier to use. And the reasoning behind the principles is so simple and straightforward.
In this article I’d like to explore principles of design as applied to writing software. Not user interface design, but the design of API’s and libraries.
There are two users of a module: the programmer, and the client code he/she writes. Both types of users are important, so I’ll try to give some useful tips about both.
Conceptual Models

DOET showed me that there is an order — a process — for making things easy to use, intuitive, and enjoyable. I want to apply that process to developing software. If I could only learn to apply the ideas of design, maybe I could make my libraries easier on the user. We’ve all used libraries that are easy to use. They feel intuitive, natural — like they “just work”.
There is no magic bullet to make that happen. It takes a lot of thought and hard work. But I can’t help but feel that the insights into design I gained from DOET can help me create libraries that are a joy to use.
The first insight? Enjoyment of a library is not based on it’s technical merits. It’s not about what algorithm is used. It’s not the efficiency or the number of features. In fact, it doesn’t happen in the computer at all.
The most important usability aspect happens in the brain of the user.
It’s about how the user thinks the library works. It’s about how well the user’s ideas about the system match with how the system actually works. Those ideas the user has are called the user’s Conceptual Model. A good conceptual model can mean the difference between a frustrating programming experience and a delightful one.
Your main task as an API designer is to communicate a correct conceptual model to the user.
An incorrect or incomplete conceptual model will make using the library confusing. The user will think an action will do one thing but it does another. It may even make it impossible to use.

Good question. And it’s pretty tough to do because the designer cannot communicate directly with the user. He/she instead must communicate through the documentation, comments, self-documenting code, and examples. All of those things the system presents to the user are collectively called the “System Image”. It’s the first and most important way to develop a correct conceptual model.

So improve your system image and you’ll improve your usability. That’s not to say that it’s all superficial. The system image needs to communicate how to use the library clearly and correctly. If you can’t find a clear, simple, and correct explanation for how to use it, it could be a sign that your library needs to be reworked from the bottom up. I’ll explore doing that in the next articles in the series.
Here’s a big diagram showing some of my ideas for ways I can communicate better to my user.

I’m looking forward to applying these to my libraries. I’m sure my users will thank me.
So, that’s just the first of the principles. It won’t solve all of your design issues, but it’s an important concept to have before we more forward. Now that the idea is there, we can tackle the idea of feedback, which is important for the programmer and the client code using your library. See you in the next article.
Popularity: 7% [?]
Filed under Programming Practices | Comments (3)6 Reasons to develop your tests first

I get frustrated a lot with the software I write. Typically, it starts to get so complex that any small, local change could potentially effect some other part of the code. And it gets so complex and unmanageable that I usually start fantasizing about rewriting it. But there’s something I’ve learned the hard way. Writing your tests first can really help remove the stress of a complex piece of software. Test Driven Development (TDD). It’s not the panacea that the hype and buzzword mongers want you to believe, but as a discipline it does relieve some issues when developing large systems.
One of the mainstays of Test Driven Development is to develop your tests first, before you write the code that the test will check. I would like to share with you some of the advantages that the discipline of TDD can impart on your code.
1. Prevent imagination overrun
As a programmer, I love tinkering with code. I love to experiment with some new way to structure my objects, or a different dispatch strategy, or any number of things. It makes me feel clever to come up with a solution that can deal with way more cases than just the few I know I need now. But all too often, those meandering experiments waste time. Usually, ten simple lines of code will do precisely what I want, and I let my imagination run wild with Rube Goldberg algorithms.

[edit: I changed the graphic to be more clear. See the original graphic.]
When you write your test first, you know exactly what your software needs to do. You write a test and watch it fail. Then you write the simplest code that makes the test pass. And that’s key, you make the simplest change to the code that makes it pass. Not the most ingenious, not the most modular, not the most general. All those things just mean complex. The discipline of writing the simplest code possible and nothing more will help you save time.
2. Know when you’re done
Related to preventing imagination overrun is knowing when to stop writing code. Let’s face it: even each class can suffer from feature creep. You want to make it handle the more general case. Or someone thinks you should use the Observer Pattern. These kinds of feature creep are just a waste of time.

Most software systems I’ve worked on required so much work that you really did need to know when to stop work on one piece and move on to something more important. A test defines when a feature is “good enough”. The discipline has a built-in check for completion. If you pass all of your tests and there are no more tests to write, it works well enough. So why waste your time on it when there’s more important things you could be doing? If you stop coding when your tests pass, you will use your time more effectively.
3. Catch regressions early
Code starts to get complex after you’ve been working on it. And especially after you’ve not worked on it for a few weeks. When you start to make changes to the code, how do you know you haven’t broken something? Maybe you can keep it all in your head. But maybe you can’t. No body can keep it all in their head. So maybe you do a few manual tests to try it out. But then you make some more changes, and you start to wonder again: “Did this break something.”
And eventually, your test reveals that somewhere in the 300 lines of code you’ve written or modified you introduced a bug. But where was it? When was the last time you ran your tedious manual tests?
When you develop your automated tests first, you know that pretty much anything important that needs to happen in your program will happen, or if it doesn’t, at least it will tell you. And your tests are so easy to run (usually a button press or an expression entered at the REPL) that you do them repeatedly. Hopefully after every tiny, atomic change. That way, you can know which change breaks the tests and fix it. Developing the discipline to run your tests often, almost compulsively, will save countless hours of frustratedly hunting down bugs.
4. Create more modular code
When I do Test Drive Development, I’m often surprised by how clean the design turns out to be. I’ll often see what are referred to as standard design patterns emerge, fully formed, completely unintentionally. I like to think of that as evidence of the hidden power of this discipline: that in testing the interface of a module (Unit testing) you are forcing the interface to be well designed.
Instead of asking yourself what should this module be able to do, the Unit test forces you to ask “how will I or other code use this” though in an indirect way. It is akin to a product designer creating something to be used by people. He/she asks what the user will need to do to the object (push a button, turn a knob), how to let the user know how to do that (making the button and knob highly visible, perhaps textured), and how to let the user know what the effect of the action is (beeping or changing the volume).

Right! It works the same for a software module. When writing the unit test, you ask “what will do I do to the object?” (what you are testing), “what function do I call?” (the interface), and finally the essence of the test: “how do I know it did what it should?”. Writing good unit tests will help you design better software.
5. Cleaner code
Have you ever wanted to reorganize some code, maybe break larger functions down into smaller ones? Move some methods to another class? But then you thought twice about it because you were afraid to break the code. When I don’t write tests I sure do. But when I have lots of tests, I usually feel pretty safe. Those corner cases are covered by the tests, so if I break something, I can always find it. Refactoring my code is actually fun, because I’m not afraid of breaking anything.
Also, since I write the simplest possible thing to make the test pass, my code is usually pretty neat anyway. So there’s less housework to do.
6. Satisfaction
And writing code can be such a chore when you’re facing the task of writing a gigantic system. Breaking your development down into small bits, each with a defined goal, makes the whole process much more pleasant. You get a little reward after each iteration (seeing all you tests pass) and you are never daunted by the apparently treacherous path to the end of the project. It lets your mind concentrate on the current step in the process instead of worrying about the big picture. Writing tests first gives you peace of mind.
Popularity: 100% [?]
Filed under Programming Practices | Comments (28)