8.14.0.4

IV Programming Interfaces🔗

    13 The Nature of Interfaces

      13.1 An Interface is the Common Ontology for Clients and Server

    14 Encapsulation via Interfaces

      14.1 Interface Inspections for Encapsulation

      14.2 Encapsulating Properly

    15 Designing an Interface

      15.1 Inspecting Simple Interface Designs

    16 Expressing an Interface

    17 Interface Inspections, Systematically

      17.1 Inspections and Design Alternatives

 

 

What is a software component? How are they developed? Used?

As the preceding chapter suggests, the first step of any software development process identifies all the major components of the system, at least as much as possible. “Identifying a component” means picking a title or name; writing down a purpose statement; and perhaps formulating an informal description of the functionality (or other attributes). Once this much is worked out—with formal documents or an informal README file or as a thought in someone’s head—the team’s developers switch from their role as architect into a role of component creator.

In essence, a component is the location of a major design decision, concerning either an outward-facing piece of software or an internal one needed for some use-cases. A design decision means choosing one of several alternatives: the data chosen to represent some particular information or the data chosen to represent intermediate results; the interpretation mapping data to information and vice versa; some examples if the data definition is complex; plus an expression of functionality signatures.

Creating a single container for a design decision should mean that any necessary revisions of the decision affect only this one place. That is, if unforeseen circumstances reveal the need to implement an alternative choice, a developer can go to this container and make appropriate changes only there. And ideally, such changes should not affect the functionality of the rest of the system. Software developers speak of encapsulating a design decision or of just encapsulation.

Realizing encapsulation of a design decision demands two steps. First, the developers must separate the implementation details of the component from how others perceive and use the component, that is,
  • the interface, i.e., how others view the component;

  • the implementation, i.e., how a developer implements the design.

Most software engineering literature says an interface describes the server component and those that use it are called client components. The creators of the client components are consumers of the server component’s interface. In this chapter, we call them consuming developers or consumers.

Figure 18 displays a schematic diagram of the arrangement. A component’s interface shields the internal details—hidden code of the server component—from the rest of the system and especially all those client components that need to use the functionality of the component.

Figure 18: An interface for hiding a component’s internals

Second, everyone else working on the system must develop to the interface of the component—not its internals—whether the project is written in a typed language with some support for interfaces or in an untyped language without any idea of an interface. This understanding of interfaces—as a policy of visibility and interaction between pieces of code—makes up half of the design of interfaces. The other half concerns the expressiveness of the interface, its convenient use, and controlling the integrity of its use.

This chapter focuses on the design of component interfaces and its consequences; the next chapter is about implementing the hidden details with code that people can understand. Designing a component interface is the key step to designing and implementing the component. It is thus often equated with programming components, hence the title of this chapter. The remaining sections introduce the proper meaning of encapsulation, an approach to designing interfaces, and ways of expressing complete interfaces. In parallel, the sections also illustrate how to inspect interface designs and what such inspections reveal about system-level, architectural decisions.

13 The Nature of Interfaces🔗

What are interfaces? How do they relate components?

While a component consists of code, an interface is a document that explains how this code should interact with the code of other components. As such they inform developers of client code about pieces of the data representation(s) hidden in the component; about the functionality that can create, instantiate, inspect, and manipulate these data representations; about conditions that these operations must obey; and perhaps even about non-functional aspects, such as resource consumption and management. Everything else remains hidden from the developer of client components.

These documents come in many different forms and shapes. In some cases, the document is just an informal description. In some research languages, the interface is just code. For typed mainstream languages, an interface document mixes informal descriptions with language constructs that allow the some amount of compile-time checking of properties.

Java serves as a good example of this common mixture. Its interface construct allows developers to write down method signatures and even default implementations and to tell developers of client code to program to those. A Java interface does not suffice, however, to describe the interfaces between components in the sense spelled out above. For example, if a method’s input is signature int x but a small natural number is required, a Technically speaking, Java lacks a contract system. developer has to add an informal comment—using, say, JavaDoc—that x must be greater or equal to 0 and smaller than some other number. Similarly, consider an interface that specifies three methods—start, extract, and endbut also requires that start is called at most once and before extract and end, the developer is again forced to describe this relationship in informal prose. In short,

a component interface is more than a Java interface.

To avoid confusion, we may occasionally use “Interface” using the capital letter to emphasize the general nature of its meaning instead of the narrow meaning of Java’s (and other languages’) interface.

In order to understand how interfaces relate components, we need to take a close look at components. For a homework in an introductory course, a component may For an example, take a look at the interface of an Android media player component. consist of a single class. A follow-up course may ask students to combine a class with a Java-style interface—for practice. In a professional setting, a developer may have to read an interface document of many pages to understand a component that comes with a dozen classes or even more. On occasion, a component may itself play the role of an interface, a situation that figure 19 captures.

Figure 19: A component as an interface between two system layers

When a component is just a single class, its code is the interface for all the objects created from this class. For example, in Java all public pieces are available to developers of client code in different packages; all private pieces remain hidden; the class encapsulates these pieces. And still, a client programmer must read the code to understand how the object functions.

A (class) framework—or just library in ancient days—tends to come with an interface document that reveals nothing about the code per se and that assumes little to nothing about the client code. Such frameworks tend to consist of many classes or modules. In Java, it is also going to include interfaces, though as mentioned, they do not describe the component’s interface, neither individually nor collectively. Introductory courses tend to use such frameworks from the standard library of the chosen language.

