A Standard Organization for Interface Documentation

This section suggests a standard organization for interface documentation (see Figure 7.1). Like all templates and organizational layouts in this book, you may wish to modify this one to remove items not relevant to your situation or to add items unique to your business. More important than which standard organization you use is the practice of using one. Use what you need to present an accurate picture of the element's externally visible interactions for the interfaces in your project.

  1. The identity of the interface: When an element has multiple interfaces, identify the individual interfaces to distinguish them from one another. The most common means of doing this is to name an interface. Some programming languages, such as Java, or frameworks, such as COM, even allow these names to be carried through into the implementation. In some cases, merely naming an interface is not sufficient, and the version of the interface must be specified as well. For example, in a framework with named interfaces that has evolved over time, it could be very important to know whether you mean the v1.2 or v3.0 persistence interface.
  2. Resources provided: The heart of an interface document is the set of resources that the element provides to its actors. Define these resources by giving their syntax, their semanticswhat happens when they're usedand any restrictions on their usage.

    1. Resource syntax: This is the resource's signature, which includes any information that another program will need to write a syntactically correct program that uses the resource. The signature includes the name of the resource, names and logical data types of arguments, if any, and so forth.
    2. Resource semantics: What is the result of invoking this resource? Semantics come in a variety of guises, including

      1. Assignment of values to data that the actor invoking the resource can access. The value assignment might be as simple as setting the value of a return argument or as far-reaching as updating a central database.
      2. Changes in the element's state brought about by using the resource. This includes exceptional conditions, such as side effects from a partially completed operation.
      3. Events that will be signaled or messages that will be sent as a result of using the resource.
      4. How other resources will behave differently in the future as the result of using this resource. For example, if you ask a resource to destroy an object, trying to access that object in the future through other resources will produce quite a different outcomean erroras a result.
      5. Humanly observable results. These are prevalent in embedded systems; for example, calling a program that turns on a display in a cockpit has a very observable effect: The display comes on.

      ADVICE

      Guidelines for Writing Down the Semantics of a Resource

      • Write down only those effects that are visible to a user: the actor invoking the resource, another element in the system, or a human observer of the system. Ask yourself how a user will be able to verify what you have said. If your semantics cannot be verified, the effect you have described is invisible, and you haven't captured the right information. Either replace it with a statement about something that the user will be able to observe, or omit it. Sometimes the only visible effect of a resource is to disable certain exceptional conditions that might otherwise occur. For instance, a program that declares a named object disables the error associated with using that name before the object is declared.
      • Make it a goal to avoid prose as the only medium of your description. Instead, try to define the semantics of invoking a resource by describing ways other resources will be affected. For example, in a stack object, you can describe the effects of push(x) by saying that pop() returns x and that the value returned by g_stack_size() is incremented by 1.
      • If you use prose, be as precise as you can. Be suspicious of all verbs. For every verb in the specification of a resource's semantics, ask yourself exactly what it means and how the resource's users will be able to verify it. Eliminate vague words, such as should, usually, and may. For operations that position something in the physical world, be sure to define the coordinate system, reference points, points of view, and so on, that describe the effects.
      • Avoid giving example use in place of specifying the semantics. Usage is a valuable part of an interface specification and merits its own section in the documentation, but it is given as advice to users and should not be expected to serve as a definitive statement of resources' semantics. Strictly speaking, an example defines the semantics of a resource for only the single case illustrated by the example. The user might be able to make a good guess at the semantics from the example, but we do not wish to build systems based on guesswork. We should expect that users will use an element in ways the designers did not envision, and we do not wish to artificially limit them.
      • Avoid giving an implementation in place of specifying the semantics. Do not use code to describe the effects of a resource.

      In addition, the statement of semantics should make it clear whether the execution of the resource will be atomic or may be suspended or interrupted.

      ADVICE

      Consider using preconditions and postconditions for documenting both resource usage restrictions and resource semantics together. A precondition states what must be true before the interaction is permitted; a postcondition describes any state changes resulting from the interaction.

    3. Resource usage restrictions: Under what circumstances may this resource be used? Perhaps data must be initialized before it can be read, or perhaps a particular method cannot be invoked unless another is invoked first. Perhaps there is a limit on the number of actors that can interact via this resource at any instant. Perhaps there is a limit of one actor that has ownership and is able to modify the element, whereas others have only read access. Perhaps only certain resources or interfaces are accessible to certain actors to support a multilevel security scheme.

      Notions of persistence or side effects can be relevant here. If the resource requires other resources to be present or makes certain other assumptions about its environment, these should be documented. Some restrictions are less prohibitive; for example, Java interfaces can list certain methods as deprecated, meaning that users should not use them, as they will likely be unsupported in future versions of the interface. Usage restrictions are often documented by defining exceptions that will be raised if the restrictions are violated.

  3. Locally defined data types: If any interface resources use a data type other than one provided by the underlying programming language, the architect needs to communicate the definition of that data type. If the data type is defined by another element, a reference to the definition in that element's documentation is sufficient. In any case, programmers writing elements using such a resource need to know (a) how to declare variables and constants of the data type, (b) how to write literal values in the data type, (c) what operations and comparisons may be performed on members of the data type, and (d) how to convert values of the data type into other data types, where appropriate.
  4. Error handling: Describe error conditions that can be raised by the resources on the interface. Because the same error condition might be raised by more than one resource, it is often convenient to simply list the error conditions associated with each resource; define them in a dictionary collected separately. This section is that dictionary. Common error-handling behavior can also be defined here.
  5. Any variability provided by the interface: Does the interface allow the element to be configured in some way? These configuration parameters and how they affect the semantics of the interactions in the interface must be documented. Examples of variability include capacitiessuch as of visible data structuresthat can be easily changed. Name and provide a range of values for each configuration parameter, and specify the time when its actual value is bound.
  6. Quality attribute characteristics of the interface: The architect needs to document what quality attribute characteristics, such as performance or reliability, the interface makes known to the element's users. This information may be in the form of constraints on implementations of elements that will realize the interface. The qualities you choose to concentrate on and make promises about will depend on the context.
  7. What the element requires: What the element requires may be specific, named resources provided by other elements. The documentation obligation is the same as for resources provided: syntax, semantics, and any usage restrictions. Two elements sharing this interface informationone providing it and the other requiring itmight each reference a single definition. If the element is being developed as an independent reusable component, that information needs to be fully documented. What the element requires may be expressed as something more general, such as "The presence of a process scheduler that will schedule in a priority-based fashion." Often, it is convenient to document such information as a set of assumptions that the element's designer has made about the system. In this form, they can be reviewed by experts who can confirm or repudiate the assumptions before the design has progressed too far.
  8. Rationale and design issues: Like rationale for the architecture or architectural views at large, the architect should also record the reasons behind the design of an element's interface. The rationale should explain the motivation behind the design, constraints and compromises, alternative designs that were considered and rejected and why, and any insight the architect has about how to change the interface in the future.
  9. Usage guide: Sections 2b and 7 in Figure 7.1 document an element's semantic information on a per resource basis. This sometimes falls short of what is needed. In some cases, semantics need to be reasoned about in terms of how a broad number of individual interactions interrelate. Essentially, a protocol of interaction is involved that is documented by considering multiple interactions simultaneously. These protocols could represent the complete behavior of the interaction or patterns of usage that the element designer expects to be used repeatedly. In general, if interacting with the element via its interface is complex, the interface documentation might include a static behavioral model, such as a state machine or examples of carrying out specific interactions in the form of trace-oriented scenarios.

