Structured Programming

Software design means different things to different kinds of applications. Jeff investigates the issues surrounding program specification, design, and implementation.


November 01, 1992
URL:http://www.drdobbs.com/architecture-and-design/structured-programming/184408884

Figure 1


Copyright © 1992, Dr. Dobb's Journal

Figure 1


Copyright © 1992, Dr. Dobb's Journal

NOV92: STRUCTURED PROGRAMMING

STRUCTURED PROGRAMMING

Implementation License

Jeff Duntemann, KG7JF

After reading my July column, a friend of mine suggested that I see Whoopi Goldberg's new film, Sister Act. "If you like nuns, Jeff, you'll love it."

Yes, I like nuns. It's hard not to admire people who make you eat your words. Thirty years ago, I heard entirely too often, "You'll all thank me someday"--usually for forcing us to bust butt on fractions, phonics, or spelling when we'd rather be out digging holes in empty lots or raiding the neighbor's garbage cans for broken TV sets. We all swore we'd hate them all our lives, but I think most of us grew up sufficiently over time to grin a little, swallow the memory of all that preadolescent anguish, and toss a little prayer in the Sisters' direction, wherever they are.

Me, I've gone farther than forgiveness. The very values I hold most important--guts, literacy, discipline, hard work, an ability to harmonize, and unswerving adherence to a set of Very High Principles--are the things nuns are made of. Laugh if you want; you could do worse.

And Sister Act is funny indeed. In the film, a black Las Vegas lounge singer witnesses a Mafia murder, and before testifying is hidden for her protection in an old-style convent in San Francisco. Among other things, she teaches the choir of mostly elderly nuns how to sing and redefines sacred music a little in the process.

At the film's finale, "Sister Mary Clarence" (Whoopi) leads the choir in a slow, very solemn hymn that took me several seconds to identify. It wasn't a hymn--not exactly. In fact, it was an old girl-group #1 rock and roll song recorded originally in 1963 by Little Peggy March: "I Will Follow Him." It was the same melody and the same words, but sung in such a way that you know they're talking about an entirely different Him.

Implementation License

Schlocky old pop music is a special contrarian passion of mine, and I know it well enough to be familiar with a number of cases like this: The same song, performed so differently by different artists can sound like, well, almost a different song.

Whether you're an aspiring musician or an aspiring programmer, sooner or later you must confront the intriguing questions: Given a design (or a score), how much room is there for innovation? How closely do you have to follow a design to stay out of trouble?

At this point, music and hacking part company, mostly because down here in Hackerville we haven't even decided yet on what constitutes a design. I've been handed a five-page text description of a 50,000-line system and been told to "Just Go Do It." That's one end of the spectrum; in cases like that, you basically do the design yourself, implement it, and then tell the Big Guys that it was just what they asked for, whether it is or not.

Some firms actually spend considerable time using formal methods to create a design, usually in the form of Yourdon-style data-flow diagrams and minispecs, or in some other formal notation system. My experience here is a little sparse, but in conversations with other programmers I've heard tales of getting so bogged down in an unimplementable design that the programmer had no choice but to resign--by that time the design had undergone a sort of apotheosis and was not challengeable by anyone beneath the vice-presidential level.

Designs become unimplementable for a number of reasons, nearly all of which have to do with designing beyond your constraints. Bad analysis can produce an unimplementable design, but even the best analysis can create a design that just can't happen. One ugly pitfall is expecting a design to be portable across platforms without a significant workover. Hell, the design worked on the Sigma 9 in 1977; why can't you put it up on a 486? Still, most unimplementable designs come from arrogant designers who simply write off brick-wall constraints as "implementation issues" and act as though they aren't there.

This is why I continue to hold that the people who write the code are the people who should draft and perfect the design. A design that runs afoul of its constraints is dead meat, and nobody understands constraints like programmers, sheesh.

I'm spending this run of columns discussing design issues. Applications come in a number of broad species, and I'll try to address all of those with which I'm familiar and the special design considerations connected to each of them.

Telecommunications Applications

