Hyper-Productivity:

A New Approach for PowerBuilder Success

by Uriel Wittenberg (uw@urielw.com)


Contents

  1. Introduction
  2. Fundamental Principles of Quality Software
    1. Simplicity
    2. Elimination of Redundancy
    3. Clarity
    4. Generality
  3. Some Conceptual History
  4. Beefing Up PowerBuilder
    1. Global Utilities
    2. Standard Object Utilities
    3. Implementation
  5. Application: The Methodology in Action
    1. Requirements
    2. Implementation
  6. Software Productivity: The Big Picture
    1. Hindrance #1 (Sybase)
    2. Further Hindrances
  7. Conclusion


Introduction

This paper presents Hyper-Productivity, a methodology whose benefits combine high quality with low development costs, and illustrates its applicability to PowerBuilder projects. The method has been successfully applied in projects using the most recent PB version at time of writing, 6.5, as well as various release levels of version 5.

This is primarily a technical paper, but some points may also interest non-technical managers.

This paper homes in on the principal criterion by which any software development methodology should be judged: programmer productivity. The business benefits of Hyper-Productivity are clear:

The bottom line is: more output, higher quality, lower cost.

Fundamental Principles of Quality Software

Simplicity

Simplicity is the central element of the Hyper-Productivity method. Simplicity, and its opposite, complexity, are illustrated in the following pieces of Powerscript:

If not gb_inline_object = TRUE Then
   this.enabled = TRUE
Else
   this.enabled = FALSE
End If

Several aspects of this code sample are unnecessarily complex. First, saying “IF NOT ... THEN ... ELSE” is always confusing. It’s better to reverse the THEN and ELSE to produce simpler code that does the same thing, as follows:

If gb_inline_object = TRUE Then
   this.enabled = FALSE
Else
   this.enabled = TRUE
End If

This can be further simplified, since “= TRUE” is redundant:

If gb_inline_object Then
   this.enabled = FALSE
Else
   this.enabled = TRUE
End If

Further simplification is still possible, since the entire IF/ENDIF structure is unnecessary:

this.enabled = NOT gb_inline_object

Even this can be simplified, since “this” is the default object when an object property is referenced:

enabled = NOT gb_inline_object

The above suggests the enormous advantages that large-scale simplification can deliver in a software project:

Software is a complicated business. The evidence is all around us, since so many systems are buggy, if not outright failures. If the objective is to produce high-quality, correctly functioning software, then simplification, which eliminates layers of unnecessary complexity, is little more than common sense.

Elimination of Redundancy

An essential aspect of simplification is the elimination of redundancy. In the above example, we changed “this.enabled” to “enabled”, since “this.” is redundant; it adds no information, it has no effect. Similarly, we changed “<condition> = TRUE” to “<condition>”.

In physical systems, like parcel delivery or data transmission, redundancy is a good thing because one component of a system can take over when another fails. But software systems are in no way analogous. A piece of code will not “break down,” if it is correct. (PowerBuilder generally involves fairly conventional corporate software applications, so a real-time AI missile-guidance system, for example, is outside the scope of this paper.) Redundancy is a violation of simplicity: it makes systems larger, harder to understand and maintain, and more prone to bugs.

Some people argue that “this.enabled” or “this.x” are more intuitive than “enabled” or “x”. But this is only one of many defaults available to us in Powerscript. When we code embedded SQL, “using SQLCA” is the default; when we code dw.update(), we are implicitly specifying the default update() arguments (TRUE, TRUE).

In daily life, the advantages of defaults are obvious. When we dial a local number, we do not dial the area code. Yet some developers seem to want to “dial the area code” to remind themselves that they’re not going outside the area.

Specifying defaults is redundant by definition. Developers should exploit the simplification opportunities that the development tool’s defaults offer.

Clarity

Clarity is another underpinning of Hyper-Productivity. The business benefits of clarity are compelling:

The following subsections list some of the ways to achieve clarity in PB.

Adherence to the PB Naming Standard

It almost goes without saying, since this is fairly generally recognized: standard PB naming conventions are mandatory for clarity. Sometimes an overriding justification can be made for specific exceptions, as proposed later in this paper.

