As an example of the refinement process, let's look at a step-by-step procedure for refining a use case. Recall that in Chapter 14, we developed the preliminary use-case model for HOLIS. At that time, it was necessary only to decide on what the basic use cases would be. We took care in doing our systems analysis work to try to identify all the significant use cases, but we didn't elaborate them. We just named them and provided a brief description. That was appropriate, and it reflected the level of abstraction at which we needed to understand the system at that time.
In later iterations, however, we need to revisit the use cases and refine them so they can be implemented and tested . Even then, we have to pick the right level of abstraction or else we may overspecify or underspecify system behavior. We'll use a simple HOLIS example: a resident activating a light in a house, using the HOLIS home automation lighting system. While the use case is a simple one, it has a few interesting facets that illustrate some of the complexities of the system and demonstrate how we can handle them with our techniques. Let's look at the use case we named Turn Light On/Off (Figure 21-1).
Figure 21-1. The HOLIS Turn Light On/Off use case
Reviewing the Actors
When we first identified the actors, we saw only one user that interacted with the light switch, so we named only one actor, the user (Resident) pressing the switch. However, from the perspective of HOLIS as a system , we also recognized that the system automatically controls the lighting and thus involves another actor, which we named Light Bank.
Reviewing the Name
As we described, each use case should have a name indicating what is achieved by its interaction with the actor(s). The name is important since, ideally , it describes in shorthand form what the actor accomplishes with the use case. We also noted that no two use cases can have the same name, and each should be unique and easily distinguishable among the use cases defined for the project. Further, use-case names often begin with an action verb to indicate the intent of the use case.
We note from this design that a single switch controls the on and off function as well as "dim." Perhaps we assumed there would be a separate slide switch or something for the dimming function, but in any case, the design is what it is. Now we have to look at our use case in light of this new information and decide what we want our use case to do. Let's make the decision to provide only one use case to control the lighting function since this would seem to simplify things. However, based on what we know now, it looks like we haven't named the use case very well. After all, the user can do more than turn the light on or off; with the same button on the control panel the resident can also brighten or dim the light. So, let's rename our use case to become Control Light since that seems more descriptive. Now the representation of our "simple and obvious" use case Turn Light On/Off has been refined as well (Figure 21-2).
Figure 21-2. The HOLIS Control Light use case
You may want to use a formal method to structure the names of use cases so as to group similar use cases with similar names. Or you may want to incorporate a "serial number" or other unique identifier into the use-case name to facilitate managing a list of the use cases. For example, a designer might specify the name of this use case as "031 Control Light." Although the spirit of this approach is laudable, our experience has shown that proper use-case naming, and perhaps the application of tools that allow us to search, sort , and analyze use cases, are usually adequate to the task, so we'll just stick with "Control Light."
Refining the Description
Since the role and purpose of our use case has evolved, let's update its description as follows .
Defining and Refining the Flow of Events
The heart of the use case is the event flow, usually a textual description of the operations by the actor and the system's various responses. The event flow describes what the system does, based on the actor's behavior. By the way, it is not required that the flow be described textually. You can use UML interaction diagrams for this purpose, and many of the other methods discussed in Chapter 24 might apply equally well to your use-case documentation, so be sure to select an appropriate technique. Remember, the goal is to convey understanding , and there is no "one- size -fits-all" approach. However, in most cases, you'll find that natural language works just fine.
Whether we previously defined a brief event flow during the inception phase of our project or just created a short description, our understanding of the system has changed , and we need to adapt. First, let's look at the basic flow of events for our use case, keeping in mind that the flow of events does not specify how the system does any of those things. It specifies only what happens from the user's perspective.
We'll define the basic flow to cover the simple on/off case we started with earlier.
As we've described, the use case may have different flows, depending on conditions present. In some cases, these flows deal with error conditions detected during processing, or they may record optional ways of handling certain conditions. Many times, these are errors and exception conditions, and discovering these is a key activity in the process of refining use cases. For example, a use case that prints a receipt for a credit card transaction may discover that the printer has run out of paper. This special case would be described within the use case as an alternative flow of events. When you record the alternative flows, don't forget to document the conditions giving rise to the flows.
There is no set limit on alternative flows, so be sure to document all alternative flows, including all possible error conditions. Indeed, one of the "dirty little secrets" of system development, and one that seems to come only with experience, is the following.
No wonder we have trouble estimating software projects!
So now is not the time to skimp on brainstorming and asking the hard "what if" questions. But it is an excellent time to involve the testers since, for reasons you can readily guess, they seem to have a real knack for the "non-happy day" problems.
Back to our example: an alternative flow of events will occur when the Resident holds a button on the Control Switch down for more than "the timer period," which we have since determined to be exactly one second. We need to add an alternative flow to the use case.
Identifying the Pre- and Post-conditions
For most cases, you will need to identify pre-conditions that affect the behavior of the system described in the use case and to describe post-conditions, such as system state or persistent data that is left when the use case is complete. There can be significant work involved in fully defining pre- and post-conditions; however, you need to use pre- and post-conditions only when necessary to clarify the behavior expressed in the use case. Figure 21-3 may help you discover the pre- and post-conditions for your use case.
Figure 21-3. Pre- and post-conditions
You may also wish to consider the following guidelines [Rational Software Corporation 2002].
As we noted, it is important to distinguish between the events that start the use-case flows and the pre-conditions, which must be met before the use-case flow can be initiated. For example, a pre-condition to the Control Light use case is that the Homeowner /Programmer has enabled a specific bank of lights for the dimming action. Another pre-condition is that the selected Control Switch button must be preprogrammed to control the Light Bank. (Presumably, other use cases describe how these pre-conditions are accomplished.)
So we'll need to state the pre-conditions for our use case.
Similarly, we need to identify post-conditions. In our use case, in order for the brightness to begin at the proper level when the Resident uses the switch the next time, the system must remember the previous brightness level that was set for a selected Control Switch button after a dimming action has occurred. So, this is a post-condition that we'll record in the use case.
Identifying Special Requirements
There is another discovery process that we must complete before we are finished with the use case: we must understand any special requirements to be imposed. Typically, these are nonfunctional specifications of usability, reliability, performance, and so on that are defined in the supplementary specification or perhaps in a regulatory standard referred to in the Vision document. In any case, we must identify those conditions that are relevant to the implementation of a specific use case. With HOLIS, for example, the Vision document has stated that there must be no "visible, perceptible delays between a homeowner action and system response." This, in turn, has caused the creation of a number of performance standards, including the following requirement that the team found in the HOLIS system-level specification.
Now let's put it all together. Table 21-1 outlines what we have after filling in all the important pieces of our use case. (Although many other pieces can be defined for a use case, they are not important to our needs now.) This use case is documented in the narrative style and may be found in the HOLIS artifacts in Appendix A.
Summary of Our Refined Use Case
For most users, especially those who are just beginning to apply use cases, the refinement steps we described above are more than adequate. Properly identifying and elaborating alternative flows, pre- and post-conditions, and special requirements provides a sufficiently comprehensive description of the system behavior in most circumstances.
However, as the complexity of the application grows and evolves over time, there are some additional use-case constructs, supported by the UML, that benefit the use-case practitioner. These topics are more advanced than those we've described so far, and we'll introduce these in the next few sections. For a more detailed discussion of these constructs, we refer you to the treatment by Armour and Miller .