Paper: Towards a New Model of Abstraction in Software Engineering

We came across an excellent 1992 paper by Gregor Kiczales: Towards a New Model of Abstraction in Software Engineering (pdf). If you are interested in abstractions, interface design, or portability, we consider this classic paper a “must read”. It is one of our favorites and has changed how we approach interfaces and abstraction.

In this paper, Kiczales addresses the common problem with abstraction: even when an abstraction works extremely well for our application, the underlying implementation may not. This is troubling because we are supposed to be able to ignore those implementation details! To address this problem, Kiczales advocates for the idea of “open implementations”. While the paper proposes that this will work through a language feature, the general idea is sufficiently interesting to be worth our consideration.

We have to keep in mind that this was written in 1992, and in some sense we live in a completely different software world. Much of the software we integrate into our products is available directly as source code that we can view and modify to our heart’s content. But this isn’t what the paper means by “open implementations”, and in some sense I don’t think it’s even ideal. Because the open source model primarily gives us a binary decision: use the abstractions as-is, or take complete ownership over the abstraction’s implementation in order to meet your specific needs. That might work, but there has to be a better way, right?

This paper describes the issues with imperfect abstractions and presents a potential solution to the problem by allowing for the customization of the abstraction itself. Kiczales proposes that we think about every module as having two interfaces. There is the good-old-fashioned abstract interface that should ideally be agnostic to the underlying implementation details. The second interface is one that allows users to adjust the module’s implementation according to their needs. This secondary interface grants users the ability to incrementally specify the behaviors they want without having to worry about those implementation details they don’t care about.

Abstract

This is an abridged version of a longer paper in preparation. The eventual goal is to present, to those outside of the reflection and meta-level architectures community, the intuitions surrounding open implementations and the use of meta-level architectures, particularly metaobject protocols, to achieve them.

The view of abstraction on which software engineering is based does not support the reality of practice: it suggests that abstractions hide their implementation, whereas the evidence is that this is not generally possible. This discrepancy between our basic conceptual foundations and practice appears to be at the heart of a number of portability and complexity problems.

Work on metaobject protocols suggests a new view, in which abstractions do expose their implementations, but do so in a way that makes a principled division between the functionality they provide and the underlying implementation. By resolving the discrepancy with practice, this new view appears to lead to simpler programs. It also has the potential to resolve important outstanding problems surround reuse, software building blocks, and high-level program- ming languages.

Files

Reading Club Discussion

This paper was selected for our members’ reading club. Follow this link to discuss the paper.

Key Lessons

This image and its explanatory figure summarizes the key point of the paper:



Kiczales proposes that we think about every module as having two interfaces. There is the good-old-fashioned abstract interface that should ideally be agnostic to the underlying implementation details. The second interface is one that allows users to adjust the module’s implementation according to their needs. This secondary interface grants users the ability to incrementally specify the behaviors they want without having to worry about those implementation details they don’t care about.

Barring a special language feature, what might this implementation interface look like in practice? Based on the current idioms in our own toolkit, we can see a few different ways to enable this secondary implementation interface:

  • We can supply multiple implementations for a common interface (such as with embeddedartistry/libmemory), allowing users to choose between implementations at build time (or extend this set with their own)
  • We can allow users to supply their own implementations for key process steps if the default implementation isn’t suitable. This can be done using the Strategy pattern, the Template Method pattern, callbacks, weakly linked functions, etc.
  • Like Jacob Beningo advocates for in Reusable Firmware Development, we can use a configuration table to allow users to modify and tune implementation details.
  • Like the Wayne brothers advocate for in Patterns in the Machine, we can make every fixed parameter in our module configurable via a compiler definition in the event they want to override our defaults.

Further Reading

We think this is a significant idea, and one we are exploring further in the following courses:

Bibliography Selections

I always try to find what to read next by searching through the bibliographies of books and papers that interest me. Here are some entries that jump out at me for potential future reads:

  • Gregor Kiczales. Metaobject protocols — why we want them and what else they can do. In Andreas Paepcke, editor, Object-Oriented Programming: The CLOS Perspective. MIT Press, 1992.
  • John M. Lucassen. Types and effects: Towards the integration of functional and imperative programming. Technical Report MIT/LCS/TR-408, MIT, August 1987.
  • Mary Shaw and Wm. A. Wulf. Towards relaxing assumptions in languages and their implementations. In SIGPLAN Notices 15, 3, pages 45–51, 1980.

Highlights and Commentary

We’ve divided the paper into the following general themes:

  1. The Nature of Abstractions
  2. The Problems with Abstraction
  3. Open Implementations
  4. Problems with Closed Implementations of Abstractions
  5. Think About Two Interfaces
  6. Summarizing the Points
  7. A Segway: Software and Physics

The Nature of Abstractions

From the opening of the paper:

We now come to the decisive step of mathematical abstraction: we forget about what the symbols stand for… [The mathematician] need not be idle; there are many operations he can carry out with these symbols, without ever having to look at the things they stand for.
— Hermann Weyl, “The Mathematical Way ofThinking”
(This appears at the beginning of the Building Abstractions With Data chapter of “Structure and Interpretation of Computer Programs” by Harold Abelson and Gerald Jay Sussman.)

General note:

abstractions do expose their implementations, but do so in a way that makes a principled division between the functionality they provide and the underlying implementation.

Terms we commonly use:

The horizontal lines in the figure are commonly called “abstraction barriers,” “abstractions” or “interfaces.” Each provides useful functionality while hiding “implementation details” from the client above.

To the degree that an abstraction provides powerful, composable functionality, and is free of implementation issues, we call it “clean” or “elegant.”

There seem to be (at least) three basic principles underlying our view of abstraction:

The first, and most important, has to do with management of complexity. In this sense, abstraction is a primary concept in all engineering disciplines and is, in fact, a basic property of how people approach the world. We simply can’t cope with the full complexity of what goes on around us, so we have to find models or approximations that capture the salient features we need to address at a given time, and gloss over issues not of immediate concern.

Second, is a convention that a primary place to draw an abstraction boundary is between those aspects of a system’s behavior that are particular to a particular implementation vs. those aspects of its behavior that common across all implementations.

Third, is a sense that not only is the kind of abstraction boundary that arises from the second principle useful, it is in fact the only one it appropriate to give to clients. That is, we believe that issues of an interface’s implementation are not of concern to, and should be completely hidden from, clients.

Upon these three basic principles lay our goals:

Layered on top of these three principles are our goals of portability, reusability and in fact the whole concept of system software. The idea has been that by taking commonly useful, “basement-level,” functionality —memory allocators, file systems, window systems, databases, programming languages etc. —giving it a general-purpose interface, and isolating the client from the implementation, we could make it possible for a wide range of clients to use the abstraction without caring about the implementation. Portability stems in particular from isolating the client from implementation details; this makes it possible to have other implementations of the abstraction which the client code can be ported to. Reuse stems in particular from making the abstraction general-purpose; the more general it is, the wider a variety of clients that can use it.

