| 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 systemreactionThe 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. Generally this covers: If there exists an error in the above sense then there sould also exist an exception. top of page The ExceptionExceptions 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 ExceptionsDelphi 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 exceptionAlways 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 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 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 exceptionsLike 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 ... finallyThe 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 ... exceptThe 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 End; Try DoSomething; Except On
e : exception do; End; //
any exception gets swallowed here, too. The Try DoSomething; Except On
e : EStringlistError do Rollback; else ShowMessage(‘uups‘); end; end;
// The EStringlistError exception gets hanled here try DoSomething; except On e : Exception do ShowMessage(e.className+‘ occured‘); end;
// all exceptions get swalloed here. The User 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 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 |