Search  
Saturday, June 24, 2017 ..:: Articole » O clasă de tratare a erorilor ::.. Register  Login
 Articole Minimize

O clasă de tratare a erorilor

Autor: Doug Hennig
Notă: Această expunere prezintă o clasă de tratare a erorilor. Ea este un excelent material de studiu asupra modului în care se tratează erorile într-o aplicaţie. De asemenea, este un bun început pentru implementarea unui sistem robust de tratare a erorilor în propriile dvs. aplicaţii.

Clasa TratErori

Pentru început, să aruncăm o privire la definirea clasei TratErori, apoi vom studia câteva exemple despre cum se instanţiază această clasă şi cum poate trata erorile dintr-o aplicaţie.

Clasa TratErori este o clasă Custom. Ea foloseşte fişierul ERORI.H pentru a defini constantele folosite în metodele clasei.

Listingul 1. Fişierul ERORI.H

#include '\VFP\FOXPRO.H'
#define ccNULL              chr(0)
#define ccCR                chr(13)
#define ccQST_DEBUG         'Afişez debugger-ul?'

* Dimensiunile masivului AERROR().

#define cnVF_AERR_MAX      7
#define cnVF_AERR_NUMBER   1
#define cnVF_AERR_MESSAGE  2
#define cnVF_AERR_OBJECT   3
#define cnVF_AERR_WORKAREA 4
#define cnVF_AERR_TRIGGER  5
#define cnVF_AERR_EXTRA1   6
#define cnVF_AERR_EXTRA2   7
#define cnAERR_MAX         cnVF_AERR_MAX + 3
#define cnAERR_METHOD      cnVF_AERR_MAX + 1
#define cnAERR_LINE        cnVF_AERR_MAX + 2
#define cnAERR_SOURCE      cnVF_AERR_MAX + 3  

Clasa TratErori are următoarele proprietăţi particulare:

Denumire
Stare
Valoare iniţială
Scop
cCurrError Protected "" Conţine numele rutinei iniţiale de tratare a erorilor (fostul ON ERROR)
lSupressErrors Protected .F. Dacă este .T., mesajul de eroare nu este afişat.
lErrorOccurred Protected .F. Capătă valoarea .T. dacă apare o valoare.
cTitle Public "Error" Titlul ferestrei cu mesajul de eroare.
aErrorInfo[1] Public "" Un masiv care conţine informaţii referitoare la ultima eroare.

În continuare voi analiza metoda Init a clasei. Ea acceptă un parametru care specifică titlul ferestrei care va fi afişată utilizatorului la apariţia unei erori. Acest titlu este stocat în proprietatea cTitle. Dacă nu transmiteţi titlul când instanţiaţi clasa, puteţi specifica titlul scriind un text în această proprietate. Metoda Init salvează numele rutinei de tratare a erorilor active la momentul instanţierii în proprietatea cCurrError, care are statutul Protected, apoi iniţializează proprietăţile lErrorOccurred şi lSuppressErrrors, dându-le valoarea .F., iar în final redirectează gestionarul de erori către metoda ErrorHandler a clasei.

* Salvează setarea ON ERROR existentă.
* Stabileşte şi titlul ferestrei, dacă este transmis.

LPARAMETERS tcTitle
This.cCurrError = ON('ERROR')
IF TYPE('tcTitle') = 'C'
	This.cTitle = tcTitle
ENDIF

* Determină ca apariţia unei erori să fie tratată de
* metoda ErrorHandler. Stabileşte şi valorile flag-urilor

ON ERROR goError.ErrorHandler(ERROR(), SYS(16), ;
	LINENO())
This.lErrorOccurred  = .F.
This.lSuppressErrors = .F.

Metoda Destroy face "curăţenie". În acest caz, restaurează denumirea rutinei implicite de tratare a erorilor. Este necesar să scriem valoarea proprietăţii într-o valiabilă, pentru că macrosubstituţia nu poate fi folosită direct cu o proprietate a unei clase.

LOCAL lcError
lcError = This.cCurrError
ON ERROR &lcError

Înainte de a analiza metoda ErrorHandler (cea care face toată treaba), vom trece în revistă şi celelalte metode utile ale clasei. Metoda DidErrorOccur este o metodă publică ce întoarce valoarea proprietăţii lErrorOccured, care este protejată şi nu poate fi modificată din exteriorul clasei. Metoda permite determinarea apariţiei unei erori.

RETURN This.lErrorOccured

