ABL Coding Standards - single page

ABL Coding Standards - single page

This document lists the ABL coding standards propagated by Consultingwerk. These coding standards are followed by Consultingwerk during tool development and until further instructions are given when working on customer projects.

The coding guide line is considered to be a living document. New versions are effective after they have been published on this page.

ASSIGN

Assign statements should be grouped together into one assign statement. This increases application performance.

ABL Keywords

Casing

As long as the official 4GL/ABL Documentation uses upper case keywords we use upper case keywords. Developers need to pay attention on casing, "the editor did not provide the proper case" is not an excuse.

All .NET Keywords (class names, class member names) should be exactly cased like in the .NET framework (typically camel cased with an initial capital letter).

The Progress Compiler relies on the first reference of a .NET Class name in exact writing anyway. Further this makes it clearer what kind of keyword is used, Sample:

USING System.Windows.Forms.* FROM ASSEMBLY . DEFINE BUTTON btnOk NO-UNDO . /* defines an ABL Button Widget */ DEFINE VARIABLE btnOk AS Button NO-UNDO . /* defines a .NET Button */

Abbreviations

Bad Examples:

Def var i as i NO-UNDO. Disp i .

4GL/ABL keywords must be written out unabbreviated. Always uppercase 4GL/ABL keywords.

  1. It’s more readable for human

  2. It’s more readable for tools (easier to search and replace)

  3. It’s more aligned with OpenEdge documentation and samples (except those published by Progress Software recently with the OERA samples)

DEFINE VARIABLE i AS INTEGER NO-UNDO . DISPLAY i .

There are a few ABL keywords where the ABL documentation obviously provides an abbreviated form. One example is the RCODE-INFO system-handle. This appears to be an abbreviation for RCODE-INFORMATION.

It is considered good practice to use the documented form and not the undocumented form in cases like this.

Code Style

Class Statement

At the beginning of a CLASS or INTERFACE block, the IMPLEMENTS and INHERITS options should be broken into the next line in the source code.

CLASS Demo.Customer.CustomerBusinessEntity INHERITS BusinessEntity USE-WIDGET-POOL:

Up to two or three (depending on the length of the Interface name) implemented Interfaces may be placed after the IMPLEMENT phrase. More interfaces should be broken up into multiple lines.

CLASS Demo.Customer.CustomerBusinessEntity INHERITS BusinessEntity IMPLEMENTS IInterface1, IInterface2, IAnotherInterface USE-WIDGET-POOL:

or

CLASS Demo.Customer.CustomerBusinessEntity INHERITS BusinessEntity IMPLEMENTS IInterface1, IInterface2, IAnotherInterface, IJustAnotherLongInterfaceName USE-WIDGET-POOL:

Declarations

Grouping of variable declarations

Group variable declarations into logical blocks. Separate those blocks using blank lines.

The AS and NO-UNDO phrases should be formatted in tabular fashion.

All variables have to be declared at the beginning of a routine level block. The ABL currently (subject to change in the future) only supports variables scoped to a routine level block (compile unit, class, method, procedure, …) and not to simple blocks like a DO, FOR EACH or REPEAT. So we want to make the scope of a variable clear by enforcing where it’s placed.

DEFINE VARIABLE cEntity AS CHARACTER NO-UNDO. DEFINE VARIABLE cTables AS CHARACTER NO-UNDO. DEFINE VARIABLE cQueries AS CHARACTER NO-UNDO. DEFINE VARIABLE cJoins AS CHARACTER NO-UNDO. DEFINE VARIABLE iNumRecords AS INTEGER NO-UNDO. DEFINE VARIABLE hFindDataset AS HANDLE NO-UNDO. DEFINE VARIABLE cInitialContext AS CHARACTER NO-UNDO. DEFINE VARIABLE iPos AS INTEGER NO-UNDO. DEFINE VARIABLE lWaitStateActive AS LOGICAL NO-UNDO. DEFINE VARIABLE cQueryString AS CHARACTER NO-UNDO. DEFINE VARIABLE lPositionWasZero AS LOGICAL NO-UNDO INIT FALSE .

