Tips_namingconventions   >   Coding Conventions

Coding Conventions

This section covers information about the various coding styles and conventions which I use.

Coding conventions and styles evolve as we learn better ways of writing code

$construct vs. $initialize

For this discussion we will talk about object classes. But the discussion can be applied to any Omnis Studio class which can be instantiated.

When you instantiate (open) an object, Omnis sends a $construct message to the object. Omnis recommends that you put your initialization code in the $construct method.

One of the problems I ran into when writing Omnis applications was that if an error occurred during the $construct of an object it was difficult to trap the error because the class which opened the object wasn't the one sending a $construct message to the object. Returning false if an error occurred in the $construct had no effect and you could end up with a cascade of error messages. In earlier versions of Omnis Studio the cascade of errors would result in hitting the method stack limit.

An alternate technique is to add a $initialize method to the class and send a $initialize message to the class instance after it has been instantiated.

ObjectA can then instantiate ObjectB without triggering any methods in ObjectB. ObjectA then sends an $initialize message to ObjectB with any necessary parameters. ObjectB would run through its $initialize method. If an error occurred, ObjectB could log the error and return false to ObjectA. ObjectA could then halt everything and return false to the visual object that sent the request to ObjectA.

Much cleaner and much safer!

Replace $constuct with $initialize and require the class which instantiates an object to send an $initialize message before using the object.

That being said, I do use the $construct method in window classes, and you will find $construct here and there in the my classes. If you don't use $construct to initialize a window being opened, the window will appear to the user before receiving its $initialize message. However, there are work arounds for that, such as opening the window off screen, sending it an $initialize message, and then moving it on to the screen.

Quit method return Something

Every method should return something to the sender.

The method where the error is first detected must immediately log an error with the error handler.

Without following this practice and having the sender check the return value you will inadvertently allow errors to crop into your code causing unexpected behavior and not knowing for certain where the error orginated.

Checking for true or false is simple.

Do List.$getAllRecords() Returns FlagOK
If FlagOK
  ; Continue processing
End if
Quit method FlagOK

