PRAGMATIC PROBLEMS WITH STEP-WISE REFINEMENT. PROGRAM DEVELOPMENT. Jorge L. Diaz-Herrera. Dept . of Computer Scienc e. Thomas Watson ...
ACM SIGSOFT SOFTWARE ENGINEERING NOTES Vol 9 No 2 Apr 1984 Page 8 0
PRAGMATIC PROBLEMS WITH STEP-WISE REFINEMEN T PROGRAM DEVELOPMEN T Jorge L . Diaz-Herrer a Dept . of Computer Scienc e Thomas Watson School of Engineerin g State University of New York at Binghamto n Binghamton, N .Y . 1390 1 ABSTRACT This paper presents a generalization of two fundamenta l programming concepts, LOCAL ABSTRACTIONS and GLOBAL ABSTRACTIONS, and a survey of several mechanisms fo r implementing them . Global abstractions are semanti c definitions (of objects) used throughout a program, wherea s Local abstractions are refinement steps generated by th e subdivision of a problem into subproblems . Globa l abstractions are very well supported in today's programmin g languages, but there is no mechanism entirely satisfactor y for the "natural" development of programs using top-dow n step-wise decomposition techniques . Current languages lac k means of expressing the hierarchical tree structure o f programs . The structure of the program must be retained explicitly, and with varying degrees of detail at differen t levels corresponding to the way the program was developed . There is an important deficiency in the process of refinin g the solution to a problem : local abstractions, whic h describe intermediate stages of the solution, are replaced by their corresponding refinements . In particluar, we discuss some inadequacies of "the facto " structuring constructs in modern languages, showing that , for instance, procedures should not be used for represetin g refinements, since these are intended to control th e "static" program structure (refinements may well represen t non-executable pieces of code!) . Finally, we propose th e introduction of two new DESIGN constructs for developin g structured programs which can show and maintain th e solution's structure as produced during the developmen t process . 1.
INTRODUCTION . We strongly believe that the prime concern in generatin g quality programs lies on the readability of the designs and th e control of complexity . Structured programming [Dahl et al 72] an d top-down step-wise refinement [Wirth 71] are two of the mos t successful and widely accepted complemetary approaches to th e problem, and the concepts of abstractions and "independent " modules in (structured) programming languages have become th e center of these ideas [Wirth 83 ; Booch 83] . It is our contention , however, that structured program text is not the panacea of th e software crisis, and that the simple use of a reduced set o f control structures is not enough . The structure of the progra m must be retained explicitly, and with varying degrees of detail a t different levels corresponding to the way the program wa s developed . That is, the tree-like program structure must b e maintained and shown to be a useful aid in program understanding .
ACM SIGSOFT SOFTWARE ENGINEERING NOTES Vol 9 No 2 Apr 1984 Page 8 1
The main pragmatic problem of using the top-down step-wis e refinement approach to program development can be best describe d by the difficulty of preserving the structure in the solution' s representation : the process of refining one ' s general solution t o a problem would require a considerable amount of updating of th e algorithm representation [Diaz-Herrera 83] . The problem i s compounded if we keep appart the design representation from it s implementation . (The former will most likely become obsolete! ) We have been very successful in enforcing the idea tha t procedures are THE method to produce structural modularity [Dijstra 76] ; unfortunately, this only simplifies our thinking . What we want to stress is the fact that the difficulties i n dealing with programming stem from the confusion between th e abstract notions of program organization, and the concret e embodiment of these notions as programming constructs . Thi s "separation of concerns " is quite important . Current language s lack means of expressing the hierarchical tree structure o f programs without commiting to the syntactic and unnecessary use o f an implementation mechanism . Programming languages should als o include design constructs [Waugh 80] . Before discussing this in more detail, let us first explor e the notion of abstraction a bit further . 2 . ABSTRACTIONS . Software design can be considered as the process of devisin g the appropriate set of higher level abstractions to represent th e objects of an application and the actions upon them . These higher level abstractions are later implemented in terms of code and dat a structures, which, ideally, should isolate all implementatio n details from their use . The more the programmer uses levels o f abstraction to explain the successive design decisions, the mor e readable the design is, and the more easily we can pinpoin t incorrect ones, if any, and separate the scope of errors . Abstractions manifest themselves in two forms or at tw o levels in a program : at the GLOBAL level and at the LOCAL leve l [Diaz-Herrera 81] . Global abstractions, also called semantic definitions, refe r to general concepts being used throughout the program fo r implementing higher-level operations on higher-level dat a structures, especially related to the particular application . Global abstractions represent a collection of data types wit h corresponding operations, which describe an interface between th e problem and the programming language used to solve it ; this is, i n a sense, the design of a (new) more problem-oriented language fo r the application . In fact, the primitive constructs of a programming language represent the best example of globa l abstractions . The semantics of global abstractions must be a s simple as possible, and at the same time, these globa l abstractions must be of general useability throughout th e application : they define the enclosing environment within which a program really exists (e .g . Standard Package in Ada*) . Th e systematic use of this concept has been formally proposed in a ne w methodology known as Object-oriented programming [Booch 83] . Local abstractions, also referred to as refinement steps, ar e generated by the division of the problem into subproblems in a top-down step-wise manner ; their sphere of influence is strictl y within the context of a subproblem . These abstractions represen t *Ada is a registred trademark of the US department of Defense
ACM SIGSOFT SOFTWARE ENGINEERING NOTES Vol 9 No 2 Apr 1984 Page 8 2
complete subalgorithms and are substantially more complex tha n global abstractions . A subalgorithm occurs only once in a program and in conjunction with (in the context of) other subalgorithms . Local abstractions can be thought of "unnamed procedures", such a s Ada's BEGIN-END blocks (called Frames), which cannot be called , which inherit everything from the context in which they ar e defined, and which make exclusive usage of the objects define d within them . Together with the idea of abstraction is the notion of a hierarchy . The use of both types of abstractions generat e hierarchies of modules, but of a different nature, which shoul d not be confused with each other . Thus, global abstractions produc e a linear hierarchy of layers of languages, one compiling into th e next ; whereas, local abstractions create a general tree structur e of the whole design, with the resulting "executable" code as th e terminal nodes . The collections of intermediate nodes in this tre e represent subalgorithms or levels of refinement . Global and local abstractions are two different complementar y things . However such a distinction has not been made explicit b y the language designer . Of the two kinds of abstractions, globa l abstractions are currently receiving most attention, and th e treatment of local abstractions seems somewhat neglected . Thus , there are facilities in current programming languages that can b e used to obtain this effect (e .g . user defined types an d operations) . Abstraction mechanisms for the "natural " specification of levels of refinement are not generally provide d in today's programming languages, and design abstractions ar e replaced by corresponding code . This is precisely the aspect we want call attention to, since all Local abstractions MUST b e present in the final solution in order to enhance its readability . There are a number of facilities in current programmin g languages that, in one way or another, can partially produce th e end result of maintaining the program structure . However, none i s completely satisfactory, for a number of reasons which we no w discuss . 3 .
ABSTRACTIONS . It has been widely accepted that the primary role which th e procedure plays in the current philosophy of programmimg is as th e agent of modularity, as Dijkstra [76] put it : "The procedure i s one of the main vehicles for expressing structure . . . " . Programming textbooks use procedural abstraction heavily as the general styl e for implementing refinement steps, they invite programmers "t o formulate a sequence of statements as a procedure . . . even when i t occurs (it is called) only once . . ." [Wirth 71] . This is a soun d proposition, but one that must be accompanied by correspondin g suitable tools . Let ' s first consider procedures as the agent for implementin g global abstractions . If we see procedures as the naming o f abstract actions (originally their primary purpose was to collec t otherwise redundant code into one place), then the issue o f readability becomes quite important . In most languages, procedur e invokation is done using a notation that resembles mathematica l formulae, and which also mirrors some code generation techniques . Here are some examples : IMPLEMENTING
ACM SIGSOFT SOFTWARE ENGINEERING NOTES Vol 9 No 2 Apr 1984 Page 8 3 GOSUB 0357 CALL SUBRT1 PERFORM SUBRT1( . . .) SubRtl( . . .)
-- in in in -- in
BASI C FORTRAN and PL/ I COBOL Pascal-like language s
Procedure calls can still be quite unreadable without lookin g at the procedure definition, as for example : ProcessArray (My Array, 0, 0, -1, 245, TRUE, 'ABC', x) ; Recent developments propose a somewhat improved format fo r procedures calls, as for example naming parameters in Ada, and th e introduction of keyword arguments [Booch 82 and Groves 82] , illustrated in the following examples : SPLIT (Tree => My Tree, Treel My Treel , Tree2 ==> My_Tree2) ; Ad a SPLIT (This_tree) INTO (Subtree l) AND (Subtree 2) ; -- Booch SPLIT This tree INTO Subtree 1 AND Subtree 2 ; -- Grove s r--
The ability to isolate portions of the program int o " compilation units " represents the state of the art in the use o f global abstractions . The concept of "a library of modules" ha s become the center of these ideas, in which components are define d and stored Eor later use among many programs . Most moder n facilities through languages provide such building-block structuring constructs, here are some : Units Envelopes Modules Packages
in UCSD Pascal [Bowles 78 ] -- in Pascal Plus [Welsh and Bustard 79 ] -° in Modula-2 [Wirth 83 ] -- in Ada [Dod 83]
The generic feature, uniquely found in Ada, provides a muc h closer mechanism to the global abstraction concept, enhancin g modularity even more, by allowing the same algorithm (template) t o be instantiated (and used) for different types of data . Let's now turn our attention to implementation mechanisms fo r local abstractions or refinement steps . Procedural abstractions , in any form or flavor, are NOT at all adequate for specifyin g refinements, this practice presents a number of significan t shortcomings, giving rise to additional problems, which can b e enumerated as follows : First, and foremost, refinements are possible (and sensible! ) for languages that do not have procedures at all (e .g . specification languages) or for program parts not involvin g control flow (e .g . data definitions) . Second, procedures are too powerful and too general, they ar e used indistinctly for local and global abstractions . Thus , procedures are used for refinement purposes (i .e . called from onl y one place and without parameters), as building blocks for a laye r (i .e . called from many places with parameters, but not recursively), and for functional programming (i .e . usin g recursion) . Third, procedures do not necessarily inherit access t o objects from the environment (where they are used) automaticall y (it depends on the context in which the procedure is declared) ;
ACM SIGSOFT SOFTWARE ENGINEERING NOTES Vol 9 No 2 Apr 1984 Page 8 4
whereas, by definition, refinements must inherit everything fro m the context where they are used (called) . Fourth, procedures can affect the program state indirectly , and in ways which are not obvious from the procedure call, b y changing the value of global variables or by permiting aliasing . When this happens, the programmer cannot reliably determine wha t abstract action a procedure provides by inspecting the procedur e in isolation from its context (something useful and common fo r global abstractions) . Finally, and less important, procedure call s In general , can be expensive if not properly implemented . procedures are a completely different language feature t o refinements : procedures govern the dynamics of control flow ; whereas refinements control the static program structure . Apart from these purely linguistic facilities, refinement s may be implemented by other means . The refinement process ca n sometimes be confortably performed with the aid of an edito r [Diaz-Herrera and Flude 80], but, usually without the cooperatio n of a compiler to carry out the actions specified . Most notably , programs cannot (easily) be partially compiled ; and in fact, thi s facility seems to be more part of an environment than of th e language itself . On the other hand, there are some compile-time options i n certain translators that, if used judisciouly, in some way o r another can produce the desired effect of local abstractions . Th e most common of such options are facilities for reading source tex t from different source files, as for example : %INCLUDE -- in PL/ I [$I } -- in UCSD Pasca l COPY -- in COBO L However, programs have to be compiled entirely (i .e . separat e compilation is not generally provided), and all files referred t o must exist at the time of the compilation ; something which is to o restrictive for top-down development, since Local abstractions ca n sensibly be used, at a higher level, without being refined at all . Some language processors provide facilities to control th e program structure ONCE it has been developed ; these are speciall y useful for documentation purposes . A good example is the PRINTLEVEL option, found in LISP, which allows the printing of th e overall structure of a program while suppressing the details belo w a certain level of nesting . We have also found facilities in Ada that, again if used with care and discipline, can serve to achieve the purpose of refining . We are talking about the separate compilation facility, th e RESTRICTED option, which confines the use of a particula r subprocess to a given environment (however, this construct wa s dropped from the Ada 80 standard), and the pragma INLINE, whic h permits the printing of a procedure's body in place of th e corresponding calls (something like a macro expansion) . Perhaps the facility closest to a local abstraction construc t is that of the block statement (Frame) in Ada . This statemen t (defined by the old BEGIN-END pair) is effectively equivalent t o an unamed subprogram, therefore it cannot be called from anywhere , it is just executed sequentially, and is limited in scope to it s immediate lexical level . The main problem, however, in using Ad a ' s blocks as local abstractions or refinement steps, is the fact that
ACM SIGSOPT SOFTWARE ENGINEERING NOTES Vol 9 No 2 Apr 1984 Page 8 5
they cannot be separately compiled (perhaps because of thei r anonymity) . We have surveyed several "traditional" and relatively ne w mechanisms for implementing abstractions . We have fund that globa l abstractions are very well supported . However, we found no construct or mechanism entirely satisfactory for the "natural " development of programs using top-down step-wise decompositio n techniques . It is not at all unreasonable to want to be able to infer th e meaning of a piece of code from the particular constructs used t o express it . For example, if only GOTO ' s are used to describe al l control structures, it will be hard to identify the importan t notions of iteration and conditional . Exactly the same assertio n is true for procedures when dealing with the equally conceptuall y important notions of local abstractions and global abstractions . In what follows, we propose the introduction of two ne w constructs especially designed for writing programs usin g refinement steps . 4 . TWO NEW PROGRAM DESIGN CONSTRUCTS . During top-down development one describes the action s required, at a given level of abstraction, using simple imperativ e natural language statements . These statements conform what we hav e called local abstractions . The next step is to refine one of th e local abstractions introduced in the solution at this level o f detail, producing a solution to this subproblem in terms of mor e local abstractions . One of these local abstractions is in tur n refined, and the whole process repeated until a level of detail i s reached in which all abstractions can be written in an existin g programming language . The tool traditionally used in this proces s is known as pseudo-code or PDL . In Top-down step-wise refinement programming, we REFINE th e solution to a problem by introducing and USING local abstractions , which are in turn refined, and so on . If we want to use thi s method in a programming language, we need to introduce facilitie s for indicating when we are REFINING a local abstraction and whe n we are USING a local abstraction (perhaps refined somewhere else) . It is more convinient to introduce these ideas informally by mean s of an example : we want to write a program that will print th e a lower left-hand triangle of a multiplication table as far as given (positive) integer (from Welsh and McKeag 81) . Thus, thi s portion of the table as far as the number 4 would be : 1 2 4 3 6 9 4 8 12 1 6 The solution to this problem can be stated as : (Print the lower left-hand triangle of a multiplication table a s far as N } The first refinement of this solution looks like :
ACM SIGSOFT SOFTWARE ENGINEERING NOTES Vol 9 No 2 Apr 1984 Page 8 8
REFINE {Print the lower left-hand triangle of a multiplicatio n table as far as N ) WHITHIN (Standard Package ) AS : DECLARE LineNumber : integer range l . .N := 1 ; BEGI N WHILE LineNumber