Note that many developers (including me) consider it unnecessary to adhere to the standard for local variables.

Short Names

Beyond adherence to naming conventions, clarity demands the avoidance of unnecessarily long names. A line like the following is simply confusing, even though it actually does very little:

customer_address = gnv_app.inv_string_service.of_replace( &
   model_dw_syntax, &
   ' name=' + as_object_name_array[1], &
   ' name=<name>')

A variable name like “customer_address” does nothing to enhance clarity. “Cust_addr” is perfectly clear, and if the context implies “customer,” then “addr” alone is sufficient.

Short names eliminate clutter, reduce the need to use line continuation (“&”), and make the logic (or lack of it) more apparent.

Unfortunately, some developers choose names without regard for the information that is readily available from context. The resulting names are unnecessarily complex. In the above example, an array variable’s name includes the word “array” even though variable references always make it clear when a variable is an array.

Words like “object” or “businessobject,” when included as part of a name, usually provide very little if any additional information.

Ninety nine percent of data models I have seen, in consulting to many organizations, have column names that indicate which table the column is in. Thus, an “address” column in a “customer” table will have a name like “customer_address”. Yet almost everyone realizes that this is completely redundant. When Broadway Ave. was named in New York City, it never occurred to anyone that it might be necessary to call it “New York City_Broadway Ave.” to distinguish it from Broadway Ave.’s in other cities. Context eliminates ambiguity. Yet modern data modelers rarely apply the same principle.

Consistency: Rules rather than Caprice

As long as we appreciate that there is no reason to attempt to make column names unique across all tables, why not standardize names, for a given app or a given database, for all columns that have a similar purpose? Here are some suggestions:

In PB, a good rule to adopt in variable naming is to omit the suffix altogether in cases where the prefix alone naturally suggests some unique thing. For example:

Writing software involves a continuous series of decisions. Much of the time spent maintaining or updating software is spent searching, searching, for which decision was made by one’s predecessor. Maintenance overhead is greatly reduced if the developer’s decisions are guided by consistent rules, instead of being made arbitrarily.

To achieve a uniform system of names, particularly on team efforts, it is often beneficial to set up a dictionary of abbreviations for reference in naming both database and PB objects (tables, columns, userobjects, variables, etc.).

Storing Minimal Information In DW’s

Information programmed in datawindows cannot be read efficiently. To examine the edit format for half a dozen columns, for example, one would have to open the dw painter and, for each in turn, find the column, select it, bring up the Properties dialogue, and select the Edit tab. The situation is identical for other kinds of datawindow information: display formats, validation rules, expressions, and so on.

Clarity demands that this kind of information be programmed in scripts, so it can organized and easily written, changed, and viewed.

Avoiding Repetitive Code Patterns

In database design, the principle of centralization is well understood: don’t keep many copies of the same information in different places. It’s redundant and wasteful, the copies are liable to get out of sync, and updates are more complicated. The principle is the same in software.

Repetitive patterns are too often found in PB systems. For example, it is not unusual to see hundreds of destructor scripts doing the same thing:

If isvalid(x) then destroy x
If isvalid(y) then destroy y
If isvalid(z) then destroy z
...
[Etc.]

The better alternative is to code an easy utility function, say “of_destroy(),” that can be invoked whenever needed:

of_destroy({x, y, z, ...})

This is clearly more readable, more maintainable, less error-prone.

[Note: This particular function is no longer needed, thanks to the automatic garbage collection introduced in v. 6.]

Generality

In order to accomplish the immense benefits of centralizing code, it is necessary to design utilities in such a way that they are usable in many different situations.

EXAMPLE OF GENERALIZATION: One can build a kitchen sink with two spouts, one for hot water, one for cold. The result makes two temperatures available, hot and cold. The more general solution features a single spout, so the user can produce a flow of any temperature between hot and cold.

Some Conceptual History

Hyper-Productivity is not new, but the methodology has not generally been applied in the PowerBuilder domain. The potential of Hyper-Productivity was demonstrated years ago by Kenneth Iverson, the Harvard mathematician, IBM researcher and software pioneer who transformed the software landscape with his creation of APL, a programming language in which a few symbols could replace paragraphs of code in conventional languages.

