Notes on Modular Design

H. Conrad Cunningham

10 June 2022

Copyright (C) 2015, 2017, 2018, 2019, 2022, H. Conrad Cunningham
Professor of Computer and Information Science
University of Mississippi
214 Weir
P.O. Box 1848
University, MS 38677
(662) 915-7396 (dept. office)

Browser Advisory: The HTML version of this textbook requires a browser that supports the display of MathML. A good choice as of June 2022 is a recent version of Firefox from Mozilla.

1 Notes on Modular Design

These lecture notes accompany the lecture notes on Data Abstraction [6]. They are based, in part, on an essay by Cunningham et al. [9], which draws on his and his former students research [7,8].

1.1 Chapter Introduction

In the provocative 1986 essay “No Silver Bullet—Essence and Accidents in Software Engineering,” software engineering pioneer Fred Brooks asserts that “building software will always be hard” because software systems are inherently complex, must conform to all sorts of physical, human, and software interfaces, must change as the system requirements evolve, and are inherently invisible entities [3] A decade later Brooks again observes, “The best way to attack the essence of building software is not to build it at all” [4]. That is, software engineers should reuse both software and, more importantly, software designs.

What was true in the 1980s is still true today. Although software development tools and practices have evolved, removing some of the “accidental” properties of software development, the essential difficulties remain. Complexity continues to increase. The interfaces to which software must conform continue to change quickly and increase in number. The requirements on software continue to evolve, driven by inexorable changes in the environment and increasing penetration of computerized processes into new aspects of society. Globalization generates new requirements, which arise from both new opportunities and new competition.

We often develop software systems in multiperson teams. In many cases, the teams are geographically distributed, perhaps even across national boundaries. Communication among team members adds complexity to software development.

How should we approach software development in this contemporary context?

As a starting point, let us again look to a software engineering pioneer: David Parnas. Parnas focuses on how to decompose a system into modules to achieve robustness with respect to change and potential reuse of software. He stresses the clarity of thought more than the sophistication of languages and tools.

Although Parnas and his colleagues published their ideas on modular specification in the 1970s and 1980s [2,12–15], the ideas are as relevant today as they were when first published.

1.2 Family of Table Implementations

These notes illustrate the approach using the development of a family of implementations of a Table Abstract Data Type (ADT).

A Table ADT is an abstraction of a widely used set of data and file structures. It represents a collection of records, each of which consists of a finite sequence of data fields. The value of one (or a composite of several) of these fields uniquely identifies a record within the collection; this field is called the key. For the purposes here, the values of the keys are assumed to be elements from a totally ordered set (i.e., each element is <, =, or > every element). The Table ADT’s operations enable us to store and retrieve a record using its key to identify it within the collection.

By approaching the Table ADT design as a software family, we seek to exploit the commonalities (the features that are “the same” for all likely implementations) while limiting the negative effects of the variabilities (the features that are potentially “different” among implementations) [5] on the development and maintenance of the software.

As in related work on this topic [7–9], here we use object-oriented programming concepts and the Java language to describe the implementation. However, we could also use similar concepts found in most procedural or functional programming languages.

1.3 Information-Hiding Modules

When programmers approach the design of a software system, they tend to break the system into several processing steps, similar to those in a flowchart, and define each step to be a module. This often results in design that is difficult to change when the requirements for the software change.

Parnas advocates a more general view of modules and an approach to decomposing a system that yields a system that is easier to modify. It seeks to isolate the aspects most likely to change inside a module, instead of spreading them across several modules.

1.3.1 Parnas principles: Modules and information hiding

Parnas defines a module as “a work assignment given to a programmer or group of programmers” [17]. It is desirable for programming environment and language features to support the programmers’ work on modules, but it is not essential.

In Parnas’s view, the goals of a modularization are to [12]:

  1. shorten development time by minimizing the required communication among the groups (independent development)

  2. make the system flexible by limiting the number of modules affected by significant changes (changeability)

  3. enable programmers to understand the system by focusing on one module at a time (comprehensibility)