Some types of applications lend themselves to certain types of designs. Some classes of applications are in fact so shaped by the nature of the platform that the design is almost a given. My best example of this is a communications program. I wrote a number of these while I worked at Xerox, and a few more since then on my own, and no matter how I approached the project at the outset, once the code was done they all ended up shaped to much the same way. I've spent a lot of time trying to do this a fundamentally better way, but no such way has ever emerged from my experiences.

This may change once we move away from DOS to a multithreaded operating system like OS/2 or Windows NT, but we won't know until we get there. (Non-C development tools are significantly absent in the OS/2 arena, which won't help the operating system's popularity, especially with this particular columnist.) I have a sense that I could break down the canonical communications-program polling loop into a number of highly independent threads, each implementing a hard-shelled little state machine that performs some part of the work. Within a couple of years, I'm sure I'll find out if this is the case.

The Algorithm that Wouldn't Die

But until then, the highest-level algorithm for a communications program will probably look very much like Figure 1. The heaviest black arrows represent the main polling loop. The pairs of smaller black arrows represent function calls out of the loop into utility procedures. The lightest arrows represent the movement of data within the system.

The shaded rings are interrupt-driven ring buffers. Ring buffers are circular queues, FIFO structures of a fixed size that wrap around seamlessly using a head pointer and a tail pointer. They can be implemented as objects as long as you take care with the interrupt drivers.

Both modem input and output are buffered. (The lightning-bolt symbols indicate an interrupt-driven data path.) When the modem receives a complete character, it generates an interrupt, and the interrupt service routine (ISR) places the inbound character into the next free position in the inbound ring buffer. The polling loop (which may be temporarily busy doing something else) then picks up the character for processing when it gets a chance. Similarly, there is an outbound character queue where the application can place large chunks of data at one time (say, from a fast disk read) and let the modem take the data as fast as it can. Typically, the modem port can generate an interrupt each time it successfully transmits a character and has room for the next one. The outbound ISR takes the next character from the outbound queue and stuffs it out to the modem for transmission. This continues (independent of the polling loop) until the outbound queue is empty.

The algorithm itself is very simple. A polling loop checks a number of devices to see if something needs to be done. The first check might be to the keyboard. Is a keystroke waiting? If so, it is picked up and processed. The character might be intended for transmission to the modem, in which case it is placed in the outbound ring buffer. The character might also be a command to the program itself, in which case the command is parsed and some utility routine in the application is called.

Once the keyboard is checked, the loop moves on to the outbound ring buffer. A file transmission may be in progress, so the ring buffer is checked to see if there is room for another slug of data bytes from the file being transmitted. If the modem is still chewing on the outbound buffer and hasn't cleared enough space in the buffer for another lump of data (1K is a common value), the loop lets the outbound buffer continue chewing and moves on.

Next, the outbound ring buffer is polled. Is a character from the remote system waiting to be processed? If so, it is picked up and parsed. If it's just data, it is grabbed and sent to the appropriate destination; typically a disk or the CRT. If it's a command, the command is parsed and the appropriate utility routine in the application is called.

So it goes, round and round, until the command to pack it all in comes from the keyboard, or perhaps from a "disconnect" command from the remote system.

Spec vs. Design vs. Implementation

What I've just described is an algorithm driven very heavily by the constraints of DOS and of our serial-port hardware. The algorithm can be considered a specification, but it is so hemmed in by considerations outside the control of the program (ISR protocols, single-threaded DOS, the PC serial port) that it is very much a design as well.

There is some leeway in implementing the design. You can implement it literally as a WHILE..DO loop (which works well for simple programs), or you can get fancier and encapsulate the bulk of the algorithm into a "telecommunications kernel" with strictly defined data paths to and from the application using the kernel. Implementing it as a kernel makes it much more reusable.

Figure 1 is, of course, not a detailed design. From this level of detail down, the design will be dominated by issues related to telecommunications protocols. You have to decide what protocols the program will support (ETX/ACK, Xmodem, Ymodem, and so on), whether there will be any attempt at terminal emulation, and if so for which terminal spec.

The Art of the State

Once you decide which protocols to support, you must rigorously define how each one works. Diagrams really help here, and while there are a multitude of ways to diagram a telecomm protocol, the best way I know is to consider the protocol a state machine and draw it using state-transition diagrams.

