Chapter 7 - Modular Programming

Introduction


This chapter focuses on combining the features and techniques
presented in earlier chapters in order to design and implement
more modular code.  Many of the concepts in Clipper 5.0, and
thus in this chapter stem from the excellent text Object-oriented
Software Construction by Bertrand Meyer.  This book is
published by Prentice-Hall, and is now in its second edition. 
Although the focus of much of the book is geared towards
Mr. Meyer's OOP language "Eiffel", much of the first two parts of
the text are applicable to Clipper Programmers.  In particular,
the first four chapters of the book, and their subsequent
influence on many of Nantucket's works, shaped the foundation
of the ideas presented here.

As has been discussed, many terms in the application
development arena (such as multi-dimension arrays, object-
oriented) have distinct connotations within the context of Clipper
5.0.  The term Modular Programming is no exception.  Some
programmers view modular programming from the perspective
of design criteria, whereas others focus on the storage classes
of the variables and functions used.

For this text, modular programming under Clipper 5.0 means
both designing a system such that individual components may
be re-used in other applications, and coding those components
so that each is independent of the others around it.

The Design Problem


There are two aspects to the design process.  The first concerns
itself with the problem that programmers of all languages face:
how to write programs which are maintainable and used easily. 
The second side concerns itself with Clipper specific issues with
regard to construction of an application as a collection of .prg
files.


Programmers want to write bug-free applications.  One way to
achieve this goal is to use code that is already written and
debugged.  The use of pre-existing, debugged code has two
benefits.  First, the debugging cycle is lessened.  Second,
higher productivity can be achieved and/or better quality
software can be written, since the time saved from writing the
code being used can be spent improving or writing other parts
of the application.

There are two methods commonly used whereby existing code
is used in development.  One method is to collect functions and
procedures that serve a common purpose into function libraries,
and use the functions and procedures in this library instead of
writing code from scratch each time.  

For example, one library might contain only functions that are
called to provide file access on a network.  Another might
contain functions used to print reports.  The advantage of this
technique is that code is written once, so maintenance becomes
an easier task.  Because the code is used by many applications,
any change made to the code will affect every application that
uses that function.  The disadvantage to library code is that the
design of an application tends to center around the functionality
available in the libraries that are used.  If the library is not
flexible, the range of problems that can be solved using the
library is limited.

The other method is to copy pre-existing code from a central
repository, and modify it to fit the current application.  The
benefit is twofold: since coding starts from a known base,
debugging is reduced to the changes that are made, and any
change to the copied code affects only the current application. 
Since the code is copied for a specific application, changes can
be made to the copied code that are application specific,
without consequence to other applications.  

The disadvantage to this mode of operation is that maintenance
across many applications can become tedious and complex. 
Since similar functions and procedures (often with the same
name) react with slight differences across programs, the
programmer must maintain a high level of expertise at the
application level, as well as at the code level, in order for
problems to be resolved.

The solution is a marriage of the above two methods, such that
the benefits of each can be realized without incurring the costs
associated with either.  One needs to program functions which
can be used in a variety of applications in slightly different ways,
without resorting to copying them each time.  Similarly, one
needs to maximize the number of problems that can be solved
by using the library.  The ability to perform such a combination
relies entirely on how one designs the code, i.e. how modular
one makes it.

The Design Goal Set


In order to establish a working definition of "modularity", it is a
good idea to define the concept of a "module".  

Prior to Clipper 5.0, the remnants of a "SET PROCEDURE TO"
approach or combining multiple .prg files to increase memory
savings often dictated the content of .obj files.  It was natural to
think of modules as .obj files, even though they often contained
functions and procedures that held nothing more in common
than similar variable names.

In "Object-oriented Software Construction", Mr. Meyer discusses
a module in terms of the task that is being performed.  That is, a
module might be a function, or series of functions, which work
to produce a definable result.  For Clipper programmers, the
idea is the same.  Clipper 5.0 introduces new technology that
helps define discreet blocks of code that accomplish specific,
well-defined tasks.  