To accomplish these goals, it is important that modules be cohesive units of functionality that are independent of one another. Parnas advocates the use of a principle called information hiding to guide decomposition of a system into appropriate modules (i.e., work assignments). He points out that the connections among the modules should have as few information requirements as possible [12].

Information hiding means that each module should hide a design decision from the rest of the modules. This is often called the secret of the module. In particular, the designer should choose to hide within a module an aspect of the system that is likely to change as the program evolves. If two aspects are likely to change independently, they should be secrets of separate modules. The aspects that are unlikely to change are represented in the design of the interactions (i.e., connections) among the modules.

This approach supports the goal of changeability (goal 2). When care is taken to design the modules as clean abstractions with well-defined and documented interfaces, the approach also supports the goals of independent development (goal 1) and comprehensibility (goal 3).

1.3.2 Table family: Modularization

For our purposes here, we consider the design of the Table family to have the following requirements [7–9]:

  1. It must provide the functionality of the Table ADT for a large domain of client-defined records and keys.

  2. It must support many possible representations of the Table ADT, including both in-memory and on-disk structures and a variety of indexing mechanisms.

  3. It must separate the key-based record access mechanisms from the mechanisms for storing records physically.

At the top level, the system seems to have four primary dimensions of likely change. We can make each of these the secret of an information-hiding module. The modules and their secrets are as follows [7]:

Client Record:
This module provides the key and record data types for the client-defined records. The client (user) of the Table family must provide an implementation of the module appropriate for the particular application; the other modules use these types. The secret of the module is the structure of the client’s record—including the identification of the key field, its data type, and ordering relation and identification of the non-key fields and their data types.
Table Access:
This module provides the client programs key-based access to the collection of (client-defined) records stored in the table. The secret of the module is the set of data structures and algorithms that provide the index for access to the records. For example, this might be a simple index maintained in a sorted array, a hash table, or a tree-structured index.
Record Storage:
This module provides the table with facilities to store and retrieve the table’s records using the chosen physical storage medium. The secret of the module is the nature of how the records are stored physically. For example, the physical storage might be a structure in the computer’s main memory or a random-access file on disk. (This module uses the facilities of the Externalization module to do the byte-level reading and writing of the records.)
Externalization:
This module provides facilities to convert “records” in memory to and from a sequence of bytes on the physical storage medium. The secret of this module is the byte-level nature of the physical data representation on the storage medium.

If we elaborate this design as a library—or API (Application Program Interface)—then the Table Access and Record Storage modules likely would be represented by various program units in the library. The Client Record and Externalization modules would be represented by specifications that the user of the library must implement.

Note: In our early work on this topic [8,9], we combine the Client Record and Externalization modules into one module. The presentation here follows our later work [7], which carried out a more refined analysis of the commonalities and variabilities present in this problem.

1.3.3 Perspective

In object-oriented languages, classes and modules can be easily confused because they are both self-contained units, and they do share some of the same goals and characteristics. The difference is, however, that a typical work assignment (i.e., module) that needs to support change is often larger than a single class; it may contain several related classes, and these classes should be designed and maintained as a unit.

Information hiding has, of course, been absorbed into the dogma of object-oriented programming. However, information hiding is often oversimplified as merely hiding the data and their representations [19]. The secret of a well-designed module may be much more than that. It may include such knowledge as a specific functional requirement stated in the requirements document, the processing algorithm used, the nature of external devices accessed, or even the presence or absence of other modules or programs in the system [15]. These are important aspects that may change as the system evolves.

Information hiding is one of the most important principles in software engineering. At first glance, it seems to be an obvious technique. However, further study reveals it to be a subtle principle that takes considerable practice to apply well in software design. As software developers, we should learn the principle and how to apply it effectively in a variety of circumstances. We also need to learn to design modules that are coherent abstractions with well-defined interfaces.

1.4 Abstract Interfaces

