Program error

Description

A program error is an error that appears at runtime and which is due to an error in the source code.

[Note]Note

The reason for program errors is human imperfection and compiler imperfection.

If a programmer was perfect, he or she would write error-free programs.

If the compiler was perfect it would detect all errors done by the programmers, so that an error-free application can be delivered to users. This, however, is technically unfeasible. The Obix compiler has been designed to detect a maximum of errors at compile-time. Errors detected at compile-time are called coding errors. All remaining errors, not detected by the compiler, potentially result in errors at runtime, and are called program errors in this context. A coding error or program errors is also frequently called a bug

An interesting fact about the number of program errors in applications can be found in the book Code complete, second edition (ISBN 0-7356-1967-0), written by Steve McConnell, at page 521:

  • Industry average experience is about 1 - 25 errors per 1000 lines of code for delivered software

This means that a mid-size application consisting of 50000 instructions typically contains not less than 50 to 1250 program errors when delivered to the customer(s)!

Program errors always appear unintentionally, because a programmer strives to write error-free programs. Therefore, program errors can appear randomly at any time and in any location of the source code.

Kinds of program errors

Although the number of program errors in an application is always unknown, there is always a fixed set of types of program errors that can occur in a programming language. The following table gives an overview of the most frequent program error types in Obix:

Table 11.1. Types of program errors

GroupTypeDescription
standardvoid_object_error

An attempt is made to use a feature (e.g. execute a command) on a void object reference. This error is similar to the NullPointerException in Java.

Example:

brain = void
brain.think
arithmetic_overflow_error

The result of an arithmetic operation exceeds the highest value that is representable with the given number of bits.

Example:

const positive32 four_billion = 2000000000 + 2000000000
system_error

A operating system error occurred.

Examples:

stack overflow, low memory, etc.
supplier_error

A script calls another script, and then an error occurs in the called script.

error_instruction_error

A program error is intentionally raised with the error instruction. see the section called “error instruction”.

Example:

error "Huston, we have a problem!"
contract programmingattribute_check_error

An attempt is made to set an attribute to an invalid value

input_argument_check_error

An attempt is made to call a command with an invalid input argument value

output_argument_check_error

A command returns with an invalid output argument value

check_script_error

The condition checked with a check script instruction is not fulfilled. see the section called “check script instruction”.


Default behavior

An important question is: What happens in case of a program error at runtime?