Mr. Meyer and Nantucket identify certain design criteria for each
module.  These criteria, if met, will help ensure that the module
in question can be part of a library that could be used across
applications, even for different purposes.  It also means that if
the modules that were contained in a library were NOT quite
what was needed, they could be modified without causing
undue hardship on existing applications.



Modular Decomposability (Purity)

Purity means that the focus of a module should be simple,
direct, and easily identified.  Modules that perform two, three, or
more tasks should be avoided.  Instead, complex tasks should
be broken into smaller units.  For example, the function
"PressKeyMsg()" found in GENFUNC.prg performs one task - it
displays a message and waits for a key to be pressed by the
user.

Modular Composability (Reusable)

A module should be able to be re-used in other applications,
even those that are not similar to the one that caused the
module to be written originally. PressKeyMsg is generic enough
to be used in any application.

Modular Understandability

Modules should be able to be understood by the reader.  This
understanding comes from modules which are simple and
direct.  It also comes from proper documentation.  For example,
each function in the Pleiades contains a header that completely
describes the function - it's name, parameters, and purpose:

File: GENFUNC.prg
Mark: GEN_MOD_#1

     /* Function PressKeyMsg
     --------------------------------------
     Module:    Appwide
     Syntax:    PressKeyMsg(<aParams>)
     Ex:        PressKeyMsg(aMsgarray)
     Returns:   Inkey() value of the key pressed to exit
     Notes:     aParams is a two element array.

                Element 1:A two element array: element 1,1 is
                          the color for the box, 1,2 is the
                          attribute to use in shadowing the
                          box.

                Element 2:An array of text to display.  The first
                          two elements may be numeric, and
                          if so, will be used for row/col
                          coordinates.
     */

Modular Continuity (Extensibility)

A module is extensible if new features can be added without
having to redesign the entire function, and with minimal impact
on other modules.  For example, PressKeyMsg() displays a
message to the screen and waits for the user to press a key. 
Since it displays text and handles key input, adding the ability to
prompt the user with text choices and return the response
would be a simple matter.

Modular Protection (Robust)

A module is robust when it handles conditions that may be
termed "error".  That is, if the function is written to handle
exceptions to the norm, then it is robust.

These are the five design criteria that software modules should
use in order to be considered "modular".  How are these criteria
to be met?  Essentially, five principles may be drawn from these
criteria that, when adhered to, help the programmer write
modular code.  These principles are not new to programmers. 
However, of the popular xBASE DBMS languages, only Clipper
5.0 enforces the goals at the language level, through its new
scoping rules (discussed below).  The principles are:

Adaptability

A module is adaptable if it can be easily changed to be used in
a way for which it was not originally designed.  For example,
PressKeyMsg() was originally designed to only display the text
"Press a Key" to the user and wait for a keystroke.  Adding the
ability to display one or more custom messages was simply a
matter of adding an array as a parameter, and processing the
array to display the contents on the screen.

Information Hiding

The idea behind this principle is that modules should be "black
box" routines.  Other routines should only know what information
a module needs, and what information the module gives back. 
How the module performs its actions is unimportant outside that
module.  The new data storage classes of Clipper 5.0, explained
below, provide this feature.

Interface Rules

A module interface is the way that modules pass information
back and forth to each other.  There are three important
interface principles:

First, a module should have explicit interfaces.  That is, a
programmer should be able to discern immediately what
information a module requires, what it processes internally, and
what it gives back to the calling program.  Review the header to
the PressKeyMsg() function listed above.  Modules should be
written which make use of variables only present in the module -
either initialized locally, or passed as parameters.  

This means that the use of variables initialized outside the
module should be avoided as a general practice.  The reason is
that it creates confusing and time consuming maintenance
problems when a module references variables outside itself.  If
no variables are used outside a module, then changes can be
performed locally, and the impact on other functions can be
traced to return values.

Consider environmental settings such as color.  Most xBase
languages contain commands to SET the color a certain way,
which has a global effect until changed again.  Functions should
be written that respect the initial color setting by saving the
current state, making a new setting, and restoring the previous
state:


File: Genfunc.prg
Mark: GEN_MOD_#1

     /* Function PressKeyMsg
     --------------------------------------
     Module:    Appwide
     Syntax:    PressKeyMsg(<aParams>)
     Ex:        PressKeyMsg(aMsgarray)
     Returns:   Inkey() value of the key pressed to exit
     Notes:     aParams is a two element array.

                Element 1:A two element array: element 1,1 is
                          the color for the box, 1,2 is the
                          attribute to use in shadowing the
                          box.

                Element 2:An array of text to display.  The first
                          two elements may be numeric, and
                          if so, will be used for row/col
                          coordinates.
     */

     *
     *
     *

          nCurstat := setcursor()
          cClrold  := setcolor()
          setcolor(aParams[1,1])
          cChrshad := aParams[1,2]
          aText2disp := aParams[2]
          setcursor(SC_NONE)

In this example, the current color state is saved to a variable, so
that the color passed to the function may be used, and the color
state restored.  As an alternative to passing the desired color to
a function, a lookup into a color table could be performed by the
function that needs to use it.  Most of the time, either of these
two strategies is acceptable.  A later section of this chapter
examines instances where variables are scoped so that they are
used by several functions within the same module.

Besides knowing WHAT variables are used in a function, explicit
interfaces describe their data type.  Hungarian Notation was
discussed in the first chapter, and is a method we use in the
Pleiades.  Consider the local variables in PressKeyMsg:

     function PressKeyMsg(aParams)
          local cScrsave        // To restore after we are done
          local nNumpassed      // Number of parameters passed
          local i               // General worker
          local lNewrow         // Whether we are passed a row to
                                // start on
          local lNewcol         // Whether we are passed a col to
                                // start on
          local nMaxwidth       // Max width of box
          local nCurstat        // Status of cursor
          local cClrold         // Current status of color
          local aText2disp      // Array of text to display
          local nParmchar       // Number of parameter that is
                                // element of message
          local nToprow         // Top row of display box
          local nTopcol         // Top left col of display box
          local nNumlines       // Number of text lines in the
                                // message array
          local nUsrkey         // Keystroke pressed by user
          local cChrshad        // Attribute byte for shadowing


The second module interface principle is that a module should
contain small interfaces.  A module should only receive as
parameters the information required to get the job done.  In the
past, the number of parameters to a function would grow in
proportion to how adaptable it needed to be.  This works, but
makes maintenance difficult (parameter 1, 5, 8, 10 and 11 are
undefined, so...).  PressKeyMsg takes one parameter - an array. 
The array contains two elements.  The first contains color
information, the second contains the contents of the message
box.

Interfaces are kept small by overloading parameters. 
Overloading parameters means that a function is flexible enough
to use a parameter in more than one way, depending on the
data passed to it.  The PressKeyMsg() function determines
through the second array element whether the user has passed
a row and/or column, and to make adjustments accordingly:

     function PressKeyMsg(aParams)
          *
          *
          *
          aText2disp := aParams[2]
          *
          *
          *
          lNewrow := (valtype(aText2disp[1]) <> "N")
          lNewcol := iif(len(aText2disp)>1,(valtype(aText2disp[2]) <> "N"),.t.)
          *
          *
          *
          //  Determine the upper-left row and column
          if lNewrow
                nToprow := (maxrow() -nNumlines) /2
          else
                nToprow := aText2disp[1]
          endif
          if lNewcol
                nTopcol := (maxcol() -nMaxwidth) /2
          else
                nTopcol := aText2disp[2]
          endif

As with explicit interfaces, small interfaces also mean restricted
use of global settings or variables.  The problem with values
available on a global basis is that since every function or
module has access to the value, maintenance becomes much
more difficult.