Figure 7.1. Documentation for an interface consists of nine parts

COMING TO TERMS

Exceptions and Error Handling

When designing an interface, architects naturally concentrate on documenting how resources work in the nominal case, when everything goes according to plan. The real world, of course, is far from nominal, and a well-designed system must take appropriate action in the face of undesired circumstances. What happens when a resource is called with parameters that make no sense? What happens when the resource requires more memory, but the allocation request fails because there isn't any more? What happens when a resource never returns, because it has fallen victim to a process deadlock? What happens when the software is supposed to read the value of a sensor, but the sensor has failed and either isn't responding or is responding with gibberish?

Terminating the program on the spot seldom qualifies as "appropriate action." More desirable alternatives, depending on the situation, include various combinations of the following:

  • Returning a status indicator: an integer codeor even a messagethat reports on the resource's execution: what, if anything, went wrong and what the result was.
  • Retrying, if the offending condition is considered transient. The program might retry indefinitely or up to a preset number of times, at which point it returns a status indicator.
  • Computing partial results or entering a degraded mode of operation.
  • Attempting to correct the problem, perhaps by using default or fallback values or alternative resources.

These are all reasonable actions that a resource can take in the presence of undesired circumstances. If a resource is designed to take any of these actions, that should simply be documented as part of the effects of that resource. But many times, something else is appropriate. The resource can, in effect, throw up its hands and report that an error condition existed and that it was unable to do its job. This is where old-fashioned programs would print an error message and terminate. Today, they often raise an exception, which allows execution to continue and perhaps accomplish useful work.

Making a strong distinction between detecting an error condition and handling it provides greater flexibility in taking corrective action. The right place to fix a problem raised by a resource is usually the actor that invoked it, not in the resource itself. The resource detects the problem; the actor handles it. If we're in development, handling it might mean terminating with an error message so the bug can be tracked down and fixed. Perhaps the actor made the mistake because one of its own resources was used incorrectly by another actor. In that case, the actor might handle the exception by raising an exception of its own and bubbling the responsibility back along the invocation chain until the actor ultimately responsible is notified.