Finally, some bespoke systems may contain components that play the role of an interface. In this case, these interface components sit between two layers of components, and making their code available is needed to completely clarify some rules of interaction. This kind of component is typically beyond lower-curriculum courses and deserves an illustrative explanation.

So let’s return to figure 19, which illustrates this idea with a diagram. Concretely, the diagram can be understood as summarizing how the game server for Ticket to Ridethe sample project from the preceding chapter—and its clients interact. Recall the architecture from the right side of figure 16. It shows how the game server and the hacker’s game players live on distinct computers, interacting via means of remote communication. Figure 17 displays a construction plan. Focus on just the interface component in the third layer from the bottom; it is supposed to specify how the referee from the game server interacts with the logical player(s). As the dependency arrows in the diagram imply, designing and expressing this interface relies on a thorough understanding of several other components: the rule checker, the game state, and those representing the game pieces. In this sense, the player-referee interfaces consist of not just something like a Java interface but of knowledge about several specific components.

13.1 An Interface is the Common Ontology for Clients and Server🔗

In some language contexts, developers think of component interfaces as essential linguistic constructs for separating the specification of functionality from its implementation; in others, it is just a mental separation of such pieces. While this separation is definitely an important aspect for all kinds of reasons—from clarity of purpose to separate compilation and variability of implementation—this view undercuts that every interface—not just external ones—is primarily for human consumption. The true purpose of an interface is to establish a common ground for conversations between the producing and consumning developers of a component.

A consumer must completely understand the meaning of the pieces of an interface to exploit the functionality of the server component properly. Typical uses compose the pieces of functionality from an interface where “composition” may mean a series of statements, including loops and conditionals, nested expressions formed from the exported methods and functions; or any other form of a composite phrase. No matter what, from a language perspective, an interface provides a vocabulary for creating “sentences” and “paragraphs” from the visible parts of a component. functions and methods. This perspective suggests the following re-statement, using the terminology of linguists:

an interface provides a common ontology for the producers and consumers of a server component.

“Common ontology” is a concise phrase for saying that an interface determines the precise meaning of the specified vocabulary. Here “vocabulary” denotes the words (names of types; function and methods names; the pieces of their signatures; and so on) mentioned in the interface. And “determines the meaning” says that an interface must make sure a reader understands how to use these words properly, especially their proper use in context, their relationships, and anything else that may affect communication between components.

To make this concrete, consider a software system that controls an autonomous vehicle. Such a system must deal with distances. The very word—or an allusion to the concept—will show up in some interfaces. The developers have to agree on its meaning. They can ask questions such as
  1. distance from where to where?

  2. distance measured in terms of which units?

Question 1 addresses the difference between theory and practice of mechanics. In a theoretical course on mechanical physics, calculations tend to assume an object is a point. A software developer in an autonomous vehicle team cannot make such an assumption; the vehicle has an extent and that extent must be taken into account. So the answer to the question clarifies on which two points the distance measurement is based. Question 2 is especially critical in a country such as the United States, which has not accepted the metric system but whose engineers often work with colleagues in countries that have.

So imagine yourself encountering the following interface element during a design inspection:

  interface Vehicle:

    ...

    int moveRight(int distance)

    ...

You must insist on a clarification from its creator. While a presenter may verbally clarify the interpretation, you must further insist that this clarification becomes a part of the method’s purpose statement. Conversely, it is easy to see that without a clarification, things can go horribly wrong.

A common ontology may require a lot more than a simple comment that explains the meaning of a parameter. Let’s once again take a close look at what a “hacker” needs to understand in order to participate in the board-game tournaments of our running example. It is best to start with a close look at figure 17, focusing on the player interface component. As the inspection of the construction plan revealed, the interface is best understood in terms of the game state to which a player component has access. In turn, this data structure refers to the map, the colored cards, the destinations, and the rails. A “hacker” can implement a player only with a complete understanding of these pieces. But this is not all. Additionally, a “hacker” must also know the protocol of how the referee interacts with the player.

Here then is a list of all the pieces that belong to the common ontology of our running case study, starting with the least important but not to be overlooked piece:
  • the rails

  • the cards

  • the destinations

  • the map

  • the game state

  • the protocol: how the methods are called and in what order.

To drive home how all this matters, consider the methods for starting a railroad game. A refined design may consist of two methods, whose plain signatures in a Java-like programming notation are like this:

  void mapForTheGame(GameMap m)

  

  Set[Destinations] choose(Set[Destinations] from)

Now consider the following questions about this variant:
  • Which set of Destinations should choose return? The ones that the player picked or the ones the player rejected?

  • Which call order makes the most sense for these methods? Should the player know that mapForTheGame is called only once?

These questions illustrate just how important it is to establish a comprehensive understanding for the vocabulary of an interface, to nail down the meaning of all functions, methods, and other pieces of an interface. Put differently, the answers to such questions make up an interface; otherwise it does not provide a common ontology.

Exercise 8. Provide answers for the above two questions. Justify your answers. Work through these points with a partner.

Different kinds of interfaces call for different kinds of common ontologies. For library interfaces and for the interfaces of plug-in frameworks, the common ontology is often the responsibility of its developer, and the consumer is typically an anonymous developer. Ensuring that the interface is properly explained calls for thorough in-person inspections and reactions to on-line reviews. For bespoke interfaces specifically created for a software system, the producer of the server module and its consumers may jointly develop the common ontology; for an inspection, they may wish to get a third party involved to overcome “group think.” When the consumer of a bespoke interface is unknown, the authors of the interface document should revisit and inspect it with the help of actual an end-users once the product has been soft-launched.