It is recommended that you always use a local variable for the return value. (not #F because it is global) The local variable name can be changed to suit the situations. (e.g. FetchStatus, bModified, LoopFlagOK, etc.)

There are a few things you need to consider when checking the return value of property methods or $ret prefixed action methods.

String values and numeric values are easy to check.

Do $retStringValue Return StringValue
If not(isnull(StringValue))
  ; Continue processing
End if
Quit method FlagOK

For list and row values you have to be a bit more careful. The following test will fail even if the $retListVar method returns #NULL.

Do $retListVar Return ListVar
; Testing for null on a list or row variable does not work!
If isnull(ListVar)
  Calculate FlagOK as kFalse
Else
  ; Continue processing
End if
Quit method FlagOK

The way to check to see if the $retListOrRowVar... method returns #NULL is to check the $colcount

Do $retListVar Return ListVar
If ListVar.$colcount=0
  Calculate FlagOK as kFalse
Else
  ; Continue processing
End if
Quit method FlagOK

Even for small private methods that can't result in an error, make it a practice to return true and test the return value. Following this practice will save you time. Its worth spending the few extra seconds it takes to type Quit method kTrue.

When to Exit a Method

When should you exit out of a method? As soon as possible, or at the end of the method?

For the first 5 years I followed the method coding convention of quitting out of a method as early as possible. We'll call this the early exit coding style.

I would quit and exit out of a method if:

  1. An error occurred in the method.
  2. An error was returned by a called method.
  3. Nothing further needed to be done.

Doing so meant that there could be several exit points from a method. My logic for using the early exit coding convention was that it was more efficient to exit the method as soon as possible.

Reading Code Complete by Steve McConnell, and discussions with a computer science graduate convinced me to rethink the early exit coding convention which I had been following.

I started using the method coding convention of no early exits and soon discovered some advantages.

A trick I have found helpful is to declare a local variable, FlagOK, and set its intial calculation to kFalse.

If your method makes calls to other methods which return true or false, you use FlagOK as the return variable. As you step through your method you use If FlagOK or If not(FlagOK) to determine the flow. At the end of the method you simply return FlagOK.

The following code demonstrates the exit early coding style.

; Delete the file
Do FileOps.$deletefile(pFilePath) Returns kErrCode
If kErrCode
   Calculate Mssg as "Unable to delete the file"
   Calculate Dtls as con("File path: ",pFilePath)
   Do $ctask.errhndlr.$logFileOpsError($cmethod,Mssg,Dtls,kErrCode)
   Quit method kFalse
End If

; Create a new file
Do oFileOpsExt.$createfile(pFilePath) Returns FlagOK
If not(FlagOK)
   Calculate Mssg as "Unable to create the file"
   Calculate Dtls as con("File path: ",pFilePath)
   Do $ctask.errhndlr.$logFileOpsError($cmethod,Mssg,Dtls)
   Quit method kFalse
End If

; Write to the new file
Do oFileOpsExt.$writefile(pFileContents) Returns FlagOK
If not(FlagOK)
   Calculate Mssg as "Unable to write to the file"
   Calculate Dtls as con("File path: ",pFilePath)
   Do $ctask.errhndlr.$logFileOpsError($cmethod,Mssg,Dtls)
   Quit method kFalse
End If

Quit method kTrue

There are four possible exit points in the above method.

The following code demonstrates the no early exits coding style.

; Delete the file
Do FileOps.$deletefile(pFilePath) Returns kErrCode
If kErrCode
   Calculate Mssg as "Unable to delete the file"
   Calculate Dtls as con("File path: ",pFilePath)
   Do $ctask.errhndlr.$logFileOpsError($cmethod,Mssg,Dtls,kErrCode)
   Calculate FlagOK as kFalse
Else
   
   ; Create a new file
   Do oFileOpsExt.$createfile(pFilePath) Returns FlagOK
   If not(FlagOK)
      Calculate Mssg as "Unable to create the file"
      Calculate Dtls as con("File path: ",pFilePath)
      Do $ctask.errhndlr.$logFileOpsError($cmethod,Mssg,Dtls)
   Else
      
      ; Write to the new file
      Do oFileOpsExt.$writefile(pFileContents) Returns FlagOK
      If not(FlagOK)
         Calculate Mssg as "Unable to write to the file"
         Calculate Dtls as con("File path: ",pFilePath)
         Do $ctask.errhndlr.$logFileOpsError($cmethod,Mssg,Dtls)
      End If
      
   End If
End If

Quit method FlagOK

There is only one exit point in the above method.

If your method doesn't call other methods or functions which return true or false, you may need to Calculate FlagOK as kTrue in your method somewhere before the Quit method FlagOK.

Visual vs. Non-Visual Classes

Visual classes are classes which the user can see on their screen.

Non-visual classes are the hidden classes.

Visual classes can communicate with the user. Non-visual classes should never directly communicate with the user. Not even error messages. If you aren't used to following this practice it might seem difficult to do... but I can tell you from experience and from reading books by object-oriented developers with a lot more experience than me that respecting this coding convention is important.

There are occasions when an object class or a task class must be considered as a visual class. In some cases this can be restricted to a one or two methods of the class. One example is the Startup_Task. When the user opens a library Omnis Studio sends a $construct message to the Startup_Task. If the startup task hits an error it has no choice but to prompt the user with an error message.

Another situation is an object class which is responsible for running reports. In order to know what records to select for the report the object class has to prompt the user for input. In this case the object class would be considered a visual class. To clearly communicate this exception I add the comment method, $#Visual Class, at the top of the object's class methods.

Pushbutton #SHIFT Breakpoint

This is handy piece of code to put in the $event method of all your pushbutton objects.

On evClick
  If #SHIFT
    Breakpoint
  End If

It has no effect on the runtime user, but it's great for debugging. You simply hold the shift key when you click the pushbutton and can then step through the code.

Flow of Method Calls

Within a class it is a good practice to design your methods so that calls flow from the top down.

  1. Public action methods can call protected methods, or private methods.
  2. Protected methods can call private methods.
  3. Private methods can call other private methods or private submethods.
  4. Private submethods can all other private submethods.

Avoid making method calls that flow upwards.

  1. Private submethods shouldn't call private methods, protected methods, or public action methods.
  2. Private methods shouldn't call protected methods, or public action methods.
  3. Protected methods shouldn't call public action methods and should try to avoid calling other protected methods.
  4. Public action methods should try to avoid calling other public action methods.

Any method can call a property method.

This isn't a hard fast rule, but it has been my experience that following this guideline makes it easier to structure, follow, and maintain your code.

$event Calls event_evEventCode

If you create more than a few lines of code in the $event method, the code can look pretty ugly in a hurry. This is partly due to the fact that the On evEventCode commands don't end all the way back at the left margin.

Treelists are a prime example of where you can end up with a lot of code in the $event method.

A coding convention I've been using to solve this mess is to add a series of private event methods to the object:

event_evClick

event_evOpenContextMenu

event_evDoubleClick

event_evTreelistExpand

The $event method traps the On evEventCode and immediately calls the appropriate event_evEventCode method. The $event method doesn't need to send any parameters because the event parameters have task wide scope.

Empty Defined List Instance Variables

If you find yourself defining the same list variable over and over in different methods of a class it might be worthwhile defining the list variable once during the construct and then simply copying it to a local list variable in the methods where you need the defined list.

By prefixing the defined list variable with 'iEmpty' you communicate to the class' methods not to use the variable directly, but rather use it to copy to other list variables.

; Put this line of code in your $construct method.
Do iEmptyContactsList.$definefromsqlclass('sContact')

; Any methods in the class where you need a fresh copy of the contacts list
Calculate ContactsList as iEmptyContactsList

Working Messages

Working messages are visual, so non-visual objects should not open or close working messages, that is the responsibility of the visual object methods. The trouble is that at times the non-visual object might be running a procedure that takes a long time and you want to provide the user with some kind of visual feedback on what is happening and the progress that is being made.

A solution which I have been using is add a startup task variable named, workingmssg. When a visual class method asks a non-visual object to do a process that could potentially take longer than a few seconds the visual class method opens a working message as follows:

; Open the task scope working message
Calculate workingmssg as "Saving changes..."
Working message Working/-1072741743,-1072741739;50;0;60 {[workingmssg]}

; Update the records
Do List.$doworkBatch() Returns FlagOK
; ... etc.

The non-visual object methods which can take longer than a few seconds recalculate the value of the task variable, workingmssg, and redraw the working message.

; Recalc and redraw working message
Calculate workingmssg as con("Updating ",%L," of ",%LN," records..."
Redraw working message

If the visual object didn't open a working message no harm is down in recalculating the task variable and redrawing the non-existant working message.

By avoiding use of the Omnis commands, Working message, and Close working message, in the non-visual objects we can have one consistent working message up and running through all the procedures.

Make use of this technique even in your visual objects. It only take two more lines of code, and if your public method calls a private method within the same class, you get the same advantage of having one consistent working message throughout all the methods called. If the private method calls a non-visual object, the original working message will be updated. If you later refactor your code and move it to a non-visual object you won't need to add Calculate workingmssg as... to the code because you already took care of that the first time you wrote the code.

Avoid using the Omnis commands, Working message, and Close working message, in protected and private methods as well. They should only be found in the public methods. If you follow the flow of method calls guidelines there won't be any conflicts with multiple methods opening or closing a working message.

Comment Lines

The following are some guidelines that I've started to develop and follow:

White Space

Allowing white space (blank lines) makes your code more readable. Compare the following code examples. Study each method on its own in the method editor. (Scroll up and down) Which is easier to comprehend? I think you will agree that the method without the white space is busy and therefore more difficult to read.

; ----- SAMPLE METHOD CODE WITHOUT WHITE SPACE -----

; Synchronize the database with this application.
Do method _syncEmptyDatabase Returns FlagOK
If FlagOK
   ; Insert new data.
   Do method _insertNewDataRecords Returns FlagOK
   If FlagOK
      ; Get the users from the security object.
      Do method _retUsersList Returns UsersList
      If UsersList.$colcount=0
         Calculate FlagOK as kFalse
      Else If UsersList.$linecount>0
         ; Add the users to the DBMS users.
         Do method _addUsersToDBUsers (UsersList) Returns FlagOK
      End If
      ; Insert empty records.
      Do method _insertEmptyRecords Returns FlagOK
      If FlagOK
         ; Rebuild the string tables (which also rebuilds windows, menus, icons)
         Calculate bRuntime as kTrue
         Do ioRebuildCachedsLists.$reloadStringTables(bRuntime) Returns FlagOK
      End If
   End If
End If
Quit method FlagOK


; ----- SAMPLE METHOD CODE WITH WHITE SPACE -----

; Synchronize the database with this application.
Do method _syncEmptyDatabase Returns FlagOK
If FlagOK
   
   ; Insert new data.
   Do method _insertNewDataRecords Returns FlagOK
   If FlagOK
      
      ; Get the users from the security object.
      Do method _retUsersList Returns UsersList
      If UsersList.$colcount=0
         
         Calculate FlagOK as kFalse
         
      Else If UsersList.$linecount>0
         
         ; Add the users to the DBMS users.
         Do method _addUsersToDBUsers (UsersList) Returns FlagOK
      End If
      
      ; Insert empty records.
      Do method _insertEmptyRecords Returns FlagOK
      If FlagOK
         
         ; Rebuild the string tables (which also rebuilds windows, menus, icons)
         Calculate bRuntime as kTrue
         Do ioRebuildCachedsLists.$reloadStringTables(bRuntime) Returns FlagOK
      End If
   End If
End If
Quit method FlagOK

$appendlist vs. $makelist

Omnis offers two choices for making a list of group members, $appendlist and $makelist.

In my earlier years of writing Omnis code I used $makelist. Later I switched to $appendlist because it allows me define and name the list columns first, making the code that follows easier to read.

; Define and build the list of classes.
Do ClassList.$define()
Do ClassList.$cols.$add('classname',kCharacter,kSimplechar,100)
Do ClassList.$cols.$add('classtype',kInteger,kLongint)
Do ClassList.$cols.$add('classref',kItemref)
Do $ctask.$lib().$classes.$appendlist(ClassList,$ref().$name,$ref.$classtype,$ref)

; Select and remove the non-system classes
Do ClassList.$search($ref.classtype < > kSystemtable)
Do ClassList.$remove(kListDeleteSelected)