Modern programming languages provide facilities for raising exceptions and assigning handlers. Program language reference manuals take a language-oriented view in classifying the world of exceptions. The C++ programming language, for instance, has built-in exceptions classes dealing with memory allocation failure, process failure, tasking failures, and the like. Those are exceptions that the compiled program is likely to encounter from the operating system.

But many other things can go wrong during execution of software, and it is incumbent on the architect to say what they are. An architecture-oriented classification of exceptions is summarized in Figure 7.2. In the context of an element's interface, exception conditions are one of the following:

  1. Errors on the part of an actor invoking the resource.

    1. An actor sent incorrect or illegal information to the resource, perhaps calling a method with a parameter of the wrong type. This error will be detected by the compiler, and an exception is not necessary unless types can change dynamically, in which case things aren't so clear-cut. If your compiler does not generate code to do runtime type checking, associating an exception with the resource is the prudent thing to do. Other exceptions of this variety describe a parameter with an illegal or out-of-bounds value. Division by zero is the classic example of this, with array-bounds violations a close runner-up. Other examples are a string that has the wrong syntax or length; in a pair of parameters defining a range, the minimum exceeds the maximum; an uninitialized variable was input; and a set contains a duplicate member.
    2. The element is in the wrong state for the requested resource. The element entered the improper state as a result of a previous action or lack of a previous action on the part of an actor. An example of the latter is invoking a resource before the element's initialization method has been called.

  2. Software or hardware events that result in a violation in the element's assumptions about its environment.

    1. A hardware or software error occurred that prevented the resource from successfully executing. Processor failures, inability to allocate more memory, and memory faults are examples of this kind of exception.
    2. The element is in the wrong state for the requested resource. The element's improper state was brought about by an event that occurred in the environment of the element, outside the control of the actor requesting the resource. An example is trying to read from a sensor or write to a storage device that has been taken offline by the system's human operator.

Figure 7.2. A classification of exceptions associated with a resource on an element's interface

Exceptions and effects produce a three-way division of the state space for every resource on an interface.

  1. First, effects promise what will happen in a certain portion of the state space, what Parnas (Parnas and Wuerges, 2001) has called the competence set of the program: the set of states in which it is competent to carry out its function. A resource invoked in a state that is a member of its competence set will execute as promised in the interface document.
  2. Second, exceptions specify the semantics in a different region of the state space, corresponding to error conditions that the architect has had the foresight to anticipate. If a resource is invoked in a state corresponding to an exception, the effects are simply that the exception is raised. (Remember, handling the exception is not in the purview of the resource but in the actor that invoked it. Raising the exception gives the actor the chance to do so.) We'll call this set of states the exception set.
  3. Third, there is everything else: the region of the state space where what happens is completely undefined if a resource is invoked. The architect may not even know and maybe even has never considered the possibility. We'll call this set of states the failure set; we could as well have called it the cross-your-fingers-and-hope-for-the-best set. The behavior may be unpredictable and hence difficult to re-create and therefore eliminate, or it may be depressingly predictable: a very ungraceful software crash.

In a perfect world, the architect squeezes the failure set to nothingness by moving failure states to the competence set by expanding the statement of effects, or to the exception set by creating more exceptions. An equally valid approach is to make a convincing argument that the program cannot possibly get into a state in the failure set.

For example, suppose that element E needs to have complete control of a shared device during the execution of resource R on interface I. If the architect wasn't sure that this would always be the case when R was invoked, he or she would either (1) specify what the element would do if the device was already in usereturn immediately with a failure code, retry a set number of times, wait a set periodor (2) define an exception for R that reported the condition back to the actor and made it the actor's responsibility to sort out. But perhaps the architect is certain that the device will never be in use, because element E is the only element that uses it. So the architect doesn't define behavior for the resource to account for that condition and doesn't define an exception for it, either. This puts the condition in the resource's failure set, but the architect can make a convincing argument that doing so is safe.

"Exceptional" does not mean "almost never happens" or "disastrous." It is better to think of an exception as meaning "some part of the system couldn't do what it was asked to do."

(Stroustrup 1997, p. 358)

ADVICE

For each resource in an interface, do the following:

  • Define what exceptions the resource detects, using the classification of Figure 7.2 to help you account for possible exceptions. Do so in terms of phenomena that are visible to an actor, not in terms of implementation details that should remain unknown to the actor and its programmer.
  • Let the specification of effects and exceptions help each other, and make sure they're consistent with each other. The exception's definition implies preconditions that must be true before a resource can be invoked. Conversely, preconditions imply exceptions that should be raised if the preconditions are not met.
  • Do your best to identify the resource's failure set, and try reducing it by expanding the resource's behavior to work in more states or by defining more exceptions. For those states that remain in the failure set, include in the element's design rationale an explanation of why the failure set is safe.

Категории