Search  
Friday, March 24, 2017 ..:: Articole » Gestionarea erorilor - partea a III-a ::.. Register  Login
 Articole Minimize

Gestionarea erorilor - partea a III-a

Autor: Doug Hennig
Notă: Obiectele au o metodă numită Error, pe care o puteţi folosi pentru a gestiona local erorile. Dar cum se implementează o rutină globală de tratare a erorilor, care să gestioneze o întreagă aplicaţie? Vom studia o strategie de proiectare a ei, pornind de la obiecte şi terminând cu aspectul global al aplicaţiei.

În expunerea precedentă am discutat câteva aspecte despre implementarea unei rutine globale de tratare a erorilor. Toate punctele discutate anterior sunt corecte, dar o parte din ele sunt doar concepte, nu cod folosibil. În această expunere vom implementa o rutină de tratare a erorilor, sub forma unui cod refolosibil. În expunerea următoare vom studia tratarea unor erori specifice, cum ar fi cele care apar în ferestrele de introducere a datelor.

Proiectarea unei scheme de tratare a erorilor

Visual FoxPro suportă atât tratarea globală a erorilor (prin intermediul comenzii ON ERROR), cât şi tratarea locală a lor, prin intermediul metodei Error. Aceste două posibilităţi sunt, de fapt, capetele unui interval.

  • Rutina globală (ON ERROR) este mult prea departe de sursa erorii pentru a putea fi lăsată să o trateze singură, în timp ce metoda Error este parte a obiectului care a generat eroarea. Astfel, metoda Error pare să "ştie" mai multe despre mediul în care a apărut eroarea, erorile posibile în contextul dat şi modul de rezolvare al lor. De exemplu, controlul ActiveX CommonDialog Controls (cel care afişează casetele de dialog File, Print, Color şi Printer), generează o eroare dacă utilizatorul face click pe Cancel. Nu este deloc inspirată metoda de a pune codul de tratare a erorii în rutina ON ERROR, pentru că ea va vedea doar o eroare OLE oarecare şi nu va şti cum să o trateze. Iar dacă am pus controlul acela în 10 locuri în aplicaţie, va trebui să scriem cod de tratare a aceleiaşi erori de 10 ori. Cum sună? Este mult mai bine să scriu codul de tratare a erorii în metoda Error a controlului. Aici am numai câteva erori posibile, care pot fi tratate individual.
  • Rutina globală de tratare a erorilor este apelată cu ajutorul schemei mai vechi de tratare a evenimentelor ("ON") - aceeaşi care este folosită şi la apelarea meniurilor. Acest lucru înseamnă că nu puteţi folosi sintaxa orientată pe obiect (cium ar fi ThisForm, şi mai mult, pot apare tot felul de complicaţii (cum ar fi sesiunile de date private).
  • Rutina globală este, în schimb, locul ideal de plasare a serviciilor de tratare a erorilor globale (cum ar fi scrierea erorilor apărute într-un fişier log, afişarea lor, etc). Este ineficient să scriem rutine de tratare a erorilor care nu pot fi anticipate (cum ar fi o reţea care a "căzut" în metoda Error a fiecărui obiect…

Hai să găsim o metodă de proiectare a unei rutine de tratare a erorilor care să ia ce este bun din ambele situaţii. Deci: doresc să tratez erorile în cel mai eficient mod posibil, dar să am în continuare posibilitatea de a trata erorile specifice obiectelor în mod individual, în metoda Error proprie fiecăruia. Iată punctele principale:

  • Un obiect are mai multe informaţii despre ce se întâmplă decât părintele său (este valabil şi pentru copiii unora dintre noi - inclusiv ai mei), aşa că metoda Error proprie va gestiona cât mai multe erori posibil. În schimb va trasmite erorile pe care nu le poate gestiona părintelui, folosind comanda DODEFAULT(). Dacă o subclasă sau o instanţă nu trebuie să trateze nici un fel de erori, atunci nu vom scrie nici un fel de cod în metoda Error, acest lucru având ca efect folosirea codului clasei părinte. Fiecare subclasă în ierarhie va face acelaşi lucru. Astfel, ADouaSubclasaText.Error va apela metoda PrimaSubclasaText.Error, care apelează Text.Error.
  • Metoda Error a clasei părinte va trata toate erorile pe care le poate trata. Pe celelalte le transmite containerului său, folosind comanda This.Parent.Error.
  • Dat fiind faptul că toate clasele container funcţionează în acelaşi fel, rezultatul este transmiterea erorilor care nu pot fi tratate la nivelul respectiv ascendent în ordinea claselor, apoi către container.
  • Metoda Error a clasei părinte al celui mai "mare" container va trata toate erorile pe care le poate trata. Pe celelalte le transmite rutinei globale de tratare a erorilor, şi anume, cea definită în comanda ON ERROR.

Acesta este modul de proiectare numit "Lanţul responsabilităţii". Fiecare obiect din lanţ tratează erorile sau le transmite superiorului său în lanţ. În acest mod, tratarea erorilor devine din ce în ce mai generală pe măsură ce se urcă în ierarhie, oferind posibilitatea de tratare a lor la nivelul corespunzător. Figura 1 ilustrează această strategie.

lant.gif

Figura 1. Lanţul responsabilităţii

Am decis ca rutina globală de tratare a erorilor să fie un obiect care este instanţiat într-o variabilă globală numită oError. Ea este conţinută în clasa SFErrorMgr. Una din metodele obiectului (şi anume ErrorHandler) este apelată direct de către obiecte, în modul descris mai sus, şi indirect, deoarece este definită şi în comanda ON ERROR. Obiectul trebuie să aibă o interfaţă simplă (şi mă refer la interfaţa pentru programator, nu pentru utilizator). Din acest motiv, clasa SFErrorMgr acceptă numai parametrii pe care îi acceptă metoda Error a obiectelor (numărul erorii, metoda şi numărul liniei) şi întoarce un şir care indică opţiunea aleasă de utilizator (sau de obiect) pentru rezolvarea erorii. Obiectul oError este la capătul lanţului şi din acest motiv nu ştie prea multe lucruri privitoare la mediul din care a fost apelat; de exemplu, ar putea să fie o altă "Data Session" - care îi este invizibilă. Ca atare nu poate rezolva prea multe lucruri. Scopul lui este să afişeze un mesaj de eroare către utilizator şi să decidă acţiunea care va fi executată (dacă poate fi prevăzut acest lucru), să înregistreze eroarea pentru a fi putea analizată post-mortem, sau să îl întrebe pe utilizator ce e de făcut. Astfel, obiectul global de tratare a erorii va fi folosit numai pentru a trata erorile care pot fi prevăzute şi pe care nu le-aţi prevăzut încă (o dată ce apar, veţi modifica obiectul, clasa sau rutina care a generat eroarea), şi erorile care nu pot fi prevăzute (bug-uri sau condiţii neprevăzute de mediu).

Obiectul oError poate lua o decizie de unul singur (afişând Debug-erul sau închizând aplicaţia) sau poate permite obiectului care a generat eroarea să decidă singur în privinţa ei. Pentru a se putea aplica varianta a doua, este necesar ca fiecare verigă din lanţul responsabilităţii să întoarcă un "cod de decizie" către nivelul precendent. Pentru simplificare, am decis ca obiectele să întoarcă un şir de caractere: "retry" pentru a executa din nou comanda care a generat eroarea, "continue" pentru a trece la linia următoare şi "closeform" pentru a închide formularul pe care se află obiectul. Fiecare obiect execută operaţiunea corespunzătoare mesajului returnat. Datorită faptului că metoda Error a unui obiect container poate fi apelată de unul din obiectele incluse sau poate chiar de una din metodele proprii obiectului container, obiectul trebuie să decidă dacă transmite mesajul de răspuns sau îl procesează el însuşi. Vom vedea puţin mai târziu codul pentru acest lucru.

Toate sunt bune şi frumoase, dar schema are o problemă: controalele care sunt conţinute în containere cum ar fi Page sau Column (care nu posedă o metodă Error) nu pot trata erori deoarece apelează o metodă inexistentă. Soluţia este transferul către superiorii în ierarhie până când se întâlneşte o metodă Error validă. În cazul în care nu este găsit un părinte care să corespundă, se afişează un mesaj generic (ceea ce este destul de puţin probabil, deoarece containerul Form are o metodă Error proprie.

Trebuie să aveţi în vedere un aspect important: rutinele de tratare a erorilor trebuie să fie absolut corecte. Mai exact: _nici_un_bug! Dacă apare vreo eroare în rutina de tratare a erorii, singura rezolvare posibilă este caseta de eroare standard VFP (cea cu Cancel/Ignore). Din fericire, dacă secvenţa de cod de tratare a erorilor este corectă, nu trebuie să vă mai bateţi capul cu ea. Ea poate să dea erori numai în circumstanţe cu totul şi cu totul excepţionale. Nu vă chinuiţi să construiţi o metodă Error în obiectul oError. Ea nu va fi apelată niciodată.

Metoda Error

Este momentul să punem la punct detaliile strategiei. Punctul de început al unei erori este metoda Error a obiectului în care a apărut eroarea, deci să începem de acolo.

Codul metodei Error a majorităţii claselor pe care le-am construit (şi care se pot descărca prin intermediul legăturii din finalul expunerii) este afişat în continuare. Constantele (cum ar fi ccMSG_RETRY) sunt definite în fişierul LIBRARY.H, care este inclus în fiecare clasă. Am scris "majoritatea claselor" pentru că obiectele container intermediar (cum ar fi PageFrame şi OptionGroup) sau containerele de nivel maxim (cum ar fi Form sau Toolbar) funcţionează puţin diferit. Aceasta este una din rarele ocazii când am dorit ca Visual FoxPro să suporte moştenirea multiplă. Din păcate nu este aşa, şi ca atare, va trebui să folosiţi metoda de subclasare din Visual Basic: trebuie să puneţi acelaşi cod în toate metodele Error ale tuturor subclaselor, selectând tot codul din metoda părinte, Ctrl+C, plasaţi cursorul în noua metodă, apoi Ctrl+V ca să îi spuneţi să folosească codul clasei părinte. <g>

Listingul 1. Metoda Error

LPARAMETERS nError, cMethod, nLine
LOCAL oParent, lcReturn

* Urc în ierarhie până găsesc un părinte
* care are cod în metoda Error.

IF TYPE('Thisform') = 'O'
	oParent = IIF(PEMSTATUS(Thisform, 'FindErrorHandler', ;
		5), Thisform.FindErrorHandler(This), .NULL.)
ELSE
	oParent = .NULL.
ENDIF TYPE('Thisform') = 'O'

DO CASE
	CASE NOT ISNULL(oParent)
		* Avem un părinte care poate trata eroarea.
		lcReturn = oParent.Error(nError, This.Name + '.' + ;
			cMethod, nLine)
	CASE TYPE('oError') = 'O' and not isnull(oError)
	   	* Avem un obiect care tratează erorile, deci
		* apelăm metoda ErrorHandler() a lui.
		lcReturn = oError.ErrorHandler(nError, ;
			This.Name + '.' + cMethod, nLine)
	OTHERWISE
	    * Afişez o casetă de eroare generică.
		MESSAGEBOX('Eroarea nr. ' + ltrim(str(nError)) + ;
			' a apărut în linia ' + ltrim(str(nLine)) + ;
			' din ' + cMethod + ' în obiectul ' + This.Name, ;
			0, _VFP.Caption)
ENDCASE

lcReturn = IIF(TYPE('lcReturn') <> 'C' OR ;
	EMPTY(lcReturn), ccMSG_CONTINUE, lcReturn)
* Gestionez valoarea care trebuie returnată.
DO CASE
	CASE lcReturn = ccMSG_RETRY
		RETRY
	CASE lcReturn = ccMSG_CANCEL
		CANCEL
	OTHERWISE
		RETURN
ENDCASE

Acest cod verifică dacă formularul pe care este controlul are o metodă FindErrorHandler (clasa de bază pentru formulare din bibliotecă are această metodă) şi, dacă există, o apelează pentru a determina primul părinte care are cod în metoda Error proprie. Această manevră împiedică stoparea tratării erorii la clasele de bază Page, Column sau alte containere care nu au metode Error proprii. Dacă este găsit un părinte corespunzător, va fi apelată metoda Error a sa, fiindu-i transmişi aceiaşi parametri, la care se adaugă şi numele obiectului (în parametrul cMethod), astfel încât rutina de tratare a erorii să ştie numele obiectului în care a apărut eroarea. Dacă nu este găsit un părinte corespunzător, dar există o rutină globală de tratare a erorilor (ON ERROR), atunci este apelată metoda Error a obiectului oError (voi discuta acest aspect puţin mai târziu). În fine, dacă nu există nimic care să trateze eroarea, este folosită funcţia MESSAGEBOX() pentru a afişa un mesaj generic. Valoarea întoarsă de rutina de tratare a erorii este folosită pentru a decide modul de rezolvare a erorii: CANCEL, RETRY sau RETURN.

Listingul 2. Metoda FindErrorHandler

LPARAMETERS oObject
LOCAL oParent

* Navighez prin ierarhie până găsesc un părinte pentru obiectul
* specificat care are cod în metoda Error proprie. Aceasta
* împiedică o problemă cu controalele aflate pe clasele
* Page sau Column - pentru acestea nu se interceptează
* erorile, deoarece nu există cod scris direct în aceste obiecte.

oParent = oObject.Parent
DO WHILE TYPE('oParent') = 'O' AND NOT ISNULL(oParent)
	DO CASE
		CASE PEMSTATUS(oParent, 'Error', 0)
			EXIT
		CASE TYPE('oParent.Parent') = 'O'
			oParent = oParent.Parent
		OTHERWISE
			oParent = .NULL.
	ENDCASE
ENDDO WHILE TYPE('oParent') = 'O' ...
RETURN oParent

Clasele container "intermediare" (SFContainer, SFControl, SFGrid, SFOptionGroup şi SFPageFrame) au un cod aproape identic, dar trebuie să întoarcă mesajul de tratare a erorii, în loc să o proceseze ele însele, dacă eroarea nu le aparţine. Acest lucru poate fi determinat prin verificarea existenţei caracterului punct (.) în metoda care a generat eroarea, fiindcă VFP transmite numai numele metodei, dacă eroarea a apărut într-o metodă proprie, iar dacă eroarea a apărut într-una din clasele subordonate, este transmis numele obiectului şi al metodei. Astfel, avem o metodă rapidă de a determina dacă eroarea a apărut în obiectul container sau într-unul din membrii săi. Mai jos aveţi codul corespunzător al acestor clase:

* Gestionez valoarea care trebuie returnată

DO CASE
	CASE '.' $ cMethod
		RETURN lcReturn
	CASE lcReturn = ccMSG_RETRY
		RETRY

Dată fiind natura lor (sunt containere de nivel maxim - eu nu folosesc FormSet), metoda Error a claselor SFForm şi SFToolbar este diferită de a celorlalte obiecte. Această metodă foloseşte o metodă suplimentară numită SetError pentru a popula câteva proprietăţi cu informaţii despre eroare, şi metoda ErrorHandler pentru a trata eroarea. Apoi procesează valoarea care trebuie returnată, ori executând o acţiune (cum ar fi închiderea ferestrei), ori returnând valoarea către obiectul care a apelat metoda Error. Este de remarcat faptul că metoda nu întoarce nici o valoare dacă eroarea a apărut în DataEnvironment. Aceste erori sunt tratate de metodă, la nivelul ei. Poate doriţi să modificaţi acest comportament.

Listingul 3. Metoda Error a claselor SFForm şi SFToolbar.

LPARAMETERS tnError, ;
	tcMethod, ;
	tnLine
LOCAL lcReturn

* Folosesc SetError() şi HandleError() pentru a obţine
* informaţii despre eroare şi pentru a o trata.

WITH This
	.SetError(tnError, tcMethod, tnLine)
	lcReturn = .HandleError()
ENDWITH

* Tratez valoarea care trebuie returnată, funcţie
* dacă eroarea a fost local sau provine de la un membru.

DO CASE
	CASE lcReturn = ccMSG_CLOSEFORM
		This.Release
		RETURN TO MASTER
	CASE '.' $ tcMethod AND ;
		NOT 'DATAENVIRONMENT' $ UPPER(tcMethod)
		RETURN lcReturn
	CASE lcReturn = ccMSG_RETRY
		RETRY
	CASE lcReturn = ccMSG_CANCEL
		CANCEL
	OTHERWISE
		RETURN
ENDCASE

Listingul 4. Metoda SetError

LPARAMETERS tnError, ;
	tcMethod, ;
	tnLine
LOCAL laErrors[1], ;
	lnI

* Semnalizez faptul că a apărut o eroare şi folosesc AERROR() pentru a popula un masiv.

WITH THIS
	.lErrorOccurred = .T.
	AERROR(laErrors)
	.nLastError = IIF(EMPTY(.aErrorInfo[1, 1]), 1, ALEN(.aErrorInfo, 1))
	DIMENSION .aErrorInfo[.nLastError, cnAERR_MAX]
	FOR lnI = 1 TO ALEN(laErrors)
		.aErrorInfo[.nLastError, lnI] = laErrors[lnI]
	NEXT lnI

	* Adaug câteva informaţii suplimentare în masiv.

	.aErrorInfo[.nLastError, cnAERR_METHOD]   = tcMethod
	.aErrorInfo[.nLastError, cnAERR_LINE]     = tnLine
	.aErrorInfo[.nLastError, cnAERR_SOURCE]   = ;
		IIF(MESSAGE(1) = .aErrorInfo[.nLastError, cnAERR_MESSAGE], '', ;
		MESSAGE(1))
	.aErrorInfo[.nLastError, cnAERR_DATETIME] = DATETIME()
ENDWITH

Listingul 5. Metoda HandleError

LOCAL lnError, ;
	lcMethod, ;
	lnLine, ;
	loError, ;
	lcMessage, ;
	lcReturn
WITH THIS
	lnError  = .aErrorInfo[.nLastError, cnAERR_NUMBER]
	lcMethod = .NAME + '.' + .aErrorInfo[.nLastError, cnAERR_METHOD]
	lnLine   = .aErrorInfo[.nLastError, cnAERR_LINE]

* Obţin o referinţă a obiectului de tratare a erorii, dacă există. Poate fi
* membru al formularului sau un obiect global.

	DO CASE
	CASE TYPE('.oError') = 'O' AND NOT ISNULL(.oError)
		loError = .oError
	CASE TYPE('oError') = 'O' AND NOT ISNULL(oError)
		loError = oError
	OTHERWISE
		loError = .NULL.
	ENDCASE

* Nu am un obiect care să trateze erorile, aşa că afişez o casetă de dialog.

	IF ISNULL(loError)
		lcMessage = ccMSG_ERROR_NUM + LTRIM(STR(lnError)) + ccCR + ;
			ccMSG_MESSAGE + .aErrorInfo[.nLastError, cnAERR_MESSAGE] + ccCR + ;
			IIF(EMPTY(.aErrorInfo[.nLastError, cnAERR_SOURCE]), '', ;
			ccMSG_CODE + .aErrorInfo[.nLastError, cnAERR_SOURCE] + ccCR) + ;
			IIF(lnLine = 0, '', ccMSG_LINE_NUM + LTRIM(STR(lnLine)) + ccCR) + ;
			ccMSG_METHOD + lcMethod
		lcReturn = IIF(MESSAGEBOX(lcMessage, MB_ICONEXCLAMATION + MB_OKCANCEL, ;
			_SCREEN.CAPTION) = IDCANCEL, ccMSG_CANCEL, ccMSG_CONTINUE)

* Am un obiect care tratează erorile, deci apelez metoda ErrorHandler() a sa.

	ELSE
		lcReturn = loError.ErrorHandler(lnError, lcMethod, lnLine)
	ENDIF ISNULL(loError)
ENDWITH
lcReturn = IIF(TYPE('lcReturn') <>'C' OR EMPTY(lcReturn), ccMSG_CONTINUE, ;
	lcReturn)
RETURN lcReturn

Metoda HandleError va încerca să transmită eroarea către obiectul global de tratare a erorii, care este referit ori prin variabila globală oError ori prin proprietatea oError a formularului. Această schemă permite obţinerea unei versiuni particularizate a rutinei de tratare a erorii pentru un anumit formular, dacă se doreşte acest lucru. Dacă nu există nici o rutină globală de tratare a erorilor, atunci este afişat un mesaj de eroare, folosind funcţia MESSAGEBOX(). Valoarea returnată de către rutina de tratare a erorilor este transmisă înapoi metodei Error.

Rutina globală de tratare a erorilor

Clasa SFErrorMgr este o clasă non-vizuală, bazată pe SFCustom. Ea este inclusă în fişierul SFMGRS.VCX din arhiva .ZIP care poate fi descărcată cu link-ul de la sfârşitul expunerii. De asemenea, trebuie spus că foloseşte fişierul ERRORMGR.H pentru definirea câtorva constante. Este instanţiată într-o variabilă globală numită oError la lansarea aplicaţiei. Nu voi expune tot codul acestei clase, ci doar metodele relevante pentru subiectul acestei expuneri. Puteţi examina celelalte metode dvs. înşivă.

Metoda Init acceptă trei parametri: titlul ferestrei care va fi afişată când apare o eroare (este stocat în proprietatea cTitle), un flag care determină dacă metoda Init va salva sau nu rutina curentă definită în comanda ON ERROR şi redirectează tratarea erorilor către metoda ErrorHandler proprie şi numele obiectului în care este instanţiată clasa (este necesar pentru comanda ON ERROR, deoarece This nu poate fi folosit.

Ca de obicei, metoda Destroy face curăţenie: în acest caz, restaurează vechea valoare a comenzii ON ERROR.

Metoda ErrorHandler este apelată în două moduri diferite: direct de către obiecte, prin intermediul "lanţului de responsabilităţi", dat fiind faptul că este activă comanda ON ERROR. Iată codul acestei metode:

Listingul 6. Metoda ErrorHandler.

LPARAMETERS tnError, ;
	tcMethod, ;
	tnLine
LOCAL lcCurrTalk, ;
	lcChoice, ;
	lcProgram
WITH THIS

* Mă asigur că SET TALK este OFF

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

* Pun eroarea în masivul aErrorInfo şi setez flagul lErrorOccured.

	.GetErrorInfo(tcMethod, tnLine)

* Dacă afişarea erorilor este activă, afişez eroarea şi îi cer utilizatorului
* să decidă o acţiune.

	lcChoice = ccMSG_CONTINUE
	IF NOT .lSuppressErrors

* Scriu eroarea într-un log, dacă este necesar.

		IF .lLogErrors
			.LOGERROR()
		ENDIF .lLogErrors

* Afişez eroarea şi îl întreb pe utilizator.

		IF .lDisplayErrors
			lcChoice = .DisplayError()
			DO CASE

* În timpul dezvoltării aplicaţiei, Cancel sau Quit: elimin orice fereastră
* WAIT WINDOW, TABLEREVERT() pe orice cursor şi dau un CLEAR EVENTS (în cazul lui QUIT)
* apoi mă întorc la programul apelant.

			CASE lcChoice = ccMSG_CANCEL OR ;
					(lcChoice = ccMSG_QUIT AND VERSION(2) <> 0)
				WAIT CLEAR
				IF lcChoice = ccMSG_QUIT
					.lQuit = .T.
					.RevertAllTables()
					CLEAR EVENTS
				ENDIF lcChoice = ccMSG_QUIT
				lcProgram = .cReturnToOnCancel
				RETURN TO &lcProgram

* Afişez debugger-ul: activez ferestrele Trace şi Debug.

			CASE lcChoice = ccMSG_DEBUG AND VERSION(2) <> 0
				ACTIVATE WINDOW DEBUG
				SET STEP ON

* Codul pentru reîncercarea comenzii: acest lucru trebuie făcut aici, deoarece
* nimeni nu va recepţiona mesajul RETRY (în cazul unui obiect).

			CASE lcChoice = ccMSG_RETRY
				lcMethod = UPPER(tcMethod)
				IF AT('.', lcMethod) = 0 OR ;
					INLIST(RIGHT(lcMethod, 4), '.FXP', '.PRG', ;
					'.MPR', '.MPX')
					IF lcCurrTalk = 'ON'
						SET TALK ON
					ENDIF lcCurrTalk = 'ON'
					RETRY
				ENDIF AT('.', lcMethod) = 0 ...

* Quit: TABLEREVERT() pe toate cursoarele existente, apoi QUIT.

			CASE lcChoice = ccMSG_QUIT
				.lQuit = .T.
				.RevertAllTables()
				ON SHUTDOWN
				QUIT
			ENDCASE
		ENDIF .lDisplayErrors
	ENDIF NOT .lSuppressErrors

* Readuc TALK la vechea valoare.

	IF lcCurrTalk = 'ON'
		SET TALK ON
	ENDIF lcCurrTalk = 'ON'
ENDWITH
RETURN lcChoice

Când apare o eroare sunt transmişi trei parametri către metoda ErrorHandler: codul de eroare, numele rutinei şi numărul liniei în care a apărut eroarea. Metoda ErrorHandler foloseşte metoda GetErrorInfo pentru a seta proprietatea lErrorOccured la .T. şi plasează informaţiile despre eroare în proprietatea aErrorInfo. Dacă proprietatea lSuppressErrors este .T., eroarea nu este scrisă în log şi nu este afişat nici un mesaj de eroare către utilizator. În caz contrar, este apelată metoda LogError pentru a scrie eroarea într-un log şi metoda DisplayError este folosită pentru a afişa un mesaj de eroare şi a cere utilizatorului să decidă ce va face în continuare. Posibilităţile sunt următoarele:

  • Debug: Această opţiune este posibilă numai dacă proprietatea lShowDebug este .T. şi rulaţi o versiune de dezvoltare a lui VFP, activând ferestrele Debug şi Trace. Proprietatea lShowDebug ar trebui să fie .T. numai pentru dezvoltatori (informaţie care ar putea fi scrisă într-o tabelă de utilizatori sau în Windows Registry).
  • Continue: Trece la linia de comandă următoare.
  • Retry: Reîncearcă execuţia liniei care a generat eroarea. Această manevră este puţin mai complexă: dacă ErrorHandler a fost apelat în mod explicit (de către metoda Error a unui obiect), nu se poate executa direct comanda RETRY, deoarece controlul va fi redat metodei care a apelat metoda ErrorHandler, şi este posibil ca nu metoda apelantă să fi generat eroarea, ci una situată mai departe în "lanţul responsabilităţilor". În acest caz, metoda doar va returna mesajul "retry". Pe de altă parte, dacă eroarea a survenit în cod (.PRG sau .MPR), metoda ErrorHandler a fost apelată ca rutină ON ERROR, aşa că returnarea mesajului "retry" nu va avea nici un efect. În acest caz, metoda ErrorHandler va trebui să dea comanda RETRY.
  • Cancel: O întrebare frecventă este: "Cum împiedic execuţia restului de cod dintr-o metodă sau program care a generat o eroare?" Nu puteţi folosi CANCEL, pentru că opreşte întreaga aplicaţie. RETURN revine în aceeaşi metodă, deci nu ajută nici el. Soluţia este reîntoarcerea la programul apelant, care vă plasează din nou în cadrul comenzii READ EVENTS (ceea ce este normal, atât timp cât nu se execută codul metodei ErrorHandler). Dar programul apelant nu este obligatoriu să fie primul în ordinea execuţiei, deci comanda RETURN TO MASTER nu este de nici un folos. Ar trebui să vă întoarceţi la programul specificat de proprietatea cReturnToOnCancel. Când instanţiaţi SFErrorMgr, scrieţi în această proprietate numele programului apelant (puteţi scrie în ea "MASTER" dacă primul program conţine comanda READ EVENTS). Notă: această metodă funcţionează numai în cazul în care comanda READ EVENTS este într-un fişier .PRG. Dacă este într-o metodă, nu merge, pentru că nu puteţi scrie RETURN TO obiect.metoda.
  • Quit: Închide aplicaţia. Există două variante: dacă rulaţi versiunea de dezvoltare a lui VFP este greu să restartaţi VFP de fiecare dată când apare câte o eroare în program. În acest caz, opţiunea Quit ar trebui să execute un CLEAR EVENTS şi să redea controlul programului apelant, astfel încât aplicaţia să se poată închide şi să redea controlul ferestrei de comenzi. Dacă rulaţi versiunea runtime a lui VFP, această opţiune ar trebui să facă ordine şi să închidă aplicaţia complet, redând controlul sistemului de operare. În ambele cazuri, ar trebui să folosiţi o metodă particularizată, numită RevertTables, care să execute TABLEREVERT() pe toate cursoarele în toate sesiunile de date, astfel încât să nu obţineţi erori suplimentare (cum ar fi nenorocirea aia de "uncommitted changes").

Concluzie

Designul şi codul pentru schema de tratare a erorilor descrisă în această expunere folosesc modul de proiectare numit "Lanţul responsabilităţilor". Acest mod de proiectare permite transferul sarcinilor de la particular la general. Dar în această expunere nu am tratat anumite erori specifice, cum ar fi cele generate de încălcarea regulilor impuse la introducerea valorilor în câmpurile tabelelor, sau erorile generate de trigger-ele bazei de date. Transferul acestui tip de erori (care pot fi prevăzute) către obiectul global de tratare a erorilor nu are sens, deoarece obiectul nu are suficiente informaţii privitoare la mediul în care a apărut eroarea.

În expunerea următoare vom arunca o privire asupra erorilor tipice care apar într-un formular de introducere de date. De asemenea, vom examina o cale de evitare a erorilor aproape catastrofale care apar în codul generat de "Referential Integrity Builder", care, în anumite condiţii, permite actualizări care sunt interzise sau ştergeri parţiale, lăsând în urmă înregistrări "orfane".

Descărcaţi Erori3.zip.


    

 Google Ads Minimize

    

Copyright 2002-2013 Profox   Terms Of Use  Privacy Statement