Ask Reuben

Modern Code

Why does my application look old? 

Why am I unable to use new functionality?

Why are you taking a deep sigh when you read my code?

I did my first Genero transformation project as a Genero customer in the early 2000’s.  At the time we took what in hindsight has turned out to be a fairly aggressive approach to the code transformation process.  We said if we were starting from scratch what would our code look like ? , and then tried to transform our code to get close to that ideal.  We figured we had a once in a generation opportunity whilst the hood was up to transform the existing sources.  If we did not take opportunity to perform certain actions on our codebase, that opportunity might not come around again for a while.

In my current role, I see code that comes in  that still has a 1990’s Informix-4gl vibe to it, and the opportunity has not been taken to make it contemporary.  I thought I’d share some of the code usage I see that makes me think, “I am glad we changed that code back in my first Genero transformation”

DATABASE -> CONNECT + SCHEMA

The DATABASE keyword has two uses depending upon where it is used in the code.  Before the MAIN it defines the database schema to use AND implies the database connection to make at startup.   After the MAIN it indicates to close the current database connection and make a new one.  This can get confusing and limits the number of database connections.

For defining the database schema to use when compiling code, you can instead use the SCHEMA syntax.  This is clear and unequivocal and does not imply any database connection at runtime.  It also removes the link between the name of the database you use for the schema and to connect to.

For connecting to the database you can instead use the CONNECT syntax.  This allows you to control when a connection is attempted to the database.  So instead of connecting at startup you can do some initialisation before hand and be in a position to handle errors with the database connection.  The CONNECT syntax also allows you to connect to multiple databases at the same time and manage their independent connect and disconnects.  The CONNECT syntax also gives you additional options to pass to the connection.

Having the ability to connect to to multiple databases at the same time also includes the possibility that one of those databases you are connected to is the SQLite in-memory database.

Static ARRAY, CHAR -> Dynamic ARRAY, STRING

As detailed in this article, it is no longer the case that for CHAR and static ARRAY datatypes that you have to come up with an arbitrary maximum size.  You can instead use STRING and DYNAMIC ARRAY.

Anytime you see CHAR[???], ARRAY[???] you should ask yourself is there any reasoning to the use of the specified size?  With an arbitrary size, you will either find you have code that checks and restricts you to the maximum size, or you may find you encounter runtime errors when the arbitrary size is exceeded.

By using STRING and DYNAMIC ARRAY, your program memory usage will also be more optimal in that the runtime will never allocate memory more that it actually uses.

SCREEN -> LAYOUT

Genero introduces new syntax to allow you to use GUI concepts in your form.

If you keep using SCREEN these forms will have their fields arranged so that they still look like the 80×25 character screens that you replaced.  By using LAYOUT instead of SCREEN you can define containers such as GRID, TABLE that will allow you to better arrange the fields in your form and to make full use of the space available, wether desktop, web or mobile.

Containers

By using LAYOUT, you can then define the various containes you would like your widgets to appear in.  The old SCREEN can be thought of as a LAYOUT plus GRID.   You should make use of TABLE or TREE or SCROLLGRID for use with DISPLAY ARRAY and INPUT ARRAY.  You should make use of GROUP or FOLDER + PAGE to consolidate what was on many forms into the one form.  You should make use of HBOX and VBOX to arrange appropriately and GROUP to add some decoration to a container.

If you use SCREEN or have a LAYOUT with a single GRID you are not making use of the various GUI facilities available to you.

If you have repeated rows in a GRID, this is known internally as a Matrix.   The expectation is that you will use a DISPLAY ARRAY or INPUT ARRAY with a TABLE, TREE, or SCROLLGRID as that gives a much more modern rendering.  There have been times where support cases with INPUT ARRAY, DISPLAY ARRA have been created and there has been some confusion until it is realised that the form is using SCREEN and the array is being viewed in a Matrix and not a TABLE or TREE or SCROLLGRID.

If I see…

SCREEN
{
...
}
END

… then my suspicion is that work has not been put into use containers. (or I am looking at a form that is intended to only run in TUI clients.)

Widgets

The other transformation to your form files is to use appropriate widgets for the various fields.  So instead of every field being a default EDIT, you use the applicable widget such as BUTTONEDIT, DATEEDIT, DATETIMEEDITLABEL, SPINEDIT, TEXTEDIT, TIMEEDIT, CHECKBOX, COMBOBOX, PROGRESSBAR, RADIOGROUP, SLIDER, WEBCOMPONENT as appropriate.

if I see no widgets, so in the attributes i.e

f01 = formonly.field1, ...;
f02 = formonly.field2, ...;
f03 = fromonly.field3, ...;

then again I suspect the work has not been in to run the form in GUI environments.

EDIT f01 = formonly.field1, ...;
DATEEDIT f02 = formonly.field2, ...;
COMBOBOX f03 = fromonly.field3, ...;

Remove Explicit Text From Forms