Reading

Ajay Harish. When NASA Lost a Spacecraft Due to a Metric Math Mistake. 2021.—Harish summarizes a NASA crash story from the late 1990s, when the agency launched the Mars Climate Orbiter. The Orbiter, a space probe, Harish at Simscale was to explore the Martian atmosphere and surface changes. In burned up and broke into pieces, because its software developers had not developed a common ontology. The story is widely studied in engineering and software circles; also research the story on-line.

14 Encapsulation via Interfaces🔗

While equipping a component with an interface has the primary objective to create a common ontology for producers and consumers, it also means that the existence of an interface relieves developers from reading the entire code of a component just because they need its services. Conversely, a component producer can rely on the existence of an interface in two ways. First, if the interface is written down up front, it can guide the implementation effort. See the next chapter for this idea. Second, the producer can use the interface to isolate design decisions from the rest of the system and thus keep open the option to change them in the future.

As mentioned, developers and programming language researchers speak of encapsulation or say that the design decision is encapsulated. Language designers recognized the importance of encapsulation a long time ago and added various forms of syntactic support to express and check some aspect of interfaces as part of the compilation process. For example, Simula’67 implemented private, protected, and public class annotations by the early 1970s so that programmers could separate what consumers should read from what they could skip. By the 1990s, typed programming languages offered linguistic constructs for specifying the interface of a component separately from the implementing code. Developers working with untyped programming languages must establish and follow some () discipline to express the same kind of separation.

Encapsulation is far more, however, than annotating the members of a class or writing an interface for a module. If encapsulation means “housing a design decision in a single hiding place,” then mere syntactic annotations simply do not suffice. To capture this difference, researchers recommend the simple policy of

hiding those pieces of knowledge of a component that restrict future changes to a data representation to this one component.

This kind of knowledge encompasses many parts, including the chosen data representation, its interpretation, some functionality, and much more.

14.1 Interface Inspections for Encapsulation🔗

To gain a solid understanding of the encapsulation concept, let’s study yet another game that could be used in the context of the running game-server example (see A Sample Project: Analysis, Discovery, Planning):

Qwirkle is a game that challenges players to construct an unbounded, contiguous arrangement of rectangular tiles. Each tile displays a shape in one of six colors. A player may add a tile to the map as long as one of its sides is aligned with the side of an already-present tile. Furthermore, the new tile must satisfy certain constraints regarding the shapes of all neighboring tiles, those to its left, right, top, and bottom.

Figure 20: Encapsulating knowledge (a Qwirkle map data representation)

This description clearly identifies four pieces of information: the arrangement, which consists of tiles plus their shapes and colors. Less obviously, it includes the information concept of neighbor, that is, that each tile has at least one and up to four neighbors. One straightforward way to represent neighbor uses a notion of coordinate, associating each tile in the arrangement with one place.

Figure 20 presents a plan in the form of a doodle diagram. It sketches the four concepts and their relationship. Each box denotes a data representation of an information concept. The Map box represents the arrangement of tiles; it suggests the use of a collection to create an association between Tiles and Coordinates. The former combines an instance of Color with an instance of Shape, just as the informal description indicates.

The intention behind such a diagram is to convey, from this perspective, several important ideas to the future maintainer of this component
  1. Map is the key class, and all knowledge about the tile arrangement is found there;

  2. similarly, Coordinate, Tile, Color, and Shape are the classes that collect the knowledge about the respective information concepts; and

  3. if any client module needs a service about any of these five concepts, it must consult the respective class.

Instead of discussing encapsulation abstractly, let’s see how a code inspection might deal with the concept: This conversation is a simple code walk. See the next chapter for many more examples.

Hi. I am presenting the design of the central data representation for the Qwirkle game. Here is the class diagram (figure 20).

Could you explain the Coordinate class first? It looks suspicious because it comes with getters for the X and Y fields?

Sure. Here is the code for the coordinate class (figure 21). It comes with a purpose statement.

This is a comprehensive purpose statement with the inclusion of the second line. But what is the interpretation of a Coordinate object?

  // represents either the location of tiles in the map

  // or: places where a player may wish to place a tile

  class Coordinate {

    private int x;

    private int y;

  

    ...

    public int getX() { return x; }

    public int getY() { return y; }

    ...

  }

Figure 21: A Java-style class for representing coordinates

We stop here and look back. The panel takes control of the code inspection because the diagram alone may suggest a problem. While the presenter and the panel could discuss this potential problem abstractly, working with the actual code is best in this situation.

Interpretation?

Well, an X-Y pair may denote Cartesian or computer coordinates.

Ah, we had the standard computer-graphics meaning in mind.

This statement should be added to the class so that a reader of the code doesn’t have to dig through the entire class to find out.

I will make sure an interpretation gets added.

Why does the class include plain getters for the fields?

The two integers are private. But the Map class needs access to their values.

What for? And how are they used?

Here is the code for Map (figure 22).

Where do the getters used?

All the computations matching a tile to its potential neighbor use the getters.

Let’s take a look.

Stop! Can you already guess what the panelist is getting at?

  class Map {

    private Assoc<Tile, Coordinate> assoc = ...

     ...

      // would `toBePlaced` at coordinate `atC` match

      // the color and shape of its up neighbors

      Coordinate up     = new Coordinate(atC.getX(), atC.getY() - 1);

      Is<Tile> neighbor = this.assoc.retrieve(up);

      Boolean matchesUp = !(neighbor.none()) && this.match(neighbor.some(), atC);

     ...

  }

