Welcome!

PowerBuilder Authors: Chris Pollach, Yakov Fain, RealWire News Distribution, Al Soucy, Elizabeth White

Related Topics: PowerBuilder, .NET

PowerBuilder: Article

Power Building with Exceptions

It’s a matter of code elegancy

How to Solve the Problems
In the suggested solution, three of the actions with the exception (creation + population + throwing) are put in one function named f_throw, so the occupied code shrinks from three lines to one and looks like this:

f_throw(PopulateError(0, "[error message]"))

As you can see, all the described conditions are satisfied. PopulateError (called inside the argument parentheses of f_throw) grabs the needed details of the thrown exception (class, script and even code line number) and stores them in an Error object. The numeric code, passed as the first argument to PopulateError(), must help the developer find the problem spot when a few exceptions are thrown from the same script; simply pass 0 if you don't need that. The function f_throw creates an instance of exception, populates it with the data stored in Error object, and throws. The function uf_msg, which must be called from within the exception handler, "knows" how to use all that data to build a nice error message. We will use the n_ex, a descendant of Exception, and later I will show how to create that pretty simple class.

try
[...code...]
if [condition of Problem 1] then f_throw(PopulateError(1, "[description of Problem 1]"))

[...code...]
if [condition of Problem 2] then f_throw(PopulateError(2, "[description of Problem 2]"))
catch(n_ex e)
e.uf_msg()
end try

Do you see how much shorter that simple example is? And if your script throws a lot of exceptions? And hundreds, if not thousands, all over the application?

In the last example the exception is thrown and caught in the same script. It's not a common practice - usually exceptions are thrown and caught in different scripts (i.e., propagate through functions calls). In that case, don't forget to populate the field "Throws:" in the called function's header with "n_ex"!

Example of Use
Let's say, the following code fragment appears in the function wf_show_classes_hierarchy of class w_spy:

try
f_throw(PopulateError(3, "Something terrible happened!"))
catch(n_ex e)
e.uf_msg()
end try

After it has been run, the following message appears (see Figure 1).

