HOW Work with Exceptions

back

Autor : Sancho Fock

If one want's to handle Errors, he will have to get straight what an error is at all. Then he will be able to decide how to handle.

What is an error?

Generally an error is a deflection of a result from the expection. In other words: If a softwaresystem reacts unexpected or returns results wich are not within the expected range, we call it an error.

 

An error must be treated in the context of the actual processing. We have an error if the processed results are unexpected implausible or the pre-conditions of the processing are not given.

 

The key here is the unexpected. This signifies that errorhandling means to expect the unexpected.


top of page

Error or systemreaction

The logical result of the prior mentioned is that a result can be an erroe in one processingstep while it is a legal casein the next processingstep.

Example:

 

try

  Qry.Fields[0].AsInteger := StrToInt(Edit1.Text);

Except

  On e : EConversionError do

    Qry.Fields[0].AsInteger := 0;

End;

 

If the property text of the EditBox does not contain a valid integer value, this leads to an error in the function StrToInt. The pre-conditions for this processing are -unexpectedly- not valid. In StrToInt we have an error. It gets escalated to an exception because StrToInt can not solve this error.

Per contra the calling processing expected that the inputvalue of StrToInt might be not valid. So it catches the expeption defines the targetvalue to 0 in this case and de-escalates the exception and transforms it to a systembehaviour.

 

As we saw here it is important to consider the context: For StrToInt there is an error for the caller not.

 


top of page

Whet to do when the Error is here?