When programmers specify the interface for a class or other program unit, they typically identify the set of operations (procedures and functions) that can be called from outside the unit. That is, they consider the return type of each operation and its signature—the name and the number, order, and types of its parameters. This describes the syntax, or structure, of the interface. However, programmers also need to describe the semantics, or expected behaviors, of the operations explicitly.

1.4.1 Parnas principles: Interface specifications

Parnas and his colleagues advocate that 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 of his own program” (italics added) [2]. In addition to an operation’s signature, this list of assumptions must also include information about the meaning of an operation and of the data exchanged, about restrictions on the operation, and about exceptions to the normal processing that arise in response to undesired events.

In Parnas’s information-hiding approach, each module must hide its secret from the other modules of the system. The module’s secret is a design decision that changes from one implementation of the module to another.

To be useful, the module must be described by an interface (i.e., set of assumptions) that does not change when one module implementation is substituted for another. Parnas and his colleagues call this an abstract interface because it is an interface that represents the assumptions that are common to all implementations of the module [2,16]. As an abstraction, it concentrates on the essential nature of the module and obscures the incidental aspects that vary among implementations.

Parnas and his colleagues take an interesting two-phase approach to the specification and design of abstract interfaces [2], one that they argue is especially important in the design of interfaces to “devices” in the environment. The method constructs two partially redundant descriptions of an abstract interface. They are redundant because they describe the same assumptions.

First, the designer carefully studies the possible capabilities of the types of devices that might be used (or module implementations that might be needed) and then explicitly states in plain English the list of assumptions that can be made about all the devices (module implementations) in the set. This list is meant for people who are experts in the application domain, but who might not be skilled programmers. This plain English list makes invalid assumptions easier to detect.

Second, the designer constructs a list of the specific operations in the programming interface and describes the signature and semantics of each operation. Every capability implied in the specifications of the operations must be explicitly stated in the list of assumptions. These programming constructs can be later used in programs. (This second specification is like the specifications given in the notes on Data Abstraction [6].)

1.4.2 Table family: Module interfaces

1.4.2.1 Client Record

Consider the abstract interface for the Client Record module in the Table family example. We want, as much as possible, to let clients (users) of the Table family define their own record and key structures. However, the Table Access module must be able to extract the keys from the records and compare them with each other. Thus, the Client Record module must implement records that satisfy the following Client Record Assumptions:

  1. A record is an object from which a key can be extracted.

  2. Two keys can be compared using a total ordering.

In a Java implementation of the Table family, the programming interface for the Client Record module consists of two Java interfaces, each with one method as shown below.

    interface Comparable
        int compareTo(Object key) 
        // compares the associated object with argument key and 
        // returns -1 if key is greater, 0 if they are equal,
        // and 1 if key is less. 

    interface Keyed 
        Comparable getKey() 
        // extracts the key from the associated record 

Note: These notes follow our previous work [7–9], which uses an early version of Java. A design using a “modern” Java version would be be similar but could take advantage of generics, thus increasing type safety.

The built-in Java interface Comparable satisfies the requirement for the keys [7,8]. Any class that implements this interface must provide the callback method compareTo() that compares the associated object with its argument according to total ordering. Clients can use any existing Comparable class for their keys or implement their own in the Client Record module.

We introduce the Java interface Keyed to represent the type of objects that can be stored and retrieved by the Table Access module [7,8]. Any class that implements this interface must implement the callback method getKey() that extracts the key from the associated record. Clients must supply a class in the Client Record module that provides an appropriate implementation of the Keyed interface. The Table Access module can use this method to extract a key and then use the key’s compareTo method to do the comparison. The details of the record structure are otherwise hidden in the Client Record module.

An implementation of the Client Record module would normally consist of a class that implements the Java interfaces Keyed and a decision on how to represent the record’s keys. The latter decision might be to construct some class that implements the built-in Java interface Comparable or it might be to choose an existing built-in class that already implements Comparable.

1.4.2.2 Externalization