The last module interface principle is that a module should
contain few interfaces.  The fewer number of modules that a
particular module interacts with, the easier it is to localize the
"rippling" effect that a change in any one module has.  This is a
very difficult principle to uphold.  One common failing of the
xBASE dialect in this regard is the environmental SET
commands.  Imagine the result of a function changing a global
SET COLOR TO, or SET EXACT, and not changing it back! 
Modules should always strive to set and reset environmental
conditions.  The PressKeyMsg() function contains an example of
saving and restoring SET COLOR and SET CURSOR values:

          *
          *
          *
          nCurstat := setcursor()
          cClrold  := setcolor()
          *
          *
          *

          setcursor(nCurstat)
          setcolor(cClrold)

As another example, consider the problem of creating a method
whereby an application sets various color values, which are
required by other modules.  How can this information be
shared?

In the Screen Editor program, certain color settings are required
by all areas of the program - inverse video, bold, an "alert" color
combination, etc.  In the SE() function, the module that creates
environmental defaults is called, and it returns a reference to the
array that contains the subject of our Screen Editor session: 

     File: SE.prg
     Mark: SE_MOD_#2

     // Create a new pseudo-object
     aPO := SENew()

     // Store original color
     DictPut(aPO[pENVIRONMENT],"cO_color",setcolor())

     // Set the environment for this PO, including color
     aPO := Envset(aParams,aPO)

     // These are static from above
     cChr_norm := DictAt(aPO[pENVIRONMENT],"cChr_norm")
     cClr_norm := DictAt(aPO[pENVIRONMENT],"cClr_norm")
     cClr_high := DictAt(aPO[pENVIRONMENT],"cClr_high")
     cClr_achx := DictAt(aPO[pENVIRONMENT],"cClr_achx")
     cChr_shad := DictAt(aPO[pENVIRONMENT],"cChr_shad")
     cBoxchars := DictAt(aPO[pENVIRONMENT],"cBoxchars")

In this example, SENew() creates a "pseudo-object", which is
essentially a reference to an array with pre-mapped regions of
elements.  Then, the Envset() function (contained in a different
module and .prg file) initializes the color to be used in the
application, and stores the result in a predetermined area within
the aPO array structure.  Finally, the aPO array structure is
searched for the correct color strings and placed in variables
that will be used by the module.

How did the color settings get into the aPO array structure? 
The Envset() function handles this, calling a function within the
GENFUNC program called GetColor().  GetColor returns the
proper color string, given the type of monitor, and the type of
color requested.  Then, the result of the GetColor() calls are
placed into a central repository called a DICTIONARY.

A dictionary is a small subsystem that creates a static array, and
stores values, and keys to those values.  Any module in the
application can utilize the dictionary, storing or getting values as
required.  We started with the dictionary supplied by Nantucket
called DICT.prg, and made our own enhancements, resulting in
SSDDICT.prg.  The dictionary subsystem allows a dictionary to
be created (DictNew()), added to (DictPut(<cKey>,<cValue>)),
and searched (DictAt(<cKey>)).

Thus, after Envset() initializes the aPO array structure with the
proper color strings, it returns back to the calling program,
which is free to access those values as needed.  As an aside,
notice that the Envset() function returns a reference to the same
array that it was passed.  For more explanation about this, see
the chapter on arrays.

The point is that the dictionary can be accessed by any
function, at any time, and it returns the appropriate color string
to use in setting the color.  Note that this also means that
potential damage can occur ACROSS MODULES if the values in
the dictionary change.  For this reason, dictionary values should
be set in one area of the application.  Further, any changes
made to the default environment (such as COLOR) should be
done in a way that sets the environment back upon exit.

Scoping to Enforce Modularity


The goals and principles behind writing more modular code
have been examined.  They are not new.  The new twist to the
issue is enforcement.  Prior to Clipper 5.0, the principles listed
above were enforced and maintained out of necessity by the
programmer.  With regard to the interface and purity principles,
application and enforcement by the programmer is appropriate. 

