Traditionally, requirements are captured in text, possibly augmented with pictures. Modeling requirements is an alternative that is gaining momentum.
There are many different modeling languages. For the purpose of this article, we consider a model to be a structured representation of the requirements. The structuring makes it possible to assign semantics and to establish formal relationships. Models can be quite simple: For instance, text templates [Templ] are a well-known technique for requirements modeling.
A more formal modeling language is the Unified Modeling Language, UML. UML is quite popular for modeling requirements (e.g. [ICONIX]). The primary model element for this purpose is the use case. A use case can have a graphical representation, but more crucial are the properties of the use case.
The following picture and table shows a simple use case that probably all readers are familiar with, logging out from a web application:
The picture is great for providing the big picture, but just as significant are the properties of the use case. All modeling tools provide access to the dozens of properties that each modeling element can carry. The following table shows those properties that we would typically use for our constraint-based approach:
|Actors||Any logged in user|
|Description||As a user, I want to log out of the system, so that nobody can access my account from this browser session.|
|Preconditions||User is logged into the system|
|Activity||[User] initiates log out
[System] logs the user out of the system and displays a corresponding message
|Postconditions||User is logged out of the system|
This use case contains two constraints, the precondition and the postconditions. We will explain constraints in more detail further down. Also, the level of modeling could be increased further, for instance by modeling the activity as an activity diagram. Use cases model the dynamic aspects of the system. They need to be complemented by a static description, the domain model. In UML, this is typically modeled as classes. The properties of the domain object are then modeled as attributes. It is possible to add dynamic behavior in the form of operations. Further, the relationships of the domain model elements can be modeled as well.
Consider the following class diagram:
The relationship indicates that a shipment consists of one or more “Items”. The classes contain a lot of information that is not shown on the diagram. The following table shows what information is associated with the domain element “Item”:
|Description||An Item is a postal piece for a specific recipient.|
|Attributes||recipient||Invariant: Must not be empty
|productType||Invariant: Must not be empty
|barcodeNumber||Type: GS1-128 barcode number, or not set|
|Invariants||If ProductType supports “tracking”, then barcodeNumber must not be empty and must be a valid barcode number.|
This (simplified) example is consciously kept informal. An Item has a recipient and a product type. Whether the barcode is required or not depends on the product type. All three attributes have constraints, from as simple as “must not be empty” to referencing a standard, in this case the GS1-128 barcode number. The class itself has an invariant, making a statement regarding the interplay of the class' attributes. Again, this is stated quite informally and could be made more precise by formally referring to certain attributes of ProductType.
Also, some aspects of this class could arguably be modeled differently. Recipient and ProductType, for instance, could have been modeled as a n:1 association, thereby implying that they must not be empty (rather than stating it explicitly).
We have now seen a few examples of constraints, but here is the formal definition: A constraint is a condition that the solution must satisfy. Constraints can have many forms. The ones that are of interest here include:
Invariant: This is a constraint that “must always be true”. For instance, the attribute “Recipient” of the class “Item” must always have a value. An Item without a recipient does not make sense (at least for this particular system).
Type: A type (e.g. “Recipient”) is also a constraint. This may feel obvious, but forgetting to provide a type can lead to real problems. For instance, the $125 million Mars Climate Orbiter was lost due to incorrect typing, as data was sent in pound-seconds (lbs s) and interpreted in newton-seconds (N s) [MO].
Precondition: This is a constraint that must be satisfied before something can happen. For instance, in order to log out, the user has to be logged in. Otherwise it does not make sense. Due to its temporal nature, a precondition does not make much sense on static elements like classes (they could be used on operations of a class, however).
Postcondition: This constraint is complementary to preconditions and must be satisfied after something happened. We have seen the postcondition in the use case above: After completing the use case for logging out, the user must be, well, logged out.
There are many more types of constraints, but these are the ones we used in this project.
Using constraints has three major benefits:
First, the models become more concise, easier to read and simpler. Constraints, as the name implies, constrain the behavior, they don't define it. To grasp the meaning of a use case “log out”, the constraint is not necessary, and usually not shown. Contrast with the options: The constraints could be captured as a requirement. But then the scope of the requirement would have to be defined – there are ways for this, but it is not as elegant and can be error prone. Or consider starting the activity for the use case “log out” with a condition: “If the user is not logged in, then do nothing and exit”. This will clutter the model.
Constraints can be applied consistently across elements. Consider access rights: they can be modeled using preconditions (“Precondition: User must have right to read”). This is much more elegant compared to adding a condition to each use case's activity diagram stating “if right to read is missing, show error page”.
Constraints can be used to model both functional and non-functional requirements. For instance, a constraint for password strength could be provided as an invariant to an attribute.
Second, they simplify testing. Deriving test cases from constraints is straight forward. All constraints are expressions that evaluate to “true” or “false”: The user is either logged out of the system or not. This is great for testing, even if the constraints are stated in natural language. No constraints on an element can mean one of two things: Either there really is no constraint. In that case, the test engineer can think up the nastiest test cases. But more likely, something is missing.
For instance, what is the point of a use case that has no post condition (in other words, has no effect)? This can indicate a problem which should be investigated. Compare this to activity diagrams, which typically cover a large number of states, if all execution paths are being considered. Here, the focus can easily get lost, when deciding which the most important test cases are. It can also result in redundancies, as certain conditions are tested multiple times as part of different execution paths. Due to the same reasons, risk assessments may be more difficult. We will demonstrate this later in the case study.
Third, the scope is well defined. The scope of a constraint is whatever it is attached to, let it be an attribute, a class or a use case. Constraints are stateless outside their defined context, they do not depend on the history of the system. One may argue that pre- and postconditions do in fact depend on a history, but only the history inside the context is of relevance. For testing the postcondition “The user is logged out”, the history of the system outside the use case does not matter.
This also makes constraint-based systems robust against changes. If parts of the system description are refactored, only the constraints of the elements that have changed have to be verified. Everything else should be unaffected.
This sounds nice in theory, so let's see how this works in practice.
This approach has been used in a real-world project. The customer was planning the reimplementation of a B2B web ordering system. This approach was used to describe the complete system, including the newly elicited requirements.
Care had been taken to carefully craft templates for document generation. For one, most consumers (readers) of the model were not trained in the use of the modeling tool (or had access to it). So they would receive a generated PDF instead. Second, reviews were performed primarily on paper. Needless to say, the constraints were part of the generated documents.
Here are a few numbers that provide a feel for the project: The domain model consisted of 72 classes and produced a 54-page document when generated. The use case model consisted of 95 use cases, resulting in 68 pages. There were another 20 pages providing context, covering a handful of cross-cutting topics like architecture, GUI concept and the like.
The Order Process
The most complicated use case of the system was the order process. The following description informally describes it:
- Users were beginning by creating an empty order.
- Individual items could be added to the order by using a simple form.
- However, items could also be added in bulk with an upload form.
- To complicate things, bulk upload was mutually exclusive with manually creating items.
- Once items were in the system, these could be edited or deleted.
- Items that were bulk uploaded were not necessarily valid. If incomplete, the missing information had to be added manually.
- Before an order could be completed, labels had to be generated. These were then printed and attached to the (physical) items.
- But bulk uploaded items may not need a printed label. (Don't ask why, it does not matterhere.)
- Items were grouped into shipments. These might (or might not) need some additional information that the user had to add.
- Once everything was complete, the order could be submitted.
Initially, we tried to capture the logic using activity diagrams. The result is shown below.
The details are barely readable, but this hardly matters, the idea is clear. The flow shows the complex interdependencies of the various activities. In fact, these were two activity diagrams with a lot of duplication between them.
But even worse, it turned out that quite a number of legal execution paths had not been modeled. In other words, if taken as face value, these activity diagrams would have restricted the solution for no good reason.
To improve things, a second activity diagram has been created, but this time it relies heavily on constraints. We did not mention this explicitly before, but constraints can be applied to activities as well (not just to use cases or classes). Here is the resulting activity diagram:
At first glance, this may be confusing: The “activity diagram using constraints” consists of a loop, and in each round, every activity could be executed. In practice, this is not the case: Each activity has preconditions that can block the execution of those activities.
To make the “activity diagram using constraints” more readable, dotted boxes show the precondition related to the selection status: For instance, “Change item” can only be executed if exactly one item is selected. Likewise, deleting items is only possible if at least one item has been selected. The dotted boxes are just informal hints, each activity contains a corresponding precondition.
There are more preconditions that can only be found by inspecting the activity. For instance, the “bulk upload” activity has the precondition that the order contains exactly zero items. Another example: The order can only be finalized if the shipment is complete. A shipment is only complete if it contains at least one item, and a number of additional invariants are true.
This notation works, as the “activity diagram using constraints” can be inspected in two steps: First, the reader can understand conceptually what activities can be part of ordering. Once that is understood, the reader can look inside the various activities to understand when they apply.
Using this approach removed a lot of redundancy, which in turn made changes easier and less error prone. The size of the generated documents shows this in an impressive way: The document for the old-style description resulted in 18 pages, while the new-style document was just 7 pages long. Again, without any loss of information, just by eliminating a lot of redundancy.
The approach shown here does not work equally well for all types of applications. While we did not try it out, we suspect that it is not as suited for embedded systems, for instance. Consider an engine controller: For such an application (especially in safety-critical fields), having an explicit execution path and state transitions may be necessary. On the other hand, constraints can still be used to record safety requirements (e.g. “the engine can only start if the clutch is pressed”). If properly tested adding this invariant will prevent that an execution path violating it is not accidentally created while working on the model.
Web applications, on the other hand, seem to be extremely well suited for this approach. This is due to the non-linear nature of web sites: We cannot and do not want to forbid the use of the back button, for instance. Therefore, we cannot presume that the user will stick to the call order of the individual pages as we intended. And this does not even take the scenario of a malicious user into account, who may try to break the system by accessing pages out of order.
The various activities in the above activity diagram could conceivably be implemented as individual web pages, with their own individual validation based on preconditions. Likewise, the page with the “List of Items” could evaluate the preconditions of the various activities and only provide controls for those that pass.
Constraints have also been used as part of the architecture for web forms. As many web applications, this one has many forms for creating and editing data, from user information, addresses, items, orders and much more. The behavior of forms had been described only once in a generic fashion in the form of an activity diagram. But part of this activity diagram is the evaluation of the invariants of the domain objects that are being edited. If an attribute invariant is violated, a corresponding error message is shown next to the attribute's GUI control. If a class invariant is violated, the error message appears on the top of the form.
Constraints in Academia
Perhaps unsurprisingly, the use of constraints was “inspired” by the fact that almost every formal method that uses at least first-order logic takes advantage of constraints one way or another.
For instance, the Event-B method [EventB] can be used for requirements modeling and allows the definition of a number of constraints, including:
Invariants: Properties of the behavioral model that must always be true
Axioms: Properties of the static model that must always be true
- Guards: Properties that must be true before an event can be triggered, similar to preconditions
The twist: Event-B has such a high level of formality, that it can be mathematically proven that constraints hold. To this end, an automated prover is integrated into the tooling (which is freely available [Rodin]). This means that on the model level, tests for ensuring that constraints hold are unnecessary. Of course, there is still plenty left to be tested. In particular whether the model describes the problem correctly (validation).
While Event-B may be much harder to read and write than UML, this still demonstrates the power of constraints, if taken to an extreme.
The use of constraints paid off, resulting in a clean, readable model. The generated documents were accepted by all stakeholders. Constraints may not be this effective in all situations. But we believe that it is an approach that every modeler should have in their toolbox.
References and Literature
- [MO] https://en.wikipedia.org/wiki/Mars_Climate_Orbiter
- [EventB] Stefan Hallerstede, Michael Jastram, Lukas Ladenberger: “A Method and Tool for Tracing Requirements into Specifications”, Science of Computer Programming, 2013
- [Templ] Klaus Pohl: “Requirements engineering: fundamentals, principles, and techniques”, Springer, 2010
- [ICONIX] Doug Rosenberg “Use case driven object modeling with UML, Addison-Wesley, 1999
- [Rodin] Jean-Raymond Abrial et. al.: “Rodin: An Open Toolset for Modeling and Reasoning in Event-B”, International journal on software tools for technology transfer, 2010