As you can see, an additional service is provided: you can open the debugger and "travel" in the functions call stack (this feature is enabled only if the application is running from the PowerBuilder IDE and doesn't work in a standalone executable).

Another Way: Custom Exception Type for Each Problem
The method, described above, utilizes only exceptions of data type n_ex. The method is based on displaying the error message generated in the same code fragment that has thrown the exception. But if you prefer to create exceptions of different types for different problem situations (in the "PL/SQL style"), then our User Manual slightly changes:

  1. You have to create the desired set of exceptions (inherited from n_ex!), one for each problem to be processed. Of course, their names must reflect their purpose.
  2. When you call f_throw for those problems, the string, passed as the second parameter to PopulateError(), must contain the name of an exception class to be thrown instead of the error message used in the previous method.
  3. A variable, used to catch the exception, must be of the same data type, otherwise the exception will propagate ahead.
  4. If the exception's processing includes displaying a message, that message should be generated in the code fragment that processes the caught exception.

Both the methods ("by error message" and "by exception type") can coexist in the same application. You can create custom exception types for certain problems and still use the first, simple method for the rest of the exceptions.

Here's an example of using both methods. Let's say you have created an exception class named n_ex_credit_prohibited (don't forget that it must be inherited from n_ex, not from Exception!):

try
// The first method:
if IsNull(as_cust_id) or as_emp_id = "" then f_throw(PopulateError(1, "No cust_id passed"))
// The second method:
if not uf_credit_allowed(as_cust_id) then f_throw(PopulateError(2, "n_ex_credit_prohibited"))
catch (n_ex_credit_prohibited e1)

// process the situation when the customer is not eligible to be credited
catch (n_ex e_others)
e_others.uf_msg()
end try

Pay attention that the function uf_msg() is not used in the second method (the kind of the error is defined by the exception's type). But if you will call it anyway in that situation, it will display something like "Exception n_ex_credit_prohibited thrown."

How to Create the Related Objects?
You have to perform five simple steps:

1. Create a class inherited from the PB built-in Exception type and save it under the name n_ex.

2. Declare in n_ex the following instance variables:

protected:

int ii_err_num
int ii_line
string is_class
string is_script

3.   Add to n_ex a public function uf_populate with the following code (the arguments are listed in the header comment):

/*********************************************************************************

Dscr:    Sets data related to the class and the script the exception is thrown from.
Called from f_throw().
----------------------------------------------------------------------------------
Arg:     int ai_err_num
string as_err_msg
string as_class
string as_script
int ai_line

*********************************************************************************/

ii_err_num = ai_err_num
SetMessage(as_err_msg)
is_class = as_class
is_script = as_script
ii_line = ai_line

4.  Add to n_ex a public function uf_msg, having no arguments, with the following code (change it if your application uses a specific messaging mechanism, or if you need to register the problem in a log):

/*********************************************************************************

Dscr:    Displays a message with all the exception-related information. Should be

called for an exception caught in "catch" section of "try...end try" block.

*********************************************************************************/

int li_user_answer
string ls_msg
string ls_header

if ii_err_num > 0 then
ls_header = "EXCEPTION #" + String(ii_err_num) + " THROWN"
else
ls_header = "EXCEPTION THROWN"
end if

if Len(is_class) > 0 then ls_msg = "Class: " + is_class + "~r~n"
if Len(is_script) > 0 then ls_msg += "Script: " + is_script + "~r~n"
if ii_line > 0 then ls_msg += "Line: " + String(ii_line) + "~r~n"
if ls_msg <> "" then ls_msg += "~r~n~r~n"

ls_msg += GetMessage()

if Handle(GetApplication()) = 0 /* running from PB IDE, not as .exe */ then
ls_msg += "~r~n~r~n~r~nDo you want to open debugger to see Call Stack?"
li_user_answer = MessageBox(ls_header, ls_msg, Information!, YesNo!, 2)
if li_user_answer = 1 /* Yes */ then
DebugBreak()
end if
else
MessageBox(ls_header, ls_msg)
end if

5. Create a global function f_throw (if you don't like global functions then you can create it as a public function of an object - uf_throw) with the following code:

/*********************************************************************************

Dscr:    Creates an Exception object (of type n_ex or its descendant),
populates the exception-related data and throws. Is called this way:

f_throw(PopulateError(0, "[string]"))

IF "[string]" is the name of a descendant of n_ex THEN
the thrown exception is of that class;
no error message is generated (the caught exceptions are distinguished
by data type, in the "PL/SQL style");
ELSE
the thrown exception is of the class n_ex;
the string is interpreted as an error message which can be displayed
with uf_msg() of the caught exception;
END IF

----------------------------------------------------------------------------------

Arg:     ai_populate_error_rc_dummy - placeholder for PopulateError()'s return code;
the only purpose of this argument is to enable writing
PopulateError inside the argument parentheses of f_throw.

----------------------------------------------------------------------------------

Thr:      n_ex
*********************************************************************************/string                                            ls_msg
string                                       ls_ex_type = "n_ex" // the default (used if Error.Text contains an error message)
n_ex                                        ln_ex
ClassDefinition           lcd

ls_msg = Trim(Error.Text)
lcd = FindClassDefinition(ls_msg
if not IsNull(lcd) /* Error.Text contains the name of a valid PB type */ then
do until lcd.Name = "powerobject"
lcd = lcd.Ancestor
if lcd.Name <> "n_ex" /* the PB type, contained in Error.Text, is NOT a descendant of n_ex */ then continue
ls_ex_type = ls_msg
ls_msg = "Exception " + ls_msg + " thrown." // usually will not be used, but anyway...
exit
loop
end if

ln_ex = create using ls_ex_type
ln_ex.uf_populate(Error.Number, ls_msg, Error.Object, Error.ObjectEvent, Error.Line)

throw ln_ex

That's it, congratulations - you now have a smart exception class.

More Stories By Michael Zuskin

Michael Zuskin is a certified software professional with sophisticated programming skills and experience in Enterprise Software Development.

Comments (0)

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.