Keep The Tricky Parts Textbook Simple

By "textbook simple", I mean code that is as easy to understand and verify as that you would expect to see in a textbook. Code does not have to be from a textbook to be textbook simple. In fact, some code found in textbooks is not textbook simple.

By "tricky part", I mean any part which you are fearful of getting right unless you either borrow it or take unusual care developing it from scratch. I don't necessarily mean code which must be reliable. Rather, I am referring to those parts of your program, if any, which are difficult to get right.

If your program has a tricky part, it ought to be isolated in a procedure, module, or class where you can take special care of it. And, it ought to be textbook simple. Are there complications on the input side of your tricky code that make it difficult to keep the tricky code textbook simple? If so, change the input side. Are there complications with data structures that make it difficult to keep the tricky code textbook simple? If so, change the data structures. Ditto, the output side.

Of course, input, output, and major data structures may have been fixed in advance because they are used elsewhere. That's OK. You can still change the way they are presented to your tricky code. To do this you write another layer of software to interface with your tricky code. This other layer of software will probably be a set of procedures and some shared data.

In an object-oriented environment, this layer of software can be implemented as a subtype of a class that already implements your input, data structure, or output.

Dijkstra's book emphasizes mathematical rigor in developing programs. This is glossed over here. But one remark is worth making: there is not much sense in borrowing code from him if you are going to change it in ways that make the mathematics inapplicable. (Unless, of course, you are prepared to update the mathematics.)

It is interesting that Dijkstra's solution to this problem, which was published in 1976, has an object-oriented flavor. The first step toward looking at the example that way is to view master file as belonging to a MASTER_FILE class, the transaction file as belonging to a TRANSACTION_FILE class, records in the master file as belonging to a RECORD class, and records in the transaction file as belonging to a TRANSACTION class.

If oldfile is of type MASTER_FILE and x is of type RECORD, then a statement that reads a TRANSACTION object from oldfile to x can be written like this

  x := oldfile.lopop  

where lopop is an operation that pops the first element off oldfile and returns it. This statement is written with notation very close to that used by Dijkstra.

Again with very little change to Dijkstra's notation: if x is of type RECORD and y is of type TRANSACTION, x can be updated with this statement

  x.update(y)  

which obviously uses an operation update belonging to the RECORD class. Also, you can recognize whether the transaction y is supposed to be used for updating by testing a boolean value belonging to the TRANSACTION class, namely

  y.upd  

By now, you are getting close to being able to read the nonmathematical parts of Dijkstra's example. Suppose you wanted to borrow his code for your project. Suppose, moreover, that your problem is different because your transactions can be cancelled. In particular, your TRANSACTION class has a boolean value which is true exactly when the previous transaction is to be thrown away -- provided the previous transaction exists and has the same KEY .

You want to borrow Dijkstra's code. He has given it to you in a form that is textbook simple. You are tempted to rewrite it, but it is tricky and it should remain textbook simple. So, if you rewrite it, then you will have to rewrite the mathematics that goes with it. What do you do?

In a batch environment, you might preprocess the whole transaction file to remove any cancel transactions along with any predecessor transactions which they cancel. After the preprocessing, your conditions match those of the textbook.

In an interactive environment, you might do the same thing on the fly by creating a subtype of TRANSACTION_FILE whose lopop operation gets only the same TRANSACTION's that would be available after the batch preprocessing. To do this your version of lopop must be able to read ahead. Your requirements will need to specify when the read ahead mechanism has waited long enough for a possible cancellation. However, that is specified, Your version of lopop will need to be able to keep what it has read in advance in a lookahead buffer.

When you have implemented and verified your TRANSACTION_FILE subtype, you are ready to borrow Dijkstra's code for use in a way that is equivalent to the way it is supposed to be used.

If writing such a lookahead buffer is tricky for you, then your implementation of the TRANSACTION_FILE subtype is another tricky part that should be kept textbook simple. In that case, don't put additional features into that subtype and take the time to write down why your implementation works.

If you are not working in an object-oriented environment, you can still implement a read operation that looks ahead and avoids cancelled transactions. Such a modified input system is an example of a virtual machine.

To rephrase this tip: always separate what will be difficult from what will not and, then, use special care to get the difficult part right.

Copyright and Permissions

Copyright, 1995 by J Adrian Zimmer

This tip is distributed to individuals free of charge from the Software Build and Fix web site. All other distribution (including but not limited to internal distribution within an organization and mirroring of any kind) is forbidden without written consent of the copyright holder.

Return to the top of this document.

Context  Some Tips for Programmers    Author J Adrian Zimmer  
Dated: August 20, 1995 ; Revised: Oct 07 1998