The Problems with Abstraction

The “abstract” (the opening paragraphs) points out that we tend to fool ourselves about how abstraction works:

The view of abstraction on which software engineering is based does not support the reality of practice: it suggests that abstractions hide their implementation, whereas the evidence is that this is not generally possible. This discrepancy between our basic conceptual foundations and practice appears to be at the heart of a number of portability and complexity problems.

The paper continues in this vein, following the three principles and the goals built atop them:

As wonderful as this may sound, few experienced programmers would be surprised if this code didn’t quite work. That is, it might work, but its performance might be so bad as to render it, in any practical sense, worthless.

the implementor is faced with a number of tradeoffs, in the face of which they must make decisions. No matter what they do, the window system will end up tuned for some applications and against others.

Implementation decisions based on these assumptions, once made, become locked away behind the abstraction barrier as implementation details.

The above is one reason we make our assumptions explicit in our interface documentation – we don’t want them locked away, requiring users to go hunting!

This goes back to the point made in the note re: “As wonderful as it may sound…”

So, predicting and/or understanding the performance properties of this program can only be done with knowledge of internal aspects of the window system implementation—the so-called “hidden implementation details.”

And we are faced with the facts of reality that most of us have experienced:

But, the reality is that the implementation cannot always be hidden, its performance characteristics can show through in important ways.

Summarizing the point:

What is clear then is that there is a basic discrepancy between our existing view of abstraction and the reality of day-to-day programming. We say that we design clean, powerful abstractions that hide their implementation, and then use those abstractions, without thinking about their implementation, to build higher-level functionality. But, the reality is that the implementation cannot always be hidden, its performance characteristics can show through in important ways. In fact, the client programmer is well aware of them, and is limited by them just as they are by the abstraction itself.

Open Implementations

This is the goal of the new framing of abstractions presented in this paper:

preserve what is good and essential about our existing abstraction framework — essentially the first two bulleted principles—while seeking to address the conflict between the third basic principle and the reality of practice.

I’m definitely already in the boat described below. Open up the implementations! We have no problem here.

very often, as in this example, our abstractions themselves are sufficiently expressive and our implementations may only be deficient in small ways. What we will end up doing is “opening up the implementation,” but doing so in a principled way, so that the client doesn’t have to be confronted with implementation issues all the time, and, moreover, can address some implementation issues without having to address them all.

Problems with Closed Implementations of Abstractions

Cases like the spreadsheet application, where an abstraction itself is adequate for the client’s needs but the implementation shows through and is in some way deficient are common. The machinations the client programmer is forced into by these situations make their code more complex and less portable. These machinations fall into two general categories: (i) Reimplementation of the required functionality, in the application itself, with more appropriate performance tradeoffs; and (ii) coding “between the lines.”

This is why not having open implementation is somewhat problematic:

reimplementing part of the underlying functionality this way increases the size of the application, and, therefore, the total amount of code the programmer must be responsible for.

In addition to making the application strictly larger, reimplementation of underlying functionality can also cause the rest of the application — the code that simply uses the reimplemented functionality — to become more complex. This happens if for some reason the newly implemented functionality cannot be used as elegantly as the original underlying functionality. This in turn can happen if, for any reason, the programmer cannot manage to slide the new implementation in under the old interface.

Simply put, the application programmer doesn’t have the time (even if they do have the interest) to design the new interface as cleanly as might be nice.

Coding between the lines is happens when the application programmer writes their code in a particularly contorted way in order to get better performance.

The example given in the paper for the point above is virtual memory – people work around the virtual memory abstraction and control allocations so they are close together. This is done to control cache behavior and avoid cache misses, which can have a significant performance impact.

Of course, working around abstractions is problematic:

When programmers are forced into these situations, their applications become unduly complex and, more importantly, even less portable.

The original implementation is simple, clear and makes the greatest re-use of the underlying abstractions (i.e. the simple spreadsheet implementation). But, when it comes time to move it to the delivery platform, a number of performance problems come up that must be solved. A wizard is brought in, and through tricks like those mentioned above, manages to improve the performance of the application. Effectively, the wizard convolves the original simple code with their knowledge of inner workings of the delivery platform.The term convolves is chosen to suggest that, as a result of the convolution, properties of the code which had been well localized become duplicated and spread out.) In the process, the code becomes more complex and implicitly conformant to the delivery platform.

When it comes time to move it to another platform, the code is more difficult to work with, and because of the implicit conformance, ti is difficult to tell just why things are the way they are.

When an application is originally written on a fast machine, the code can start out being simple. To port the code to a delivery platform a wizard—someone who understands the inner workings of the delivery platform— is brought in to tune the code. The application gets larger and more complex, and above all it becomes implicitly adapted to the delivery platform. It is then even more difficult to move it to another platform.

  • I found a large number of programs perform poorly because of the language’s tendency to hide “what is going on” with the misguided intention of “not bothering the programmer with details.”
    – N. Wirth, “On the Design of Programming Languages”

Perhaps this is a summary of the above points: the supposedly hidden properties of the implementation are critically getting in the way. Leaky abstractions, in Joel Spolsky’s terms.

While the CLOS language abstraction itself is perfectly adequate to express the behavior they desire, supposedly hidden properties of the implementation—the instance representation strategy — are critically getting in the way.

Think About Two Interfaces

So what do we do? Think about two interfaces for our abstractions! The approach described in the paper turns now to a language feature, but we think the general idea itself is most important.

In the metaobject protocol approach, the client ends up writing two programs: a base-language program and an (optional)meta-language program. The base-language program expresses, the desired behavior of the client program, in terms of the functionality provided by the underlying system. The meta-language program can customize particular aspects of the underlying system’s implementation so that it better meets the needs of the base-language program.

Figure 5: The dual-interface framework supports the notion of an open implementation. The client first writes a base-program, and then, if necessary, writes a meta-program to customize the underlying implementation to meet the base-program’s needs. The curved arrow under the meta-level interface is intended to remind us that it provides access to what have traditionally been internal properties of the implementation.

(Figured noted above)

The two interfaces are the base interface, which is what we are used to, and the meta-level interface, which controls and customizes the implementation to suit the needs of the application programmer.

A high-level system(i.e. CLOS) presents two coupled interfaces: base- and meta-level. The base-level interface looks like the traditional interface any such system would present. It provides access to the system’s functionality in away that the application programmer can make productive use of and which does not betray implementation issues. The client programmer can work with it without having to think about the underlying implementation details.

But, for those cases where the underlying implementation is not adequate, the client has amore reasonable recourse. The meta-level interface provides them with the control they need to step in and customize the implementation to better suit their needs. That is, by owning up to the fact that users needs access to implementation issues (i.e. instance implementation strategy), and providing an explicit interface for doing so, the metaobject protocol approach manages to retain what is good about the first two principles of abstraction.