You are free to keep explicit text in a form and a label will be generated by the compiler.  However consider the following …

  • how do you translate such text ?
  • how do you reference such text to show/hide at runtime ?
  • how do you change such text at runtime ?
  • how do you maintain right or center alignment with such text when proportional fonts are in use.

By creating a LABEL for such text, you can then translate and you can reference the text so that you can show/hide it at runtime or change the value at runtime.  For column headers, you can also use the TITLE attribute instead of having explicit text in the form.

UNBUFFERED

Informix-4gl had the concept of the buffer.  This meant there was a disconnect between the value displayed in the field and the underling 4gl variable.  After changing the 4gl variable, you then had to explicitly display it.  This lead to many extra lines of code.

The UNBUFFERED mode was introduced which removes the need for all these DISPLAY’s.  The developer can be assured that what is in the 4gl variable is on the screen.

With the unbuffered mode, program variables and form fields are automatically synchronized, and the dialog instruction is sensitive to program variable changes: You don’t need to display values explicitly with DISPLAY TO or DISPLAY BY NAME. When an action is triggered, the value of the current field is validated and is copied into the corresponding program variable. If you need to display new data during the dialog execution, just assign the values to the program variables; the runtime system will automatically display the values to the screen after user code of the current control or interaction block has been executed

By using UNBUFFERED throughout your application then you can remove a number of DISPLAY statements from your code.  This meant that less lines of code were required and you can code faster and more accurately.

TTY Attributes -> Presentation Style

The continual use of TTY attributes in the code and lack of presentation style are also giveaways that advantage has not been taken advantage of using GUI features.

Using presentation styles allows you to define styles attributes once.  It also enables you to make changes easily and removes the linkage between the code and the implementation.  You can say STYLE=”important” and then it is upto the .4st to define how “important” fields are rendered.

One of my first GUI transformation had the following code pattern …

BEFORE FIELD name
   DISPLAY value TO fname ATTRIBTUES(REVERSE)

AFTER FIELD name
   DISPLAY value TO fname ATTRIBTUES(NORMAL)

… in order to give better visibility to the field with focus.  By transforming to Genero that unnecessary code was removed therefore saving on coding and maintenance costs.

ON KEY / COMMAND [KEY] -> ON ACTION

How do you press F8 on a mobile device?  That is a good question to ask someone who has ON KEY(F8) in their code?  By using ON ACTION instead of ON KEY you are not only future proofing your application, you are also allowing your code to be independent of how an action is actually triggered.  You don’t care if the user pressed a button in the action panel or clicked a toolbar or if they typed a keyboard combination, this is the code to execute.

By using ON ACTION and Action Defaults you can define properties of your common actions once.  Define the image, text, accelerator, comment etc once in the Action Defaults.

Using COMMAND or ON ACTION is a good question.  Deep down COMMAND “name” “text” is the equivalent of ON ACTION name ATTRIBUTES(TEXT=name, COMMENT=text) and so my rule of thumb is to allow COMMAND for those one-off actions that are not defined in an action defaults file.  If the action is in the Action Defaults file then use ON ACTION instead of COMMAND.

C Extensions

As I discussed in this Ask Reuben article, you may find that you no longer need any C Extensions.  Functionality has been added to Genero, particularly in area of file handling, operating system calls, and maths including random number generation that means your code that used to have to be written as c extensions, can now be written as Genero code.

This aids portability as you can run the code on different servers without having to worry about recompiling the c code.  It also aids maintenance as any of your Genero developers can read and maintain that source.

GLOBALS -> IMPORT FGL

There is no need to use GLOBALS anymore.  You should be able to code as IMPORT FGL and ensure that global definition is the same in all files that reference the “global” variable.

See this Ask Reuben for more details.

Same but Different

One of the characteristics of Informix-4gl code I despised was how you ended up with near duplicated dialog statements and forms to handle a simple difference.  Examples might include

  • two forms and two INPUT dialogs where the only difference is that one field is in one dialog/ form but is not in the other.
  • two forms where the only difference was a difference in FORMAT attribute i.e. decimal places or date

For the case of hidden and inactive fields and dialogs on a form / dialog there are now methods such as ui.Dialog.setActionActive , ui.Form.setElementHidden, and ui.Form.setFieldHidden that can be called to change the characteristics of a dialog / form.

For the cases of changing field attributes, there might be methods such as ui.Form.setFieldStyle, or it might be a case of doing some DOM-tree manipulation (see fgl_auitree), or using ui.Form.setDefaultInitializer to set the attribute at run-time.

If you do end up in a situation where you have slightly different dialog statements and forms based on a certain condition, you should be saying to yourself “how can I code these as a single dialog statement and form, rather than repeating the code in the dialog statement and form definition”.

Repeated Code replaced by Pre-processor Macros and/or Generic Code

Similar to the above point, another one of my bug bears is repeated code.  Consider

AFTER FIELD field1
    CALL valid_field1() RETURNING, ok, error_text
    IF NOT ok THEN
        ERROR error_text
        NEXT FIELD field1
    END IF