A state machine is a logical abstraction that defines the operation of certain kinds of "well-behaved" software. You set a program up such that at any moment the software is in one of a limited number of well-defined "states," where any subsequent possible circumstance that affects the software might transfer the system to another defined state. A system, for example, may be set up to exist in a processing state or a waiting state. It's in one or the other, period--there's no third or fourth option. While it's in a waiting state, there is a set of events that can move it to a processing state, and all other events leave it in a waiting state. Similarly, while the system is in a processing state, a well-defined set of events will move it into a waiting state, while all others leave it in the processing state.

Telecomm protocols lend themselves well to implementation as state machines because while the system is transferring information, it will be in one of only a few different states: waiting for the initial "let's go!" character, accepting characters, calculating a checksum, transmitting the checksum, waiting for an ACK, and so on. It's easy to define how the system moves from one state to the next, and the conditions that force a change of state aren't excessively complex or varied. Furthermore, the sequence of state changes remains the same from one file transfer to the next.

Understanding state machines and defining them is worth a couple of columns all by itself, and I hope to get to it within the next few months. It's too large a subject to cover in detail this month.

Pushing the Platform

While I was struggling to learn Turbo Vision, I was a little dismayed to watch my friend George Seaton (who already knew Turbo Vision) struggling to create a professional telecommunications system with it.

Telecomm is one of those categories of applications that I consider "real time;" that is, telecomm apps must be able to deal with protocols that happen at speeds independent of the program's operation. 9600 baud is 9600 baud no matter what your machine's clock speed, and if you have to run at 9600 baud, you'd better be sure your machine won't roll its eyes at you and laugh. Most of George's problems stemmed from a need to work at defined (and high) baud rates on underpowered machines.

Turbo Vision got in the way here simply by being too busy in the background, soaking up cycles, doing its own thing beneath the surface and out of sight. Turbo Vision's TTerminal object looks very attractive, because it implements a scroll-from-the-bottom TTY-style window. Feed it a character from the input ring buffer, and TTerminal will take care of the rest.

At 1200 baud, a breeze; at 2400 baud, no big sweat; at 4800 baud, well, things start to get tight. At 9600 baud, it's definitely white knuckles on the steering wheel. George eventually had to buy the Turbo Pascal runtime source and start tearing expendable features out of TTerminal to make it run fast enough to meet his specs, which involved interfacing to an existing mainframe system and were not negotiable.

The lesson here is that Turbo Vision brought with it a set of constraints that didn't appear until George turned up the steam and started to push the platform. He managed to make the system work effectively, but using Turbo Vision took the hardware to its limits at 9600 baud, which isn't as astonishingly fast as it was a few years ago.

