User Mental Models
- To improve the readability of object-oriented code by giving system behavior first-class status;
- To cleanly separate code for rapidly changing system behavior (what the system does) from that for slowly changing domain knowledge (what the system is), instead of combining both in one class interface;
- To help programmers reason about system-level state and behavior instead of only object state and behavior;
- To support an object style of thinking that is close to peoples' mental models, rather than the class style of thinking that overshadowed object thinking early in the history of object-oriented programming languages.
The problem recognised by this paradigm is that system behaviour gets fragmented in traditional OOP, over a large number of classes and sub-classes. The code directly related to a use-case gets fragmented and "lost". The boundaries for the behaviour end up being class boundaries and pieces of behaviour are stuck with the data on which they operate, so that the code has high cohesion. If you want to read the code related to a use-case, you will struggle to extract it and understand it. I agree with these problems, but have not really encountered them personally for a long time, because I do most of my work predominantly in the SOA paradigm.
When I first read these goals and all the articles I could find about DCI, I interpreted them as roughly:
- Separating system behaviour from data, to give behaviour first class status;
- Allowing the programmer's mental model to become the same as the user's mental model and hence reduce the number of mappings between programmer's and user's mental models;
- Making code use-case centric, rather than class- or data-centric;
- Making code reviewable and having less reliance on testing, by keeping fragments of use-case code physically close so it's not fragmented.
I thought about these interpreted goals hard, because I was convinced that in the applications I help to write, we were achieving them. I questioned the DCI forums at length about why a SOA architecture couldn't achieve these goals. When I say SOA, I don't mean the modern version involving metadata to describe remote interoperable services like Web Services. I mean services, like what people used to write in the early 2000's when EJB stateless session beans first came out. Java classes which were stateless and contained just behaviour. Let's call such service "local services" to distinguish them.
Today around 12pm, I finally understood what the people at the DCI boards meant, when they said that my ideas were not DCI and could not fulfil the DCI goals... The very first DCI goal shown above relates to object oriented code. It does not mean make it more readable by splitting the behaviour out and having dumb data objects used by behaviour which is implemented elsewhere, such as happens with SOA. It really means, letting the code stay object oriented. I found that really stange, that in 2010, people still want to really program in traditional object oriented styles. Let me explain. In a Zoo, you have animals, and they can all "talk" to some extent. So you create a model which uses a super-class "Animal" which has a talk method which each animal class can override. Then maybe you introduce some movement and provide a "walk" method (hmm... snails... guess we need a new level in our inheritance tree to differentiate between walkers and sliders... oh dear our inheritance tree is exploding...).
Fair enough for a zoo. But in software, especially business software, how often do we really model behaviour inside the entity classes? Consider a bank account, and transferring money between two of them. Does the account transfer money from itself to another one, or does "something else" do it? Or think about a complex set of tickets which are related, for example a train ticket, a hotel booking and a ticket to a concert. You could buy these as a discounted package. The package might be just clever enough to give you the price based on adding up the price of its parts, but would you expect the package object to work out the package discount, or would it be the system which works out the discount, and sets it as an attribute on the package object?
In cases where it is something else providing functionality, rather than an entity, we use services. Services meld really well with object oriented data models. The services contain no data, just functionality, and the data model contains data, but no real functionality.
I am sure there are users, business analysts and requirements engineers out there, who think of these behaviours as belonging to objects, rather than the system, but for me, this thinking is wrong. DCI aims to make the programmer's mental model the same as the user's, so now we have a problem, because these user's are not thinking "right". But if you think about it, we don't write software for a single user. We write software for lots and lots of users (I once wrote an app costing nearly a million CHF for 3 users, but that was an exception). How can we ensure that their mental models are the same? And there is usually more than one programmer doing the writing, so how can we ensure their models are also the same? In agile programming, the users, customers, programmers, analysts, architects, business people and all other stake holders get together and talk. During that discussion, it is fine to encourage user's with a "strange" way of looking at the world to think a little differently (it might involve beer, donuts, or threats, but it is possible). To take a users mental model without encouraging them to refine it is dangerous. Typically, software is created to improve the user's life. If you ask a standard computer user with no IT training, to tell you what they want, in my experience it is not usable. We once asked our user's to define the colours of new system which would replace a 20 year old green-screen system. Their input: light green, a bit of dark green. Black text. They had no concept of modern software and what it could do for them or how it might make their lives easier. You are definitely allowed to encourage users to expand on and improve their original mental models.
So what about software which manipulates things on screen, i.e. the UI part? Imagine a word processor. You are typing away and have the feeling that you are on a type writer. You then want to add a picture. To add a picture, the program needs to respond to your click, open a file dialog and show you the files in a directory. You choose a file, click OK, and the picture is read and displayed where the cursor was. You click the "center" button to centre justify the picture. What goes on, in the user's head? What handles each of these inputs? Is it a document which responds to his wanting to insert a picture, or the system? Is it the document which puts the image in the document, or the system? Is it the document which formats the picture to become centred or is it the system? In my world, it is the system which does this, and as such, I can program these things to be part of my MVC controller which handles complex state updates. In my world, the controller would not tell the document to centre the image, but would do so itself. The document object contains the system state and knows where the image is in the document, and that the paragraph has the centred style. But the document does not have the ability to do anything useful itself.
In Eclipse RCP UI programming, clicking a button to load a file is handled as an action; you don't even have the choice of letting your "document" react to the event, unless you are backward thinking and have code in the action which fetches the document from the data model and calls the "insert image" method on it.
I just spent ten minutes getting my wife to open the word processor and describe to me what happens when she inserts an image and left justifies it. My wife is a standard user, who is word processor and browser proficient. When talking about actions and reactions (e.g. clicking the ok button in the file dialog which inserts the picture into the document), she always referred to either her or the system doing the work. If I asked her not to refer to herself, rather an element of the word processor, it was always the system which did the work. If I were programming the word processor, I therefore wouldn't be allowed to attach the behaviour to the button or document, rather the button causes the system to load the file. I could create a class called the system, but for me, a controller which relies on using services to do the work is acceptably close to the user's mental model.
Maybe I am just at an advantage, because I have lived in the service world for so long. Either way, the thing to take away is that local services empower the programmer to have a mental model close to the user's mental model, so that code becomes simpler to maintain. They also let the system behaviour be first class just as data is, and make code more reviewable by allowing it to be use-case-centric, which reduces reliance on testing.
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)