This is important because we are good at designing interfaces that do not betray the implementation. We just don’t have a way to deal with the control of implementation details that then impact the program.

we have become quite good at designing interfaces that do not themselves betray the implementation.

We should be able to make base-level interfaces even more clean because we will now have a principled place to put implementation issues that the client must have access to— the meta-level interface.

What follows are considerations of how a meta-level interface should be approached.

Scope control means that when the programmer uses the metalevel interface to customize the implementation, they should be given appropriate control over the scope of the specialization.

Other classes, particularly classes that are part of other applications, should not be affected.

Conceptual separation means that it should be possible to use the meta-level interface to customize particular aspects of the implementation without having to understand the entire metalevel interface.

The challenge, as discussed in [LKRR92], is to come up with a sufficiently fine-grained model of the implementation.

Incrementality means that the client who decides to customize some aspect of the implementation tradeoffs wants to do just that: customize those properties. They don’t want to have to take total responsibility for the implementation and they don’t want to end up having to write a whole new implementation from scratch. It must be possible for them to say just what it is they want to have be different, and then automatically reuse the rest of the implementation.

Robustness simply means that bugs in a client’s meta-program should have appropriately limited effect on the rest of the system

What is clear is that there is no one right or most elegant metaobject structure, each has relative costs and advantages, and we need to keep experimenting to learn about how to handle locality this way.

How do we come to build a dual-interface abstraction? Iteratively!

It is also possible to make a basic comment about the way the designer of a dual-interface abstraction—or any open implementation — works: iteratively. They start with a traditional abstraction (i.e., a window system or CLOS), and gradually add meta-level interface as it becomes clear what kinds of ways a close implementation can cause problems for users.

Kiczales argues that it is much better to iterate than to try from the start:

Moreover, it isn’t a good idea to try and make the first version of a new kind of system open in this sense. Opening the implementation critically depends on understanding not just one implementation the clients might want, but also the various kinds of variability around that point they might want. In this mode of working, user bug-reports and complaints about previous versions of the system take on an important value. We can look for places where users complained that they wanted to do X, but the implementation didn’t support it; the idea is to add enough control in the meta-level interface to make it possible to customize the implementation enough to make X viable. (In fact, in work on the CLOS Metaobject Protocol, we spent a lot of time thinking about these kinds of bug reports.)

It suggests that anytime we find ourselves saying “well, I’ll implement this feature a particular way because I think most users will do X,” we should immediately think about the other users, the ones whose options we are about to preempt, and how, using a meta-level interface, we might allow them to customize things so they can do other than X.

You might even think of “Future You” as one of those users 🙂

Summarizing the Points

Here is a great restatement of the main point:

In practice, high-level abstractions often cannot hide their implementations — the performance characteristics show through, the user is aware of them, and would be well-served by being able to control them. This happens because making any concrete implementation of a high-level system requires coming to terms with a number of tradeoffs. It simply isn’t possible to provide a single, fixed, closed implementation of such a system that is “good enough” that all prospective users will be happy with it. In other words, the third principle of abstraction presented above appears to be invalid, at least in actual practice.

Under this framework the abstraction presented by a system is divided into two parts: one that provides functionality in a traditional way and another that provides control over the internal implementation strategies supporting that functionality.

A number of programming language projects have discovered that attempting to give their users a black-box abstraction with a single fixed implementation does not work.

In the previously mentioned paper by Shaw &Wulf they make the claim that top-down programming is fundamentally at odds with reusable code libraries and even the notion of system software. Their argument, as I understand it, is that a reusable library essentially blocks, at the abstraction boundary, the downward flow of design decisions, preventing those decisions from leaking into the library’s implementation as we would like.

From the dual interface abstraction point of view, the conflict is not between top-down programming and reusable code; it is between top down programming and closed implementations of reusable code.

The idea is that reusable code should be like a sponge: It provides basic functionality (the base-level interface), basic structure (the default implementation) but also allows the user to “pour in” important customizations from above to “firm it up.”

This view of top-down programming makes it clear that opening an implementation only to the client immediately above is not enough. We need to do better than that; all layers need to be open to all layers above them. So, for example, when an application is written on top of a high-level language, which itself sits on top of a virtual memory system, the application code needs to be able to control not just how the language uses the memory it is allocated, but also how that virtual memory system allocates that memory.

But a clear lesson from the metaobject protocol work is that users can also take productive advantage of being able to customize the semantics (or behavior) of systems they are building on top of.

What it seems we want to be able to do is to allow the user to use natural base-level concepts and natural meta-level concepts— as if they were the x and y axes of a plane — to get at just what it is in the implementation they want to affect. The problem is that the “points” in the plane spanned by these two axes are not necessarily easy to localize in an implementation.

We are, in essence, trying to find a way to provide two effective views of a system through cross-cutting “localities.”

One strategy—the one that has been prevalent in existing meta-level architectures— is to make the problem easier by delaying the implementation of strategy selection until run-time or thereabouts. So, for example, the existing metaobject protocols address only those issues which do not need to be handled in a compile-time fashion. The various systems that address distribution, concurrency and real-time are also addressing problems which are amenable to architectures with runtime dispatch.

All the dual-interface framework does is:
(i) make it more clear that this problem needs to be solved, and
(ii), give one particular organization to the relation between the two different localities.
Of course, looking at the problem this way makes it clear that we may well want more than two, cross-cutting, effective interfaces to a system—the dual interface framework may quickly become the multi-interface framework.

The “abstractions” we manipulate are not, in point of fact, abstract. They are backed by real pieces of code, running on real machines, consuming real energy and taking up real space. To attempt to completely ignore the underlying implementation is like trying to completely ignore the laws of physics; it may be tempting but it won’t get us very far.

A Segway: Software and Physics

Instead, what is possible is to temporarily set aside concern for some (or even all) of the laws of physics. This is what the dual interface model does: In the base-level interface we set physics aside, and focus on what behavior we want to build; in the meta-level interface we respect physics by making sure that the underlying implementation efficiently supports what we are doing. Because the two are separate, we can work with one without the other, in accordance with the primary purpose of abstraction, which is to give us a handle on complexity. But, because the two are coupled, we have an effective handle on the underlying implementation when we need it. I like to call this kind of abstraction, in which we sometimes elide, but never ignore the underlying implementation “physically correct computing.”

Because we are engineers, not mathematicians, we must respect the laws of physics — we cannot hope to completely ignore the underlying implementation.

What will remain, in the long term, is the intuition of physically correct computing and the requirement that we build open implementations.

700-799 Creative Practices/707 Reading/707.11 Readwise/Articles/Towards a New Model of Abstraction in Software Engineering%%
parent:: Reading Club Papers
parent:: Field Atlas Entries