However, the crux of modular programming lies in the ability to
enforce the information-hiding principle.  Versions of Clipper
prior to 5.0, and all current xBase dialects continue to place the
enforcement issue as the responsibility of the programmer. 
Under prior versions of Clipper, variables were declared either
PUBLIC or PRIVATE.  In each case, routines called from other
routines could modify the contents of the variables in the calling
procedure.  Content of variables was available on a global basis. 
Information hiding depended on the programmer adhering to
rigid programming methodologies.

In contrast, Clipper 5.0 enforces the information-hiding principle
at the language level, by the introduction of Lexically Scoped
variables and functions.  "Lexical" refers to the ability of the
language to enforce scoping to the individual code block,
function, or .prg file.  "Scoping" refers to two aspects of a
variable or function in a Clipper program: it's visibility and life
span.  The formal definition of Lexical Scoping was given in
chapter three of this text, and is repeated here:

"When variables are declared of the LOCAL or STATIC data
storage class, Clipper determines the visibility of the variables
based on the location of the declaration in the source code. 
Clipper classifies all source code as one of three lexical units:
physical .prg files, functions or procedures, or code blocks. 
Thus, visibility of a variable is normally restrained to the lexical
unit in which it is declared.  As we have seen, a STATIC or
LOCAL variable declaration made within a function normally has
visibility only within that function.  The reason is that by the
position of the declaration, Clipper knows to attach the variable
to that lexical unit only.  Variables and functions that are
restrained to lexical units are said to be lexically scoped."

This new feature contains far-reaching ramifications with respect
to modular programming.  If a variable is scoped to the module,
then only the module can see that variable.  Other modules can
use variables with the exact same name, without conflict.  Even
if a module calls itself, lexically scoped variables declared for
that module are protected.  The programmer is guaranteed that
the values from one module will not overwrite values of another. 
Using lexically scoped variables and functions means that true
"black box" routines can be created and used.

Lexical scoping provides the means whereby discreet code
modules are possible, enforced by Clipper, not the programmer.
Whether a particular .obj file contains one or many modules,
modules are individual units.

Variables are lexically scoped when they are declared LOCAL or
STATIC.  LOCAL variables have the scope of two types of lexical
units: the function that creates them, or the code block that
takes them as parameters.  LOCAL variables have the life of the
duration of that function or code block.  This means that only
during the execution of the function that declares a local
variable, can that variable be seen.  When that function
terminates, the value in the local variable is lost, and the
memory used by the value is collected and placed back in the
free pool.

STATIC variables have the scope of two types of lexical units:
the individual function that creates them, or an entire .prg file. 
They have life until the entire application ends.  Thus, if declared
STATIC within a function, values can be assigned to the
variable, then checked on subsequent calls to the function.

A file-wide static variable takes on the "inheritance" aspects of a
PUBLIC variable.  However, there is an important difference
between a file-wide static and a PUBLIC variable.  The file-wide
static variable can only be altered by functions within the file,
while the PUBLIC variable can be altered by any function in the
application.  File-wide statics are an appropriate tool to solve
certain design problems that may appear when designing a
multi-function module.

In the case of the Screen Editor, execution speed for one
module contained in the SE.prg file is a major concern.  The
module handles cursor movement, displaying the row/column
information, and displaying information about any fields that the
cursor might pass through.  Rather than carry the overhead of
looking up the color string as required, variables are initialized
which hold the color information, and are available on a .prg
wide basis.

Functions in any .prg file may be called by functions in any
other .prg file, unless the STATIC declaration has been used
with the function name.  In that case, the function can only be
seen by the other functions in the same .prg file.  This is helpful
when trying to adhere to the "few interfaces" principle.

These five goals and five principles dictate how to code under
Clipper 5.0.  As an example of a modular unit, here is the
complete PressKeyMsg() function for review:

/*   Function PressKeyMsg
     --------------------------------------
     Module:    Appwide
     Syntax:    PressKeyMsg(<aParams>)
     Example:   PressKeyMsg(aMsgarray)
     Returns:   Inkey() value of the key pressed to exit
     Notes:     aParams is a two element array.

     Element 1: A two element array: element 1,1 is the color for the box, 1,2 is
                the attribute to use in shadowing the box.

     Element 2: An array of text to display.  The first two elements may be
                numeric, and if so, will be used for row/col coordinates

*/
function PressKeyMsg(aParams)
     local cScrsave       // To restore after we are done
     local nNumpassed     // Number of parameters passed
     local i              // General worker
     local lNewrow        // Whether we are passed a row to start on
     local lNewcol        // Whether we are passed a col to start on
     local nMaxwidth      // Max width of box
     local nCurstat       // Status of cursor
     local cClrold        // Current status of color
     local aText2disp     // Array of text to display
     local nParmchar      // Number of parameter that is element of message
     local nToprow        // Top row of display box
     local nTopcol        // Top left col of display box
     local nNumlines      // Number of text lines in the message array
     local nUsrkey        // Keystroke pressed by user
     local cChrshad       // Attribute byte for shadowing

The module provides clarity as to what it is supposed to do by
the header, the Hungarian Notation, and in-line comments. 
Interface is small.

     // Save current environment that we will change
     nCurstat := setcursor()
     cClrold  := setcolor()
     setcolor(aParams[1,1])
     cChrshad := aParams[1,2]
     aText2disp := aParams[2]
     setcursor(SC_NONE)

The module has an explicit interface.  Changes in values that
might affect other modules are saved and restored.

     nNumpassed := len(aText2disp)
     lNewrow := (valtype(aText2disp[1]) <> "N")
     lNewcol := iif(len(aText2disp)>1,(valtype(aText2disp[2]) <> "N"),.t.)

     //  Handle situation where they passed row and/or col
     nParmchar := 1
     nParmchar += iif(lNewrow, 0, 1)
     nParmchar += iif(lNewcol, 0, 1)
     nNumlines := nNumpassed - (nParmchar-1)

     // Find the max length
     nMaxwidth := 0
     i := nParmchar
     aeval(aText2disp,{|aEle| nMaxwidth := max(nMaxwidth,len(aEle)),;
                          i++},i,nNumpassed)

The module is adaptable enough so that the programmer need
not worry about the row/column information, or how to outline
the message.

     //  Determine the upper-left row and column
     if lNewrow
          nToprow := (maxrow() -nNumlines) /2
     else
          nToprow := aText2disp[1]
     endif
     if lNewcol
          nTopcol := (maxcol() -nMaxwidth) /2
     else
          nTopcol := aText2disp[2]
     endif

     //  Save screen plus two char either direction for shadow.
     //  Then clear the area and draw a box with a shadow.

     cScrsave := savescreen(nToprow -1, nTopcol -1,;
                     nToprow +nNumlines +2,;
                     nTopcol +nMaxwidth +3)
     @ nToprow -1, nTopcol -1 clear to nToprow +nNumlines+1,;
                                     nTopcol +nMaxwidth+1
     @ nToprow -1, nTopcol -1 to nToprow +nNumlines+1,;
                                 nTopcol +nMaxwidth+1
     // Shadow Bottom
     Newcolor(nToprow +nNumlines+2,nTopcol,nToprow +nNumlines+2,;
                                nTopcol +nMaxwidth+1,cChrshad)
     // Shadow Side
     Newcolor(nToprow,nTopcol +nMaxwidth+2,nToprow +nNumlines+2,;
                                nTopcol +nMaxwidth+2,cChrshad)

     //  Display the text-lines and wait for a keystroke
     i := nParmchar
     aeval(aText2disp,{|cEle| devpos(nToprow + (i - nParmchar), nTopcol), ;
                     devout(cEle),i++}, i, nNumpassed)
     @ nToprow +(i -nParmchar), nTopcol say "...any key"
     inkey(0)
     nUsrkey := lastkey()

     restscreen(nToprow -1, nTopcol -1, nToprow +nNumlines +2,;
                                nTopcol +nMaxwidth +3,cScrsave)
     setcursor(nCurstat)
     setcolor(cClrold)
     return nUsrkey
     // eof PressKeyMsg