If we have an error in the prior sense that can not be fixed by the actual processing then the actual processing must escalate this error. If we work with exceptions this means to re-raise (or just don't catch) the existing exception or create a new exception and raise it. Additionally the processing must go sure that all ressources allocated by her (e.e. Memory or transactions) will still be released.

 

If the actual processing can fix the error or let the system react senseful. The Exception mus be catched and the error fixed. In this way the escalation get stopped willingly. In this case the error is no error anymore. This is what we call exceptionhandling.

 

If an error in shape of an exception will not be handeled the default ExceptionHandling will be called. Unlikely this is nothing more but showing a message box by default in a TApplication descant. For this reason any application should implemet further features like a global handler, tracer and logger.
Librarys like the JCL help to get this job done.
Products like madExcept deliver this functionalties completely.

 

Generally this covers: If there exists an error in the above sense then there sould also exist an exception.

 


top of page

The Exception

Exceptions are a help to implement a structured errorhandling. They hay the following key features and possibilities:

-          An Exception is a class. For this reason it is possible to classify Errors. With the help of the inheritance it is possible to do this as detailed as nessesary.

-          Polymorphism can be used for the handling. This means that it is possible to handle an error correctly even if you don't know this explicit error when implementing the handling. It is enough to have knowlege of the ancestor exception class.

-          The system offers a automatic escalation. This helps to be sure that there are no unnotifierd errors in the system (if an exception does not get handled where it occures it will automaticly escalated to the caller. When one uses Errorcodes this is opposite: If the caller does not explicitly ask for an error nothing happens).

-          The system provides an automatc abort trough all stack frames exactly there where the exception "happend".

 


top of page

VCL Exceptions

Delphi already delivers a set of exception classes, they are already thrown by the VCL and it's components. If unhandled exceptions appear within a own application this can be lead back to a programming error in nearly all cases.

In other words: All prpobably appearing exceptions should be handled on a proper place.

 


top of page

Own exception

Always when there occures an error within a processing step that the programmer has expected, but the programmer is unable to solve the problem (here) he has to create and throw a own exception. Therefore he should always create an own exception type and place it on a proper place in the exception class hierarchy.

 Examples:

// Example 1: When the function shall show a form, but
// the forms reference is not assigned the function is
// "helpless" and must throw an exception

function ShowWindowModal(aForm : TForm) : TModalResult;

Begin

  if not assigned(aForm) then

    raise EObjectNotAssignedException.create(‘The Form was not created‘);

  Result := aForm.ShowModal;

end;

 

// Example 2: The function "knows" that the access to the
// errorlist might cause a problem, but can not solve this
// For this reason it creates a new exception which makes
// it clearer to the caller what happend.
// If for example an acess violation occures the caller
// of getUserBirthday can not know what a problem had
// occured. But if getUserBirthday raises an exception that
// illustrated the problem more concrete the caller is
// probably able to react better.

function getUserBirthday(Name : String) : TDateTime;

var

  thisUser : TUser;

Begin

  try

    thisUser :=(myStringList.Objects[myStringList.IndexOf(Name)] as Tuser);

    result := thisUser.Birthdate;

  Except

    on e : EstringListError do

      raise EuserNotFoundEx.Create(‘The User ‘+Name+‘ is unknown‘);

    on e : EAccessViolation do

      raise EuserListCorrupt.create(‘User List not assigned‘); 

  end;

end;

 


top of page

Handling of exceptions

Like already mentioned exceptions get escalated automaticly. This means that they will work even if one has no own implementation for them.

As a matter of the fact that exceptions do abort a process the programmer must ensure that resources like the memory or transactions stay safe.

If it is known, that a action can cause an exception this should be handled as early as possible. If it is not possible to handle an exception at one place it should not be handled here except of it might be senseful to change the errorcontext.


top of page

Try ... finally

The try...finally construct is used to save resources. If there occures an exception within the try block (or break is called) the processing will be aborted at here. But the finally block will still be executed and the exception will be re-raised after the finally block.

Examples for the correct usage of try...finally

 

function getUserName(FileName : String; Index : Integer) : String;

Var

  Sl : TStringList;

Begin

  Sl := TStringList.create;

  Try

    Sl.LoadFromFile(FileName);

    Result := sl.strings[Index];

  Finally

    FreeAndNil(Sl);

  End;

End;

 

Procedure GlobalBlockedIncrement;

Begin

  While bGloablIsInProcess do

    Sleep(1000);

  bGloablIsInProcess := True;

  Try

    iGlobalCounter := iGlobalCounter + 1;

    myGlobalObject.process(iGlobalCounter);

  finally

    bGlobalIsInProcess := false;

  end;

end;

 


top of page

Try ... except

The try...except construct is used to recognize occured exceptions and handle them.

If an exception gets caught by a try...except it's escalation will be stopped.

If a except block does not specify at least one type with "on" all exceptions will be handeld here. If there are classes specified will all other classes be re-raised here.

The objective of a good errorhanling is that no error get's lost. For this reason except must only be used if one or more exceptionclasses can be handled here. Otherwise use try...finally and never catch all exceptions just to get one.

Alway avoid things like the following:

 

// try ... except nono‘s

try

  DoSomething;

Except // any exception gets swallowed here. The      
       // caller gets no feedback that an        // error has occured

End;

 

Try

  DoSomething;

Except

  On e : exception do;

End; // any exception gets swallowed here, too. The
     // caller get's no feedback that an
     // error has occured

 

Try

  DoSomething;

Except

  On e : EStringlistError do

    Rollback;

  else

    ShowMessage(uups);

  end;

end; // The EStringlistError exception gets hanled here
     // but all other exceptions get swalloed. The User
     // gets ony a message that is not adequate for him
     // The caller gets no feedback, that an error has
     // happend.

 

try

  DoSomething;

except

  On e : Exception do

    ShowMessage(e.className+‘ occured‘);

end; // all exceptions get swalloed here. The User
     // gets ony a message that is not adequate for him
     // The caller gets no feedback, that an error has
     // happend.

 

If it should be nessesary by a strong reason, that all exceptions mus be chaught at a special location then this reason should be mentioned as a comment.

 

Examples for the correct usage of try...except:

 

// close the open transaction and inform the caller
// with a new exception

Db.beginTransaction;

Try

  QryInsert.ExecuteSQL;

  QryUpdateRelations.ExecuteSQL;

  QryDeleteOldRel.ExecuteSQL;

  Db.commit;

Except

  On e : EdatabaseException do

  Begin

    Db.rollback;

    Raise EAppendCustomerNotPossibleEx.create(e);

  End

  Else

  Begin

    Db.rollback;

    raise;

  End

End;

   

// repeat a failed action:

Function AddNewCustomer : Integer;

Var

  Succ : boolean;

  NewID, failures : Integer;

Begin

  Failures := 0;

  Succ := false;

  Qry1.sql := ‘select max(ID) from customer‘;

  Repeat

    Qry1.open;

    NewID := qry1.fields[0].AsInteger;

    Qry1.close;

    With qry2 do

    Begin

      Sql := 'Insert into customer (ID) values ('+IntToStr(NewID)+‘)‘;

      try

        ExecuteSQL;

        Succ := true;

      Except

        On e : EDatabaseError do

          Inc(failures);

      End;

    end; 

  Until Succ or (Failures > 10);

  If not Succ then

    Raise EInsertingCustomerFailedEx.create;

  Result := NewID;

End;

 

// Change the errorcontext

function getUserBirthday(Name : string) : TDateTime;

var

  thisUser : TUser;

Begin

  try

    thisUser :=(myStringList.Objects[myStringList.IndexOf(Name)] as Tuser);

    result := thisUser.Birthdate;

  Except

    on e : EstringListError do

      raise EuserNotFoundEx.Create('the User '+Name+‘ ist unknown‘);

    on e : EAccessViolation do

      raise EuserListCorrupt.create('Userlist not assigned‘); 

  end;

end;

 

// making an exception to a processing state

try

  Qry.Fields[0].AsInteger := StrToInt(Edit1.Text);

Except

  On e : EConversionError do

    Qry.Fields[0].AsInteger := 0;

End;

 


top of page

back