Bringing Architecture of Operating Systems to XXI Century

4 May 2021 by Phillip JohnstonI selected this series from the IT Hare blog as reading club entry: Bringing Architecture of Operating Systems to XXI Century. This series summarizes Sergey’s view of the direction in which we should collectively be headed. While not explicitly about embedded systems, I think there are many ideas that are tied into our field of study. Given that, I wanted to see what insights and lessons we can pull out of this series and apply to our embedded systems work. Given the different directions I’m exploring, such as portability, driver design, and asynchronous programming, I …

To access this content, you must purchase a Membership - check out the different options here. If you're a member, log in.

Paper: A Procedure for Designing Abstract Interfaces for Device Interface Modules

15 July 2020 by Phillip Johnston • Last updated 15 August 2023″A Procedure for Designing Abstract Interfaces for Device Interface Modules” is an absolute must-read paper for embedded systems developers. Here’s the abstract: This paper describes the abstract interface principle and shows how it can be applied in the design of device interface modules. The purpose of this principle is to reduce maintenance costs for embedded real-time software by facilitating the adaptation of the software to altered hardware interfaces. This principle has been applied in the Naval Research Laboratory’s redesign of the flight software for the Navy’s A-7 aircraft. This …

To access this content, you must purchase a Membership - check out the different options here. If you're a member, log in.

A Procedure for Designing Abstract Interfaces for Device Interface Modules

I recently discovered the paper “A Procedure for Designing Abstract Interfaces for Device Interface Modules”.

Although it was published in March 1981, the challenges discussed in this paper still affect embedded systems developers today. This paper is provides a procedure for creating abstract interfaces, explores approaches to handling common design challenges when creating abstract interfaces for device drivers, and provides an example of iteratively improving an abstract interface used in a real-world avionics program.

Unlike many other papers, which assume an academic audience, this one is targeted at embedded software developers in industry. There are many practical ideas that can be extracted from this paper and applied to your embedded software work.

Since the paper is marked as “Government work not protected by U.S. copyright”, I decided to retype the paper and host it on the website. I’ve kept to the original formatting as much as possible. Some improvements have been made, such as adding a table of contents and internal hyperlinks. I’ve also placed the figures in-line with the appropriate text sections, rather than grouping them all together as was done in the original publication. To see a larger version of a Figure, please click on the image to be taken to the file.

You can also download a PDF copy of the original paper. Members can access our commentary in the field atlas and discussion in the community forum.


A Procedure for Designing Abstract Interfaces for Device Interface Modules

By Kathryn Heninger Britton, R. Alan Parker, David L. Parnas1

Code 7590, Naval Research Laboratory, Washington, D.C. 20375

Abstract

This paper describes the abstract interface principle and shows how it can be applied in the design of device interface modules. The purpose of this principle is to reduce maintenance costs for embedded real-time software by facilitating the adaptation of the software to altered hardware interfaces. This principle has been applied in the Naval Research Laboratory’s redesign of the flight software for the Navy’s A-7 aircraft. This paper discusses a design approach based on the abstract interface principle and presents solutions to interesting problems encountered in the A-7 redesign. The specification document in the A-7 device interface modules is available on request; it provides a fully worked out example of the design approach discussed in this paper.

Keywords

  • software design techniques
  • module specifications
  • abstract interfaces
  • software maintenance cost reduction
  • information-hiding modules
  • real-time software
  • embedded software
  • device interface modules
  • virtual devices

Table of Contents

  1. Introduction
    1. Background
    2. Contents
  2. Objectives
  3. Definitions
  4. Design Approach
    1. Description 1: Assumption List characterizing the virtual device
    2. Description 2: Programming constructs embodying the assumptions
    3. Why Two Descriptions?
    4. Design Procedure
    5. Illustration
  5. Design Problems
    1. Major variations among available devices
    2. Devices with characteristics that change independently
    3. Virtual device characteristics that are likely to change
    4. Device-dependent characteristics that vary at run-time
    5. Interconnections between virtual devices
    6. Inconsistencies in the hardware interfaces
    7. Switch nomenclature
    8. Switches with hardware side-effects
    9. Reporting changes in device state
    10. Devices requiring information from the software
    11. Virtual devices that do not correspond to hardware devices
  6. Summary
  7. Acknowledgements
  8. References

I. Introduction

Background

At the Naval Research Laboratory, we are redesigning the flight software for the Navy’s A-7 aircraft in order to evaluate the applicability of new software engineering techniques for embedded software design. (An embedded software system is a single component of a significantly larger hardware or software system. For a more complex description, see Parnas’ paper on abstract interfaces5.) We intend to provide fully worked out examples of both well structured software and helpful documentation in order to help other designers apply the techniques that are found useful. For more information, see Heninger’s paper about the project.1

Much of the complexity of embedded real-time software is associated with controlling special-purpose hardware devices. Many designers seek to reduce this complexity by isolating device characteristics in software device interface modules, thereby allowing most of the software to be programmed without knowledge of device details. While these device interface modules generally do make the rest of the software simpler, their interfaces are usually the result of an ad hoc design process, and they fail to encapsulate the device details completely. As a result, device changes lead to changes throughout the software, rather than just in the device interface module. We developed a systematic procedure based on the abstract interface principle5 to design the interfaces to A-7 device interface modules. We believe the resulting interfaces will successfully encapsulate device dependencies, so that replacing or modifying a device will require only changes in the device interface module, not in the rest of the software. This paper explains and illustrates this procedure.

Contents

Although the underlying principles described in this paper are not new, the design procedure is both new and a significant deviation from current practice. The procedure is a practical approach to a recurring problem. As a result, we expect the paper to be of more interest to practicing software engineers than to researchers interested in today’s hot topics.

Section II discusses device interface modules and the goals designers hope to achieve by including them in a system. Although this material is not new, it is included because it motivates the rest of the paper.

Section III defines terms that are used in the rest of the paper. Although the definitions are not new5, they are not widely known. The presentation of the procedure relies on precise use of these terms.

Section IV describes and illustrates the systematic procedure. The illustration shows several stages in the development of an abstract interface for one of the A-7 device interface modules.

Even systematic procedures do not make software design easy. In the A-7 design we often had to make difficult trade-offs between flexibility and run-time efficiency. In retrospect, we identified several recurring problems. None of these problems forced us to change the basic procedure, but they did cause us to add some additional guidelines for device interface design. Section V describes some of these problems and the resulting guidelines.

II. Objectives

Embedded real-time software systems usually have complex and restrictive external interfaces. Since embedded software is usually a small component of a much larger system, the interfaces are seldom modified for the convenience of the software designer. The A-7 avionics software is typical: twenty-one devices are connected to the computer, including sensors, displays, and equipment controlled by the computer. Arbitrary interface characteristics, such as value encodings and timing quirks, are subject to change both during development and after initial deployment. Inadequacies may be discovered in the device specifications; a supplier may deliver a device that is judged adequate even though it does not exactly meet its specifications; a device may be replaced by an improved device; or new connections may be added between devices.