Figure 22: A Java-style method in the Map class for Qwirkle

It’s probably best to look at the computation that makes sure tiles match an up neighbor. These three lines are the key.

This comment cleanly separates one direction from the other. Can you explains the three lines?

The first line computes the place that is exactly above the given coordinate.

How does this computation work?

It retrieves X and Y parts, subtracts 1 from the latter, ...

Stop. Why does it subtract 1?

Isn’t it the natural way to determine the Y part of the place above the given c?

Only if the person who reads the code of Map also knows the interpretation of the Coordinate class

Oh.

.. which isn’t even written down.

At this point, the panelists have clarified why the Coordinate class does not encapsulate the coordinate information concept from the problem statement. Even though the x and y fields are private and thus hidden, exposing plain getters leaks knowledge that should stay internal to the class, specifically its interpretation. Here the presenters know that the interpretation of Coordinate is the normal computer-graphics meaning, that is, if y1 < y2, then y1 is above y2. Thus computing atC.getY() - 1 gets the correct result in this case. If, for some reason however, the instances of the Coordinate class should be interpreted as ordinary Cartesian coordinates, changing just Coordinate will not work.

14.2 Encapsulating Properly🔗

The code walk’s focus on encapsulation leads to a general lesson about. The developer of a component who wishes to encapsulate knowledge must ask

whether a change in interpretation or any internal code can possibly affect application code, i.e. code that uses the functionality of the class or module.

If so, we say that the interface is too wide. Conversely, a component’s interface should be sufficiently narrow so that essential knowledge doesn’t leak. “Narrow” means do not expose functions or methods that enable client programmers to exploit the internals of the component. While fields and some methods are obviously internals, the informal data interpretation may have to count, too, especially if the interface includes plain getters.

In Java, and many object-oriented languages, the most basic meaning of “interface” is the collection fields, methods, and other pieces of an object design that are accessible to a client programmer. This collection may or may not come with a Java interface or an Interface document; indeed, a Java component may need both.

  interface ICoordinate {

    // INTERPRETATION

    // a data representation

    // for Map coordinates

    ...

    // the place logically

    // above `this` coordinate

    Coordinate above();

  

    ...

    Coordinate below();

     ...

    Coordinate left();

  

    ...

    Coordinate right();

  }

          

  class Coordinate {

    private int x;

    private int y;

    ...

    public Coordinate above() {

      int newX = this.x;

      int newY = this.y-1;

      return

       new Coordinate(newX, newY)

   }

    ...

  }

  

  

  

  

  

Figure 23: A Java-style class for an encapsulated coordinate representation

Let’s take a look at how a Java developer could react to the code inspection. The minimal reaction is to remove the getters from the class; in their place, the class comes with methods that determine the coordinates of neighboring places. For example, the code snippet of the Map class (in figure 22) would use the method above to compute the value of matchesUp. See the right-hand side of figure 23 for the result of such a revision.

The left-hand side of figure 23 displays a second, complementary way of reacting to the criticism—if the coordinate class should be considered a component. The displayed interface hints at what an Interface document might look like. Here the Interface consists of a simple data An instructor of a course on object-oriented programming may ask students to design an interface per class as just such an exercise. interpretation statement, plus the signatures and purpose statements of methods that compute the four coordinates of immediate neighbors. That is, the document consists of a mix of actual code, say, a Java interface and plain English. The next section tackles the task of how to design Interfaces for different kinds of components.

15 Designing an Interface🔗

Figure 18 shows two ways of looking at an interface: as the producing developer and as the consuming one. From the perspective of the first, it is the document that guides the implementation. The component must live up to this interface; it must implement the promised functionality and everything else that the document says it delivers. From the perspective of the second, it is the document that tells everyone how to use the component.

As such, the figure suggests that an interface is the result of a “negotiation” between the two parties. In many cases, a consumer would like to get a lot more functionality than the producer wishes to show. The consumer may even like to know more about the design decisions and the resulting implementation details than the producer wishes to make publicly visible.

The word “negotiation” seems to imply that the producer knows the consumer but this clearly isn’t always the case. A component might be one-of-a-kind, a part of a large bespoke system, and the two parties know each other to actually negotiate the interface. Historically, component meant a library. Once developers primarily used object-oriented languages, these components became frameworks, such as the game server analyzed in the preceding chapter. Given this variety, the design of an interface is clearly not going to follow one and only way either.

Since the role of an interface includes encapsulation of design decisions, understanding the interface-design process can start with asking how future changes affect an interface. When a change of a bespoke component affects its interface—the result of an actual negotiation between two parties—the producer can alert the consumer, who, in turn, can search the code base for potential problems. For example, if some visible function must consume an additional argument, the creator of client just looks for applications of this function and fixes those call sites.

By contrast, the producers of a library or framework rarely know their consumers and must therefore be much more careful about changes. In many cases, changing the interface is nearly impossible. External programmers and developers may have created extensive bodies of code relying on the published interface. Hence, removing functionality is essentially impossible, which imposes constraints on changes to internals of the component. Adding named functionality to the interface may interfere with names that client programmers chose for something. Finally, even changing a “bug”—meaning a contradiction between interface elements and implementation—may affect the behavior of client code; consumers may have developed elaborate work-arounds for such short-comings.

