Chapter 16. Error Handling and Debugging

Table of Contents

1. Error Messages
1.1. Emailing Errors to the Developer
2. Error Types in Biferno
3. How to Catch and Manage Error Conditions
4. How to Force an Error Condition
5. The Debug Instruction
6. User Class Errors

We have seen in the previous chapters a number of situations in which a Biferno instruction generates an error. Passing non-compatible parameters to a class method or to a function, or trying to open a non-existent file, we can cause errors that interrupt the execution of our code.

Besides errors generated by the code itself, another possible source of trouble in a Web application is input data originated by the user via a HTML form or by other external sources. When developing a Web application it is good practice to make sure from the beginning that the application must be able to handle situations where data to be manipulated might not be in the correct format, or not be available because of errors or malfunctioning in systems that an application interfaces to, such as a file system or DBMS. To avoid this kind of situations, Biferno makes available to the programmer specific tools to intercept and manage errors generated by Biferno during script execution. In this way it is possible to act in the proper fashion in case of error, e.g. by informing the user that there is a problem or by automatically correcting the situation and allowing the program to continue.

This chapter describes the Biferno diagnostic system, the handling of errors in a script, and the debugging of the code. Finally we will explain how a user class can generate its own errors defined internally to the class.

1. Error Messages

When Biferno encounters an error during the execution of a script, execution is interrupted and a message in HTML format is sent in output to the browser that contains an error description and some additional data.

We have described in Chapter 9, Applications and Variable Scope, when we discussed the use and format of the "Biferno.config.bfr" application configuration file, how to specify one or more IP addresses for developers who are authorized to received detail information on the content of variables when an error is generated during the execution of a script belonging to that particular application.

Figure 16.1, “Error description screen (developer version)”shows a sample error screen as it is sent to a developer.

The picture does not include the full identifier table with the corresponding values for all entities (variables and constants), both local and with wider scope, that were defined when the error occurred. As mentioned already, this table is sent in output only to the developer.

Figure 16.1. Error description screen (developer version)

Error description screen (developer version)

Figure 16.2. Error description screen (user version)

Error description screen (user version)

Figure 16.2, “Error description screen (user version)” shows the message for the same error as it is sent to the user. If we do not wish to use the standard error screen for the user, Biferno allows to specify a personalized error page for our application. This feature is activated by initializing the ERROR_PAGE variable in the "Biferno.config.bfr" application configuration file and setting its value to the path of the page to be visualized. An example is:

ERROR_PAGE = "error.bfr"
    

The page specified in this variable is shown only to the user, while the developer always receives the standard page.

The error information reported to the developer is:

  • The error type (see following section)

  • The numerical code identifying the error

  • The corresponding description string

  • The complete path of the script where the error was generated

  • The line number of the instruction causing the error

  • The corresponding code line

  • Finally, if available, an explanatory note that helps understanding the cause of the error and may suggest a remedy. An example is the name of the variable causing the error.

The full entity table is shown sorted by scope, starting with local entities, then reporting global, application (including all standard default parameters defined in the "Biferno.config.bfr" configuration file), session and persistent entities. For each entity the name, type (variable or constant), class, and value (or, more precisely, the string describing the corresponding value) is reported.

The error screen also includes information about the current script, including the client IP address and the time in ticks used by Biferno for script processing (one tick is 1/60th of a second).

Hide and Show

If we want to avoid including the value of a variable in the error screen, e.g. because it contains a password in clear text, we can mask the value by calling the object.Hide predefined static method, with prototype:

void object.Hide(obj varToHide)
     

An example is:

Hide(pass)
     

To undo the effect of this function the complementary method object.Show can be invoked.

1.1. Emailing Errors to the Developer

If an error is caused by a user (not by the developer identified by DEVELOPER_IP), it is possible to notify the developer via email sending the complete error screen, while a different error message is displayed to the user. If suitable values have been assigned to the MAIL_HOST (set to an email server address), NOTIFY_MAIL_ERR (set to true) and WEBMASTER (set to the email address of the individual to be notified), the complete error screen will be automatically sent via email to the webmaster with no need for user intervention. The user will be displayed the error page or, if no error page is defined, a message containing the error string: "An error occurred while processing a script. The webmaster has been notified".

2. Error Types in Biferno

Biferno errors belong to two distinct categories: language errors (e.g. syntax errors, identifier errors, function or class member invocation errors), called Biferno errors in the following, and errors generated by classes (Class errors).

Biferno errors are identified by the "BIFERNO ERROR" string in the header of the error screen, while class errors are identified by the string "Class <class_name> Error", where <class_name> is the actual name of the class generating the error.

Figure 16.3. A sample error from a predefined Biferno class

A sample error from a predefined Biferno class