Because program errors appear randomly (i.e. the programmer doesn't know in advance when they appear and what causes them to appear), Obix provides a default behavior for all program errors at runtime.

The default behavior is to simply write an error message to the system's error device which, by default, is the system console. Then the execution of the thread in which the error occurred, is stopped. The error message contains an explanation of the error, the location in the source code that caused the error, as well as the trace of all called scripts, up to the root script.

Here is an example of such an error message, caused through a division by 0:

OBIX PROGRAM EXECUTION ERROR
----------------------------
        feature: ty_integer32_value.co_divide
        library: org.obix.obix_core.basics.scalars.number.integer.integer32
           line: 46
           time: Jan 11, 2011 11:10:27 AM
    description: Division by zero. [division_by_zero_error]
input argument : i_operand

>
        feature: fa_positive32.co_divide
        library: org.obix.obix_core.basics.scalars.number.integer.integer32
           line: 79
           time: Jan 11, 2011 11:10:27 AM
    description: An error occurred in a supplier. [supplier_error]

>
        feature: se_tests.co_run
        library: li_tests
           line: 9
           time: Jan 11, 2011 11:10:27 AM
    description: An error occurred in a supplier. [supplier_error]

Customized program error handling

While Obix's default behavior might be suitable during development, it is generally inappropriate for applications running under production mode, for the following reasons:

  • The default behaviour of stopping the application is often inappropriate, or even dangerous, depending on the part of the application that was executed (e.g. in medical applications: imagine a surgeon's robot halting during surgery). Specific behaviors might be necessary depending on the context, and sometimes it is better to simply ignore the error instead of stopping the whole application.

  • Program errors should be logged (for example in a file or database) to help debugging, and the creator and/or maintainer of the software should possibly be informed quickly and automatically (e.g. via email notification).

  • The end-user should get a more user-friendly error message, possibly displayed in a graphical user interface (GUI) that provides a comprehensible message and suggests different options to escape.

Therefore, the application's behavior in case of a program error can be programmatically customized at different levels.

At the highest level, we can change Obix's global default program error handling that applies to all errors for which no specific handling is defined. At the lowest level, we can change the error handling for a single instruction. We can also define error handling at any intermediate levels in order to customize error handling for specific parts of an application. The details are explained in the following sections.

Changing the default global program error handling

As said already, Obix's default handling of program errors at runtime is to write error information (i.e. error message, date and time of occurence, location in source code and stack trace) to the system's error device, which, by default, is the system console.

Hence, instead of dispaying error information on the system console, the information can be redirected to any other system device by using the operating system's error redirection mechanism which is available on Unix-like systems, and on Windows.

For example, to log all program errors in a file named errors.txt, we can append 2>> errors.txt to the system command that starts the application. Suppose the application's name is cybernetics. Then the system command would be:

cybernetics 2>> errors.txt
[Note]Note
Please refer to your operating system's documentation for more information about redirecting the error device or visit http://en.wikipedia.org/wiki/Redirection_(computing).

There is, however, a more powerful way for customization.

Obix's default error handling for program errors is defined by attribute a_program_error_handler in service li_obix.li_system.se_system_utilities, which is defined as follows:

attribute program_error_handler type:program_error_handler default: fa_system_err_program_error_handler.create kind:variable setable:all end

Whenever a program error appears at runtime, Obix calls the error handler assigned to se_system_utilities.a_program_error_handler. Because a_program_error_handler is a variable attribute (kind:variable setable:all), we can assign any appropriate program error handler at runtime. Typically, this is done once when the application starts, but we can, if needed, change the error handler at any time during execution of the application. For example, the error handler could be specifed through a configuration file, or a user with the right privileges could choose the error handler in a menu of the application.

There are two steps involved in providing your own customized error handler:

  • create a factory that implements type ty_program_error_handler.

  • assign an instance of that factory to se_system_utilities.a_program_error_handler.

Type ty_program_error_handler is defined as follws:

///
   Copyright (C) 2009-2012 Christian Neumanns (www.rps-obix.com)
   This code can be used under the terms of the 'GNU Afero General Public License version 3'
   The full text of this license can be found at http://www.gnu.org/licenses/agpl.html
   THIS CODE IS DISTRIBUTED WITHOUT ANY WARRANTY. See the license for details.
end ///

type program_error_handler

   inherit error_handler end

   command handle_program_error
      in error type:program_error end
   end

end

ty_error_handler is just a type with no features, used as the parent type of all error handlers:

///
   Copyright (C) 2009-2012 Christian Neumanns (www.rps-obix.com)
   This code can be used under the terms of the 'GNU Afero General Public License version 3'
   The full text of this license can be found at http://www.gnu.org/licenses/agpl.html
   THIS CODE IS DISTRIBUTED WITHOUT ANY WARRANTY. See the license for details.
end ///

type error_handler

	attribute error_count type:zero_positive32 kind:readonly_variable end

end

Hence, in the simplest case, the only thing to do in a customized error handler factory is to implement command handle_program_error. All information about the error is available in input argument error. The actual type of object in this input argument is a child-type of type program_error and corresponds to one of the error types listed in Table 11.1, “Types of program errors”.

Let's have a look at an example of a customized program error handler. Suppose that:

  • in case of a system error, an email is sent to an administrator.

  • in case of any other error, an email is sent to a developer service center.

  • in any case the date, time and identification of the error is logged to file errors.txt

The code of the factory could be as follows:

factory my_program_error_handler type:program_error_handler

   // this private attribute holds a reference to the file used to log program errors
   attribute log_file type:file private:yes end

   // any time a program error occurs, the following command is called
   // the error is supplied in input argument i_error
   command handle_program_error
      script

         // depending on the case of error, send an email to the administrator
         // or to the developer service center
         case type of i_error
            when system_program_error then
               // send mail to administrator
               // (code not shown for brevety)
            otherwise
               // send mail to developer service center
               // (code not shown for brevety)
         end case
       
         // display short message on the system's err device
         system.err.write_line ( string = "A program error occured!" )
       
         // log error in log file
         const file_error log_file_error = se_text_file_IO.append_string_to_new_or_existing_file ( &
            string = i_error.to_string &
            file = a_log_file )
         if log_file_error #r void then
            // send mail to administrator, because error could not be written to log file
            // (code not shown for brevety)
         end if
     end
   end
   
   // The creator requires one input argument: the file used to log program errors
   creator create
      in log_file type:file check:i_log_file.exists end
      
      out result type:program_error_handler end
      
      script
         o_result.a_log_file = i_log_file
      end
   end

end factory

To activate this customized program error handler (and replace Obix's default error handler), the following code has to be added to the application's initialisation code:

const file my_log_file = fa_file.create_from_string.result ( string = "/var/log/my_application/errors.txt" )
if not my_log_file.exists then
	my_log_file.create_on_device
end if
se_system_utilities.program_error_handler = fa_my_program_error_handler.create ( my_log_file )

Changing local program error handling

As said already, the global default program error handling can be overwritten for specific parts of the application. This can be done by applying the following rules:

Examples

The basic steps for local customized error handling are:

  • use the on_error:continue clause to avoid invoking the global error handler and stopping the application

  • use the implicitly defined script variable v_program_error_ to check if a run-time error occurred and to (optionally) analyze the error

  • provide appropriate instructions in case of a runtime error

The general skeleton for source code that provides customized program error handling is shown below:

result = do_something on_error:continue // continue execution if an error occurs in 'do_something'
if v_program_error_ =r void then        // check if an error occured
   // :-) everything is ok
else
   // :-( we have a problem! analyse data in v_program_error_ and do whatever is appropriate
end if
// in any case, continue with following instructions
[Note]Note

The above code is similar to the following Java code that uses the try-catch-finally statement, which is also used in other programming languages:

try {
   result = do_something();
   // :-) everything is ok
}
catch (Exception e) {
   // :-( we have a problem! analyse data in e and do whatever is appropriate
}
finally {
   // in any case, continue with following statements
}
[Note]Note

There is no try-catch-finally instruction in Obix. At first this might appear as a surprise, because the try-catch-finally instruction, first introduced in Java, was especially invented to handle exceptional situations at runtime. Moreover, it has been added in a number of other popular languages, because of its general acceptance.

Although the try-catch-finally instruction is certainly a very useful and powerful instruction if used properly, it is also one of the most debated and misused one, because it requires a thorough understanding and discipline from the part of the programmer, as explained in some good articles on the internet, and demonstrated by a number of bad uses in real code (sometimes even encouraged in magazines and books). Ironically, while the try-catch-finally instruction has been specifically invented for error handling, there are a number of cases where new errors have been introduced because of not properly using the try-catch-finally instruction. Therefore, runtime errors in Obix are handled in a simpler and less error-prone, but not less powerful way, without the need for a try-catch-finally instruction.

The following code illustrates different ways to handle program errors.

Example 11.1. Customized program error handling examples

service error_handling_examples

     command example_2
      script
         // declare a void constant and a variable
         const string void_string = void
         var character first_char

         // case 1:
         // ignore the error
         first_char = void_string.first_item on_error:continue // 'void object' runtime error will occur!
                                                               // but program execution continues
         system.console.write_line ( "1: error ignored" )
         
         // case 2:
         // do something in case of an error
         first_char = void_string.first_item on_error:continue
         if v_program_error_ #r void then
            system.console.write_line ( "2: the following error occured:" )
            system.console.write_line ( v_program_error_.to_string )
         end if
         
         // case 3:
         // do something in case of an error
         // do something else in case of no error
         first_char = void_string.first_item on_error:continue
         if v_program_error_ =r void then
            system.console.write_line ( "3: no error occured" )
         else
            system.console.write_line ( "3: an error occured" )
         end if
         
         // case 4:
         // default behaviour (the application is stopped)
         first_char = void_string.first_item
         system.console.write_line ( "this message will never be displayed" )
      end
   end

end service

The above code will display the following on the system console, then stop the application and display a default runtime error message.

1: error ignored
2: the following error occurred:
        feature: se_error_handling_examples.co_example_2
        library: li_explore.li_doc_examples.li_advanced
           line: 14
    instruction: first_char = void_string.first_item on_error:continue
        message: Feature execution on void object. [void_object]
3: an error occurred