Skip to main content

Getting the requirements right with object cases

One of the biggest problems in software projects is poor requirements. We always seem to misunderstand what the user is really trying to do or discover some missing functionality at the end of the project. Agile methods reduce this problem with short feedback cycles to discover misunderstandings early, but it would be even better to get rid of the misunderstandings before implementation.

Two things that really help me in this area are examples and objects. This article describes how to combine these with a method I am calling object cases.

An object case is an example of a user function connected to domain objects. It consists of 4 parts:
  1. Initial objects
  2. Input
  3. Output
  4. Result objects

A typical week using object cases can be as follows:
  1. Monday morning: Go through new functionality with the client.
  2. Monday afternoon: Planning and estimation.
  3. Tuesday: Implement automatic tests.
  4. Wednesday - Friday: Implement the actual functionality.
My experience is that object cases help us a lot in all this activities. When discussing requirements, the objects generate questions such as "How do I find this object?" or "When is this object created?" These kinds of questions helps us to discover missing functionality early and saves us lots of rework.

A consequence of this is that the plans are really good. It doesn't happen often that we forget tasks in the plan.

Another experience is that after Monday, I hardly need to discuss anything with the client. The object cases give me all the details I need to implement automatic tests and production code without guessing.

Examples

I always use some examples to make things concrete. This helps to clear out the inevitable misunderstandings and reveal details we did not think of.

By example, I mean a concrete example of something a user can do. For instance, here is an example of how to use a system for enrollment in university seminars:

Jane Enrolls in Seminar
  1. Jane enters her name: “Jane Jackson” and student number: "111-222".
  2. System displays the available seminars: ENG 103, COM 402.
  3. Jane chooses seminar ENG 103.
  4. System calculates and displays fees: $800
  5. Jane indicates she wants to enroll.
  6. System enrolls Jane in the seminar.
  7. System sends a bill to Jane Jackson with the following content: ENG 103, fall 2011, $800
  8. System prints an enrollment receipt: Jane Jackson, ENG 103, fall 2011.

Note that the example has a concrete user and concrete input and output values everywhere.

I have never studied in America, so I am sure there are some mistakes and missing details. But that is one of the main points of using concrete examples: It is easy to spot mistakes.

The alternative is an abstract requirement, such as a use case:

Enroll in Seminar
Basic Course of Action:
  1. Student inputs her name and student number.
  2. System verifies the student is eligible to enroll in seminars. If not eligible then the student is informed and use case ends.
  3. System displays list of available seminars.
  4. Student chooses a seminar or decides not to enroll at all.
  5. System validates the student is eligible to enroll in the chosen seminar. If not eligible, the student is asked to choose another.
  6. System validates the seminar fits into the student’s schedule.
  7. System calculates and displays fees.
  8. Student verifies the cost and either indicates she wants to enroll or not.
  9. System enrolls the student in the seminar and bills them for it.
  10. The system prints enrollment receipt.

(Source: Agile Requirement Modeling by Scott W. Ambler.)

The use case above is actually very good, but the example has more information:
  • A student number is in the format 111-222.
  • The University has seminars that are called for instance ENG 103 and COM 402.
  • A typical fee for a seminar can be $800.
  • The bill includes information about the student name, seminars, semester and total price.

When I discuss the example with the stakeholders, they are likely to find errors in my assumptions and point them out. This gives me better quality of the requirements before I start development.

More importantly, examples enable further discussions with the customer, such as:
  • Which information should be displayed in the list of seminars?
  • Which seminars shall be listed?
  • This may for instance be: "All seminars with seats left that the student is eligible to." An obvious follow-up question would be what it means to be eligible. Be sure to ask for some examples.
  • How is the fee of $800 calculated?
  • The answer may be something like: "A fee of $100 per term and $700 for ENG 103."

The answers to these questions will likely lead to requirements for more functionality that the system needs to support. For instance, functionality is needed to edit course fees.

Objects

The next step is to model these examples as objects.

In the example above, we need the following initial objects:


In the resulting objects, a Bill object should be created and a Seminar object updated:


The objects give structure to the examples. This makes it easy to find missing pieces in the requirements. They inspire more questions, such as when is the Student object created, what happens when the Bill is paid or not paid?

Just using examples and objects informally will greatly improve the communication between stakeholders and programmers. The stakeholders understand the examples and the programmers get all the information they need. Another benefit is that the examples can be used as functional tests.

Completeness

The examples can be used to find objects, which can be used to find more examples, and so on. In this way, we can build a complete specification.

To ensure that the model is complete, we ask these questions:
  1. Can all output be calculated from the objects?
  2. Is all input used to find or modify objects?