It is a common but undesirable property of embedded software that a change in a device interface requires widespread changes to the software because many programs are based on arbitrary interface details. If an interface changes, programs depending on it become invalid. Because these dependencies are seldom explicitly documented, interface changes often have surprising ramifications.

To avoid these problems it is common to divide the software into two groups of components: 1) the device interface modules containing the device-dependent code, and 2) the device-independent remainder of the software, including the user programs, so called because they use the device interface modules. Device interface modules provide virtual devices, that is, device-like capabilities that are partially implemented in software. For example, there is a virtual altimeter on the A-7 system. The virtual altimeter returns a value of type range, instead of the bit string read in from the actual sensor. The raw data is read, scaled, corrected, and flittered within the altimeter device interface module. This software structure is illustrated in Figure 1.

Design of device interface modules has the following goals:

  1. Confining changes
  2. Simplifying the rest of the software
  3. Enforcing disciplined use of resources
  4. Code sharing
  5. Efficient use of devices

Confining changes

Designing device interface modules is a special case of the information-hiding approach6; hardware interface details are hidden within modules that should be the only system components requiring changes when devices are modified or replaced by others that can perform the same basic functions. Problems in confining change are caused by three types of errors:

  1. The device interface module allows user programs to exploit special characteristics of a particular device so that user programs must be revised if the device is replaced.
  2. The virtual device lacks essential capabilities, so that user programs must access the actual device directly; again user programs must be revised if the device is replaced.
  3. Programs that are not necessarily device-dependent are included in the device interface module. As a result, the device interface module may need to be changed if the requirements change even if the device is not changed. Furthermore the module will be harder to change when the device is changed.

In summary, a device interface module will ideally

  1. be the only component that needs to change if a device is changed;
  2. not need to change unless the device is changed; and
  3. be relatively small and straightforward so that it is easy to change.

Simplifying the rest of the software

Embedded software is often hard to understand because its correctness depends on many arbitrary interface details. If these details are confined to device interface modules, user programs should be simpler, easier to write correctly, and easier to understand, than they would be if they used hardware interfaces directly.

Enforcing disciplined use of resources

Software reliability is enhanced when all programs that access a device adhere to certain disciplines, such as regular checks or undesired events7 and standard protocols for device sharing3. If these disciplines are built into the device interface modules, they are systematically enforced; programmers writing user programs need not be concerned with them.

Code sharing

When many programs access a device directly, they often contain similar subprograms performing the same device control functions. With device interface modules, this code need only be written once, saving programming, debugging, and testing time, and possibly computer storage.

Efficient use of devices

Independently written programs often cause devices to repeat actions unnecessarily. Centralizing device-access code should make it easier to avoid unnecessary operations.

To achieve these goals and avoid the mistakes mentioned earlier, the interface between a device interface module and user programs should be an abstract interface, as defined in the next section.

III. Definitions

  • Interface: The interface between two programs consists of the set of assumptions that each programmer needs to make about the other program in order to demonstrate the correctness his own program. For convenience, we use the phrase “assumptions made by program A about program B,” to mean the properties of B that must be true in order for A to work properly. These assumptions are not limited to the calling sequences and parameter formats traditionally found in interface documents; they include additional information such as the meaning and limits of information exchanged, restrictions on the order of events, and expected behavior when undesired events occur. There is an analogous definition of the interface between a program and a device.
  • Abstraction: An abstraction of a set of objects is a description that applies equally well to any one of them. Each object is an instance of the abstraction. For a non-trivial abstraction, there is a one-to-many relationship between the abstraction and the objects it describes. Differential equations are an example of a mathematical abstraction representing systems as diverse as electrical circuits and collections of springs and weights.
    • An abstraction that is appropriate for a given purpose is easier to study than the actual system because it omits details that are not relevant for that purpose. A road map is an abstraction used to represent a road network; the graph represents the directions, relative lengths, and intersections of roads, but it does not show whether a road is made of asphalt or how is banked. It is far easier to find a good route by studying a road map than by exploring the actual roads.
    • Any result obtained by studying an abstraction can be applied to any system represented by the same abstraction. Well-known graph theoretic results can be applied to a road map to determine the shortest route; the same methods have been applied o solve a wide variety of problems in other systems represented by directed graphs. Results may be misleading if they are obtained from an inappropriate abstraction, i.e., one that omits relevant details. For example, a road map is not sufficient to find the quickest route because it does not show other factors affecting driving time such as speed limits.
  • Abstract interface: An abstract interface is an abstraction that represents more than one interface; it consists of the assumptions that are included in all of the interfaces that it represents. An abstract interface for a given type of device reveals some, but not all, properties of the actual device: it describes the common aspects of devices of that type, omitting the aspects that distinguish them from each other.
  • Device interface module: A device interface module is a set of programs that translate between the abstract interface and the actual hardware interface. The implementation of this module is possible only if all assumptions in the abstract interface are true of the actual device.
  • Secret: Secrets of a device interface module are assumptions about the actual device that user programs are not allowed to make. The secrets are information about the current device that need not be true of other devices with the same functions. Secrets must be taken into account somewhere in correctly working software; they are encapsulated in a device interface module.
  • Undesired event assumptions: The interface between programs A and B includes both the assumptions made by A about B and the assumptions made by B about A. Systems can be designed so that only one of two programs relies on the other meeting its specifications. A program can be designed so that it does not rely on user programs using it correctly; it can check for improper uses and signal undesired events when they occur. However, the error checking and reporting require extra instructions. In development versions of the A-7 software, the device interface modules will assume that undesired events can occur; they will contain code to check for errors made by user programs. In the production version there will not be room for that error-checking. The device interface modules will assume that improper uses will not occur; the error-checking code will be omitted to make the system smaller and faster. If a problem occurs during operation of the production system, the error-checking will be reinserted to help locate the cause. The software will be written in such a way that the error checking code can be easily included or omitted when the program is assembled. This applies only to programming errors; error checks specified in the software requirements2 will never be omitted.
  • Access functions: An access function is a program that is part of one module and may be called by programs in other modules. There are different kinds of access functions; some return information to the caller; others change the state of the module to which they belong.
  • Events: Events are signals from a module to user programs indicating the occurrence of some state change within the module. They resemble hardware interrupts because they occur at unpredictable times and are not synchronized with the control flow of the user programs. In the A-7 system, modules will use a mechanism such as eventcounts8 to signal the occurrence of an event to user programs that are waiting for it to occur.

IV. Design Approach

This section describes a procedure for the design of abstract interfaces. The procedure is based on obtaining two partially redundant descriptions of the interface.

Description 1: Assumption List Characterizing the Virtual Device