Metoda ResetError (publică) care scrie în proprietatea lErrorOccured valoarea .F. şi reiniţializează masivul aErrorInfo. Această metodă este folosită înaintea execuţiei unei linii de cod care este posibil să genereze o eroare (cum ar fi deschiderea exclusivă a unei tabele), pentru a fi sigur că valoarea proprietăţii lErrorOccurred este .F. (dacă a fost vreo eroare înainte, ea rămâne poziţionată pe .T., ceea ce v-ar putea face să credeţi că linia curentă a generat o eroare).

This.lErrorOccured = .F.
DIMENSION This.aErrorInfo[cnAERR_MAX]
This.aErrorInfo = ""

Metoda SetSuppressErrors scrie în proprietatea protejată lSuppressErrors valoarea transmisă de metodă şi întoarce valoarea sa iniţială. Dacă proprietatea lSuppressErrors are valoarea .T., metoda ErrorHandler nu va afişa nici un mesaj de eroare către utilizator. În acest fel aveţi posibilitatea ca pentru anumite erori să preluaţi dvs. conducerea; tratarea acestor erori "deosebite" va rămâne în sarcina dvs.

LPARAMETERS tlSuppress
LOCAL llSuppress
llSuppress = lSuppressErrors
This.lSuppressErrors = IIF(TYPE("tlSuppress") = "L") ;
	AND NOT ISNULL("tlSuppress"), tlSuppress, ;
This.lSuppressErrors
RETURN llSuppress

În continuare aveţi un exemplu de folosire a acestor trei metode. Să presupunem că vreţi să deschideţi o tabelă exclusiv, dar nu puteţi, şi nu doriţi ca utilizatorul să vadă mesajul generat de Visual FoxPro, ci aţi dori să vadă propriul dvs. mesaj. Mai întâi, metoda SetSuppressErrors determină ca rutina de tratare a erorilor să nu afişeze nici un mesaj de eroare, metoda ResetError scrie valoarea .F. în proprietatea lErrorOccured valoarea .F. (această proprietate este flagul de eroare). Metoda DidErrorOccur vă informează dacă apare vreo eroare la tentativa de deschidere a tabelei:

llSuppress = goError.SetSuppresErrors(.T.)
goError.ResetError()
USE clienti EXCLUSIVE
goError.SetSuppressErrors(llSuppress)
IF goError.DidErrorOccur()
	= MESSAGEBOX("Fişierul de clienţi nu poate " + ;
		"fi deschis în acest moment")
	RETURN
ENDIF

Acum este momentul să analizăm metoda ErrorHandler. Ea devine rutina de tratare a erorilor din aplicaţie, atâta vreme cât obiectul goError este activ. Dacă apare vreo eroare sunt transmişi trei parametri către metoda ErrorHandler: ERROR() - codul de eroare; SYS(16) - numele programului în care a apărut eroarea şi LINENO(), numărul liniei care a generat eroarea. Metoda ErrorHandler foloseşte metoda GetErrorInfo pentru a seta proprietatea lErrorOccured la .T. şi pentru a plasa informaţii despre eroarea apărută în masivul aErrorInfo. Dacă proprietatea lSuppressError are valoarea .T., nu se va afişa nici un mesaj de eroare. În caz contrar, metoda DisplayError afişează un mesaj de eroare şi pune la dispoziţia utilizatorului trei acţiuni posibile: afişarea debugger-ului, ignorarea erorii şi închiderea aplicaţiei.

LPARAMETERS tnError, tcMethod, tnLine
LOCAL lcCurrTalk, lnChoice

* Mă asigur că TALK este OFF.

IF SET('TALK') = 'ON'
	SET TALK OFF
	lcCurrTalk = 'ON'
ELSE
	lcCurrTalk = 'OFF'
ENDIF

* Plasez informaţii despre eroare în masivul aErrorInfo
* şi poziţionez flagul lErrorOccured.

This.GetErrorInfo(tcMethod, tnLine)

* Dacă este activată afişarea erorilor, atunci afişez
* un mesaj de eroare şi pun utilizatorul să acţioneze

IF NOT This.lSuppressErrors
	lnChoice = This.DisplayError()
	DO CASE

	* Cancel: Şterg toate ferestrele WAIT, dau un
	* CLEAR EVENTS şi apoi un CANCEL.

	CASE lnChoice = IDCANCEL
		WAIT CLEAR
		CLEAR EVENTS
		CANCEL

	* Afişare debugger: Afişez Debugger-ul sau ferestrele
	* Trace şi Debug.

	CASE lnChoice = IDYES
		ACTIVATE WINDOW DEBUG
		SET STEP ON	
	ENDCASE
ENDIF