Parameter Definitions

All Parameters should be defined at the beginning of a PROCEDURE or FUNCTION.

Class Variables

Variables should be defined without the package name to enhance readability of source code.

DEFINE VARIABLE ... AS {unqualified class of ...} NO-UNDO .

Indentation

Test Code (Debugging through messages)

While all program code should be nicely indented following it's block structure, test messages etc. are supposed to be aligned to the very left of the source code.

Example:

DatasetHelper:ThrowDatasetErrors (DATASET dsAuftragsart:HANDLE) . FIND FIRST eAuftragsart . BufferHelper:ShowBuffer (BUFFER eAuftragsart:HANDLE) . /* Mandant/Unternehmen should be assigned by HaufeDataAccess:CommitChanges */ FIND Auftragsart WHERE Auftragsart.MandantNr = oAppSession:Tenant:Mandant AND Auftragsart.Unternehmen = oAppSession:Tenant:Unternehmen AND Auftragsart.AuftragsartNr = iAuftragsArtNr NO-LOCK NO-ERROR .

This makes it more obvious that the code should be removed before finalizing the work.

When commiting your code review it for debug code that can be removed.

Check closley if statements on position 0 aren’t there by accident. If they are, correct you indentation.

IF and CASE

If nested formatting of a single IF statement increases readability, this is considered good practice. But it should not be overdone.

IF THIS-OBJECT:DesignTime THEN RETURN FALSE . IF VALID-HANDLE(hDataset) AND NOT THIS-OBJECT:FindString > "":U THEN hDataset:EMPTY-DATASET () . IF VALID-OBJECT (THIS-OBJECT:SmartDataSource) AND THIS-OBJECT:ForeignFields > "":U THEN THIS-OBJECT:FetchQueryString = SUBSTITUTE("&1 &2":U, THIS-OBJECT:EvaluateParentQuery (), THIS-OBJECT:QuerySort). ELSE DO: IF THIS-OBJECT:QueryString = "":U AND THIS-OBJECT:QuerySort > "":U THEN THIS-OBJECT:FetchQueryString = SUBSTITUTE ("FOR EACH &1 &2":U, THIS-OBJECT:EntityTable, THIS-OBJECT:QuerySort) . ELSE THIS-OBJECT:FetchQueryString = SUBSTITUTE("&1 &2":U, THIS-OBJECT:QueryString, THIS-OBJECT:QuerySort). END.

A THEN DO or ELSE DO block is recommended when a THEN contains an IF statement and one of the two IF statements uses an ELSE option.

In case of a sequence of IF statements on related conditions it is recommended to use a CASE block.

When coding an IF statement with only one command following no DO block is required and the statement should be broken with indentation into the following line.

IF ... THEN Statement IF ... THEN DO: Statement. Statement. END.

When inserting more than one statements between the if statement the DO is kept in the line with the IF.

Inline statements are indented with 4 leading spaces and the END is on the same indentation level as the IF.

IF ... THEN IF ... THEN Statement. ELSE Statement. IF ... THEN DO: IF ... THEN Statement. ELSE Statement. END. ELSE Statement.

When there is more than one comparison for a single IF statement then the operator AND / OR is at the beginning of the line. Each boolean check should be in a separate line. Nested boolean operation can be placed in the same line if they are not too complex. The Code should be fluent to read to make the boolean operation easy to understand.

IF a = b AND c = d THEN Statement. IF (a = b AND c = d) OR x = y THEN Statement.

When the DO Block following the IF requires additional options, the following formatting should be considered:

IF ... THEN DO TRANSACTION: END. IF ... THEN DO ON ERROR UNDO, THROW: END. IF ... THEN FOR EACH|REPEAT END.

Temp-Table Definitions

TEMP-TABLE definitions should be stored in include files to allow reuse unless it’s not used as a parameter.

It is considered very helpful to provide compile time parameters for the access mode to those temp-tables:

DEFINE {1} TEMP-TABLE ttXXX NO-UNDO ...