Iverson created what he called “a tool for thought,” a programming language designed to promote simplicity, brevity, and consistency. This actually made intrinsically difficult ideas easier to understand.

The manipulation of concepts is at the heart of the business of producing software. As noted above, there can be little question that the concepts are challenging, even in mundane corporate applications, considering that correct and satisfactory software is such a rarity.

Hyper-Productivity is all about bolstering native PowerBuilder with an array of well-designed, generic utilities. The advantages of a well-designed language, or notation, for improving our ability to manipulate difficult ideas has been understood since long before Iverson’s “tool for thought.” In 1911, the mathematician Alfred North Whitehead wrote:

By relieving the brain of all unnecessary work, a good notation sets it free to concentrate on more advanced problems, and in effect increases the mental power of the race. [...] By the aid of symbolism, we can make transitions in reasoning almost mechanically, by the eye, which otherwise would call into play the higher faculties of the brain. It is a profoundly erroneous truism [...] that we should cultivate the habit of thinking of what we are doing. The precise opposite is the case. Civilization advances by extending the number of important operations which we can perform without thinking about them. [An Introduction to Mathematics (New York and London, 1911), p. 59.]

Similar insights were articulated centuries earlier, at a time when the general practice was to express mathematics in prose, without symbols:

[...] Which Treatise being not written in the usuall synthetical manner, nor with verbous expressions, but in the inventive way of Analitice, and with symboles or notes of things instead of words, seemed unto many very hard; though indeed it was but their owne diffidence, being scared by the newness of the delivery; and not any difficulty in the thing it selfe. For this specious and symbolicall manner, neither racketh the memory with multiplicity of words, not chargeth the phantasie with comparing and laying things together; but plainly presenteth to the eye the whole course and processe of every operation and argumentation. [William Oughtred, The Key of the Mathematicks (London, 1647), Preface.]
The mathematician Howard Aiken once remarked: “Don’t worry about people stealing your ideas. If your ideas are any good, you’ll have to ram them down people’s throats.”

This is illustrated by early opposition to the use of symbols, a way of working with ideas that is taken for granted today (at least in mathematics). One objection came from Thomas Hobbes (1588-1679), the famous English philosopher and political theorist:

Symbols, though they shorten the writing, yet they do not make the reader understand it sooner than if it were written in words. For the conception of the lines and figures [...] must proceed from words either spoken or thought upon. So that there is a double labour of the mind, one to reduce your symbols to words, which are also symbols, another to attend to the ideas which they signify. Besides, if you but consider how none of the ancients ever used any of them in their published demonstrations of geometry, nor in their books of arithmetic [...] you will not, I think, for the future be so much in love with them. [Quoted in “A History of Mathematical Notations,” by Florian Cajori (The Open Court Publishing Co., Chicago, 1928), pp. 426-431.]

Beefing Up PowerBuilder

Global Utilities

To enable Hyper-Productivity, PB needs to be beefed up. Many generic, widely needed functions are not built-in. And many that are built-in are inadequately designed and need to be replaced.

IF() is an example of an omission. PB developers are familiar with its usefulness as a datawindow painter function. It is just as useful in PowerScript. Other languages provide it, but not PB.

OPENSHEETWITHPARM() is an example of inadequate function design. It is the unreliable method provided by PB for passing parameter information to a new window. OPENSHEETWITHPARM() uses the global MESSAGE variable, which may be overwritten by other processes (specifically, those resulting from the execution of the Constructor scripts of the new window’s objects) before it is read by the new window. Moreover, OPENSHEETWITHPARM()’s design makes parameter passing overly awkward.

User-defined functions for such tasks, having generic applicability, are referred to as “utility functions.” Since the utility functions we are adding to PB are needed pervasively in our code, we want to be able to regard them as part of the basic language facility. We want them to be as accessible as PB’s own native functions. We do not want to suffer the awkwardness of writing something like:

is_msg = gnv_app.inv_beef.of_if( &
   ib_success, 'It worked.', 'It failed!')

... whenever we need to invoke them. In the Hyper-Productive framework, we can invoke utilities as easily as:

is_msg = g.if1( &
   ib_success, 'It worked.', 'It failed!')

Note the convenience. We purchase this convenience at the expense of specific, limited, and well-defined deviations from the PB naming standard:

Standard Object Utilities

In addition to the above, we have a base class standard user object corresponding to each Standard Window Object (e.g. datawindow, commandbutton, etc.) and each Standard Non-Visual Object (e.g. datastore, transaction). This permits us to further supplement PB’s built-in functions by adding utilities to these as needed.

Our names for these base objects would be the standard prefix without the suffix (e.g. “u_dw” for the base datawindow object) -- except that this is how PFC’s base objects are named, and we want to be able to use these objects in projects that have previously chosen to use PFC. Therefore we append a “1” suffix, so the name for our base dw object is “u_dw1”.

In these objects, we have two categories of object functions:

Our system is to omit the “of_” prefix and add a “1” suffix in naming functions in the first category -- thus, “setrow1()”; and to comply with the standard in naming functions in the second category -- thus, “of_fixstatus()”.

Implementation

n_cst_global and the base objects described above are all stored in UTILS.PBL, which is appended to the application’s library search path.

The global variable <g> is declared as:

n_cst_global    g

The app open event does:

g = create n_cst_global

In order for the utilities in u_dw1 to be available in a specific datawindow, it must be inherited (directly or indirectly) from u_dw1. Similarly for the other base objects in UTILS.PBL.

Application: The Methodology in Action

UTILS.PBL is offered here as freeware by the author (utils.zip), but no package of such routines is ever complete.

To apply Hyper-Productivity, the developer must appreciate:

The art of utility design often involves an incremental process of re-design and modification. To achieve great utilities, the developer must not be content with adequacy. He/she should be prepared to fix things that “ain’t broke.” That is the path to Hyper-Productivity.

This paper’s illustration of Hyper-Productivity therefore does not dwell on UTILS.PBL or the implementation details of utility functions. What we present here is a single, specific illustration of the methodology in action.

Requirements

Suppose user requirements call for the Runtime Datawindow shown (in various horizontal scroll states) in the figures. (We accept as given the users’ assurance that this particular presentation style suits their purposes.) The datawindow’s columns are dynamically determined by information in the database. On the “Certificate Structure” tabpage (not shown), users may add or delete certificates, and checkboxes enable users to mark each certificate as being either of type “Credit Support” (CS) or “non Credit Support” (non-CS). For purposes of illustration, the CS certs in the example are named after cities, the non-CS certs after automobiles.

The “Certificate Data” tabpage (shown in the figures) groups the certificates, showing, from left to right, the CS certs alphabetically, then the non-CS certs alphabetically. Each CS cert has an Amount entry column and two computed fields; each non-CS cert has only the Amount entry column. In addition, there is the date column at the extreme left, as well as the aggregation columns for CS and non-CS certs.

Note the sheet title, indicating that the data pertains to a specific policy (selected by the user elsewhere in the system). Our database accesses need to refer to the appropriate policy.

Implementation

Our tabpage is a custom visual user object named u_polpc. The datawindow control it contains is u_dwpolpc, a userobject inherited from u_dw1 (our base datawindow control).

We begin with script “ue_setupds” of u_dwpolpc, whose purpose is to set up datastores. It begins by defining ids_certs, a datastore with columns CERT_NAME and CREDIT_SUPPT_FLAG (a CHAR(1) column with value “Y” or “N”). These columns tell us the names of the certificates we have to display in the datawindow, and whether each is type CS or non-CS. We define ids_certs as follows:

/* sqlcerts
select 
   CERT_NAME, 
   CREDIT_SUPPT_FLAG
from POLICY_CERT 
where POLICY_ID = <pid>
*/

ids_certs = g.createds(g.rplc(&
   g.getstring(this, 'ue_setupds', 'sqlcerts', true),&
   '<pid>', &
   string(il_pid)))

What the above is doing is, first, generating the SQL Select string which is used to dynamically create the datawindow object for the datastore. This is done by getting the model Select statement shown in the comment, then modifying it by replacing “<pid>” (policy ID) by the actual policy number.