Now consider the abstract interface to the Externalization module. The Record Storage module must be able to write records to and read records from the physical locations on the chosen storage medium in an appropriate format. For in-memory implementations of the Record Storage module, this is not a problem; they can simply clone the record (or perhaps copy a reference to it). However, disk-based implementations must write the record to a (random-access) file and reconstruct the record when it is read.

In this design, we take a low-level approach. The Record Storage module may need to convert the client’s record to and from a sequence of bytes. Thus the Externalization module must implement records that satisfy the following Externalization Assumptions:

  1. A record can be converted to a finite sequence of bytes.

  2. A record can be reconstructed from a finite sequence of bytes. The process of converting a record to bytes and back results in an equivalent record.

  3. It is possible to determine the number of bytes in a sequence corresponding to a record.

In a Java implementation of the Table family, the programming interface for the Externalization module consists of a Java interface with three (callback) methods as shown below.

    interface Record 
        void writeRecord(DataOutput) 
        // writes the record to a DataOutput stream 
        void readRecord(DataInput) 
        // reads the record from a DataInput stream 
        int getLength() 
        // returns the number of bytes that will be written by 
        // writeRecord() 

We introduce the Java interface Record to represent the type of objects that can, if needed, be converted to and from a sequence of bytes [7,8]. This interface has the three methods writeRecord(), readRecord(), and getLength() to write the physical record, read a record, and return the size of the record, respectively.

The Record Storage module calls the Record methods when it needs to read or write the physical record. The code in the Record-implementing class (e.g., defined in the Externalization module) converts the internal record data to and from a stream of bytes. The Record Storage module is responsible for routing the stream of bytes to and from the physical storage medium.

An implementation of the Externalization module includes a class that implements the Java interface Record. In most situations, the same client record classes will need to implement both the Keyed and Record interfaces, effectively merging implementations of the Client Record and Externalization modules. As we see below, in some cases a second instance of the Externalization module classes will be needed for the physical records passed between the Table Access and Record Storage modules.

1.4.2.3 Record Storage

Now consider the abstract interface to the Record Storage module, which depends upon the Externalization Module. This module requires the records it stores and retrieves to satisfy the Externalization Assumptions above. The module must also satisfy the following Record Storage Assumptions:

  1. A record can be stored on the physical storage medium as a sequence of bytes at some location if the sequence is shorter than the specified maximum length.

  2. A record can be retrieved from the physical storage medium as a finite sequence of bytes from some location. The record retrieved is equivalent to the one stored at that location.

  3. It is possible to allocate and deallocate physical record locations on the physical storage medium.

The programming interface for the Record Storage module consists of a pair of closely related abstractions represented by the Java interfaces RecordStore and RecordSlot. These abstractions manage the physical storage facility; collectively, they have eight operations as we describe in other work [7].

Aside: Our 2010 paper on this topic [7] uses an abstract predicate isStorable(Record) to help formalize the semantics of the Record Storage module. This abstract predicate would be false when (a) a record cannot be stored at an allocated location for some reason and (b) there are no unused locations to allocate. In these notes, we simplify this condition as follows.

1.4.2.4 Table Access

Finally, consider the abstract interface to the Table Access module, which depends upon all three other modules. This module requires client records that satisfy the Client Record Assumptions and physically storable records that satisfy the Externalization Assumptions and are shorter than the maximum length supported by the Record Storage module (Record Storage Assumption 1). The Table Access module must also satisfy the following Table Access Assumptions:

  1. A key occurs at most once in the table.

  2. A record can be stored in the table using the record’s key if no other record with that key occurs in the table.

  3. A record stored in the table can be retrieved using its key. The record retrieved is equivalent to the one stored most recently with that key.

  4. A record stored in the table can be deleted from the table using its key.

  5. It is possible to determine whether there is record with a given key in the table, whether the table is empty or is full, and how many records are stored in the table.

The programming interface to the Table Access module consists of a Java interface Table that represents the Table ADT. In other work [7], we define this interface to have eight operations and extend it with several different iterator operations.