When temp-tables are used within Business Entities further options may be needed as compile time parameters:

DEFINE {&ACCESS} TEMP-TABLE eCustomer{&SUFFIX} NO-UNDO {&REFERENCE-ONLY} &IF DEFINED (NO-BEFORE) EQ 0 &THEN BEFORE-TABLE eCustomerBefore{&SUFFIX} &ENDIF

Include File Parameter

Description

Include File Parameter

Description

ACCESS

Class Member Access (PRIVATE/PROTECTED/STATIC)

SUFFIX

A name suffix if a routine needs to define the same temp-table twice in a single compile-unit

REFERENCE-ONLY

Allows to pass the "REFERENCE-ONLY" option when a temp-table is only needed with BIND or BY-REFERENCE

NO-BEFORE

When set, the temp-table definition omits the definition of the before-table

This allows to provide the PRIVATE/PROTECTED/STATIC option as a parameter to those include files if needed.

String Concatenation

For two Strings it is ok to combine them using a “+” when both arguments are variables (and not constants) and it is either guaranteed that none of the strings is unknown value or it is o.k. that the resulting string is the unknown value.

As soon as a third String is concatenated developers should consider to combine the three strings using the SUBSTITUTE function.

Block Style

END statements

End statements should use the option to specify the block type they are closing, like:

END CASE. END METHOD. END CONSTRUCTOR. END FUNCTION. END PROCEDURE.

There is no need for an empty line before an END statement. The block should be identifiable by the indentation.There is no need for an empty line before an END statement. The block should be identifiable by the indentation.

FOR EACH

Formatting of FOR EACH blocks

FOR EACH Table1 WHERE Table1.Field

or

FOR EACH Table1 WHERE Table1.Field1 = xyz AND Table1.Field >= xyz AND Table1.Field <= xyz NO-LOCK, EACH|FIRST|LAST Table2 WHERE (Table2.Field = xyz AND Table2.Field >= xyz) OR (Table2.Field = xyz) NO-LOCK BY xxx

Remarks:

Place the lock statement (NO-LOCK|EXCLUSIVE-LOCK) on an individual line in more complex queries so that it clearly visible and can not be overseen.

ASSIGN statements

assign iChunkIndex = iChunkIndex + 1 iLastSpaceIndex = 0.

Align the = signs to make the code easier to read. Do not place the . on a seperate line to let the statement look clean.

Comments

Style

A normal comment uses the "old" style of coding.

/* here is the comment */

A typical comment including the name of the editor and the date would look like this:

/* Marko Rueterbories / Consultingwerk Ltd. 04.06.2017: Changed something here */

Restriction

As a matter of fact, as long as we are supporting older versions of OpenEdge prior to 11.6 it is not allowed to use double slashes "//" style comments in the SmartComponent Library. Doing this would break the build in all older versions of OpenEdge.

Support for the SmartComponet Library on OpenEdge 11.6 has been cancelled in Sping 2020. However, I would like to ban single line comments from the Consultingwerk package until Januar 2021 to be able to support customers on older releases for a grace period.

In customer projects, when agreed with the customer / responsible architect the use of the single line comments is o.k..

File Header

Always give an Author Name and the Company. Author Name / Consultingwerk Ltd.

Use proper case when invoking ABL classes and procedures

Unless explicitly agreed with the project manager / architect of the project all calls to ABL classes and Progress procedures (.p and .w files) and include file references must be properly cased.

The ABL compiler and runtime ignore the file case on Windows - so you might not be seeing the error during development on Windows. But during a Linux build or runtime or in Docker environments your code will fail to compile or execute.

e.g. TestProgram.p (TestProgram.r)

The RUN Statement must be executed like this:

RUN TestProgram.p.

Annotations

  • Leave an empty line before and after annotations

  • Do not use annotations in include files

  • @uppercase and @lowercase is ritten in lower case

Error Handling

In all new code, we use structured error handling. So all new classes and procedure files use

ROUTINE-LEVEL ON ERROR UNDO, THROW.

or