g.getstring() is the utility used to get the model Select statement stored in the comment. g.getstring() returns a single, arbitrary-length string stored between comment delimiters in an event or function script. Its arguments are: an object handle; the name of the event containing the desired string; the string identifier (in this case, “sqlcerts”); and a boolean indicating whether special characters (carriage returns, linefeeds, tabs) are to be removed. (getstring()’s implementation uses the Classdefinition property.)

Once we have the form of the Select statement, we replace “<pid>” with the actual policy number. We now have the actual Select statement to be used in creating the datastore, and we can invoke g.createds(), a utility whose return value is a datastore of type n_ds1 (our base datastore object).

Our datastore is done, except for a couple of details. We do not want to assume that the database column will necessarily contain a “Y” or “N”. So we create a computed field which is “Y” if the flag column is “Y” or “y”, and “N” if it is anything else (including NULL):

ids_certs.of_createcf(&
   'csflag', &
   "if(isnull(CREDIT_SUPPT_FLAG), 'N', " + &
      "if(upper(CREDIT_SUPPT_FLAG) = 'Y', 'Y', 'N'))")

Finally, we set the datastore’s sort order, in accordance with the required left-to-right cert sequence:

g.syserr(ids_certs.setsort('csflag D, cert_name A'))

We take a diversion here to discuss the very useful g.syserr(). PB is full of built-in functions which can fail, and which indicate failure only via a return value. Error results are generally one of: a negative number (usually -1), the boolean FALSE, or a non-empty string.

Few PB developers check all return values for errors. The conventional IF / ENDIF code pattern for testing these values clutters code up considerably, harming clarity, and many tests are considered unnecessary. Still, an immediate warning of an unexpected error can save a lot of debugging time during development. Hence the usefulness of g.syserr().

g.syserr() does nothing if its argument (numeric, boolean or string) indicates no error. But if an error is indicated, it deliberately produces a PB runtime error condition by dividing a number by 0. When running an app in PB 6.5 under NT 4 via “running man,” this opens up the debugger and shows the line in syserr() where the zero-division is coded. Thanks to 6.5’s enhanced debugger, the developer can then view the calling stack and see exactly where the unexpected error has occurred. In the case above, he/she will see the line containing the call to SETSORT(). Via the debugger’s “Set Context,” one can at that point do things like examine the values of local variables in the script containing SETSORT().

Variations on this technique were also available in PB 5. There one could code:

g.l = g.syserr(ids_certs.setsort('csflag D, cert_name A'))

g.l was a public instance var in n_cst_global of type long. g.syserr() returned a long value if no error, or a string if its argument indicated error. Thus PB would crash on the line containing the problem (i.e., the line containing setsort()).

g.syserr() can be used to confirm any condition, not just those involving return codes.

Of course, we want smoother handling of unexpected conditions in distributed executables. But we don’t want to have to change our scripts. Therefore g.syserr() tests the condition,

0 = handle(getapplication())

and alters its deliberate crash behavior if the result is FALSE.

Continuing with our u_polpc tabpage, we now need to define our next datastore:

/* sqlppc
select 
   POLICY_ID, DISTRIB_DATE, CERT_NAME, BAL_AMT  
from POLICY_POOLDISTRIB_CERT 
where POLICY_ID = <pid>
order by distrib_date
*/       
ids_ppc = g.createds(g.rplc(&
   g.getstring(this, 'ue_setupds', 'sqlppc', true), &
      '<pid>', &
      string(il_pid)), &
   {'POLICY_ID', 'DISTRIB_DATE', 'CERT_NAME'})

Since we will be calling ids_ppc.update(), we use the polymorphic variant of g.createds() which permits us (via its second argument) to specify the datastore’s unique key columns (i.e., the table’s Primary Key, which in this case is composite).

BAL_AMT (“balance amount”) is user-editable. We will need to rearrange this data to map it to the Runtime Datawindow, which has the certs arranged horizontally.

The system now proceeds to the ue_setupdw script, in which the Runtime Datawindow displayed in the figures is produced.

Our dw gets the look of perfection by having its objects positioned with mathematical precision. Consider the dw’s groups and items.

