Exception Handling

Exceptions are abnormal runtime events that may occur after executing a software instruction. Uncaught exceptions cause code execution to stop and the application to exit with a description of the error and where it occurred.

Exceptions provide a useful way of identifying coding mistakes or other errors that you did not expect, enabling you to fix the bug or handle the situation and thereby improve the robustness of your application.

An exception is raised in the following ways:

  1. By an explicit throw or rethrow statement.
  2. By a statement that causes a $procerror, if that statement is in a module that has a throws statement, or is inside a try block, a catch block, or a finally block.
  3. By a statement that causes a $procerror, if that statement is inside a try block, a catch block, or a finally block.

The following simplified code shows how an exception can be raised and handled:

entry myStore
throws ;   [1]
params
  string pEntName: IN
endparams
  try  ; [2]
    ; Code that can cause a runtime error (negative $procerror)   
    ; If $procerror < 0, code execution continues at the catch-block.
    store/e pEntName  [3]
    commit  ; 
  catch  ;  [4]
    rollback 
    rethrow
  finally  [5]  
    clear/e pEntName
  endtry
end
  1. The throws command declares that any runtime error (negative $procerror) that occurs in the module will be regarded as an exception.
  2. Code that could result in a runtime error is contained by a try block.
  3. If the code that is tried by the try block is successful, normal code execution continues. In this case store is tried, and if it succeeds, commit is executed.
  4. If an exception arises, this is caught by the catch block where it is handled, in this case by rolling back the data before rethrowing the error. This causes the error to bubble up the call stack, where it will either be caught by another catch block, or result in an uncaught exception which causes code execution to stop.
  5. The finally block declares code that is executed irrespective of whether an exception occurs. In this case, the data is cleared from the component both if the store is successful, or it if fails. The finally block executes before the caught exception is rethrown.

Note: catch, finally, and rethrow are optional within the try block.

ProcScript for Exception Handling

Uniface provides the following ProcScript statements for enabling and handling exceptions:

ProcScript Statements for Exception Handling
Statement Action
throws Enables exception handling for a module.
try Block declaration that specifies the code that can throw an exception.
catch Specifies code within the try block that will handle any resulting exceptions.
rethrow Rethrows an exception that was caught in the catch block.
finally Declares ProcScript inside a try block that must be executed, regardless of whether an exception occurs or not.
endtry Defines the end of the try block.
throw, throw/list Throws a custom exception.

Enabling Exceptions

To enable a module to throw exceptions, you need to explicitly declare that it can do so by adding the throws declaration as the first statement in the module, or by using a try...endtry construction around the whole module implementation. For example:

entry myretrieve
throws 
    retrieve/e "AN_ENTITY"
end					

If for some reason the retrieve instruction fails, an exception is thrown. Because there is nothing in this module that would prevent or catch the exception, the exception would be thrown up to the next module in the call stack, such as the module that called this entry.

Exceptions can also be enabled for a particular instruction by putting it in a try block. A try block can optionally include one or more catch blocks, a finally block, and a rethrow statement.

A module with a throws or try declaration, or an instruction within a try block can be said to be exception-enabled.

Exceptions can also be enabled implicitly, using a throw or rethrow statement.

Exception Bubbling

Any module that is exception-enabled can raise an exception to the calling module, where it will bubble up through the call stack, until it is caught and dealt with in a catch block, or it reaches the top of the call stack as an uncaught exception. An uncaught exception stops code execution and causes the application to exit.

Note: Once an exception is thrown in an exception-enabled module, every other module in the call stack has the ability to raise the bubbling exception up to the caller.

Consider an activate statement that calls an exception-enabled operation. If the activate was successful but the operation throws an exception, it bubbles up to the activate statement. If the activate does not catch that exception, it propagates it up to the next level.

Note: An exception can also be thrown from an error in a catch block or a finally block. The exception will bubble up to a higher level.

Catching Exceptions

Letting uncaught exceptions bubble up is a useful technique for finding problems during development, but you really want to prevent them from happening, or catch and handle the exception.

One way to prevent uncaught exceptions is to catch and handle exceptions that result from anticipated runtime errors using a try-catch construction.

For example, the following code does an ordinary retrieve in the try block, and catches the exceptions that are raised if the entity has no occurrences (-2) or table does not exist (-4). Any other exception is thrown to the calling module because the checkDataExists entry contains a throws declaration.

entry checkDataExists
throws ; this module throws an exception if an error is returned
returns boolean
  try
    retrieve "MYENTITY"
  catch <UIOSERR_OCC_NOT_FOUND>, <UIOSERR_OPEN_FAILURE>  ; -2, -4
    return <FALSE>  ; No data available
  endtry 
  return <TRUE> ; Data available
end

If you have made a typo, such as misspelling the entity name, you are confronted with an exception at an early stage in the development process.

  • If the code sets $procerror to a negative value, code execution continues in this operation's caller right after that call, with that negative $procerror value.
  • If the caller called this from within a try block, code execution continues in one of the associated catch blocks, if there is an applicable one. If not, the exception bubbles up to the next try-level or to the caller of the module.
  • If the caller has no associated catch blocks, it throws the exception further, to its own caller. This continues until the exception is caught or exits as an uncaught exception.

Example: Exception Handling

For a more realistic example, consider the following code for an entry that checks whether data exists in the database for a provided profile and entity, and returns true or false:

entry dataExists
throws ; declares this module breaks with an exception on any $procerror
returns Boolean
params
  string pEntityName: in
  string pProfileData: in ; “fieldname1=profile1{;...}”
endparams
variables
  string vFieldName, vFieldProfile
endvariables
  clear/e pEntityName                                  ; See Note 1
  forlist/id vFieldName, vFieldProfile in pProfileData ; See Note 2
    @("%%(vFieldName).%%(pEntityName)")/init = vFieldProfile
  endfor
  try  ; introduces code that may result in an error 
    retrieve/e pEntityName
    catch -2, -4 ; catches specific errors and handles them 
      return "F"
  endtry
  return "T"
end

Notes

  1. If you pass an incorrect entity name when calling this function, the clear/e statement is the first instruction that uses the entity name parameter. The clear/e statement is not inside a try-catch block and will cause the function to exit with an exception ("ERROR=-1102; DESCRIPTION=Entity not valid"). This is a good result, because you now know you've made a mistake and can correct the entity name.
  2. Similarly, if you pass the incorrect profile data, an error might occur while assigning the profile to the field using a field indirection @("%%(vFieldName).%%(pEntityName)"). As this statement is not inside the try...catch...endtry block, it will also cause the function to exit with an exception ("ERROR=-1101; DESCRIPTION=Field not valid"). The error indicates that you did not call the function correctly, and you can quickly correct your mistake.

Related Topics