For an application area such as avionics, many devices fall into standard types; all devices of a given type have many common characteristics. For example, as shown by advertisements in Aviation Week and Space Technology, computer panels vary little in the features seen by the pilot. For each hardware device, make a list of the characteristics that are not likely to change if the device is replaced by another device of the same type. To do so requires considerable study of devices that are available or being developed. The list of common characteristics is a description of the assumptions that user programs are allowed to make about the virtual device. The assumptions characterize device capabilities, modes, information requirements, behavior, and proper use of the device. A typical assumption might be:

The device produces information from which barometric altitude can be determined.

We are quite certain that only devices satisfying this assumption will replace the current barometric altitude sensor. Note that this assumption does not describe the form of the information, which may vary from one device to another.

Many assumptions will appear innocuous, but they must be recorded anyway. During the A-7 design reviews, some seemingly innocuous assumptions were found to be false

Description 2: Programming Constructs Embodying the Assumptions

The second description specifies the access functions and events that can be used by user programs. The access functions can be called by user programs to access the data or facility provided by the virtual device. For example, an interface might provide an access function GET_BAROALT, which returns a barometric altitude value. For each access function, we specify the values returned, the limitations, and the effect it has on the virtual device to which it belongs. User programs can also use the events in order to be signaled when the virtual device changes state. For example, user programs may need to be signaled when a virtual sensor is no longer operational.

Why Two Descriptions?

These two descriptions are partially redundant, i.e., the specifications for the programming constructs imply the assumptions. For example, specifications for the access function GET_BAROALT imply the assumption that the device provides information from which barometric altitude can be determined. The access function specifications provide additional information, namely the form of the data exchange between the device interface module and the user programs. For example, rather than provide barometric altitude directly, the device interface module might provide two or three quantities from which it could be computed. Such a design change would require a change int he function specification but not in the assumption list.

The two versions of the interface have different purposes: 1) the assumption list explicitly states assumptions that are implicit in the function specifications, making invalid assumptions easier to detect, and 2) the programming constructs can be used directly in user programs. It is essential that the two descriptions be consistent. The assumptions should be embodied clearly in the programming construct specifications, and the programming construct specifications should not imply any capabilities that aren’t stated in the assumption list. The assumption list should be reviewed by programmers, users, and hardware engineers who have the knowledge to check it for validity and generality. For example, the A-7 assumption were reviewed by system engineers and hardware engineers familiar with the A-7, A-6, and F-18 aircraft. Assumptions written in prose are easier for non-programmers to review. The specifications of the programming constructs should be reviewed by programmers who have worked with similar programs. These reviewers evaluate how well the abstract interface supports user programs and whether the device interface module can be implemented efficiently. The procedures and interface modules are described in device interface document4.

Design Procedure

Obtaining a correct and consistent dual description of an abstract interface is an interactive process. Although we attempted to list assumptions first, many of the necessary assumptions were quite subtle and only became apparent when we designed the programming constructs. Review of the assumption lists revealed errors in the programming constructs. The interfaces are the result of several cycles of review, both internally at NRL and by the A-7 maintenance team at the Naval Weapons Center (NWC). The first drafts were reviewed several times informally by the NRL A-7 team before they were submitted for an informal review at NWC. After further revisions within the NRL team, we held a formal review at NWC, resulting in the current version.

Illustration

As an example of this procedure, this section sketches the development of the abstract interface to the Air Data Computer (ADC). The ADC is a sensor that measure barometric altitude, true airspeed, and the mach number representation of airspeed.

Figures 2 through 4 are excerpted from successive versions of the ADC abstract interface. Each figure includes an assumption list and tables showing the associated programming constructs. These figures are not the complete specifications; other tables define system generation parameters and specify the ranges and resolutions of values. In the access function tables, “I” indicates an input parameter whose value is supplied by user programs; “O” indicates an output parameter whose value is returned by the ADC module.

Although the early draft shown in Figure 2 seemed simple and reasonable to use, NRL and NWC reviewers found the following errors in it:

  • The current ADC hardware and most replacement devices include a built-in test capability that cannot be accessed with the current interface.
  • The description does not specify the values to be returned by the access functions while the ADC is in a failed state.
  • The secretion does not specify the range of possible values for the measured quantities. The ranges are device-dependent, but they also affect user programs.
  • The virtual ADC does not signal when it fails. User programs must poll the validity function to detect changes in reliability.
  • The description does not make it clear whether the module performs device-dependent corrections to the raw sensor values.

After we corrected these errors, the interface shown in Figure 3 was reviewed formally at NWC.

The following problems were pointed out.

  • There is a device-dependent correction necessary for actual sea level pressure. The assumption of constant pressure is a poor one as it will force a device-dependent correction to be done by user programs. Future hardware may perform this correction automatically.
  • Although the current hardware has one reliability indicator for all three values, replacement devices might not. In replacement devices the measurements might be made using independent sensors.
  • We cannot assume that the minimum mach and true airspeed values are zero; some devices might not be capable of measuring such low values.

Figure 4 illustrates the product of the final revisions.

The development of the ADC abstract interface shows how the procedure supported our design efforts. Even the version in Figure 2 is a reasonable design; the errors it contains; the errors it contains are typical of errors made in embedded software. As a result of our procedure, the erroneous assumptions were written explicitly in a form meant for review, rather than left implicit. Unwise assumptions, which might have escaped our notice until after the code was written, were caught when they could be corrected relatively easily. The current version of the ADC interface may not be perfect, but it is much better than it would have been if we had not followed the procedure. Having two partially redundant descriptions of the interface was very important. We have examined our records and found that some errors were found in the assumption lists and others in the programming construct specifications. Seldom was the same error found in both versions.

V. Design Problems

The design considerations mentioned earlier serve as design guidelines and as standards for judging results. Although they help considerably, it is not always easy to apply them. Conflicts arise among three design goals: small device interface modules, device-independent user programs, and efficiency. What if user programs could use a device more efficiently if they could exploit assumptions that are not valid for all possible replacement devices? What if encapsulation of assumptions that are not always valid makes a device interface module slower or bigger? Acceptable compromises must be based on estimates of the likelihood of future changes. This section shows tradeoff problems and how we resolved them, attempting to minimize the expected cost of the software over its entire period of use.

Problem 1: Major variations among available devices

Deciding how much capability to include in a device interface module is particularly difficult when there are major differences among replacement devices. For example, new Inertial measurement Set (IMS) models produce present position data; other IMS devices produce velocity data; and the current A-7 IMS produces only velocity increments. In order to simulate an IMS that produces present position using the current IMS hardware, much of the navigation software would have to be inside the IMS device interface module; the result would be a very large module. One must choose between a) confining major changes within the device interface module, and b) keeping the device interface module small, so that minor but more likely changes are easier to make. Our compromise limits the range of devices represented by our virtual IMS, with the understanding that the remaining differences can be confined to a small set of user programs. Although our virtual IMS does not provide present position, it does provide velocities rather than velocity increments; the velocity increments are only used to compute velocities, and the velocities are widely used. The resulting virtual IMS is considerably eerier to use than the hardware IMS, yet we expect the IMS module to be reasonably small.