* Restaurez valoarea lui TALK.

IF lcCurrTalk = 'ON'
	SET TALK ON
ENDIF

Metoda GetErrorInfo setează proprietatea lErrorOccured pe .T. şi plasează informaţii despre eroare în masivul aErrorInfo:

LPARAMETERS tcMethod, tnLine
= AERROR(This.aErrorInfo)
DIMENSION This.aErrorInfo[cnAERR_MAX]
This.aErrorInfo[cnAERR_METHOD] = tcMethod
This.aErrorInfo[cnAERR_LINE]   = tnLine
This.aErrorInfo[cnAERR_SOURCE] = message(1)
This.lErrorOccurred            = .T.

După cum bine ştiţi, funcţia AERROR() încarcă într-un masiv informaţii despre eroarea apărută. Din păcate, masivul are cam puţine informaţii despre eroare. În masivul aErrorInfo sunt şi restul informaţiilor. Iată structura lui:

Numărul elementului Conţinut
1-7 Aceleaşi informaţii ca şi AERROR()
8 Procedura sau metoda în care a apărut eroarea
9 Numărul liniei în care a apărut eroarea
10 Codul liniei în care a apărut eroarea


Metoda DisplayError afişează o casetă de dialog în care se scriu informaţii despre eroarea apărută.

LOCAL lcLine1,lcLine2,lcLine3,lcLine4,lnPos, ;
	lcModule,lcObject,lcMethod,lcLine5, ;
	lcLine6, lcLine7
lcLine1 = "Cod eroare: " + ;
	LTRIM(STR(This.aErrorInfo[cnVF_AERR_NUMBER]))
lcLine2 = "Mesaj: " + ;
	This.aErrorInfo[cn_VF_AERR_MESSAGE]
lcLine3 = "Line: " + ;
	LTRIM(STR(This.aErrorInfo[cnAERR_LINE]))
lcLine4 = "Codul liniei: " + ;
	This.aErrorInfo[cnAERR_SOURCE]
	
* Îmi fac o imagine despre obiect, eroare şi modul
	
lnPos = RAT(' ', This.aErrorInfo[cnAERR_METHOD])
lcModule = SUBSTR(This.aErrorInfo[cnAERR_METHOD], ;
	lnPos + 1)