There are multiple groups of items: the date items (text and column object) are one group; “Amsterdam” is another group, consisting of four text objects, a column, and two computed fields; and so on. We are going to make the horizontal gap between each pair of consecutive groups identical. We call this gap il_gap.

The CS cert groups contain multiple horizontally spaced items: Amount, % Curr, % Begin. We are going to make the horizontal gap between each pair of contiguous items identical, within each CS cert group. We call this gap il_itemgap.

The static version of our tabpage’s datawindow object (see figures) has everything that the Runtime Datawindow shown in the figures has, except that it has exactly one CS cert and one non-CS cert.

We set il_gap as the distance between the date text object and the CS cert group header text object in our Static Datawindow. Similarly, we set il_itemgap as the distance between the “Amount” and “% Curr” text objects in the CS cert group of the Static Datawindow:

// gap between groups of items:
il_gap = of_hgap('distrib_date_t', 'cshd_t1')

// gap between items within group:
il_itemgap = of_hgap('cs_t1', 'cs_curpct_t1')

Here we are using of_hgap(), a utility in our base dw which yields the horizontal gap between two dw objects.

With this technique, adjusting the identical horizontal gap between all groups in our Runtime Datawindow, or between items in all groups, is a simple matter of opening the dw painter and repositioning the objects being used as models.

We will need to set the X and TABSEQUENCE properties of dw objects. For this purpose we initialize il_cumpos and ii_cumtabseq:

il_cumpos = descl('cshd_t1.x') 
ii_cumtabseq = 1

u_dw1.descl() is like dw.describe() except it returns its result as type LONG.

The vars above are “cumulative” in the sense that they keep track of increasing values as we go left to right, setting dynamic datawindow object properties.

Now we are ready to call of_setupgrps(), a u_dwpolpc object function that is called once to set up all the CS groups, and a second time to set up all the non-CS groups.

It is time for another diversion, since one of of_setupgrps()’s arguments is a structure type.

We rarely enter the structure painter, because UTILS.PBL already contains a structure, called “s_generic,” which fits just about every need:

global type s_generic from structure
   integer      i[]
   long         l[]
   string       s[]
   date         d[]
   any          a[]
   any          a_sheetkey
   powerobject  po[]

Occasionally a need arises to add something that is missing in the above, e.g. datetime dtm[]. Such additions to s_generic have no effect on existing code.

In this case, the only structure property of interest is the string array, s[]. The others are unused. We are using s_generic only in order to get an array of arrays.

of_setupgrps()’s first job is to “set up groups” of CS certs and define istr_cs[], an instance var in u_dwpolpc of type s_generic representing CS-type certs. of_setupgrps() defines istr_cs[j].s[i] as the name of the j’th dw object in the i’th group of CS certs.

Similarly, when it is called a second time, of_setupgrps() “sets up groups” of non-CS certs and defines istr_ncs[].

The items for CS group 1 and non-CS group 1 all exist already in our Static Datawindow (see figures). of_setupgrps() dynamically creates the objects for any other cert groups, depending on the dynamic cert data in ids_certs. Also, in case the data indicates 0 CS groups, of_setupgrps() makes the items in CS group 1 (which already exist) invisible; similarly if there are 0 non-CS groups.

of_setupgrps() takes the following arguments:

ref s_generic astr_grp[],
   // reference argument

readonly string as_grpitems[],
   // the names of the items in the group, 
   // minus the suffix indicating group #

readonly string as_objtype,
   // the i'th character in this string is a code indicating
   // the type of as_grpitems[i]: Date, Amount, Count, or Percent; 
   // or X if the format property does not need to be set.

readonly integer ai_widthidx,
   // index # of item which defines the group's width;
   // - as_grpitems[ai_widthidx] spans the whole group width-wise

readonly integer ai_textidx,
   // identifies the item (as_grpitems[ai_textidx] whose TEXT 
   // property needs to be set dynamically according to the cert name. 

readonly boolean ab_cs
   // Indicates whether certs to be set up are CS-type.

The two calls to of_setupgrps() are as follows:

of_setupgrps ( &
   istr_cs, &
   {'cshd_t', 'cs_t', 'cs_curpct_t', 'cs_beginpct_t', 'cs', &
           'cs_curpct', 'cs_beginpct'}, &
   'XXXXAPP', &
   1, &
   1, &
   true)