An example is given in Figure 16.3, “A sample error from a predefined Biferno class” showing the error reported by the file class when we try to open an alias.

3. How to Catch and Manage Error Conditions

Catching error conditions is the first step in the implementation of an error management system. Normally, all Biferno errors are blocking, i.e. they interrupt script processing. This behavior can be modified by using the error.Resume() predefined language method with the following prototype:

void error.Resume(string funcName)
    

When called with no parameters, this function instructs Biferno not to block code execution if there is an error, but rather to continue from the code line following the line where the error occurred. This behavior is set only for the current script and only starting from the code line following the call to the error.Resume() method. We will see in the following the meaning of the funcName parameter.

The complementary method error.Suspend() (with no parameters) resets Biferno to the normal error handling behavior.

Not all error conditions can be ignored. After calling the error.Resume method, some Biferno errors can still be blocking, while errors generated by predefined classes are always considered non-blocking. Non-blocking Biferno errors include all operating system errors and e.g. the illicit TypeCast error (Err_IllegalTypeCast). The resumable property of the error class can be used to enquire if an error is blocking.

It is easy to see that in most cases continuing script execution after an error has occurred can cause a chain of related errors and ultimately complete malfunctioning of the script itself. When using the error.Resume method, our script has to be able to foresee and catch all possible error conditions. In general, it is not advisable to use the error.Resume method during the application development phase, because it would make debugging extremely difficult.

The err predefined global variable allows us to enquire if a Biferno instruction has caused an error and to receive at the same time the code of the generated error. This variable belongs to the error predefined class and contains all information we have seen when describing Biferno error messages.

In particular, the errNum property of the error class always contains an integer value which, if non-zero, corresponds to the numerical code of the last error generated by the current script. The name property contains the name, i.e. the descriptive string, of the error (e.g. "Err_IllegalTypeCast").

Notice that all numerical error codes are stored in static constants within the class they belong to. Biferno error codes are constants of the error class. For all predefined classes, the names of these constants correspond to error names reported in the name property of the error class. If we write:

      <? errCode = error.Err_OSError ?>
     

the errCode variable contains the integer value 1, corresponding to the Err_OSError Biferno error, indicating an error generated by the operating system. To better understand the use of the error.Resume method and of the err variable, we show how to intercept errors generated during file access operations. Let's look at the following script:

<? 
  error.Resume()

  myFile = file("doc001.txt", , r) 
  myFile.Put(0, "text") 
  if (err) 
    { 
       print("I/O error on the file named &quot;" + myFile.name + "&quot;.<br>\n") 
       if (err.name == "Err_OSError")
         print("System Error " + err.subErrDescr) 
       else 
         if (err.name == "ErrFileIsNotOpen") // File is not open 
           print("The file is not open<br>\n") 
    } 
?>
    

After calling the error.Resume method, we open a file for reading only and subsequently try to write the string "text" into it. This operation will fail and, in normal conditions, we would receive an error message. Because we called the error.Resume method, we can now catch this error condition. The state of the err global variable is checked on the next line and, if its value is non-zero, we warn the user that an error has occurred. The rest of the code shows how to implement further error management actions acting on the values of properties of the err object.

The script manages two possible errors by checking the value of the name property. If the property contains the "Err_OSError" string (Biferno error number 1), the error is a system error. In this case we print the string stored in the subErrDescr property, containing numerical code and error name (the subErr property contains only the numerical code). If the name property contains the "ErrFileIsNotOpen" string (error generated by the file class), an additional message is printed.

The err variable can be also directly compared to the values of class error static constants. E.g. the code fragment of the previous script that checks the type of error generated could be rewritten as follows:

<?
	if (err == error.Err_OSError) // System error
		print("System error " + err.subErrStr)
	else if (err == file.ErrFileIsNotOpen)
		print("The file is not open<br>\n")
?>
    

The err variable is global and it would be good practice to refer to it as global err. The latter is mandatory within a function or method.

The error.Resume method can be also called by passing it the name of a function as parameter. This function, which must be able to manage the error and to output the appropriate messages, will be called every time a non-blocking error occurred, and must have the following prototype:

function void function_name (error theErr)
    

In the following script we show an example of a function that prints error messages on the base of the value of the properties of the error class. The errClass property contains the name of the class that generated the error or, for Biferno errors, the "BIFERNO ERROR" string.

<?
function void HandleError (error theErr)
{
	print("Warning! An error has occurred.<br>\n")
	if (theErr.errClass == "BIFERNO ERROR") // Biferno error
	{
		if (theErr.name == "Err_OSError")
			print("System error " + theErr.subErrStr + "<br>\n")
		else
			print("Biferno error nr. " + theErr.errNum + " (" + \
						theErr.name + ")<br>\n")
	}
	else // Class error
		print("Error from class " + theErr.errClass + " nr. " + \
					theErr.errNum + " (" + theErr.name + ")<br>\n")
}