lcModule = SUBSTR(lcModule, RAT('\', lcModule) + 1)
lcModule = LEFT(lcModule, AT('.', lcModule) - 1)
lcObject = LEFT(This.aErrorInfo[cnAERR_METHOD], ;
	lnPos - 1)
lcObject = SUBSTR(lcObject, AT(' ', lcObject) + 1)
lnPos = RAT('.', lcObject)
lcMethod = SUBSTR(lcObject, lnPos + 1)
lcObject = LEFT(lcObject, lnPos - 1)
lcLine5  = 'Metodă:  ' + lcMethod
lcLine6  = 'Obiect:  ' + lcObject
lcLine7  = 'Modul:  ' + lcModule
	
* Afişez mesajul de eroare şi întorc alegerea utilizatorului.
RETURN messagebox(lcLine1 + ccCR + lcLine2 + ;
	ccCR + lcLine3 + ccCR + lcLine4 + ccCR + ;
	lcLine5 + ccCR + lcLine6 + ccCR + lcLine7 + ;
	ccCR + ccCR + ccQST_DEBUG, MB_YESNOCANCEL + ;
	MB_ICONSTOP, This.cTitle)

Folosirea clasei TratErori

Clasa TratErori trebuie iniţializată într-o variabilă globală numită goError. Dacă nu vă place varianta cu stocarea într-o variabilă globală, atunci puteţi să creaţi o proprietate a obiectului aplicaţie, astfel:

oApp = CREATEOBJECT('Application')
oApp.oError = CREATEOBJECT('ErrorMgr')

În această situaţie, metoda Init a clasei trebuie modificată pentru a seta ON ERROR la oApp.oError.ErrorHandler în loc de goError.ErrorHandler.

Aveţi în continuare şi un program care demonstrează iniţializarea clase TratErori, modul în care clasa treatează erorile, şi modul în care mesajul de eroare poate fi eliminat şi eroarea tratată local.

* Instanţiez clasa TratErori. Variabila
* goError este privată, nu locală.
* Astfel avem o rutină generală de tratare

PRIVATE goError
SET CLASSLIB TO TratErori
goError = CREATEOBJECT('ErrorMgr', ;
	'Titlul casetei de eroare')

* Hai să facem o tâmpenie, ca să vedem cum
* percutează interceptarea erorilor.

? sfsff			&& Linia asta va da eroare

* Acum împiedic apariţia mesajelor de eroare
* ca să demonstrez cum se poate trata
* "manual" o eroare

llSuppress = goError.SetSuppressErrors(.T.)
goError.ResetError()
? sfsff			&& Linia asta va da eroare
goError.SetSuppressErrors(llSuppress)
IF goError.DidErrorOccur()
	WAIT WINDOW 'A apărut eroarea : ' + goError.aErrorInfo[2]
ENDIF
RETURN

Dacă doriţi ca un anumit obiect să trateze el însuşi anumite erori, dar să atribuie restul de erori clasei TratErori, folosiţi următorul cod în metoda Error a obiectului:

LPARAMETERS nError, cMethod, nLine
IF nError = ### && codurile erorilor tratate local
	* tratarea erorilor
ELSE
	goError.ErrorHandler(nError, cMethod, nLine)
ENDIF

Extinderea clasei TratErori

Clasa TratErori este un model extrem de simplu al unei rutine de tratare a erorilor. Ea doar afişează un mesaj de eroare şi oferă trei opţiuni. Acesta este motivul pentru care am specificat la începutul expunerii că ea este doar un început. Iată câteva idei pentru dezvoltarea ei:

Afişarea ferestrei Debugger-ului este utilă pentru dezvoltator, dar nu şi pentru utilizator, ca atare ar trebui să nu existe în aplicaţia distribuită. Veţi avea nevoie să împiedicaţi această facilitate în .EXE.

De asemenea, ar trebui implementată şi posibilitatea de reîncercare a comenzii care a generat eroarea.

Revenirea la programul apelant este şi ea o opţiune care trebuie luată în considerare.

Este foarte probabil că doriţi să trataţi erori diferite în moduri diferite. De exemplu, dacă o eroare este generată de imprimanta off-line, ar trebui să îl informaţi pe utilizator şi să îi oferiţi posibilitatea să reîncerce după ce rezolvă problema. Dacă o înregistrare este blocată, ar trebui să îi oferiţi şi posibilitatea de a reîncerca. În cazul unei erori serioase (cum ar fi insuficienţa memoriei), ar trebui să îi oferiţi doar varianta ieşirii din program. Pentru diversificarea variantelor, metoda DisplayError ar trebui modificată astfel încât să caute eroarea într-o tabelă predefinită, din care să citească mesajul pe care să îl afişeze şi opţiunile pe care să le pună la dispoziţia utilizatorului. De asemenea, trebuie modificată şi metoda ErrorHandler, pentru a mări numărul de opţiuni posibile.

O metodă extrem de elegantă pentru a afişa mesajele către utilizator este următoarea:

Textele mesajelor sunt definite ca fiind constante într-un fişier INCLUDE. Aceste mesaje conţin şi locul unde se inserează informaţiile particularizate pentru mesaj. De exemplu, mesajul de eroare pentru "Database not open" arată astfel:

#DEFINE ccERR_DB_NOT_OPEN "Baza de date <Insert1> nu este deschisă"

În acelaşi fişier există şi o constantă care este folosită ca punct de inserţie (dacă sunt mai multe puncte de inserţie, sunt mai multe constante):

#DEFINE ccMSG_INSERT1 <Insert1>

Un astfel de mesaj se foloseşte prin intermediul funcţiei STRTRAN(), care înlocuieşte textul din punctul de inserţie cu informaţiile particularizate:

WAIT WINDOW STRTRAN(ccERR_DB_NOT_OPEN, ccMSG_INSERT1, "Baza1")

Puteţi folosi o tehnică similară pentru a stoca mesajele într-o tabelă cu mesaje de eroare, apoi să căutaţi şi să particularizaţi mesajul înainte de a-l afişa utilizatorului.

Dacă doriţi să înregistraţi erorile apărute, ar trebui să creaţi o metodă nouă şi să modificaţi metoda ErrorHandler astfel încât să apeleze noua metodă înainte de a verifica dacă lSuppressErrors este .F.; în acest fel, chiar dacă mesajele nu sunt afişate utilizatorului, eroarea este înregistrată.

Concluzie

O clasă de tratare a erorilor vizibilă în întreaga aplicaţie, aşa cum este TratErori, poate creşte robusteţea programului dvs. Mai mult, ea poate fi imbunătăţită sau subclasată pentru a permite servicii mult mai robuste decât varianta iniţială.

Descărcaţi Erori2.zip.


    

 Google Ads Minimize

    

Copyright 2002-2013 Profox   Terms Of Use  Privacy Statement