In most cases, the client records supplied to the Table Access module must satisfy the Externalization Assumptions as well as the Client Record Assumptions.

1.4.3 Perspective

Parnas’s ideas on abstract interfaces [2] have been refined by others and incorporated into methods such as Meyer’s design by contract [11]. Meyer’s approach involves specification of invariants, preconditions, and postconditions similar to the approach used in the Data Abstraction notes [6].

In other work [7,8], we give the semantics of the operations in these Table ADT modules in terms of formal design contracts and information models. The key contribution of that work is the specification of modules with abstract interfaces that enabled the separation of the key-based access mechanism in the Table Access module from the physical storage mechanism in the Record Storage module.

Parnas method of using two partially redundant descriptions has not been used extensively. It deserves more attention in a world where a program may require services from the interfaces of many other programs and, in turn, provide other programs interfaces to its services [18].

Like information hiding, design of elegant and effective module interfaces is an important skill that expert software developers should learn. Abstract interfaces and information hiding are the key concepts enabling the construction of software families.

1.5 Software Families

We often practice code reuse in an informal manner. When given a new problem to solve, we may find a program for a similar problem and, using a text editor, modify the program to get a solution to the new problem.

This may be a reasonable approach for small, simple programs in a situation in which it is legitimate to adapt the existing code, a solution is needed quickly, there is little concern about the efficiency or elegance of the program, and the program will only be used for a short period of time.

However, if these conditions do not hold, this undisciplined technique can lead to a chaotic situation where many versions of a whole program must be maintained simultaneously in source code. Changes and error corrections cannot be conveniently and reliably moved among the different versions as needed.

To overcome these problems, we should use a disciplined technique from the beginning.

Information hiding modules and abstract interfaces are the basic concepts needed to design multi-version programs. The information hiding approach seeks to identify aspects of a software design that might change from one version to another and to hide them within independent modules behind well-defined abstract interfaces. Because one implementation can be easily substituted for another, this type of design can be considered as defining a software or program family to use Parnas’s terminology.

1.5.1 Parnas principles: Program families

Parnas defines a program family as a set of programs “whose common properties are so extensive that it is advantageous to study the common properties of the programs before analyzing individual members” [13]. In his view, a family member is developed by incrementally identifying the common aspects of the family and representing the intermediate forms of the program as they evolve. These intermediate forms should be documented fully and saved for development of future family members. Instead of developing a new family member by modifying a previous member, the designer finds the appropriate intermediate representation and restarts the design from that point.

In Parnas’s module specification approach [13], which is based on the principles of information hiding and abstract interfaces, the technique is to define a software system by giving the “specifications of the externally visible collective behaviors” of the modules instead of the internal implementation details. It works by identifying “the design decisions which cannot be common properties of the family” and hiding each as a secret of a module.

1.5.2 Table family: Encapsulating variabilities

Again consider the Table family. The analysis of the problem domain led to a design in which the primary expected sources of change (i.e., the variabilities) are encapsulated within four information-hiding modules with carefully defined abstract interfaces. This generated a program family in which the different members vary according to their selections for the Client Record, Table Access, Record Storage, and Externalization module implementations.

In our previous work [8], we discuss a software family that includes two different Table Access module implementations; one is a simple in-memory index that uses sorted arrays of keys and binary search, and the other is an in-memory hashed index. Similarly, there are three implementations of the Record Storage module; two of these use in-memory data structures, and the third uses a random-access file on disk. A client can configure a system by combining implementations of the Table Access and Record Storage modules with an implementation of the Client Record and Externalization modules with appropriate definitions of the records and keys.

1.5.3 Perspective

Since Parnas’s paper [13] on the concept of program family first appeared, considerable interest has grown in what is now called a software product line [1,20] or software family (which we prefers). Parnas observes that there is “growing academic interest and some evidence of real industrial success in applying this idea,” yet “the majority of industrial programmers seem to ignore it in their rush to produce code” [16]. He warns, “If you are developing a family of programs, you must do so consciously, or you will incur unnecessary long-term costs” [16]. The importance of this issue has not diminished since Parnas’s papers on information hiding and program families a half-century ago.