BLOCK-LEVEL ON ERROR UNDO, THROW.

BLOCK-LEVEL should be used when we know that code is only executed on OpenEdge 11.3 and above. It is not allowed to use conditional compilation to determine the use of ROUTINE-LEVEL or BLOCK-LEVEL as error handling is a critical property of code execution.

When ROUTINE-LEVEL error handling is used, all loops with default error handling, should use the ON ERROR UNDO, THROW option:

FOR EACH Customer ON ERROR UNDO, THROW:

As typically the mix of standard (old style) error handling options like ON ERROR UNDO, NEXT from within loops and ON ERROR UNDO, THROW for routine blocks has unexpected side effects.

Block with no default error handling should use the ON ERROR UNDO, THROW option when they need a CATCH or FINALLY block.

IF <condition> THEN DO ON ERROR UNDO, THROW: CATCH err AS Progress.Lang.Error: END CATCH. END.

The error handling instruction must be placed above any USING statement.

Event Handler

All UI Event handler should have a CATCH for Progress.Lang.Error at the end. If error are not catched here, errors would be thrown to the WAIT-FOR statement which would only display the default error message dialog, which does not look pretty and does not provide further debugging output (other than the stack trace).

METHOD PRIVATE VOID button1_Click (sender AS System.Object, e AS System.EventArgs): /* Event handler code */ CATCH err AS Progress.Lang.Error: Consultingwerk.Util.ErrorHelper:ShowErrorMessage (err) . END CATCH . END METHOD.

Error Classes

Especially in framework code – but typically also in application code – the usage of specialized error classes is recommended when throwing errors, so statements like this

UNDO, THROW NEW Progress.Lang.AppError (“This is an error”, 0).

should be avoided.

For Consultingwerk all custom error classes should inherit from Consultingwerk.Exceptions.Exception as this carries further debugging information.

All error classes should be defined SERIALIZABLE on OpenEdge 11.4. The SERIALIZABLE flag might by defined with conditional compilation so that in OpenEdge 11.3 and before the SERIALIZABLE is not used (the Service Interface will ensure the proper transportation of the error message in that case).

{Consultingwerk/products.i} USING Consultingwerk.Exceptions.* FROM PROPATH . USING Progress.Lang.* FROM PROPATH . CLASS Consultingwerk.Exceptions.InvalidValueException INHERITS Exception {&SERIALIZABLE}:

{&SERIALIZABLE} is defined in Consultingwerk/products.i

Errors should always be thrown with error messages. As such the constructor of the Progress.Lang.AppError should be invoked with a CHARACTER and a number (zero is o.k.). Because otherwise the error would be initialized with a RETURN-VALUE property and the default AVM error handling would not show the error message.

Errors should be thrown like this (or preferably a derived type resulting in the same constructor):

UNDO, THROW NEW Progress.Lang.AppError (“Error message”, 0).

These errors should NOT be used:

UNDO, THROW NEW Progress.Lang.AppError (“Return value”).

Cleaning Code

All Cleanup code needs to be put into a FINALLY block to ensure that it is processed even if an error occurred.

Note, that each nested undoable block may have it’s own FINALLY block.

FINALLY: IF VALID-OBJECT hQuery THEN DELETE OBJECT hQuery . END FINALLY. FINALLY: SESSION:SET-WAIT-STATE (“”) . END FINALLY.

Handling of STOP conditions

Always use the Consultingwerk/Windows/ui-catch.i include to display errors on the screen.

Explicit simple CATCH Block with just 

CATCH err AS Progress.Lang.Error: ErrorHelper:ShowErrorMessage (err) . END CATCH.

 should be avoided.

There is also a new switch in products.i that for OE12 will enable handling of Stop conditions via CATCH. On historic 11.7 this needs be manually set when -catchStop1 is used.

Note: As -catchStop 1 is an inofficial feature of 11.7.5 (a pre-release), I do not recommend it’s usage on OE11. So use it in projects on historic 11.7.x at own risk 😊

 The whole feature however will allow to distinguish “any” STOP condition from STOP-AFTER, or record locked errors.