In the example above, this becomes:

Jane Enrolls in Seminar
  1. Input name: “Jane Jackson” and student number: "111-222". (Used to find a Student object.)
  2. Output available seminars: ENG 103, COM 402. (Calculated from the Seminar objects.)
  3. Input seminar ENG 103. (Used to find a Seminar object.)
  4. Output fees: $800. (Calculated from the Bill object.)
  5. -
  6. -
  7. Output bill: ENG 103, fall 2011, $800.
     (How is 'fall 2011' calculated? We need to add this to the object model, perhaps in a new Term object.)
  8. Output enrollment receipt: Jane Jackson, ENG 103, fall 2011. (Same as above.)

This results in the following class model:


We can use the class model to find more examples. We need examples that create, delete, update and read all the classes.

Our example creates a Bill object. But when are Student, Seminar and Term objects created? When are these objects updated and can they be deleted?

If we ask the client, they will tell us more functionality that the system needs to have, for instance:
  • Register student
  • Plan seminar
  • (and some more examples to update and delete objects)

Finally, we can ask the client for examples of how this information is used (read). Our example reads information from Student and Seminar objects. But when are the Bill objects read? Again, this needs to be discussed with the client. Instead of just creating a simplistic user interface with CRUD operations that displays tables of seminars, students etc, we should find out what the users really want to do. This will help us to design a good user interface.

In this case, the client might want the following functionality:
  • Print a list of all unpaid bills.
  • Print a list of all students in a seminar.
  • Send email to all students in a seminar.
  • Calculate the total seminar fees for a term.

When we make concrete examples with input and output for all this, we will probably need even more objects, and so the requirements grow. Connecting objects and examples is a great way to discover missing functionalilty.

How does this help?

Examples improve the communication. They give clients, analysts, developers and testers a common understanding of what the system needs to do. They are sufficiently detailed so people don't have to guess.

Objects give structure to the examples. This helps us to make the examples consistent and complete.

Objects and examples is a great starting point for developers. Firstly, they have very detailed requirements. They don't need to guess or ask the client all the time. Secondly, the developers can use the object model as a basis for implementation. And thirdly, they can write automatic tests from the examples.

The examples are testable, so the testers can basically test the examples, but they will need to write more examples than the "sunny day" scenarios that clients and developers come up with. It is a good idea to involve the testers in defining the examples to discuss these details early.

Comments

Popular posts from this blog

The problem with use cases

The greatest benefit I get from use cases is that they focus on the user. Use cases help me to think about what the user wants to do instead of only focusing on implementation details. The biggest problem I have with use cases is that they are not structured. They are basically free text. For instance, if we have a use case Withdraw money from ATM, we may define that it has a precondition that Open account is performed, but we don't get any help from the method to see that. What happens if someone later changes the Open account use case or defines a Close account use case? How do we find which other uses cases that need to be modified? We can look through the old use case diagrams and find dependencies, but I can almost guarrantee that these dependencies have not been maintained after they were initially created. The solution to this is to connect the use cases to an object model. I don't mean a use-case realization with view and controller objects like ATM_Screen and ATM

The Pessimistic Programmer

I decided to change the title of this blog to "The Pessimistic Programmer". Why? Am I a depressed person that thinks nothing will work? No, I am an optimist in life. Something good is going to happen today :-) But in programming, something will surely go wrong. I don't actually view this as pessimism, but as realism. I want to be prepared for the worst that can possibly happen. Hope for the best, prepare for the worst. But my wife explained to me that pessimists always say that they are just being realistic. So, I might as well face it: I am a pessimist. I think a good programmer needs to be pessimistic; always thinking about what can go wrong and how to prevent it. I don't say that I am a good programmer myself. No, I make far too many mistakes for that. But I have learnt how to manage my mistakes with testing and double checking. Über-programmers can manage well without being pessimistic. They have total overview of the code and all consequences of changes. But I

Use examples to make your code easier to understand

Programmers are used to abstract thinking. To program is to generalize: A method is a general specification of what to do during execution. A class is a general specification of objects. A superclass is a generalization of several classes. Altough our minds are capable of abstract thinking, concrete thinking is much easier, and concrete examples are the foundation for abstractions. For instance, when we were children, our parents didn't try to teach us about cars by explaining to us cars are and what they can do. Instead, they just pointed at a car that was driving by and said ”Look, a car!” When they had done that a number of times, we knew what a car was. Another example is prejudice. We all have prejudices, because this is the way our minds work. If we have met a few people from Denmark in our lives, and those people were friendly, we ”know” that Danes are friendly. And this works even stronger for negative prejudices. My point is that we learn by examples. Einstein said t