Welcome!

PowerBuilder Authors: Dan Joe Barry, Carmen Gonzalez, Ian Thain, Yakov Werde, Paul Slater

Related Topics: PowerBuilder

PowerBuilder: Article

Using Properties to Make Your PowerBuilder Application More Robust

A best practice that gives developers greater control of how data is used

Like Java and C# programmers, PowerBuilder developers can create properties via the undocumented keyword indirect. Properties look like ordinary variables, only their value isn't accessed directly. When you declare a variable using indirect, you have to specify a function that's called when the variable is assigned (a setter) and another function that's called when the variable's value is returned (a getter).

Below is an example syntax for declaring a property using indirect:

      // l_patient_id is an instance on a NVO
indirect long l_patient_id { of_set_patient_id( *value ), of_get_patient_id() }

You have to write the getter/setter functions in the same context as the indirect declaration. You cannot use indirect with "pure" global variables; in this article, "global variables" are instance variables declared on a global NVO 'g'. The getter/setter functions would be on that NVO. They can (and should) refer to a private variable:

    // Accessed ONLY by the getter/setter functions so it's private
    private long l_patient_id

    // Getter on the NVO
    function long of_get_patient_id()
       return l_patient_id

    // Setter on the NVO function void of_set_patient_id( long al_patient_id )
       l_patient_id = al_patient_id

Given this declaration and getter/setter then assigning to the g.l_patient_id:

g.l_patient_id = 1234

is turned into a function call by the PowerBuilder compiler, in effect:

g.of_set_patient_id( 1234 )

where 'g' is the name of the global NVO. Accessing the value becomes another function call, so:

ll_current_patient = g.l_patient_id

becomes the equivalent of:

ls_current_patient = g.of_get_patient_id()

How Could Properties Be Useful?
You can use properties to make your PB application more robust.

Using properties is a best practice with modern programming languages like Java and C# because they are safer than directly accessed variables. Consensus says that properties make code more robust and reliable, principally because they give the developer greater control of how the data is used.

Properties are more powerful than ordinary variables because the setter/getter functions give you a hook where you can write code, e.g., to validate the data. Is there something you want to do whenever a variable changes? If you don't now you will later, and the setter function gives you a place to write the code to do that.

Example:
Converting Globals to Properties

There are a lot of PB applications out there that were crafted before any of us knew what we were doing. In particular, they make extensive use of global variables, which are inherently unsafe. There's a good reason why we were all taught to avoid globals! The indirect keyword gives us a way to convert globals to properties.

My example comes from a legacy medical application that used a long patient ID instance on the global NVO 'g' to keep track of the current patient. The global patient ID was set and reset a couple hundred times in the application. In some contexts, there was no patient until the user selected a row, or until the code processed a row in a DataWindow. In those circumstances, the global would be set to zero to signify that there was no current patient. When the patient was selected, a new patient ID would be assigned to the global long.

One problem was that another global variable needed to be kept in sync with the global patient ID. Whenever the patient ID changed, the global patient name string had to be reset too. Keeping them in sync was essential. The application perpetually displayed the current patient's name in the MicroHelp area, and it routinely displayed the global patient name on reports, without first refreshing it to make sure that it corresponded to the global patient ID.

This led to bugs. We'd get support issues like "The prescription had the correct patient ID but the wrong name." Inspecting the code, I found that most of the time a change to the global patient ID was done by a call to a function that reset the patient name accordingly. However, there were a dozen patient ID assignments where that function call was missing. Bugs!

There was also nothing in the application that cleared the patient's name from the MicroHelp when the patient ID was zeroed out to signify "no current patient." The MicroHelp conveyed that so-and-so was the current patient, while the application would put up messages like "You must select a patient before..."

I could have simply added a reset-the-patient-name function call to the dozen places where it was missing. However, that would have left the vulnerability intact, and subsequent programmers would have had to be aware of the requirement to call the function whenever they set the patient ID. That wouldn't have done anything to fix the misleading MicroHelp that presented the current patient's name when there was no current patient.

A better solution was to replace the global patient ID and patient name with properties.

Implementing the Global as a Property
The process of converting the variables on the global NVO 'g' to properties took just a few minutes. The process was:
1.  Declare private variables on the 'g' NVO to hold the patient ID and patient name:

    // On the application object
    private long il_patient_id
    private string is_patient_name

In effect, these become new global variables equivalent to the old ones that are get/set using the properties.

2.  Write getter/setter functions for the new private variables. For the global patient ID:

    // Patient ID Getter
    function long of_get_patient_id ()
       return il_parient_id

    // Patient ID Setter
    function void of_set_patient_id ( long al_patient_id )

       string ls_pt_name // Name shortened for this article! :)

       // Validate the patient_id
       if IsNull( al_patient_id ) then
         g.warn( "NULL patient ID!" )
         al_patient_id = 0
       elseif al_patient_id < 0 then
         g.warn( "Negative patient ID: " + string( al_patient_id ) )
         al_patient_id = 0
       end if

       // Do we HAVE a patient ID?
       ls_pt_name = "" // In case the ID is invalid
       if al_patient_id > 0 then // Yes

       // Does the patient ID exist?
       select pt_name into :ls_pt_name from patients where pt_id = :al_patient_id;
       if sqlca.sqlcode = 100 then
         g.warn( "Invalid patient ID: " + string( al_patient_id ) )
         al_patient_id = 0
       end if

       end if

       // Save the patient ID
       il_patient_id = al_patient_id

       // Reset the patient name property (so the setter() is executed)
       g.s_patient_name = is_pt_name // SELECTed above (or blank)

The patient ID setter illustrates the advantage of the property over the global variable. It provides a hook where the patient_id can be validated, especially against the ever-troublesome NULL value. The g.warn() presents its message in a console window during development and always logs the message so the developer will get a clue if some error is leading to assigning invalid patient IDs and the support team can inspect the log file when "wrong name" bugs appear.


More Stories By Hoyt Nelson

Hoyt Nelson is an independent contractor, a 10-year PowerBuilder veteran, and father to the amazing Nelson, Kent, Emma, and Molly (Hi, kids!).

Comments (1) View Comments

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.


Most Recent Comments
Arthur Hefti 01/18/07 02:15:38 AM EST

I checked it with PB 11 Beta and it works with .NET generation also.