Naming Conventions

Buffers and Temp-Tables

DEFINE BUFFER {Buffer-Name} FOR Customer. DEFINE TEMP-TABLE ttCustomer NO-UNDO LIKE Customer.

When defining a buffer or a temp-table the name should be build out of the role of the buffer or temp-table in the current context and the table name of the related database or temp-table as a prefix.

{Buffer-Name} -> SourceCustomer

If we now add a column name as shown below this provides an effective advantage when searching for all occurrences of a column name of a table.

SourceCustomer.CustNum

This is a nice to have rule. So exceptions are o.k.

Before-Tables defined for ProDataset temp-tables do not follow this form. As before-tables and after-tables (the normal temp-table name) are typically used in the same context it is considered that this makes code easier readable.

Type Prefixes

Type

Prefix

Possible alternative name

Type

Prefix

Possible alternative name

INTEGER

i

i j n m

INT64

i

 

DECIMAL

de

 

DATE

da

 

DATETIME

dt

 

DATETIME_TZ

dtz

 

CHARACTER

c

 

LONGCHAR

lc

 

HANDLE

h

 

COM-HANDLE

ch

 

LOGICAL

l

 

RECID

re

 

ROWID

ro

row_id, rowId

MEMPTR

m

 

Progress.Lang.Object (and derived)

o

 

Nomen nest Omen – names should specify what they are used for. It is considered a better practice to define two explicitly named variables rather than reusing a variable with an unclear name “just because it’s no longer needed in the original context”.

The variable names [i, j, n, m] are allowed as loop counters (INTEGER).

Must-have rule: If the variable names are short then only those four names should be used.

Should Rule: If there are nested loops then the pairs of i + j and n + m should be used.

Parameter prefixes

While passing a variable to a procedure or defining a parameter the direction (INPUT / OUTPUT / INPUT-OUTPUT) it is preferred not to specify the INPUT option. It’s common to most development systems that the default parameter direction is INPUT.

Inside a procedure / function / method parameter names will be prefixed (before the data type prefix) with a lower case p.

In property SET methods, the parameter name may remain “arg” as suggested by the OpenEdge Architect wizard.

Usage:

Sample

Description

Usage:

Sample

Description

Variable:

daAppointmentDate

Datatype as prefix

Parameter:

pdaAppointmentDate

p{arameter} plus datatype as prefix

In the default CATCH block template provided by Progress Developer Studio it is o.k. to use the name “err” for the error being caught. In cases where multiple CATCH blocks for different error types are needed in a routine-level block it is recommended to use the normal naming rules for error objects (oException, oNotSupportedException, ...).

The name of variables used for Object references should allow to guess the type name of the referenced objects.

oForm, oParentForm oBusinessEntity

If the context makes it clear which “type” on object is from, it is o.k. to name reference variables based on the context only:

Sample

Description

Sample

Description

oCustomerDataset

o.k. for CustomerDatasetModel

oCustomer

o.k. for CustomerTableModel

Class Names

Class names should use the name of the most relevant base class as a suffix in their name to increase descriptiveness:

Samples:

CustomerBusinessEntity CustomerDataAccess CustomerDatasetModel CustomerTableModel CustomerForm (from SmartWindowForm which is a Form) WeekdayEnum

Classes that are used as parameter objects only to specific method of another class should be named as ...Parameter, samples:

ShipOrderParameter YearEndReportParameter

When these parameter objects actually inherit from Consultingwerk.JsonSerializable, it is considered better practice to qualify those class names as “Parameter” objects than as “Serializable” (as serializable objects are typically used as parameters for AppServer calls).

Interface Names

Interface type names will be prefixed with a capital I.

Method Names

Method and Class names begin with a capitalized letter. Variable names begin with a lower case letter.

Properties

Property names begin with a capitalized letter and do not use a type prefix.

File and Path Naming Convention

File Name Casing

Names of files with the extensions .i / .p / .w start with a lower cased letter and after that camel case the words.

All temp-table .i files have to be prefixed with “tt” or “e” for example “ttCustomer.i”.