To be fair, Turbo Vision made it unnecessary to reinvent a CUA-compliant user interface, and George had the luxury of "not recommending" 8088-class machines to his corporate users. He thinks the benefits are worth the costs. (Having watched him suffer through the process, I'm very glad he forgives as easily as he does.)

Turbo Vision Trinkets

Fairly late in the Turbo Vision learning game I caught on to something slick: Borland tossed in a number of well-encapsulated little Turbo Vision gizmos with Turbo Pascal 6.0, which, if you didn't look closely at all the stuff the automated installer shoveled onto your hard disk, you probably missed.

Look again. In particular, load and compile the TVDEMO.PAS program and check out the little calculator and calendar. These trinkets can be lifted and dropped into your own Turbo Vision applications with almost no effort.

I added both the calculator and calendar to my HCALC.PAS mortgage calculator in about 20 minutes from a dead stop. Here's all you have to do:

Rebuild the whole mess, and that's it. You now have a serviceable, if not fancy calendar and calculator with virtually no effort. When I reached this point, I felt that the effort expended in learning Turbo Vision had begun to pay off.

Clearing the Deck

If you remember, my HCALC program has a menu item that clears all opened windows from the desktop. I did it as an experiment in broadcast events, but it has proven to be a handy feature that I suspect I will add to all my future Turbo Vision apps. The calculator and calendar objects from TVDEMO.PAS, of course, don't understand the cmCloseBC broadcast event and hang sullenly around when the desktop is cleared.

It didn't take much to add the ability to respond to cmCloseBC. I've written-up a short file (see Listing Two, page 188) to give you the new code you need. Here's what you do for the TCalculator object:

That's all you have to do. The mods for TCalendar are similar and will make a good exercise. One thing to watch out for: Both the calendar and the calculator objects have two parts to them. The calculator is part TView and part TDialog, and the calendar is part TView and part TWindow. It's not entirely clear to me why Borland wrote them this way, but in my first experiments I made the mistake of adding the response to cmCloseBC to the TView part of the calculator. It was easy because the TView portion already had a HandleEvent method. I was wrong--and closing down the TView aspect of the calculator without closing down the TDialog aspect crashed my machine hard.

Here's the rule: You must close down what you instantiate. In the TVDEMO.PAS program, the TDialog aspect of the calculator is what is instantiated and added to the desktop, so that's what you have to shut down with a call to destructor Done, from within the HandleEvent method you added. Take a look at the calendar code and the way that TVDEMO.PAS invokes it, and choose which aspect of the calendar needs to respond to cmCloseBC.

And don't forget to call the ancestor class's HandleEvent before diving into your own HandleEvent!

The Vision Continues

As hard as I've tried to be done with Turbo Vision, it's been harder to shake than an Arizona staghorn cluster stuck halfway through your hiking boots. Next month we'll take a look at a couple of new Turbo Vision tools in an effort to generalize a little about the design of Turbo Vision applications.


_STRUCTURED PROGRAMMING COLUMN_
by Jeff Duntemann


[LISTING ONE]


PROCEDURE TMortgageApp.HandleEvent(VAR Event : TEvent);

{-----------------------------------------------------------------}
{ The following are procedures local to TMortgageApp.HandleEvent: }
{-----------------------------------------------------------------}

PROCEDURE Calculator;
VAR
  P: PCalculator;

BEGIN
  P := New(PCalculator, Init);
  P^.HelpCtx := hcNoContext;      { Used to be hcCalculator }
  IF ValidView(P) <> NIL THEN
    Desktop^.Insert(P);
END;

PROCEDURE Calendar;
VAR
  P: PCalendarWindow;

BEGIN
  P := New(PCalendarWindow, Init);
  P^.HelpCtx := hcNoContext;         { Used to be hcCalendar }
  Desktop^.Insert(ValidView(P));
END;

BEGIN
  TApplication.HandleEvent(Event);
  IF Event.What = evCommand THEN
    BEGIN
      CASE Event.Command OF
        cmNewMortgage  : NewMortgage;
        cmLoadMortgage : LoadMortgage;
        cmSaveMortgage : SaveMortgage;
        cmCloseAll     : CloseAll;
        cmPrint        : PrintMortgage;
        cmCalculator   : Calculator;     { Calculator is NOT a method! }
        cmCalendar     : Calendar;       { Calendar is NOT a method!   }
      ELSE
        Exit;
      END; { CASE }
      ClearEvent(Event);
    END;
END;




[LISTING TWO]


{-----------------------------------------------------------}
{ This file describes mods needed to make the TV calculator }
{ respond to the broadcast command cmCloseBC used in Jeff   }
{ Duntemann's HCALC mortgage calculator program.            }
{ THIS IS NOT A COMPLETE, COMPILABLE FILE!  This is just a  }
{ collection of mods to make to Borland's TV Demo file      }
{ CALC.PAS.                                                 }
{ By Jeff Duntemann  8/2/92                                 }
{-----------------------------------------------------------}

{ Add this constant definition up front somewhere in CALC.PAS: }
CONST
  cmCloseBC = 196;

{ Modify the object definition for TCalculator like this: }
TCalculator =
  OBJECT(TDialog)
    CONSTRUCTOR Init;
    PROCEDURE   HandleEvent(VAR Event : TEvent); VIRTUAL;
  END;

{ Add this method definition to the CALC.PAS file: }
PROCEDURE TCalculator.HandleEvent(VAR Event : TEvent);
BEGIN
  TDialog.HandleEvent(Event);
  IF Event.What = evBroadcast THEN
    IF Event.Command = cmCloseBC THEN
      Done;
END;













Copyright © 1992, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.