Problem 2: Devices with characteristics that change independently

Some devices can have several sets of characteristics that can change independently. For example, the Projected Map Display Set (PMDS) consists of a set of filmstrips and a hardware drive that positions a filmstrip in a display. The same drive could be used with new filmstrips containing maps in a different format, and the same filmstrips could be used with a different drive. According to the information-hiding principle, two independent sets of characteristics should be hidden in different modules so that they can be changed independently. However it is unnecessary for user programs to be aware of the separation. We chose to hide both sets of characteristics in one PMDS device interface module. The module will later be divided into two submodules, each insulated from changes in the other. Since the division is a secret of the PMDS device module, it is not apparent to user programs and is not presented in the interface specification.

Problem 3: Virtual device characteristics that are likely to change

Some changeable device characteristics must be revealed to user programs os that they can exploit the device effectively. Examples include measurement resolutions, the number of positions on switches, the device limitations such as a maximum displayable value. Although we would like user programs to be insulated from all device changes, they must behave differently if such characteristics change. For example, user programs controlling the PMDS must behave differently if the virtual PDMS provides maps of three different scales instead of just two. We represent such characteristics by symbolic constants. Both user programs and device programs are written in terms of symbolic constants rather than actual values. At system generation time, code can be generated by conditional macro expansion based on the actual values of the parameters.

We defined system generation parameters for the range and resolution of input and output data because 1) range and resolution are highly likely to vary among different devices; and 2) this information is needed by user programs in order to perform arithmetic accurately and efficiently. User programs written in terms of system generation parameters do not need to be rewritten if the parameter values change.

Initially we assumed that all parameter values would be known at system generation time; i.e., we explicitly assumed that whenever a replacement device is introduced, a new version of the program will be generated and deployed with the device. This assumption was questions at the design review. It is Navy policy not to have multiple versions of software in the fleet, even though equipment changes cannot be made simultaneously. Furthermore, we cannot require a new system generation if a new device breaks down and is temporarily replaced by a device of the old type. As a result, some of the parameters must be changeable at run-time. In theory this is true for any of the parameters; in fact, changeover problems are more likely for some devices than others. The cost of run-time variability also differs among devices, depending on whether the parameter can be used to control code generation so that run-time tests can be avoided. If changes are unlikely, we are reluctant to give up the efficiency advantages of binding the value at system generation time; if binding the value early causes no significant savings, we are reluctant to give up flexibility. We decided each case individually, using the following guidelines:

  1. Parameters with low cost for variability are treated as run-time variables, whatever the likelihood of change. Access functions to store and retrieve values appear in the module interface. See problem 4 for an additional problem about these parameters.
  2. Parameters with a low likelihood of change and a high cost for variability are bound at system generation time.
  3. For parameters with both a high likelihood of change and a high cost for variability, there are two possible solutions: a. They can be treated as run-time variables, with the option to bind them earlier by providing values at system generation time. This option allows us to delay the final choice until we have more information. b. We can find a conservative value that can be used for both devices, allowing use to bind the value at system generation time.

Problem 4: Device-dependent characteristics that vary at run-time

In some circumstances, user programs must handle device-dependent data. For example, when one IMS is replaced by another of the same type, the software must be adjusted because of manufacturing variations. The IMS software is parameterized so that it can be tailored to fit a particular piece of hardware. It is a requirement that we be able to change these parameters without reassembly. The parameters are entered at run-time through the computer panel. To receive the data, the IMS module provides access functions, which are called by the user programs that read in the panel data. Unfortunately, the existence of a run-time parameter such as drift rate reveals a secret of the IMS module, i.e., that the actual device drifts out of alignment. An additional drawback is that a replacement device might require different calibration data, requiring a change in the interface. We restrict use of these access functions so that the software making use of them is limited and easily identified. The restricted assumptions and access functions are called reconfiguration interfaces and are appended to the normal interfaces.

Problem 5: Interconnections between virtual devices

Ideally, virtual devices would be independent of each other, allowing the associated abstract interfaces to be designed independently of each other. However the A-7 system has device inter-dependencies introduced for hardware convenience. Some of these interdependencies are based on assumptions made by hardware designers about the software. For example, the Doppler and Ship Inertial Navigation Set might share a data path because someone assumed that the software will not need both devices simultaneously. We can hide the nature but not the existence of the interconnections; if we hid the interconnections, later changes in the user programs might result in attempts to access the devices incorrectly. The existence of interconnections is revealed in the assumption lists in terms of restrictions on the use of virtual devices. For example, there may be an assumption that two virtual devices cannot be used simultaneously. If the hardware interconnection is later removed, additional uses of one or both devices may become possible and desirable. Making such additions will inevitably require changes on both sides of the abstract interface: changes in user programs to exploit the new capabilities and changes in device interface modules to remove the restrictions. Since this cannot be avoided, there is no loss in revealing the restriction.

A similar problem can arise within a single device interface module if the present hardware does not allow two capabilities of the virtual device to be used at once. Again, we chose to reveal the restriction to user programs even though it might not be true of future devices.

Interconnection problems also arise where one device (the provider) provides information used by another (the receiver). There are two cases to consider:

  1. The computer can detect the failure of the provider. If so, the virtual receiver signals a failure when the provider fails, even if the actual receiver does not detect the failure. The device interface module for the receiver can simulate detection of the failure, thereby hiding the inter-connection.
  2. The computer cannot detect the failure of the provider. The undetectable failure of the provider must also be considered an undetectable failure of the receiver. People writing user programs that rely on the virtual receiver must be aware that undetectable failures are possible, but they need not be aware of the interconnection.”

Problem 6: Inconsistencies in the hardware interfaces

A hardware interface may provide similar functions in dissimilar ways. For example, the symbols on the HUD have three states: ON, OFF, and BLINKING. The HUD provides a hardware blink command for some symbols, but for others the software must simulate the blinking state by alternating the symbol between the ON and OFF states. Whenever the hardware interface provides some means to perform that action, we provide the feature consistently in the virtual device. The virtual HUD has commands to blink all symbols. However, when the hardware interface does not provide a way to perform an action, we were forced to reveal the inconsistency in the interface. For example, some H UD symbols have only two states: OFF and BLINKING.

Since there is no way to simulate the ON state, the virtual device cannot provide it. If the hardware limitation is removed in the future, the inconsistency can be removed from the virtual device. It is unavoidable that this changes oddly require changes on both sides of the abstract interface: changes in user programs to exploit the new capability and changes in the device interface module to implement it.

Problem 7: Switch nomenclature