All dataset .i files have to be prefixed with “ds” for example “dsCustomer”.

RUN statements and {include file references}

Every call to RUN statements and every reference to include files needs to be done with the full relative path to the file.

In both cases the forward slash has to be used to allow compatibility with UNIX systems.

The casing in the code has to be the same as it is in the file system to allow compatibility with UNIX systems.

Events

Event signatures

Events with a specialized event argument

The events should be defined with two parameters:

sender AS Progress.Lang.Object, e AS DeveloperFellAsleepEventArgs

The sender should be included in any case. For future dynamic event publishing and subscription it’s probably helpful to have a consistent event signature.

Event arguments should inherit from either Consultingwerk.EventArgs or Consultingwerk.CancelableEventArgs

Events from .NET inherited controls

If possible a matching .NET delegate should be used for defining the event. This allows the event subscriptions to be made from the Visual Designer. If there is no matching .NET delegate, either option 1 or 3 should be implemented

All other events (ABL events with no argument)

The events should be defined with two parameters as well:

sender AS Progress.Lang.Object, e AS Consultingwerk.EventArgs or Consultingwerk.CancelEventArgs

The sender should be included in any case. For future dynamic event publishing and subscription it’s probably helpful to have a consistent event signature.

Event methods

Named events in classes follow the usual .NET convention: Events will be raised only from within a method called On.

METHOD PROTECTED VOID OnDeveloperFellAsleep (e AS DeveloperFellAsleepEventArgs):

Within the event method it’s either verified (assertion) that the event argument is passed in as a valid argument to the On method or the event argument is initialized with a valid default (like Consultingwerk.EventArgs:Empty).

The only other functionality of the Event method (On method) is to publish the event and verify, that the event can be published (depending on the state of the object).

THIS-OBJECT:<EventName>PUBLISH (THIS-OBJECT,e) .

The event method is typically PROTECTED. This allows two things:

  1. The On... method can be overridden and seen as the event handler for child classes. The advantage is that the child classes can determine if their code is executed before or after all other event subscribers. In principle the child classes can also avoid the event from being published this way – if the omit executing the SUPER method from the child method.

  2. This allows child classes to also raise the event by invoking the PROTECTED On... method. In exceptions the Event method may be declared as PRIVATE. But this should be explained in a comment.

Progress.Lang.Object naming

whenever referencing Progress.Lang.Object, please refer to it as Progress.Lang.Object and not as Object.

 This avoids confusion with System.Object.

 The same applies to Progress.Lang.Class too as this might be confused with the keyword CLASS.

 NO-UNDO

Always use the NO-UNDO option while defining variables or temp-tables. It increases the runtime efficiency of the generated R-Code cause no “before values” will be processed and stored in the local .lbi-file or memory. It is especially recommended to use this option with temp-tables because the overhead is larger here than it is for a single variable.

DEFINE VARIABLE i AS INTEGER NO-UNDO . DEFINE PARAMETER piParameter AS INTEGER NO-UNDO .

Must-use rule: Always use “NO-UNDO“ option

In those really rare circumstances where the default undo behavior is needed, the developer has to provide a short description like the following so that other developers don’t get the impression that the NO-UNDO option was just forgotten and may break the code during refactoring.

DEFINE VARIABLE i AS INTEGER. /* UNDO, as the loop counter needs to be reset in case of error. */

Record Locking

FIND (Table / Buffer) [NO-LOCK | SHARE-LOCK | EXCLUSIVE-LOCK] FOR EACH [NO-LOCK | SHARE-LOCK | EXCLUSIVE-LOCK] PRESELECT EACH [NO-LOCK | SHARE-LOCK | EXCLUSIVE-LOCK] GET [NO-LOCK | SHARE-LOCK | EXCLUSIVE-LOCK]

It is strongly recommended to read all data from a database in NO-LOCK mode.

The session-parameter (-NL NO-LOCK by default) was introduced to change the compiler default we do consider it as problematic when code from different sources is combined in a single project and changing the default lock mode can cause issues (SHARE-LOCK may be expected by legacy code or the Progress Dynamics source code as the default).