1.6 Discussion

Three decades after Parnas first articulated the principle, he argued that information hiding is still “the most important and basic software design principle” [16]. Yet, he observed that “it is often not understood and applied” despite being the intellectual underpinning of recent ideas such as object-oriented and component-based programming. He laments that he commonly sees “programs in both academia and industry in which arbitrary design decisions are implicit in intermodular interfaces making the software unnecessarily hard to inspect or change” [16].

The basics of information hiding can be explained in one lecture in a typical college-level class. However, the principle “is actually quite subtle” and usually “takes at least a semester of practice to learn how to use it” [16].

Information-hiding modules must, of course, have interfaces that hide the secrets of the modules. The interfaces must be “less likely to change than the ‘secrets’ that they hide” [17]. This is not an easy process. The design of an appropriate abstract interface “requires both careful investigation and some creativity” [17] on the part of the software designer. As with information hiding, the concept of abstract interfaces is not difficult to explain. It is, however, a subtle concept that takes considerable practice to be able to apply well.

The principles of information hiding and abstract interface design are key underlying concepts for the construction of software families. However, design of a software family requires more. The designers must analyze the application domain and explicitly identify the common and the variable aspects of the family members [1,20]. The common aspects can be incorporated into the module structure and the variable aspects made secrets of modules.

The techniques and tools for building software product lines can be quite complex, involving special-purpose translators and configuration tools [1,20]. Hence, general product line construction is difficult to teach within the confines of a college course.

However, the software framework approach [10] is often more accessible to those first learning to design program families. An object-oriented software framework consists of design specifications and program code and builds upon standard object-oriented programming concepts learned as an undergraduate. A framework consists of a library of concrete and abstract classes (also interfaces in the Java sense) that forms the skeleton of systems within the family. Programmers develop applications of the framework by defining the needed concrete classes for their specific application.

The Table ADT family outlined in these notes uses the software framework approach. For more detail on the software framework design, see our paper [7].

Still yet, developing a non-trivial framework is not easy. Software developers must practice the framework design principles consistently over time to master the methods.

1.7 What Next?

TODO

1.8 Exercises

TODO: Add

1.9 Acknowledgements

In Spring 2015, I adapted this chapter from the papers [9] and [7]. Former graduate students Yi Liu, Jingyi Wang, and Cuihua Zhang were co-authors on one or both of those papers. Former colleague Robert Cook and former graduate student “Jennifer” Jie Xu made suggestions for improvement of paper [8], which is an earlier version of [7]. Former graduate students Chuck Jenkins and Pallavi Tadepalli also made useful suggestions on [7].

Jingyi Wang implemented a version of the Table framework for her MS project in 2000. Some of the ideas reflected in the Table framework arose from earlier MS projects on related frameworks by graduate students Wei Feng, Jian Hu, and Deep Sharma during the 1990s.

In Spring 2017, I adapted the notes to use Pandoc. Subsequently, in 2017, 2018, and 2019, I continued to update the format of the document to be more compatible with he evolving document structures for my course materials.

I retired from the full-time faculty in May 2019. As one of my post-retirement projects, I am continuing work on possible textbooks based on the course materials I had developed during my three decades as a faculty member. In January 2022, I began refining the existing content, integrating separately developed materials together, reformatting the documents, constructing a unified bibliography (e.g., using citeproc), and improving my build workflow and use of Pandoc.

I maintain this chapter as text in Pandoc’s dialect of Markdown using embedded LaTeX markup for the mathematical formulas and then translate the document to HTML, PDF, and other forms as needed.

1.10 Exercises

TODO: Add

1.11 Terms and Concepts

TODO

1.12 References