error.Resume("HandleError")

myFile = file("doc001.txt", , r)
myFile.Put(0, "text")
?>
    

Two more predefined Biferno methods for error handling are defined. The error.Function method returns a string containing the name of the function specified in the last call to the error.Resume method. The error.State method returns a Boolean value reflecting the present error handling behavior in the current script. The value true indicates that the error.Resume method has been invoked.

4. How to Force an Error Condition

The err predefined global variable can be accessed in write mode. By directly assigning an integer numerical value to the err variable, script execution is immediately interrupted (unless error.Resume was called) and the error corresponding to the specified numerical code is generated, i.e. an exception is generated.

Only Biferno errors can be generated in this fashion. If an error code is specified, which does not correspond to a Biferno error, we obtain the "Unknown Error" error message. We generally advise to always use the error constants of the error class instead of the corresponding numerical values, as the latter may change in future Biferno releases. Let's see how we can force a Biferno error (with value 1):

<?
	global err = error.Err_OSError
?>
    

Remember that the global scope identifier can be omitted within the main body of a script.

To generate errors of the predefined classes, or errors defined by a user class, we can use the ThrowException static method of the error class, with the following prototype:

static void ThrowException(int errNum, string class)
    

As it can be seen from the prototype, using this method allows to specify not only the numerical code of the error we want to force, but also the class it belongs to. To force the ErrFileIsNotOpen error of the file class, we can write:

<?
	error.ThrowException(file.ErrFileIsNotOpen, "file")
?>
    

It is possible, and actually advisable, to use the ThrowException method also to force Biferno errors, instead of directly assigning a value to the err variable, as in:

<?
	error.ThrowException(Err_OSError)
?>
    

Notice that, because the second parameter was omitted, the error code is implicitly attributed to the error class, and therefore a Biferno error is generated.

A complete listing of all Biferno and predefined classes error codes with the corresponding meaning is contained in "Biferno: Reference Guide".

5. The Debug Instruction

An alternative way to force an error in Biferno is via the debug instruction. When this instruction is encountered, script execution is interrupted and a specific error is generated (UserBreak).

The debug instruction allows the developer to check the state of variables at a certain point in the code to investigate the reason of program malfunctioning. By inserting the debug instruction in strategic points in a Biferno script effective code debugging can be performed.

6. User Class Errors

In Biferno a user defined class can generate its own errors, which are different from predefined language errors raised by Biferno or by predefined classes.

Error codes for a user class can follow an arbitrary numbering and, as for predefined classes, must be associated to static constants of the class. An error constant from a user class has to be declared using the following syntax:

static const error constant_name = integer_value
    

Let's use again the person class defined in Chapter 11, User classes for exemplification purposes and modify its behavior by adding to the class the ability to generate specific errors corresponding to wrong user input.

To do this we make the sex and age properties public and catch the values assigned to them via the SetProperty method (See also Section 3.1, “The SetProperty Method”).

<?
class person
{
string	firstName
string	lastName
char		sex
int		age

static const error	ErrInvalidSexId	= 1,
			ErrInvalidAge	= 2

void person (string n1, string n2, char s, int a)
{
	firstName = n1
	lastName = n2
	sex = s
	age = a
}
 
void SetProperty (string propertyName)
{	
	if (propertyName == "sex")
	{
		if (!sex.In("M", "F"))
			error.ThrowException(person.ErrInvalidSexId, "person")
	}
	else if (propertyName == "age")
	{
		if (age < 1)
			error.ThrowException(person.ErrInvalidAge, "person")
	}
}
}
?>
    

The SetProperty method checks that only legal values are assigned to the sex and age properties and, if this is not the case, generates an error of the person class using the ThrowException method.

In the example the two errors generated by the person class have numerical codes 1 and 2, but could in general be numbered in an arbitrary fashion.

When necessary a user class can associate additional information to its own error codes under the form of a message that will be visualized by Biferno as a note in the error screen. To do this we use the special user class static method GetErrMessage, with the following prototype:

static string GetErrMessage (int errCode)
    

Every time the ThrowException is invoked specifying the name of a user class as the second parameter, Biferno automatically calls the GetErrMessage method of the class (if it exists) and passes the error code to this method. The GetErrMessage, if implemented correctly, returns a string containing a different message for each error constant declared in the class.

As an example consider the following implementation of the GetErrMessage method for the person class:

static string GetErrMessage (int errCode)
{
	switch (errCode)
	{
		case person.ErrInvalidSexId:
			errMessage = "Specify M or F for the sex"
			break;
		case person.ErrInvalidAge:
			errMessage = "Age must be an integer positive number"
			break;
		default:
			errMessage = "Unknown error "
	}
	return errMessage
}