AFTER FIELD field2
    CALL valid_field2() RETURNING, ok, error_text
    IF NOT ok THEN
        ERROR error_text
        NEXT FIELD field2
    END IF

AFTER FIELD field3
    CALL valid_field1() RETURNING, ok, error_text
    IF NOT ok THEN
        ERROR error_text
        NEXT FIELD field3
    END IF

This code be coded using the pre-processor as

&define after_field(p1) AFTER FIELD p1 \
   CALL valid_## p1 ##() RETURNING, ok, error_text \
    IF NOT ok THEN \
        ERROR error_text \
        NEXT FIELD p1 \
    END IF

after_field(field1)
after_field(field2)
after_field(field3)

Now imagine the following maintenance tasks.  If you need to add field4, you only have to type

after_field(field4)

If you want to change what the repeated code does, perhaps add a style to the ERROR, then all you have to do is replace the line in the pre-processor definition, recompile and it is done.

Using this type of Function Macro definiton also has the property that it not only it reduces the number of lines of code you are looking at and maintaining, but it also means it is more likely you see a more complete picture of your code in the visible lines you have available at any one time.

The pre-processor is one technique you can use to add global actions and on idle.

Another technique to remove repeated code is to use generic code.  fgl_zoom is an example where generic code can replace many instances of near repeated code.  base.Sqlhandle, Dynamic Dialogs, and creating forms dynamically is syntax and techniques you can use to remove repeated code and code concepts once.

As a challenge it is possible to write the following functions …

FUNCTION key_value_exists(table_name STRING, key_value STRING) RETURNS BOOLEAN 

FUNCTION value_exists(table_name STRING, column_name STRING, value STRING) RETURNS BOOLEAN.

… thus anytime you need check if a value exists in the table you can have a lone line of code function call, and do not need to explicitly prepare and create database cursors etc, you can do this all generically.

Initializers

When a program starts, you will typically do some initialisation.   This might include

Ideally you only code this once.  I expect to see some library functions called as soon as practical after the MAIN and before the END MAIN similar to  …

MAIN
   CALL pre_tasks()
   ...
   CALL post_tasks()
END MAIN

… That is  pre_tasks() and post_tasks() is code you want executed at the start and end of every program.   if you want any variation based on the type of program then you can pass the type as an argument to these functions.

This then gives you options where you can immediately make changes to all programs.

There is also a function ui.Form.setDefaultInitializer that can be used to execute code when all forms are loaded and so make forms consistent.  This is a good example of defining something once and having it apply in many places rather than coding repeatedly.  It is also a good example of some code you might place in your library function that is called as soon as possible after MAIN.    Sometimes in support we have suggested using ui.Form.setDefaultInitializer() but then the client has had to add the call in multiple programs when ideally they could add it once in their library function.

Exception Handling

As much as we try, sometimes programs encounter unexpected conditions and will error.  That is where good use of the errorlog and use of exception handling can lead to the capturing of as much information as possible when things to go wrong.

Linking -> IMPORT FGL

The concept of linking has been superseded by the use of importing modules via IMPORT FGL.

When you use linking, each .42m does not know anything about the other .42m in a Genero application.  It does not know anything about the PUBLIC TYPE, VARIABLES, and FUNCTIONS.    Hence when  editing and compiling a file it is not possible to provide code completion, and real time syntax checking of references to other files.  You only find errors at link time or runtime.

By using IMPORT FGL, each individual .4gl is more complete and correct as the symbols of the named module can be referenced in the current module.

New Functionality

We have added a lot of functionality to Genero over the years.  Sometimes I can be looking at code and thinking, why aren’t they using Multiple Dialogs instead of two dialogs, Drag and Drop would be perfect here, why aren’t those totals in an aggregate field, why isn’t this information displayed in a Infographic or Chart via Web Components.  In future years I suspect I’ll be saying something similar about the responsive features added in 4.00.

If you have an aspiration model, continuously evaluate it, and incorporate this new functionality where appropriate.

Summary

Whilst all of the above is nice, any code transformation has costs and risks.  Any changes need to be evaluated to measure the benefits against the costs.  I think the benefits of change are underestimated.  For instance a new developer may find it easier to code in your environment if he is using Genero Studio with code completion because you have used IMPORT FGL.  They will have better job satisfaction because by using UNBUFFERED, they are not having to code redundant code.  They can developer better forms by using LAYOUT.  They can quickly change the appearance by using Presentation Styles etc  They can code quicker.

I also think some developers are scared at making changes to their entire code base and get scared about the volume and management of such a task.  I’ll discuss that more next week.

In the meantime, if you don’t already, code a sample program how you would like to code it if you were unrestricted by your existing code base and standards.

I mentioned at the beginning then my first Genero transformation was quite aggressive in what we did.  One thing we casually observed afterwards was that we needed less developers for basic maintenance and enhancement tasks.  Not having to type unnecessary ATTRIBUTES, DISPLAYs etc and code near repeated code make a difference.