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:
- By an explicit throw or rethrow statement.
- 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.
- 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
- The throws command declares that any runtime error (negative $procerror) that occurs in the module will be regarded as an exception.
- Code that could result in a runtime error is contained by a
try
block. - 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.
- 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.
- 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:
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. |
|
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
- 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. - 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 thetry...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.