Page 1 of 1

Make DYNAMIC calls safer

PostPosted: 19 Feb 2013, 22:27
by Ursego
Prior to calling a script DYNAMICally, check the type of the object, pointed by the variable, using ClassName(), TypeOf() or uf_is_descendant() (provided).

Unfortunately, even with such a type check, it's still not type-safe. For user-defined functions, calling a non-existing script (misspelled or deleted/renamed in the future) will result in a run-time error, not a compilation error. But for PB built-in functions, we get a bit more type-safety since the functions, called dynamically, will never be deleted or renamed. They still can be misspelled when called dynamically, but that bug is likely to be found very early because the fragment will crash in the unit test. So, the described method is definitely a good defensive programming approach in situations, when DYNAMIC calls make sense (STATIC calls are preferable by default, but sometimes DYNAMIC calls can significantly simplify scripts).


1. A custom-made error message will say which types are expected and which type is actually provided (in contrast to the ugly PB message in that situation).

2. Clarity to code readers if that is a user-defined function. Developers immediately see the class(es) where the called function exists. Otherwise, they have to guess or investigate.

In the following example, the function receives parameter apo_data_object of type PowerObject:

Code: Select all
if not iin(apo_data_object.TypeOf(), {DataWindow!, DataStore!, DataWindowChild!}) then // iin():
   // f_throw():
   f_throw(PopulateError(0, "Invalid type of apo_data_object argument: " + apo_data_object.ClassName() + ". It must be DataWindow!, DataStore! or DataWindowChild!."))
end if

ll_row_count = apo.dynamic RowCount()

If you dynamically call a function, defined in the ancestor, on an arbitrary descendant, you can utilize the function uf_is_descendant() (whose major logic was taken from the function of_IsAncestorClass() of PFC object n_cst_metaclass) - grab the source code of uf_is_descendant() here and create it in your util NVO:

Code: Select all
Dscr:   Determines if apo_source is an descendant of as_ancestor_class
Args:   PowerObject      apo_source
      string         as_ancestor_class
Ret:  boolean
ClassDefinition   lcd_source
ClassDefinition   lcd_ancestor
ClassDefinition   lcd_test

choose case true
case IsNull(as_ancestor_class), as_ancestor_class = ''
   f_throw(PopulateError(1, "Argument as_ancestor_class is empty.")) // f_throw():
case not isValid (apo_source)
   f_throw(PopulateError(2, "Argument apo_source is invalid - cannot check if " + as_ancestor_class + " is its ancestor."))
end choose

lcd_source = FindClassDefinition(apo_source.ClassName())
lcd_ancestor = FindClassDefinition(as_ancestor_class)

// Check if there is an ancestor relationship:
do while true
   lcd_test = lcd_source.ancestor
   if not IsValid (lcd_test) /* the root of the inheritance hierarchy is reached */ then
      return false
   end if
   if = then
      return true
   end if

The following example shows how to use uf_is_descendant(). That is a fragment of a function which receives parameter apo_transport of type PowerObject:

Code: Select all
if not gnv_util.uf_is_descendant(apo_transport, "nvo_transport") then
   f_throw(PopulateError(0, "Invalid type of apo_transport argument: " + apo_transport.ClassName() + ". It must be a descendant of nvo_transport."))
end if

ll_max_speed = apo_transport.dynamic uf_get_max_speed()

Usually, uf_is_descendant() is helpful in programmer-intervention scripts of frameworks while working with universal parameters and instance variables. But if the whole calls chain is under your control, then make an effort to avoid situations when uf_is_descendant() is needed. In the previous example, it would be much better to pass a parameter anv_transport of type nvo_transport rather than a parameter apo_transport of type PowerObject, so uf_get_max_speed() could be called statically.