Exercise 9. Research how the designers of Java introduced modified libraries when they added expressive power to the type system—parametric-polymorphic types, often called generics. By adapting the run-time libraries interfaces, they could promise that the compiler would catch certain classes of problems at compile time.

In sum, designing library and framework interfaces requires considerations in two directions—producers and consumers; demands some foresight into what the latter may need from the former; and in general just deserves great care. With this much understood, let’s consider what goes into the design of an interface: the content and the process.

Content The preceding sections implicitly suggest a number of items that show up in basically every interface:
  • It should start with a succinct, focused purpose statement.

  • When needed, it should include a possibly partial interpretation of the implemented data representation(s).

  • It should specify the functionality that the component makes available. These specifications typically contains the following kinds of functionality, though not necessarily all:

    • ways to create or initialize a component;

    • ways to observe properties of the state of the component;

    • ways to extract properties from the state of the component; and

    • ways to modify the component’s state.

    Each of these specifications states the name of the functionality; its purpose (unless the name is truly bringing across what the function computes); a signature, possibly expressed as a comment if the underlying language is untyped; and any constraints that the signatures cannot express.

  • Finally, an interface often comes with constraints on pieces of functionality or the interaction of various pieces of functionality.

While the above list covers a lot of ground, interface documents differ from case to case, in particular, depending on the role they play relative to a system and depending on whether their creators know the consuming developers.

Figure 24: Feedback loop for the design of interfaces

Process Given the nature of an interface as the result of a “negotiation,” the design process should involve both parties. The creator is clearly in charge of proposing drafts of an interface. But, a representative consumer must often, and perhaps continuously, check whether it is possible to get work done with the proposed interface. If the consumer representation determines that the interface lacks functionality or is too inconvenient to use, it is back to the drawing board for the producer of the component.

What these sentences describe is a feedback loop. Figure 24 shows the essential elements of this interface-design feedback loop. The process starts with the vague idea cloud on the left, which symbolizes the perceived purpose of the component and its publicly visible pieces. Once the developer writes down a draft of the component’s interface, someone must inspect this draft from the perspective of writing a client component. This evaluation step results in suggestions on how to modify either the starting point or the written document or both. Once the interaction has converged on an interface document, the producing developer can (finish) implement(ing) the component’s internals.

Keeping this feedback loop in mind, let’s take a close look. Like for any building block in the world of software development, the design process for interfaces must start with a focused purpose statement. Without such a statement, it is too easy to pack too much functionality into an interface or even too little. That is, when during the somewhat parallel development of the component and its interface questions arise as to where functionality should reside, a focused statement can help answer them.

An alternative look at the sample problem from the preceding section illustrates this point. The panelists could have asked the creators of the Coordinate class what the purpose statement exactly means. Concretely, they could have asked

“if the class represents coordinates, why are there basic computations on coordinates in the Map class?”

Once the inspection clarifies that the purpose statement means basic computations on coordinates must happen in the Coordinates class. This focused purpose statement also answers the next question, namely, which functionality to add in place of these generic observers. Since the presenter’s client needs the neighbors of a coordinate, adding four corresponding methods solves the problem.

Generally speaking, a focused purpose statement helps with the balancing act of deciding
  • which parts of the component must remain hidden; and

  • which parts to make public and what explanations are needed so that client programmers can get their work done.

Unsurprisingly, at this early stage, it is also common to consider alternatives, mostly for the proposed pieces of functionality.

For the first aspect, the authors of interfaces must decide how much of the data interpretation to encapsulate. It is the interpretation that really describes hidden pieces. Take the Coordinate class as an example again, a client programmer doesn’t need to know whether the coordinates are of the Cartesian or computer-graphics kind. Even the existing purpose statement implies this much. Hence the interface designed should not make any functionality available that allows client code to determine the Y dimension of a coordinate.

For the second aspect, the authors of the interface must imagine the use contexts with the goal of identifying desirable functionality. In the case of one-of-a-kind components, the system specification usually provides a sufficient number of hints. The code inspection of the preceding section exposed one concrete use case, and this use case also clarifies the exact functionality needed from the Coordinate class. When it comes to libraries, the interface authors must use their imagination as to how developers may wish to use the component’s functionality.

In sum then, the process works as follows:
  1. start by writing down a focused purpose statement

  2. imagine a client component by name

  3. informally write down descriptions of public information:

    • a data interpretation;

    • the purpose of the pieces of functionality;

    • signatures, formal ones if a typed language is used;

    • any additional constraints on the latter.

  4. sketch use cases based on the informal interface description;

  5. formulate alternatives, based on these use-case considerations;

  6. inspect the informal design and request comments

  7. re-start at step 3 or even step 1, until the process converges.

Once an inspection does not uncover major gaps or problems, a developer will begin to work on expressing the interface properly and on the implementation. Right now, though, it is time to work through a simple example.

15.1 Inspecting Simple Interface Designs🔗

The diagram of figure 20 is a good starting point for illustrating how a software developer may develop an interface. Recall that the figure relates data representations of all the Qwirkle game pieces, plus the synthetic Coordinate class and their relationships. So imagine that a developer for our game-server start-up wishes to write down an Interface for this component. Notice the capital “I” because this section is not about a Java interface even though it ends up using Java notation.

Following the above summary, the developer states a purpose:

The component represents all basic game pieces for a variant of the Qwirkle game.

The diagram identifies the data representations needed based on the problem statement, plus the Coordinate class, which is needed to refer to places on the existing configuration of tiles or free spaces next to it.