[1]
Mark Ardis, Nigel Daley, Daniel Hoffman, Harvey Siy, and David Weiss. 2000. Software product lines: A case study. Software—Practice and Experience 30, (May 2000), 825–847.
[2]
Kathryn Heninger Britton, R. Alan Parker, and David L. Parnas. 1981. A procedure for designing abstract interfaces for device interface modules. In Proceedings of the 5th international conference on software engineering, IEEE, San Diego, California, USA, 195–204.
[3]
Frederick P. Brooks, Jr. 1987. No silver bullet: Essence and accident in software engineering. IEEE Computer 20, 4 (1987), 10–19.
[4]
Frederick P. Brooks, Jr. 1995. ’No Silver Bullet’ refired. In The mythical man month (Anniversary). Addison-Wesley, Boston, Massachusetts, USA.
[5]
James Coplien, Daniel Hoffman, and David Weiss. 1998. Commonality and variability in software engineering. IEEE Software 15, 6 (1998), 37–45.
[6]
H. Conrad Cunningham. 2022. Notes on data abstraction. University of Mississippi, Department of Computer and Information Science, University, Mississippi, USA. Retrieved from https://john.cs.olemiss.edu/~hcc/docs/DataAbstraction/NotesDataAbstraction.html
[7]
H. Conrad Cunningham, Yi Liu, and Jingyi Wang. 2010. Designing a flexible framework for a table abstraction. In Data engineering: Mining, information, and intelligence, Yupo Chan, John Talburt and Terry M. Talley (eds.). Springer, New York, New York, USA, 279–314.
[8]
H. Conrad Cunningham and Jingyi Wang. 2001. Building a layered framework for the table abstraction. In Proceedings of the ACM symposium on applied computing, Las Vegas, Nevada, USA.
[9]
H. Conrad Cunningham, Cuihua Zhang, and Yi Liu. 2004. Keeping secrets within a family: Rediscovering Parnas. In Proceedings of the international conference on software engineering research and practice (SERP), CSREA Press, Las Vegas, Nevada, USA, 712–718.
[10]
Ralph Johnson and Brian Foote. 1988. Designing reusable classes. Journal of Object-Oriented Programming 1, 2 (1988), 22–35. Retrieved from http://www.laputan.org/drc/drc.html
[11]
Bertrand Meyer. 1997. Object-oriented program construction (Second ed.). Prentice Hall, Englewood Cliffs, New Jersey, USA.
[12]
David L. Parnas. 1972. On the criteria to be used in decomposing systems into modules. Communications of the ACM 15, 12 (December 1972), 1053–1058.
[13]
David L. Parnas. 1976. On the design and development of program families. IEEE Transactions on Software Engineering SE-2, 1 (1976), 1–9.
[14]
David L. Parnas. 1979. Designing software for ease of extension and contraction. IEEE Transactions on Software Engineering SE-5, 1 (1979), 128–138.
[15]
David L. Parnas. 1979. The modular structure of complex systems. IEEE Transactions on Software Engineering SE-11, 13 (1979), 128–138.
[16]
David L. Parnas. 2001. Software design. In Software fundamentals: Collected papers by David L. Parnas, D. M. Hoffman and D. M. Weiss (eds.). Addison-Wesley, Boston, Massachusetts, USA.
[17]
David L. Parnas. 2001. Some software engineering principles. In Software fundamentals: Collected papers by David L. Parnas, Daneil M. Hoffman and David M. Weiss (eds.). Addison-Wesley, Boston, Massachusetts, USA.
[18]
James Waldo. 2001. Introduction: A procedure for designing abstract interfaces for device interface modules. In Software fundamentals: Collected papers by David L. Parnas, Daniel M. Hoffman and David M. Weiss (eds.). Addison-Wesley, Boston, Massachusetts, USA.
[19]
David M. Weiss. 2001. Introduction: On the criteria to be used in decomposing systems into modules. In Software fundamentals: Collected papers by David L. Parnas, Daniel M. Hoffman and David M. Weiss (eds.). Addison-Wesley, Boston, Massachusetts, USA.
[20]
David M. Weiss and Chi Tau Robert Lai. 1999. A family-based software development process. Addison-Wesley, Boston, Massachusetts, USA.