of_setupgrps ( &
   istr_ncs, &
   {'ncshd_t', 'ncs_t', 'ncs'}, &
   'XXA', &
   1, &
   1, &
   false)

Here is how of_setupgrps() does the job:

First, it defines <cnt>, the number of cert groups to be created. This depends on the argument ab_cs, which tells of_setupgrps() whether it is being called to set up CS or non-CS groups of certs:

// # of groups of certs of this type:
flagexpr = 'csflag="' + g.if1(ab_cs, 'Y', 'N') + '"' 
      // e.g.: csflag = "Y"
cnt = ids_certs.descl(&
   "Evaluate('Sum(if(" + flagexpr + ", 1, 0))', 0)")

Next, the main job is carried out:

// create groups of items (or make grp 1 invisible if cnt=0):
for j = 1 to upperbound(as_grpitems)
   // before duplicating it, set format of object j:
   fmt = g.case1(&
      mid(as_objtype, j, 1), &
      'DACP', &
      {iw.is_fmtdate, iw.is_fmtamt, iw.is_fmtcount, &
               iw.is_fmtpct, 'X'})
   if fmt <> 'X' then &
      modify1(as_grpitems[j] + '1', &
         {'format = "' + fmt + '"'})
   
   of_dupobj(as_grpitems[j], cnt, astr_grp[j].s)
next

This involves a couple of utilities we have not yet seen. g.case1() is straightforward. By way of illustration,

case1('d', 'abcde', {1, 2, 3, 4, 5, 6}) 

returns 4; and

case1('f', 'abcde', {1, 2, 3, 4, 5, 6}) 

returns 6.

u_dw1.of_dupobj() is more involved. As an example, of_dupobj("woody", 10, arr) would expect an object “woody1” (of any type -- column, text object, computed field, etc.) to exist in the dw. It would make 9 copies of it, so there would be 10 in all; name the new ones “woody2,” “woody3,” ..., “woody10”; and, for the the caller’s convenience, fill reference string parameter arr[1 to 10] with the values, “woody1” to “woody10”.

If 0 were passed to of_dupobj() instead of 10, all it would do is make “woody1” invisible, then exit.

Much of of_setupgrps()’s job is done on completion of the loop above. If cnt = 0 there is nothing left to do, and it returns. Otherwise, it has to set the x, text, and tabsequence properties for various objects:

grpsize = descl(as_grpitems[ai_widthidx] + '1.width') + il_gap

// define item offsets within groups:
for j = 1 to upperbound(astr_grp)
   offset[j] = descl(astr_grp[j].s[1] + '.x') - &
               descl(astr_grp[1].s[1] + '.x') 
next

// set x, tabsequence, text:
// txt1 is text of item ai_textidx up to and incl CRNL:
txt1 = describe(astr_grp[ai_textidx].s[1] + '.text')
// now cleanup, because describe() returns it with
// surrounding quotation marks!
txt1 = trim(txt1)
txt1 = g.midse(txt1, 2, len(txt1) - 1)

txt1 = left(txt1, pos(txt1, g.crnl(1)) + 1)
for i = 1 to cnt
   lrow = ids_certs.find1(flagexpr, lrow + 1)
   g.syserr(lrow > 0)
   txt = txt1 + ids_certs.object.cert_name[lrow]
   modify1a(astr_grp[ai_textidx].s[i], &
      'text = "' + txt + '"')
   for j = 1 to upperbound(astr_grp)
      modify1a(astr_grp[j].s[i], &
         'x=' + string(il_cumpos + offset[j]))

      if upper(describe(astr_grp[j].s[1] + '.type')) = &
              'COLUMN' then 
         if descl(astr_grp[j].s[1] + '.tabsequence') > 0 then 
            ii_cumtabseq += 1
            modify1a(astr_grp[j].s[i], &
               'tabsequence=' + string(ii_cumtabseq))
         end if
      end if
   next
   il_cumpos += grpsize 
next

The above involves a few simple utilities not yet seen:

g.crnl(ai_count):    
     returns fill('~r~n', 2 * ai_count)