As for clients of this component, two come to mind: the referee that resides on the game server and the player that the start-up team must produce to test the referee. Both need to create, inspect, and manipulate maps and the tiles that make up these maps. The referee must maintain the map that all players see; as such it must be able to create the initial map, add tiles on behalf of players, check the legality of such player requests, and so on. A player component may need to compare the value of two distinct maps, each created by adding different tiles to the map that it receives when it is told to take a turn. These rough sketches of client suggest that three classes definitely must be public: Map, Tile, and Coordinate.

  

  **Component** : represents  all basic game pieces for Qwirkle

  

  Its public classes and their public methods:

  

  - Map : a data representation of a contiguous collection of tiles

  

    - create : creates the map, from a starter tile, so that we do

      not have to deal with the degenerate case of an empty maps

  

    - adjacentPlaces : delivers the coordinates where tiles can

      be added to `this` map

  

    - addTile : adds a tile to `this` map at coordinate `c` that

      is adjacent to an existing tile

      CONSTRAINT: `c` is in `this.adjacentPlaces()`

  

    - fits : checks whether a tile would fit at coordinate `c`

      according to the rules

  

    - render : display `this` map as an image

      RATIONALE: looking at a visual presentation of facilitates

      the creation of unit tests

  

  - Tile : a data representation of (valid) tiles

  

    - create : creates a tile from a shape and a color;

      CONSTRAINT: consumes plain strings, then checks validity

  

    - getters for shapes and colors (returns strings)

  

  - Coordinate : a data representation for places on a Map

Figure 25: A draft interface for the basic games pieces component

Equipped with a purpose statement and a quick look at client components, our imaginary developer can enumerate some basic and public pieces of functionality for the three classes that are going to be public. Figure 25 displays the result of this first attempt.

  // ignore the !isPresent() case

  Placement pickNextCoordinate() {

    Stream<Coordinate> free = map.adjacentPlaces().stream();

    Coordinate best_coord   = free.findFirst().get();

    Tile best_tile          = this.availableTiles.get(0);

    for(Tile t : this.availableTiles) {

      Stream<Coordinate> legal =

        free.filter(c -> map.fits(c,t));

      Coordinate best_t  =

        legal.max((c, d) -> evaluate(c,t) > evaluate(d,t)).get();

      if (evaluate(best_t, t) > evaluate(best_coord, best_tile)) {

         best_coord = best_t;

         best_tile = t;

      }

    }

    return new Placement(best_coord, best_tile);

  }

Figure 26: A sketch of a method for picking a player’s next tile placement

Could 17 or 17.1 be incorporated into this chapter to illustrate weighing alternatives?

 

let's work through an example with an interface for the component that represents

all the Qwirkle game pieces

 

then let's work through one aspect of a complex example to study serious

alternatives:

-- feedback-loop style design often means presenting and discussing alternatives.

 

-- one aspect to consider: once the functionality interface is done and a first

   implementation of the component exists, it may already be time to consider

   simple performance stress tests. Example: queue

 

-- after release: be prepared to fix; it is critical to backwards compatible as

   much as possible

16 Expressing an Interface🔗

  // represents either the location of tiles in the map

  // or: places where a player may wish to place a tile

  interface ICoordinate {

    // INTERPRETATION a computer-graphics coordinate

    // the coordinate directly above this one

    Coordinate above();

  

    // the coordinate directly below this one

    Coordinate below();

  

    ...

  }

Figure 27: A Java-style interface for the Coordinate class from figure 23

   expression:

    - types: static checks; highly successful

    - behavioral contracts; often documented

      - pure pre and post conditions;

      - dependency on argument -- does it get checked in the post condition?

    - protocols: almost always documented only

      - order of calls

      - history dependency of calls

contracts !!