Many of the switches do nothing more than set a bit that the computer can read. The label on a switch is an easily changed characteristic and could be hidden. We could name the switches anonymously (e.g. with integers) and use non-mnemonic names for the settings. However, a change in switch nomenclature will most likely be accompanied by a change in the requirements. The expected cost of working with non-mnemonic names, i.e., more errors, is high. In line with our basic principle of trying to minimize the expected cost of the software over the whole prior of use, we have chosen to reveal the nomenclature in switch names and mnemonic values for the switch settings. The names may suggest more than is actually stated in the assumptions; programmers are cautioned not to make assumptions about the switches beyond those that are explicitly stated in the interface documents.

Problem 8: Switches with hardware side-effects

When a switch also affects other devices, the meaning associated with it is not solely a software decision; major hardware changes would be required to use it for any other purpose. We consider such a switch part of the device that it affects, even if it is not physically located with the device. As far as user programs are concerned, the switch does not exist; the effects of the switch appear as changes in the operating mode of the virtual device. For example, the “Terrain Following” switch located on the master function panel affects the state of the Forward Looking Radar (FLR). Instead of appearing in the same interface description as the other master function switches, it is hidden within the FLR device interface module. User programs cannot read the switch, but they can call an access function that reveals the operating mode of the FLR.

Problem 9: Reporting changes in device state

User programs are often required to respond quickly to a change in device state. For example, user programs determining the current navigation mode need to know when the reliability status of the IMS sensor changes. The device interface modules can either 1) provide an access function reporting the current state of the device, or 2) signal the state-change event. The choice of a mechanism depends on whether user programs base decisions on the current state or wait for the state to change. If we based the design on the requirements of user programs, changes in their requirements might result in changes in the device interface module, violating the design goal that the device interface module should not need to be changed unless the device is changed. We chose to specify both mechanisms in every case, but plan to implement only the ones that are actually needed. Thus requirements changes may require changes to device interface modules, but these changes will consist of implementing previously specified features. By specifying possible additions in advance, we expect to reduce the cost of later reprogramming.

Problem 10: Devices requiring information from the software

Some hardware devices require information that is not calculated within the associated device interface module. For example, the current IMS device needs to know whether or not the aircraft is above 70 degrees (symbol) latitude, even though latitude is not calculated within The IMS module. One must chose between two ways to get the information to the device: 1) the device module can provide an access function that a user program calls in order to provide the information; or 2) programs in the device module can call other programs to get the information. Our decision is based on whether or not the information requirement I common to the class of replacement devices. If it is, the device interface module provides access functions for receiving the information. This solution results in added requirements for the rest of the software, and the user programs supplying the information must change if the information need changes. If the information need is peculiar to a particular hardware device, device interface programs call other programs to get the data. As long as the needed information is available from the rest of the system, no program outside the device interface module needs to change if the need for information disappears. We chose the later solution for The IMS example because not all IMS devices require a signal from the computer at 70 degrees (symbol) latitude.

Problem 11: Virtual devices that do not correspond to hardware devices

Initially, we assumed that there would be one virtual device for each hardware device. We found that modeling the virtual devices on the actual devices does not always result in clear interfaces. Some related capabilities are scattered among several hardware devices; some unrelated capabilities occur in the same device for physical convenience; other groupings can only be explained in terms of historical development. For example, weapons-related capabilities in the A-7 are scattered among several devices. Some weapons data comes from the device that controls weapon release, some is stored in tables, and some is provided by the pilot through switches. Additionally, the weapons release device fills two distinct roles: it is both a source of input data and the device that releases a weapon under computer control. Our final design includes one virtual device for weapons release and one for weapon data. The virtual devices are much simpler to understand than the actual devices. It is important not to be unduly influenced by the physical location of hardware units.

VI. Summary

We have applied a systematic procedure based on the abstract interface principle in the design of a substantial system. We find that the abstract interfaces make the system easier to code, and we expect to find that they make it easier to change in the future. Although the success of our abstract interface design cannot be judged until the A-7 implementation is complete and undergoing maintenance, our experiences so far have given us confidence to recommend the procedure to other designers.

This paper serves as an introduction to a more complete report4. Along with complete specifications for all the A-7 device interface modules, the report contains a description of the documentation organization and notation, a discussion of additional design problems, and a description of the procedures and questionnaires used for the major design review. The document is available from the authors.

VII. Acknowledgements

By reviewing interface specifications and discussing design problems, John Shore contributed greatly to the design of the A-7 abstract interfaces. Our collaborators at the Naval Weapons Center reviewed the A-7 abstract interface specifications, pointing out errors and helping us resolve the design problems. The authors also thank Edward Britton, Paul Clements, Constance Heitmeyer, and David Weiss for their careful reviewers of an earlier version.

References

  1. K. L. Heninger, “Specifying Software Requirements for Complex Systems: New Techniques and their Application,” IEEE Trans. Software Eng., vol. SE-6, pp. 2–13, Jan. 1980.
  2. K. L. Heninger, J. Kallander, D. L. Parnas, and J. E. Shore, Software Requirements for the A-7E Aircraft, Naval Research Lab., Washington, D.C., Memorandum Report 3876, 27 Nov. 1978.
  3. Hoare, C. A. R.; “Monitors: An Operating System Structuring Concept;” Commun. of ACM, vol. 17, no. 10; Oct. 1974.
  4. R. A. Parker, K. L. Heninger, D. L. Parnas, and J. E. Shore, Abstract Interface Specifications for the A-7E Device Interface Modules, Naval Research Lab. I Washington D.C., Memorandum Report 4385, 20 Nov. 1980.
  5. D. L. Parnas, Use of Abstract Interfaces in the Development of Software for Embedded Computer Systems, Naval Research Lab., Washington, D.C., Report 8047, 1977.
  6. D. L. Parnas, “On the Criteria to be Used in Decomposing Systems into Modules,” Commun. Ass. Comput. Mach., vol. 15, no. 12, p. 1053–1058, Dec. 1972.
  7. D. L. Parnas and H. Wuerges, “Response to Undesired Events in Software Systems,” Proc. Second Int. Conf. Software Eng., pp. 437–446, 1976.
  8. D. Reed and R. Kanodia, “Synchronization with Eventcounts and Sequencers,” Commun. Ass. Comput. Mach., v. 22, no. 2, pp. 115–123, Feb. 1979.

  1. On leave from University of North Carolina at Chapel Hill. Also at IBM Corporation, Federal Systems Division, Bethesda, MD.

Practical Decoupling Techniques Applied to a C-based Radio Driver

We advocate for spending time decoupling your firmware from the underlying hardware to enable portability and reusability. We often have developers tell us that they see the value in these outcomes, but they could use practical examples for how to effectively decouple firmware from the underlying hardware. Others have concerns about whether we can truly create portable APIs. While preparing …