Applying design principles to software, Part II

January 28th, 2008

It’ll Brighten Your Smile

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:

User - System Feedback

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

User revising 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.

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.

Knowledge in the Head

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?

I Love Feedback

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.

I learn so quicklyThe 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: 7% [?]

Applying design principles to software, Part I

January 21st, 2008

A woman likes designSometimes 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

Good or bad conceptual model

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.

Two dandies discussing the user’s model

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.

Communicating Conceptual Models

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.

Communicate with the 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: 8% [?]

6 Reasons to develop your tests first

January 11th, 2008

Regret testing

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.

Unit test graph

[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.

TDD Flowchart

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

Unit tests first!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).

Woman gets TDD

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

What glorious design!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

TDD’s a cinchAnd 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% [?]

LispCast Episode 6

January 6th, 2008

Episode 6 in the Reddit series is finally here!

In this episode I revise the Acceptance Tests to work with the new semantics of users that I developed in Episode 5.

Watch the video. Download the code for the acceptance tests. The main source file is unchanged since the last episode.

Popularity: 7% [?]