Here is a piece of functionality:

  (define (create width height channels colorspace)

    (unless (> width 0)

      (error 'create "expected width > 0: ~a" width))

    (unless (> height 0)

      (error 'create "expected height > 0: ~a" height))

    (define size (* width height))

    (unless (<= size P-MAX)

      (error 'create "expected width * height <= ~a: ~a" P-MAX) size))

    (unless (<= 3 channels 4)

      (error 'create "expected channels = 3|4: ~a" channels))

    (unless (<= 0 colorspace 1)

      (error 'create "expected colorspace  = 0|1: ~a" colorspace))

    (define load (make-bytes (* 4 size)))

    (private-image width height channels colorspace load))

Can you spot the computation that the method actually performs?

Instead write a contract that informs client programmers about the constraints:

  [create

    (-> (and/c integer? (>/c 0))

        (and/c integer? (>/c 0))

        (between/c 3 4)

        (between/c 0 1)

        image?)]

  

  (define (create-image width height channels colorspace)

    (define size (* width height))

    (unless (<= size P-MAX)

      (error 'create "expected width * height <= ~a: ~a" P-MAX size))

    (define load (make-bytes (* 4 size)))

    (private-image width height channels colorspace load))

Figure 28: Contract 1

If it is critical to let the “client” of create know about the size restriction, incorporated a constraint into the contract:

  [create

    (->i ([width (and/c integer? (>/c 0))]

          [height (and/c integer? (>/c 0))]

          [channels (between/c 3 4)]

          [colorspace (between/c 0 1)])

         #:pre (width height) (<= (* width height) P-MAX)

         (r image?))]

  

  (define (create-image-2 width height channels colorspace)

    (define size (* width height))

    (define load (make-bytes (* 4 size)))

    (private-image width height channels colorspace load))

- the ``game plan''

- the milestones, planning one step ahead

- iterative: refine as you go but keep the overall idea in mind

- contracts?

: Methods and Functions are the Basic Building Blocks

- keep them small

- develop them systematically: design recipe

17 Interface Inspections, Systematically🔗

Reconcile with the below

In the running example, the external interface between referee and player is critical to the entire enterprise. Its pieces therefore deserve a thorough inspection, like the one in the preceding section, which focused on the logic and the temporal relationships. Recall figure 29 and how the inspection forced the presenter to clarify the timing of player notifications relative to game-state changes. The method documentation explicitly states

the player is informed of any change to the publicly visible state of the game as soon as it happens

The “hacker” participating in these game tournaments may consider such a constraint critical, because it allows a speculative approach to determining future turn responses. Hence, supplementing the formal interface with such informal comments and even diagrams is critical, because these constraints aren’t expressible.

A combination of formal code with informal comments has direct implications for the development process. While language implementations can check the formal elements of interfaces, the informal ones don’t undergo any mechanical validation. Hence, implementing interfaces or referring to them in other modules quickly reveal flaws in their formal elements. The informal ones, however, aren’t subjected to this form of mechanical checking and tend to remain flawed more easily. It falls to the team as a whole to ensure that the interface component is complete, logically coherent, and unambiguous.

Frequent and thorough inspections can help achieve this satisfactory mix of formal and informal interface elements. For external interfaces, it is also necessary to work with the person in charge of writing the visible documentation. Indeed, once a team thinks some interface documentation is ready for release, it should still ask an outsider—a developer unfamiliar with the specification and the implementation—to use it and to provide feedback on its (lack of) clarity and completeness. To clarify this point, the remainder of this section explains what such interfaces actually are.

17.1 Inspections and Design Alternatives🔗

The inspection of the construction plan points to an implicit design decision when one of the reviewers asks about the back-and-forth between referees and players. As imagined, the conversation glosses over the rather obvious question whether there are alternatives to the presenter’s idea of using a RESTful—really, functionalinterface. Since it is the point of this stage to settle architectural-level design decisions and since the dialog points to the central role of the player interface component, this question deserves more attention than it receives in the sample review dialog. If not at this point, the question will come up when a pair of team members starts working on the player interface component.

Figure 29: A restful interface for the player-client

Given that work on the player interface may progress in parallel to work on the foundation layer, the decision can be discussed now as an illustration of how a design conversation may proceed. Thus imagine that the responsible team members convene a meeting to explain the choice they are facing:

We discussed the various ways in which the referee may interact with the players. But, it turns out there are two alternatives.

Can you summarize these alternatives with a few words or perhaps an adjective?

Sure. The first one is a functional interface. Here is the spec (shows figure 29).

What does “functional” mean here?

The referee calls the player with all of the relevant data, and the player returns a complete response.

Is it correct that the signatures and the type definition of TurnAction suggest a response is a request to the referee?

Yes. The referee executes actions on behalf of the player, especially for turns, so it can check their legality.

Agreed. The referee can check because it knows everything about the game and the players’ game pieces.

And to take a turn, a player must know what other players have done during their turns.

When we discussed the construction plan, we settled on adding a public game state component for this purpose.

Correct, but the existence of this component is not enough.

What else do we need?

We need to settle on when to convey this game state from the referee to the player.

That makes sense. The signature of takeTurn tells us that the player receives a GameState when it is told to take a turn.

Yes. That’s what “functional” means. The player can compute the desired action from the given information for the one call.

Doesn’t it need the GameMap and the Destinations?

The referee could include those in the GameState, or we could agree that these two pieces of information are essentially global constants.

Yes, sending them over with the first method suffices. And now it is clear why this proposed take-turn method is easily testable.

Good. Ready to move on to the alternative?

Let’s take a look at the alternative.

The interface in figure 29 employs no known programming language (but alludes to some) and thus deserves a bit of additional explanation. It specifies five named pieces of functionality via functional signatures. Each signature lists the input types first, followed by ->, and a result type. Finally, each piece of functionality is accompanied by a purpose statement, a comment that explains what (not how) the functionality computes.

Figure 30: A stateful interface for the player-client

The bold-faced signature of the takeTurn functionality is an illustration of what “functional” means. The function consumes a GameState and returns a TurnAction, a type that is indicated to exist but isn’t defined yet. The takeTurn functionality is not supposed to need any other information to determine the action that the player wishes to take.

The rest of the inspection might proceed like this:

Here it is (shows figure 30). I have bold-faced the difference to the first alternative.

What does the additional functionality provide?

It informs the player about state changes.

When does this update go out?

As soon as one player finishes a turn.

Does the referee send out the new state or just the change information?

It is simplest to send out the entire state.

Why is that?

There are several kinds of changes that can happen.

True. A player may choose different kinds of actions.

Correct, and even worse, a player may fail to respond.

It makes sense. Sending the entire state covers all possibilities.

The signature of takeTurn in the alternative interface says that this functionality does not consume any arguments. By implication, the player must get the relevant information about the GameState from calls to other methods. This is where the additional method, inform, comes in.

Figure 31: A diagram illustrating stateful interactions (figure 30)

What these textual interfaces do not show clearly is how these functions relate to each other. A design inspection should pick up on this aspect:

So the referee calls inform as soon as the state changes?

Its purpose statement says so.

Oh. Is there anything better?

We doodled a diagram on a napkin (shows figure 31).

Can you walk us through this diagram?

The components are listed at the top. The vertical lines below are their life lines.

Does time flow from top to bottom?

It does. And calls go from one life line to another.

Just to clarify. Do the first three calls show us how the referee sets up the game?

They do. And the dotted lines go in the reverse direction to indicate that the result is Void.

In that case the second batch of arrows indicates how the referee runs a single turn.

Yes! The call arrow indicates that the referee requests a decision. And the return arrow says player0 tells the referee which action it wants to take.

And then the diagram very clearly says that the referee immediately uses inform to tell the other two players of the action.

As this dialog illustrates, this kind of diagram—inspired by the UML sequence notation—brings across temporal relationships among calls much more clearly than text. Since such relationships are common, it is good practice to supplement the textual specifications with such “doodle diagrams.” A photo (or some ASCII art) suffices, and the README should reference or include the diagram. It is unnecessary to follow a particular standard, such as UML, for discussions on napkins or design inspections such as those discussed here.

Let’s move on to an even more interesting part of the diagram:

One more thing. Check the bottom of the player2 lifeline

Right, what does the triangle mean?

It indicates that the client component terminates, possibly in an irregular fashion.

That seems to be a special case.

We wanted your opinion about it.

Does it mean player2 does not respond?

Precisely.

And do the next two arrows mean player0 and player1 find out about player2 and its unresponsiveness?

We were thinking that such a player should be terminated and the others should immediately find out.

It seems to be a reasonable decision, considering that the players will exist on remote computers and such a failure could cause havoc for the overall system.

This is what we decided

To confirm. At the bottom of the sketch, both players have the information.

Figure 32: A diagram illustrating RESTful interactions (figure 29)

The dialog brings out how the inspecting team members can infer the correct timing of calls from these diagrams. Specifically the last two interactions clarify when player components find out what about their peers.

Additionally, the construction of the diagram allows the designers to present a potentially ambiguous error situation. Even if all components exist in a single program in one and the same programming language, a bug in the player component could bring down the entire system. Similarly, a remote component may become unresponsive due to other reasons. Given that the team has decided that such single-component failures should not terminate the game server, the team should inspect how referee reacts in these situations and what players find out about them when.

With these sequence diagram, the designers can also contrast the two alternatives in some detail:

Do you have a doodle diagram for what you originally called the RESTful alternative?

Yes, we have such a sequence diagram for the functional alternative (shows figure 32).

Is it worth comparing the two?

It is is quite instructive to compare them, especially the failure scenarios.

What can fail here? The player doesn’t have an inform functionality.

True, but any method can fail.

Of course. It looks like you worked through a failure of the takeTurn method to player2 this time.

Exactly. Notice how the lifeline of player2 ends during the call, before it returns a turnAction.

Is it correct that the next call of the takeTurn method to player0 delivers this knowledge about the elimination of player2 as part of gameState?

It does. And you probably guessed that player1 has not found out yet.

Yes, that’s clear. You should add these diagrams to the repository and point to them from the README file.

Let’s stop here. Reflect on the style of the design inspection dialog and its content.

Exercise 10. Consider the follow scenario. The referee successfully calls a method of player1 but the player fails after it returns a takeAction result. When do player0 and player1 find out according to the two alternative interfaces? Sketch sequence diagrams. Work through these diagrams with a partner.

Exercise 11. The design inspection has demonstrated that the interface of figure 29 differs from the one in figure 30 in significant ways. Write down a concise explanation of an advantage or disadvantage to the developer of the game-server software, and similarly, articulate an advantage or disadvantage to the developer of the player clients. Work through these points with a partner.

Old Material

Deep down buildings consist of bricks and mortar, but what people see are walls and ceilings. These large pieces hide the fundamental building blocks, and over time, what they consist of changes. Often these changes enable the construction of much larger building from one era to another.

Software systems are the analogues of entire houses or even highrises. Just like with buildings, the fundamental building blocks—functions and methods for data representations—are hidden behind large pieces: modules and classes. Usually, software architects make plans for just the latter, but software developers see all levels. Once handed a plan, they built things from bottom up, starting with simple classes and methods, ending with complex classes or even components that consist of many classes.

More Old Stuff

As a result, an interface is probably the most important example of writing that isn’t (just) code. At a minimum, an interface demands supplementary descriptions of what the arguments and the results of a method or function mean. Similarly, interfaces of bespoke components or sophisticated frameworks work properly only if additional conditions are met. Often these conditions are about temporal relationships among method calls; at other times, they concern the connection between the values of different calls to the same method. Finally, contemporary software systems also offer interfaces for external developers, people whom the team members are unlikely to ever meet. These interfaces tend to impose the same kinds of logical and temporal constraints as internal ones as well as formatting specifications for remote access.

-

Designing an interface can go wrong in several ways. It may expose too much

functionality and information about the internals, as in the Coordinate

example of the preceding section. The interface may expose too little

functionality. And, the interface may be in conflict with the actual

implementation.

 

Once released, changes to an interface should come with versioning so that the

creators of client code can specify which version of the

interface-implementation combination their code depends on. When the creator of

a framework releases a new interface, the release documentation must clarify in

what way the new version may break compatibility relative to the preceding

version. Documenting this compatibility comparison helps programmers adjust

application code; in the ideal case, new releases are backwards compatible, meaning client programmers can switch from the old to the new

version without problem.