Lecture 4: Components and Interfaces
Today
Let’s try readability again: two functions, one meaning.
Homework Statement C, Solution
Lecture
Let’s take a close look at the design of components and interfaces.
Good software developers grow into software architects, people who identify and specify components. In this course, you get plenty of chances to practice these design aspects. Not that anyone ever masters this skill perfectly, but the way to acquire it is to try and fail, over and over again.
Components need Interfaces
How do you design an Interface? The capital “I” is used to unconfuse Java programmers.
The receipt component deals with all the items that a single customer is purchasing. This includes taxing the various items at the correct rate, grouping the items into different categories, accounting for discounts and coupons, and computing the total.
The first design step is to figure out which forms of data in your chosen language are a good representation for the information.
Note Dictionaries are not the only form of data in the world.
You figure out information examples from the requirements and specification write-up. You turn them into data examples.
When you have honed in on a data example description, you go the other way, meaning, you make up data examples and figure out how to interpret them as information.
Unlike in Fundamentals I and II, a component has two vies: the internal one, which tells us how it works, and the external one, which tells us and others what it may compute for client modules. To use a component, others may need to know some aspects of the chosen data representation, but not necessarily all; if you want to put a first-time co-op student on the task of implementing a component, complete knowledge is the data representation choice is a good idea.
Now it’s time to tease out how this component can serve others and how other
developers might be able to use it. While on some occasions other components may
exploit the data representation itself—
After you have figured out the data representation, you move on to signatures and purpose statements. For those you use the external view, because you may wish to change the internal one down the road. Here are some basic hints for signatures and purpose statements:
Choose consistent function/method names.
Choose consistent signatures.
This won’t work for binary methods/functions, but those are exceptions. Formulate the purpose statement in terms of the dominant argument.
Example: Receipt Component
Every item on a receipt comes with a quantity (number of items, weight), a price per quantity, and a total cost.
A Receipt is ... |
|
An Item is ... |
|
A Quantity is one of ... |
|
A Price is ... Amount ... |
|
An Amount is ... |
addItem : Receipt x Item x Quantity -> Receipt |
// addItem(r, i, q) adds q basic quantities of item i to receipt r |
|
computeTotal : Receipt -> Receipt x Amount |
// computeTotal(r) computes the total amount due for receipt r |
// finalizes the receipt and returns the computed amount |
class Receipt: |
|
addItem : Item x Quantity -> Receipt |
// addItem(r, i, q) adds q basic quantities of item i to this receipt |
|
computeTotal : -> Amount |
// computeTotal(r) computes the total amount due for this receipt |
// finalizes the receipt, returns the computed amount |
If your have chosen to work in an untyped programming language, you must write down data definitions and their interpretations as comments and you must keep these things straight in your head.
Doing so calls for a tremendous mental discipline. Because it is also likely that you work in this world on your first co-op, we practiced this style starting in Fundamentals I.
For now, we lump the last kind of programming language into the untyped camp. This includes Python and C++.
If you have chosen a type programming language, you need to articulate your choices for data definitions and signatures as types.
You will still need to formulate interpretations as comments. There is no way around the problem that an int can mean all kinds of things in the real world, and without such a comment you and your successor will soon be lost.
A typed language has a number of intellectual advantages here, not to mention work flow advantages in the IDE. The key is that it checks types automatically for you inside and outside of your components. Because types are meaningful, the language also cuts down the debugging space when run-time exceptions (errors) occur. This idea was the topic of Fundamentals II.
Best of all, if you choose great names for your methods/functions, you get away with sparser purpose statements than in the untyped world. Since purpose statements are interpretations of functions, it is still a good idea to write down what you had in your head when you developed the function. Keep in mind that purpose statements are as much about designing the function as they are about conveying meaning to future readers.
Note An Interface is not an interface. If you expose class constructors – in some languages you have no choice – you need to spell out how to use constructors, something that is not a part of interface specifications. In general you’re better off exposing a function that constructs instances of a class and reserve the class exposure for inheritance.
When your component is a piece of a large project, it is extremely unlikely that the first or second or third draft of the Interface is the final one. As the initial design of the project proceeds, the design of client components is due to uncover gaps in the Interface, for example, missing parameters for a function, the need for additional functions, the need for alternative functions, etc. Once a sw sys is deployed, such changes become difficult though still necessary. A systematic design will facilitate such changes; an ad hoc design will cost the future developer time and energy.
Postscript The story differs if you are developing a library or a framework into which others plug in specialization code. Once an Interface for such a component is released, it becomes nearly impossible to make changes that “upset” existing code. So planning, developing, and deploying such components takes much more care than developing ordinary project components. This course does not cover this case.
Homework C: Show and Tell
Explain the homework problem.
Any textual input could be written in plain text (strings) or in a language whose grammar dictates some structure.
Plain text makes it easy for people to write down ideas and read them. But turning plain-text information into internal data is a big problem (with its own sub-field in computer science).
Grammar makes it difficult to write grammatically correct specs, especially for people who don’t program often (like dev ops). But software systems can process such information into data easily.
People recognized this problem in 1960 and invented a solution: S-expressions. Because people don’t read and forget if they read (and people also hate, absolutely hate round parentheses), some form of S-expression has been re-invented over and over and over again. The most recent incarnations are XML and JSON.
JSON information can be read and understood by human beings. In most PLS, every element of JSON has a direct data representation. And most PLs have libraries for reading JSON and turning it into internal data representations and also for rendering such internal data representations as JSON.
+ ----------+ + ------------------+ + ----------+
| JSON text | ~~~~~ read and parse ~~~> | data rep. of JSON | ~~~ write ~~~> | JSON text |
+ --------- + + ------------------+ + --------- +
Don’t ever forget the information-versus-representation separation. If you do, it’ll bite you.In this course, we use JSON for two purposes: integration tests and communication among distributed components of a software system. Your amazing software architects will make sure that the two are related so you don’t have to do (much) extra work.
Explain Racket solution.