The Physical File Problem


Besides the design component, modular coding has another,
important consideration - physical file management.  How
should modules be housed?  There are two polar extremes:
place code for all modules into one large .prg file, or store the
code for a single module into a single .prg file.

Before Clipper 5.0, the advantage to collecting all code into one
or two program files before compilation, or utilizing .clp file
technology (combining many .prg files together to form one .obj
file) during compilation was a saving in memory.  Because
Clipper creates a symbol table entry for all tokens found in a
.obj file, multiple .obj files that contained the same token ("x", for
example) would each cause an entry into the symbol table.

With Clipper 5.0, there is no reason to utilize .clp files for
memory savings because when the new storage classes STATIC
and LOCAL are used, the symbol table is not used.  Symbol
table memory usage can be further reduced by using linkers
which perform this task, such as Blinker, from Blink, Inc. or
Warplink from Hyperkinetix, Inc.

There are numerous advantages to organizing applications into
many smaller .prg files.  The first advantage is increased speed
in the edit/compile/link cycle.  Using the RMAKE utility, compiling
and linking program files can be managed by the computer. 
The programmer can make the changes desired, and the
computer handles re-compiling only the module that was
changed.  

Besides the advantage in speed, shorter programs are more
manageable for working in development teams.  When printed
output can be reduced to ten pages (or better yet, two or three)
instead of fifty or one hundred, sharing a problem with others
becomes easier.  

Finally, constructing and maintaining libraries is much easier
when there are many small files, instead of one large .prg file.  

In contrast, recall that one of the lexical units is the .prg file.  
Modules that are composed of multiple functions that need
common access to arrays or variables should be housed in a
single .prg file.  The "common access" variables and arrays
should be declared static to the .prg file.  The GET system in
Clipper is an example of this type of design.  

In addition, programs that contain several modules, but that
need to be restricted in scope should be stored in a single .prg
file.  For example, the Pleiades utility TreeDos is actually
composed of four modules, but is contained in one .prg file.  All
functions are declared STATIC, so that the .prg may be
compiled and linked into any application without fear of
interfering with any other functions or variables.  When multi-
module .prg files are used, care should always be taken to fully
document the places where the globally available values are
modified.

In the end, decisions for housing modules are a result of
deciding how much management Clipper should do, versus
individuals.  It makes little difference to Clipper whether an .obj
contains one or fifty modules.  It may, however, make a large
difference in managing the day to day development cycle.  If
design criteria requires file-wide static arrays and variables, and
.prg files that contain more than one module, documentation of
the connections between variables and modules becomes
paramount.



Practical Interface Considerations


Once the decision has been made to produce many .prg files,
the issue of sharing data becomes important.  For example, the
Screen Editor program contains a large number of variables and
information that are required by a variety of functions.  The
virtual screen contains some fifty-odd values to be maintained,
the editing session itself contains fifty or sixty more, and so on. 
What is the best approach to passing this data as a parameter
to the functions that require it?

The answer lies in the Clipper 5.0 implementation of arrays. 
When Screen Editor was broken into separate modules, the
virtual screen and other "pieces" of an editing session were
assigned array elements in the session "pseudo-object" array
(aPO).  The assigned elements were given manifest constants,
so that referencing them would be readable.  For example, the
element of aPO that contains an array of environmental settings
is accessed by writing aPO[pENVRIONMENT].  In this way,
passing the entire aPO reference, or the appropriate element of
aPO became a way to share data among separate "black
boxes".

Of course, once Nantucket supplies a means to create user
defined classes, and objects, then "pseudo-objects" will not be
necessary.  Instead, we will have an object from a class "Screen
Editor", and the environment might be accessed "SE:Environ". 
But until that day, an array structure that is passed will do
nicely.

The switch from utilizing many variables and many parameters
to localizing variables to functions, and consolidating information
to be shared into a central receptacle such as an array structure
requires a little time, because the approach is different. 
However, as we move towards embracing objects, it helps make
the steps a little easier to take. 