Therefore we will neither use the –NL startup parameter nor rely on any default lock mode.

The usage of SHARE-LOCK may be required in some (rather rare) circumstances. There should be a short explanation why it is used in source code.

Must-use rule: Always specify the lock mode using the NO-LOCK / SHARE-LOCK / EXCLUSIVE-LOCK option with each record access. Do not rely on –NL or any default lock mode.

Any data access from database tables without explicitly specifying the LOCK type is prohibited.

Temp-Tables data access does not require the specification of a LOCK type. However it is not considered a mistake when data access on temp-tables is coded with a LOCK type specification.

While refactoring code a buffer once defined against a database table might be used for a temp-table table later and vice versa. When it is expected that code written against temp-tables initially may be used against database tables later, it is considered good practice to specify the LOCK type when writing the code initially.

FIND-FIRST ttCustomer NO-LOCK.

USING

By default we will be USING full packages (namespaces) like this:

USING {Package}.{Subpackage}.* USING Consultingwerk.SmartComponents.Implementation.*

If only one subclass is needed or there are conflicts with other classes we will specify individual class references in the USING statement:

USING {Package}.{Subpackage}.Classname USING Consultingwerk.Dynamics.SpecialClass

However, it should be attempted to avoid naming conflicts between class names at all by using clear and individual class names!

To improve compile time performance, add a USING statement with the FROM ASSEMBLY or FROM PROPATH option for every Class or Namespace reference used in the code.

This will have big impact on compile time performance when the source code (or parts of it) are located on a network share.

So a USING statement looks like in example:

USING Consultingwerk.SmartComponents.Base.* FROM PROPATH. USING Progress.Lang.* FROM ASSEMBLY.

For better readability USING statements should be formatted in tabular order and sorted by alphabet (unless this would have an impact on the class name resolution in case of naming conflict, which should be documented using a comment anyway).

You can use the Source->Organize USING Statement functionality of OpenEdge Developer Studio to organize your USING statements:

 

Please note that USING statements have to follow the Error Handling statements.

Do not place USING Statements (like BLOCK-LEVEL/ROUTINE-LEVEL) not in the definitions section.

Place them in a dedicated “Declarative Statements” section.

Otherwise the AppBuilder generated FRAME-Query Definition would be before the USING Statements in the source code. This would cause compile errors.

THIS-OBJECT

Usage of THIS-OBJECT references

When accessing an object instance's own members (methods, properties, events), we should always use THIS-OBJECT as this makes the scope clearer:

THIS-OBJECT:RetrieveData () .

When accessing static members (methods, properites, events) of the same class, we'll always be referenceing the member with the (short) class name:

BufferHelper:UniqueIndexFields () .

Exception from those rules are class wide (static or instance scoped) variables (variables defined outside any method). Those may be referenced by the short variable name only.

WIDGET POOL

If WIDGETs are created in the code, always create a WIDGET POOL using the “CREATE WIDGET POOL” statement or the USE-WIDGET-POOL option of the CLASS statement.

And then use the "DELETE OBJECT" statement with "IF VALID-HANDLE" or "IF VALID-OBJECT" in the FINALLY block (alternatively use the Consultingwerk.Util.GarbageCollectorHelper).

Exception are those widget references that are returned to the outside from factory methods.

FOR EACH IF THEN

FOR EACH statements

FOR EACH ...: IF ... THEN NEXT.

Coding an “IF ... THEN NEXT.” is only allowed if the condition cannot be added to the query. (e.g.: UDF UserDefinedFunction). A reason should be added as a comment why the condition is not expressed in the query.

FOR EACH ...: FIND [FIRST] {WHERE|OF} ... . IF AVAILABLE THEN DO:

The “FIND ...” should be included into the “FOR EACH” and if needed coded with an OUTER-JOIN.

OEDT Editor

Our standard Editor in Progress OpenEdge Developer Studio is OEDT.

Do not use other editors.

Settings to be enabled:

  • Trim lines on save