A new Directive ROUTINE-LEVEL LOCAL-DEFAULT-BUFFERS. - Community - Products Enhancements - Progress Community

 Community

A place for users to suggest enhancement requests and/or ideas for making this Community even better

A new Directive ROUTINE-LEVEL LOCAL-DEFAULT-BUFFERS.

ROUTINE-LEVEL LOCAL-DEFAULT-BUFFERS.

This Enhancement is Request is about mastering the key construct of the 4GL/ABL that still makes this technology attractive: mastering the record buffer. Many quality issues on the fields are due to a poor handing of it.

We need a new directive to enforce the Best Practice of using record buffers with a local scope as much as possible:

Each routine (method/procedure/UDF) should use only local buffers and use buffer parameters when it is needed to share them with other routines. This applies to both database and temp-table buffers.

Note: a default buffer is the buffer named like its target database table or temp-table. The ABL defines it implicitly with a default global scope (the entire Class or External Procedure).

The idea:

The above suggested ROUTINE-LEVEL LOCAL-DEFAULT-BUFFERS. new directive referred in a Class or in an External Procedure would make the compiler define implicit default buffers locally bound to each individual routine, except for default-buffers that are received as buffer parameters (they are already locally bound), or for buffers that are explicitly defined in the definition block implicitly to get a global scope*. It would be relatively straight forward to adopt this feature progressively in existing code.

For now, without this new feature, we have to do the following manually in routines:

DEFINE BUFFER customer FOR customer.

DEFINE BUFFER ttcustomer FOR ttcustomer.

DEFINE BUFFER salesrep FOR salesrep.

Etc…

For now, it is possible to detect buffers with unwanted global scope by analyzing XREF files, but it is rather tedious.

 

* One example for the need of a global scope: an old .w UI file with a browse bound to a static query and buffer, the buffer does need to be global. If people need to keep the browse bound to such a default-buffer, then we need to be able to define the default buffer as global explicitly in the definition block with:

DEFINE BUFFER customer FOR customer. /*global scope for my browse */

 

Further Notes:

A] The main advantages of the Locally Scoped Buffers Best Practice:

A.1) Avoid record lock leaking/bleeding for ABL objects and persistent procedure with long life (especially on AppServers).

Example: One hit on an AppServer loads a class or a persistent procedure then calls a routine in it that fetches a record with an exclusive-lock. If the buffer scope is global, then it remains available as long as the Class or External Procedure remains in memory with a lock downgraded to share-lock, which causes lock conflictz with other users or AS Agents.

A.2) Avoid no-lock data leaking/bleeding.

Bad Example:

METHOD PUBLIC VOID MethA():

   FIND FIRST ttcustomer.

   MethB().

END METHOD.

 

METHOD PUBLIC VOID MethB()

   IF AVAILABLE ttcustomer THEN…

END METHOD.

 If MethB is called from somewhere else, then we hit a data bleeding due to the context of MethA that was called ages ago, perhaps even in another AppServer Hit for another client.

Note : with the new directive, it would be nice to lead a case like MethB raise a fortunate compilation error like this (close to error 232):

** Missing FOR, FIND or CREATE for <buffer> in current block. (<new message number>)

 

Good example:

METHOD PUBLIC VOID MethA():

    DEFINE BUFFER ttcustomer FOR ttcustomer. /* buffer bound to the scope of MethA */

    FIND FIRST ttcustomer.

    MethB(BUFFER ttcustomer).

END METHOD.

 

METHOD PUBLIC VOID MethB(BUFFER ttcustomer FOR ttcustomer):

    /* the scope of the ttcustomer buffer is bound to the caller */

    IF AVAILABLE ttcustomer THEN…

END METHOD.

 

A.3)  Aside Data bleeding, isolation with less global resources leads to smarter code that is easier to analyze and maintain.  Each routine hold the responsibility of the scope of its buffer even if it shares it with sub-routines.  One no longer has to wonder "where can this routine be called from regarding the scope of this buffer?"  (traditional spaghetti coding...)
 

B] Notes about the bad practice of using non default buffers:

DEFINE BUFFER bcustomer FOR customer.

FIND FIRST bcustomer WHERE

  [much further in the code]

IF AVAILABLE customer THEN

=> Here, the ‘b’ prefix is missing by mistake. It compiles just fine and is pretty hard to diagnose.  Hence the following 2nd Best Practice for record buffers:

Define an additional (non-default) buffer only when you need a *functional* additional buffer, and if possible with a functional name. Otherwise you shall use the default buffer.

Example of good functional names (ban the ‘b’ or ‘b_’ prefix)

DEFINE referencecustomer FOR customer.

DEFINE BUFFER childtier FOR tier.

 

Indeed, encouraging the usage of default-buffers will also make a developer think better about how to name an additional buffer when they really need one, and then produce higher quality code.

Comments
  • Sorry, I should have submitted this idea on the OpenEdge Development group.  (the UI was a bit confusing)