g.midse(as, al_start, al_end):    
     like mid(), but args specify start and end

u_dw1.find1(as_expr):    
     returns find(as_expr, 1, rowcount())

u_dw1.modify1a():    
     similar to modify()

The job is not quite complete. Some additional work remains, for example, positioning the aggregate CS and aggregate non-CS columns. But the above (which runs successfully in PB 6.5) should go some way towards describing the flavor of Hyper-Productivity and showing its potential.

Software Productivity: The Big Picture

You can have the most sophisticated luxury sedan in the world. But if snipers are shooting from rooftops along your route, you still might end up dead before reaching your destination. A paper on PowerBuilder productivity would therefore be incomplete without mention of the many hindrances faced by even technically adroit PB developers.

Hindrance #1

Our first problem is the company that owns and maintains PowerBuilder. An outsider dealing with Sybase managers becomes accustomed to limitless enthusiasm. They are “committed,” they are “proud of what we have accomplished,” they welcome our input and take our suggestions very very seriously. Unfortunately, all this talk generally translates into very little or no action.

Policies that make no sense either for Sybase or its customers cannot be meaningfully discussed, much less changed. PowerBuilder’s innumerable quality problems have always taken a heavy toll on productivity, and the situation persists in version 6. Awkward bug-reporting procedures convey more convincingly than words that Sybase is not keen to receive bug reports. Known bugs appear to be a corporate secret. Technical support is a disaster.

The people most intimately familiar with PB’s quality problems are the PB software developers of the world. Although one would expect software professionals to want to devote their energies to true software challenges, rather than spending time rebooting computers and hunting for bugs that don’t actually exist, developers have for the most part been uncritical. Some are vociferous in defending PB’s failings.

PB developers are like nurses in a mismanaged hospital, routinely observing hair-raising accidents: fingers lopped off by a slipped scalpel, heavy equipment dropped by a surgeon onto an exposed heart, cleaning solution pumped into patients on dialysis machines. The nurses are resigned to the institution’s shortcomings and can even joke about them, but outsiders would be horrified by what goes on.

These are some of the fundamental quality issues that have persisted in successive versions of PB over the years:

Aside from the fundamental quality issues, the sheer sloppiness indicated by many minor issues suggests a disdain for quality. The following are examples in v. 6.5:

While it is regrettable that quality issues have not aroused more protest from PB developers and within Sybase itself, the scant attention paid to quality by corporate clients is the real puzzle. They are after all the ones for whom the defects translate directly into concrete monetary losses. Yet one does not get a sense that clients have been clamoring for Sybase to address the product deficiencies and poor tech support that have been an integral part of the PB experience for years. While these problems have persisted, Sybase has expended apparently substantial efforts on the addition of language features that offer little real benefit to the majority of clients. One example is the illusory “distributed processing” features of version 5, which in the typical case probably yielded only negative benefits by luring clients into expensive and fruitless technology experiments.

It seems improbable that Sybase has been defying demands and pressure from a large segment of the PB client base. More likely, decision-makers in client companies are unaware of the extent of quality problems, or have failed to properly assess their cost.

Managers and developers concerned about productivity should be alert to featuritis, the tendency to evaluate software products by adding up the number of check marks in a feature list. While such a superficial evaluation procedure seems laughable, it may be more prevalent than one would suppose. (It is sometimes abetted by developers themselves, more eager to gain experience with “cutting-edge” technology features than to develop systems that work.)

Beyond avoiding featuritis, managers should appreciate that productivity -- hence their bottom line -- is powerfully impacted by the quality of the software tools in use by developers. It would therefore make sense for corporations with significant software development expenses to pay serious attention to quality problems.

Further Hindrances

With tenacity and the right approach, one can overcome PB’s defects and produce reliable applications with a rich and sophisticated GUI. But additional hazards along the way must be avoided:

Conclusion

Decent systems that function correctly and have an attractive and efficient user interface can be achieved with PowerBuilder. Unfortunately, such systems are the exception. Ambitious managers and developers can produce dramatically superior systems by multiplying conventional developer productivity levels. This paper highlights the ways to achieve the most substantial gains:


Related: Reflections on Corporate America


Home