Search  
Wednesday, September 20, 2017 ..:: Articole » Gestionarea erorilor specifice ::.. Register  Login
 Articole Minimize

Gestionarea erorilor specifice

Autor: Doug Hennig
Notă: Transmiterea anumitor erori specifice şi predictibile către rutina globală de tratare a erorilor nu este întotdeauna o idee fericită, deoarece rutina nu are informaţii complete privitoare la mediul în care a apărut eroarea şi nu poate decide ce acţiune să execute. În această expunere vom studia tehnicile de tratare a erorilor predictibile la nivelul corespunzător.

În expunerea precedentă am descris modul de proiectare şi am ilustrat codul unei scheme generale de tratare a erorilor. Această schemă foloseşte modul de proiectare numit "Lanţul responsabilităţilor" care permite transmiterea erorilor de la metoda Error a obiectelor în sens ierarhic până la rutina globală de tratare e erorilor, care oferă serviciile de tratare comune întregii aplicaţii. Dar nu am pomenit nimic de tratarea anumitor erori cum ar fi încălcarea regulilor de câmp sau de înregistrare sau erorile survenite în trigger-ele bazei de date. Deşi acest tip de erori sunt predictibile (şi conform concluziei din expunerea anterioară ar trebui transmise rutinei globale de tratare a erorilor), nu este o idee prea bună transmiterea lor până la nivelul maxim de tratare. Rutina globală de tratare a erorilor nu poate şti care sunt condiţiile de mediu sau ce trebuie să facă pentru a rezolva problema.

În această expunere voi studia schema de tratare a erorilor tipice unui formular de introducere a datelor. De asemenea, voi exemplifica un mod de evitare a problemelor aproape dezastruoase care pot surveni în codul generat de "Referential Integrity Builder" în anumite circumstanţe: permite actualizări care sunt interzise sau nu duce până la capăt ştergerea înregistrărilor relaţionate.

Dar mai întâi de toate trebuie să fac o rectificare asupra unei afirmaţii din expunerea precedentă. Am spus că puteţi da comanda RETURN TO către rutina care conţine comanda READ EVENTS pentru a împiedica execuţia restului de cod din metoda care a generat eroarea. În cursul testelor, am observat că RETURN TO generează o eroare dacă se încearcă revenirea la metoda unui obiect, ca atare, am presupus că Visual FoxPro nu permite acest lucru. Nu este adevărat acest lucru; întoarcerea funcţionează dacă eliminaţi numele obiectului, ca în exemplul următor:

RETURN TO obiect.metoda && generează o eroare
RETURN TO metoda && funcţionează corect

Gestionarea erorilor specifice

Schema de tratare a erorilor din expunerea precedentă este destul de generală - orice eroare are ca rezultat apariţia unei casete de dialog care conţine butoanele "Cancel, Continue, Retry, Quit". Aceasta nu este potrivită pentru erorile predictibile; ar trebui folosită numai pentru erorile nepredictibile. Erorile predictibile trebuie tratate în metoda Error a obiectului în care a apărut eroarea.

Cu titlu de exemplu în acest sens voi discuta clasa SFMaintForm (situată în SFFORMS.VCX) ca subclasă a lui SFForm (clasa de bază formular definită în SFCTRLS.VCX). Această clasă a fost proiectată în mod special pentru a servi drept formular de introducere de date. Este un exemplu excelent în privinţa modului în care un obiect cu suficiente informaţii despre mediul de date poate trata erorile predictibile.

Aşa cum vă aşteptaţi, o rutină care tratează erori specifice este compusă dintr-un set de comenzi CASE, care tratează erorile specifice şi o comandă OTHERWISE care transmite toate celelalte erori către SFErrorMgr. În cazul în formularului SFMaintForm, vom trata următoarele erori:

  • Erorile din DataEnvironment, trigger-e şi reguli de validare a câmpurilor - Vor fi descrise puţin mai târziu în această expunere.
  • Reguli de validare a tabelei, "primary/candidate index violation" sau blocarea înregistrării de către alt utilizator - În afara proiectării corespunzătoare a unui sistem, pentru a se micşora şansa apariţiei acestor erori, cum ar fi chei primare atribuite de sistem şi folosirea blocării optimiste în locul celei pesimiste), nu sunt prea multe lucruri de făcut în privinţa acestor erori; de cele mai multe ori este sarcina utilizatorului să le rezolve. Deci voi apela o metodă care afişează un mesaj corespunzător.
  • "The record was modified by another user when this user tries to delete it" - Voi afişa un mesaj şi apoi reîmprospătez fereastra pentru a afişa modificările celuilalt utilizator.
  • "The record was modified by another user when this user tries to edit it" - Voi folosi o rezolvare a conflictelor pentru a aplana conflictul. Codul poate fi găsit în metoda ErrRecChangedByAnotherUser.

Bineînţeles, acestea sunt doar o parte din erorile posibile, dar sunt un eşantion reprezentativ.

În expunerea precedentă am studiat modul în care metoda Error a clasei SFForm apelează metoda HadleError, care transmite la rândul ei eroarea metodei ErrorHandler a clasei SFErrorMgr. Acum vom modifica puţin comportamentul metodei HandleError din clasa SFMaintForm, astfel încât să trateze erorile enumerate mai sus. Iată codul acestei metode (constantele folosite, cum ar fi cnAERR_NUMBER sunt definite în fişierul LIBRARY.H):

Listingul 1. Metoda HandleError

LOCAL lnError, ;
	llDisplay, ;
	lcReturn, ;
	loObject
WITH THIS
	lnError = .aErrorInfo[.nLastError, cnAERR_NUMBER]
	DO CASE

* Tratez situaţia când nu există o tabelă sau bază de date în DataEnvironent.

	CASE (lnError = cnERR_TABLE_MOVED OR ;
			lnError = cnERR_FILE_NOT_FOUND) AND ;
			'DATAENVIRONMENT' $ UPPER(.aErrorInfo[.nLastError, cnAERR_METHOD])
		llDisplay = oError.lDisplayErrors
		oError.lDisplayErrors = .F.
		oError.ErrorHandler(lnError, ;
			.aErrorInfo[.nLastError, cnAERR_METHOD], ;
			.aErrorInfo[.nLastError, cnAERR_LINE])
		oError.lDisplayErrors = llDisplay
		MESSAGEBOX(ccERR_TABLE_MOVED, MB_ICONSTOP, _SCREEN.CAPTION)
		lcReturn = ccMSG_CLOSEFORM

* Tratez situaţia când accesul este interzis pe o tabelă în DataEnvironment.

	CASE lnError = cnERR_ACCESS_DENIED AND ;
			'DATAENVIRONMENT' $ UPPER(.aErrorInfo[.nLastError, cnAERR_METHOD])
		llDisplay = oError.lDisplayErrors
		oError.lDisplayErrors = .F.
		oError.ErrorHandler(lnError, ;
			.aErrorInfo[.nLastError, cnAERR_METHOD], ;
			.aErrorInfo[.nLastError, cnAERR_LINE])
		oError.lDisplayErrors = llDisplay
		MESSAGEBOX(ccERR_ACCESS_DENIED, MB_ICONSTOP, _SCREEN.CAPTION)
		lcReturn = ccMSG_CLOSEFORM

* Tratez situaţia "table in use" în DataEnvironment.

	CASE lnError = cnERR_TABLE_IN_USE AND ;
			'DATAENVIRONMENT' $ UPPER(.aErrorInfo[.nLastError, cnAERR_METHOD])
		llDisplay = oError.lDisplayErrors
		oError.lDisplayErrors = .F.
		oError.ErrorHandler(lnError, ;
			.aErrorInfo[.nLastError, cnAERR_METHOD], ;
			.aErrorInfo[.nLastError, cnAERR_LINE])
		oError.lDisplayErrors = llDisplay
		MESSAGEBOX(ccERR_TABLE_IN_USE, MB_ICONSTOP, _SCREEN.CAPTION)
		lcReturn = ccMSG_CLOSEFORM

* Tratez situaţia "DataEnvironment already unloaded", eliminând mesajul de eroare.

	CASE lnError = cnERR_DE_UNLOADED
		lcReturn = ccMSG_CONTINUE

* Tratez eroarea în trigger

	CASE lnError = cnERR_TRIGGER_FAILED
		lcReturn = .ErrTriggerFailed()

* Tratez eroarea la validarea unui câmp.

	CASE lnError = cnERR_FIELD_RULE_FAILED
		loObject = .ErrFieldRuleFailed()
		IF NOT ISNULL(loObject)
			.ActivateObjectPage(loObject)
			loObject.SETFOCUS()
		ENDIF NOT ISNULL(loObject)
		lcReturn = ccMSG_CONTINUE

* Tratez eroarea la validarea unei tabele.

	CASE lnError = cnERR_TABLE_RULE_FAILED
		lcReturn = .ErrTableRuleFailed()

* Tratez eroarea "primary/candidate index violation".

	CASE lnError = cnERR_DUPLKEY
		lcReturn = .ErrDuplicatekey()

* Aceasta este situaţia când altcineva a blocat înregistrarea.

	CASE lnError = cnERR_RECINUSE
		lcReturn = .ErrRecordInUse()

* Situaţia când o înregistrare este modificată de alt utilizator
* în cursul ştergerii ei.

	CASE lnError = cnERR_RECMODIFIED AND lcMethod = 'DeleteRecord'
		MESSAGEBOX(ccERR_REC_MODIFIED, MB_ICONSTOP, _SCREEN.CAPTION)
		.REFRESH()
		lcReturn = ccMSG_CONTINUE

* Situaţia când înregistrarea a fost modificată de alt utilizator
* în cursul ştergerii ei.

	CASE lnError = cnERR_RECMODIFIED
		lcReturn = .ErrRecChangedByAnother()

* În orice altă situaţie, se foloseşte rutina implicită de tratare
* a erorilor.

	OTHERWISE
		lcReturn = DODEFAULT()
	ENDCASE
ENDWITH
RETURN lcReturn

DataEnvironment

Cu toate că DataEnvironment are o metodă Error proprie, clasele nu au un DataEnvironment propriu, şi în acest caz va trebui să copiaţi codul în metoda Error a fiecărui formular pe care îl creaţi. În locul acestei manevre, ar putea fi mai indicat să tratez erorile lui DataEnvironment în metoda HandleError a clasei SFMaintForm. Acestea sunt "Table or database not found", "Table access denied" sau "Table is in use" (şi sunt sigur că puteţi găsi şi altele de acelaşi gen). De obicei nu sunt prea multe lucruri de făcut în privinţa lor; cea mai bună rezolvare este afişarea unui mesaj de eroare şi închiderea formularului. Dar fiindcă sunt necesare servicii de tratare a erorilor, vom apela metoda oError.ErrorHandler după ce setăm valoarea proprietăţii lDisplayError la .F., pentru ca eroarea să fie scrisă în log, dar să nu se afişeze nimic. Puteţi afişa un mesaj particularizat înainte de a returna şirul "CloseForm" către metoda Error a obiectului care a generat eroare, determinând închiderea formularului.

Eroarea "DataEnvironment already unloaded" este puţin mai specială. Această eroare apare când un formular este închis fără ca DataEnvironmentu-ul său să fie încărcat din cauza uneia din erorile descrise mai devreme. În acest caz nu trebuie să faceţi nimic în acest sens. Trebuie doar împiedicată apariţia mesajului de eroare.

Trigger-e

Dacă un trigger generează o eroare şi obiectul care a determinat apelarea trigger-ului are cod în metoda Error proprie, atunci este apelată această metodă Error, nu rutina ON ERROR stabilită de către trigger (procedura RIError). Ei bine, asta este problema: RIError iniţializează o variabilă publică numită pnError şi îi dă o valoare diferită de zero, care este folosită de celelalte rutine generate de "Referential Integrity Builder" pentru a determina dacă a apărut vreo eroare. Imaginaţi-vă următoarea situaţie: utilizatorul execută ceva într-un formular, cum ar fi ştergerea unei înregistrări, şi trigger-ul generează o eroare. Aceasta are ca efect apelarea rutinei de tratare a erorilor, dar datorită faptului că trigger-ul a fost apelat de un obiect care are cod în metoda Error proprie, va fi apelată această metodă, nu cea din procedura RIError stocată în baza de date. După executarea metodei Error, controlul este returnat trigger-ului, dar pentru că variabila pnError nu a fost modificată (acţiune pe care ar fi trebuit să o facă procedura RIError), trigger-ul nu va şti că a apărut o eroare şi îşi va continua execuţia. Ca rezultat, trigger-ul va eşua parţial, făcând numai o parte din acţiunile pe care trebuia să le facă.

Iată un exemplu concret: tabela CUSTOMER are o regulă de ştergere în cascadă pe tabela ORDERS, iar tabela ORDERS are o regulă de interzicere a ştergerilor în tabela ORDITEMS. Înregistrarea curentă din tabela CUSTOMER are două înregistrări relaţionate în tabela ORDERS (baza de date din fişierul ataşat acestei expuneri are exact această configuraţie). Când ştergeţi înregistrarea din tabela CUSTOMER, ce credeţi că se întâmplă? Veţi obţine o eroare generată de trigger, dar veţi descoperi că înregistrarea din tabela CUSTOMER şi prima înregistrare din tabela ORDERS au fost şterse. Mai există numai a doua înregistrare din tabela ORDERS, şi, evident, este orfană. (dacă vă închipuiţi că este un exemplu căutat mult, vă înşelaţi: mi s-a întâmplat mie, cu o altă bază de date. Aşa am descoperit chestia asta).

Soluţia este ca rutina de tratare a erorii să seteze variabila pnError la o valoare diferită de zero; dacă examinaţi codul metodei ErrTriggerFailed al clasei SFMaintForm veţi vedea că face numai acest lucru. Dar această manevră nu rezolvă complet problema: în procedurile RIDelete şi RIUpdate (cele care şterg sau actualizează o înregistrare) generate de "Referential Integrity Builder" este un bug trebuie eliminat. Aceste rutine setează valoarea variabilei llRetValue (valoarea returnată) la .F. dacă pnError este diferită de zero, ceea ce informează celelalte rutine că trigger-ul a eşuat. Dat fiind faptul că procedura RIOpen (care este apelată pentru a deschide tabelele copil pentru verificarea existenţei înregistrărilor relaţionate) deschide tabelele fără buffering, următorul nivel al trigger-ului (care este apelat pentru verificarea tabelelor "nepot") nu este apelat decât după comanda UNLOCK, care este situată după setarea valorii llRetValue.

Astfel, o eroare în încercarea de a şterge sau a actualiza o tabelă "nepot" (care determină apariţia mesajului de eroare a trigger-ului) nu va seta llRetValue la .F., şi trigger-ul nu va eşua, deşi ar trebui. Soluţia este mutarea instrucţiunii care asignează valoarea variabilei llRetValue după comanda UNLOCK. Vedeţi codul listat mai jos. Datorită faptului că RIOpen şi RIDelete sunt rutine generice, aţi putea copia codul acestor rutine în procedurile stocate în baza de date, după linia cu "RI Footer line", şi să faceţi modificările acolo. În acest fel nu veţi fi nevoit să faceţi modificările de fiecare dată când generaţi codul RI.

Listingul 2. Eliminarea bug-ului din codul generat de RI Builder

PROCEDURE RIDELETE
LOCAL llRetVal
llRetVal=.t.
IF (ISRLOCKED() AND !DELETED()) OR !RLOCK()
	llRetVal=.F.
ELSE
	IF !deleted()
		DELETE
		IF CURSORGETPROP('BUFFERING') > 1
			=TABLEUPDATE()
		ENDIF
		*** Tăiaţi linia următoare ...
		*      llRetVal=pnerror=0
	ENDIF not already deleted
ENDIF
UNLOCK RECORD (RECNO())
*** ... şi copiaţi-o aici
llRetVal=pnerror=0
RETURN llRetVal

Încălcarea regulilor de validare la nivel de câmp

Un bug în funcţiile SYS(2018) şi AERROR() în Visual FoxPro 5.0 (inclusiv 5.0a) împiedică folosirea numelui câmpului când este încălcată o regulă de validare la nivel de câmp. În schimb, veţi obţine ori mesajul de eroare pe care l-aţi introdus în regula de validare a câmpului (dacă aţi scris vreunul), ori un mesaj generic de genul "Field <field> validation rule is violated".

Dar n-ar fi frumos dacă aţi afişa un mesaj de genul: "Introduceţi o valoare corectă pentru câmpul <field caption>" dacă nu este nici un mesaj stabilit? Problema este că dacă nu ştiţi câmpul a cărui regulă de validare a fost încălcată, cum ştiţi ce "caption" să folosiţi? N-ar fi frumos ca după mesajul de eroare să puneţi focus-ul în câmpul a cărui regulă de validare a fost încălcată, înlesnindu-i astfel corectarea? N-ar fi frumos să îi schimbaţi şi culoarea de fundal, ca să sară în ochi câmpul cu pricina?

Manevra de evitare a acestui bug ("facilitate nedocumentată" - conform denumirii dată de suportul tehnic <g>), trebuie să procedaţi astfel: Dacă textul casetei de dialog este "Field <field> validation rule is violated", daţi afară numele câmpului. Dacă textul casetei este un alt şir de caractere, va trebui să aflaţi care este câmpul tabelei pentru care s-a introdus respectivul şir. Metoda SFMAintForm.ErrFieldRuleFailed face acest lucru în locul dvs. Iată codul ei:

Listingul 3. Metoda ErrFieldRuleFailed

LOCAL lcAlias, ;
	lcTable, ;
	lcMessage, ;
	lcField, ;
	lnSpace1, ;
	lnSpace2, ;
	lnI, ;
	lcText, ;
	loObject
WITH THIS

* Încerc să aflu despre ce câmp este vorba. Va trebui să evit un bug
* din VFP: al treilea element din masiv ar trebui să fie câmpul în
* chestiune, dar nu este. În acest caz, dacă textul "validation rule is violated"
* apare în mesaj, atunci nu am un mesaj definit, aşa că fac un parsing
* pe mesaj şi dau afară numele câmpului. În caz contrar, va trebui să
* aflu ce câmp are mesajul afişat în caseta de dialog.

	lcAlias   = ALIAS(.aErrorInfo[.nLastError, cnAERR_WORKAREA])
	lcTable   = CURSORGETPROP('SourceName', lcAlias)
	lcMessage = .aErrorInfo[.nLastError, cnAERR_MESSAGE]
	lcField   = ''
	IF ccVFP_VALIDATION_MSG $ lcMessage
		lnSpace1  = AT(' ', lcMessage) + 1
		lnSpace2  = AT(' ', lcMessage, 2)
		lcField   = SUBSTR(lcMessage, lnSpace1, lnSpace2 - lnSpace1)
		lcMessage = ccERR_FIELD_RULE_FAILED
	ELSE
		FOR lnI = 1 TO FCOUNT(lcAlias)
			lcText = DBGETPROP(lcTable + '.' + FIELD(lnI, lcAlias), 'Field', ;
				'RuleText')
			IF NOT EMPTY(lcText) AND EVALUATE(lcText) = lcMessage
				lcField = FIELD(lnI)
				EXIT
			ENDIF NOT EMPTY(lcText) ...
		NEXT lnI
	ENDIF ccVFP_VALIDATION_MSG $ lcMessage
	lcField = LOWER(lcAlias + '.' + lcField)

* Afişez mesajul de eroare şi găsesc obiectul care are câmpul meu
* în ControlSource.

	MESSAGEBOX(lcMessage, MB_ICONSTOP, _SCREEN.CAPTION)
	loObject = .FindControlSourceObject(lcField)
ENDWITH
RETURN loObject

După ce metoda "se prinde" care-i treaba, apelează metoda FindControlSourceObject pentru a afla care este obiectul din formular care este "bound" la câmpul respectiv. Dacă găseşte acest obiect (nu este obligatoriu să existe unul), întoarce o referinţă către el, astfel încât metoda HandleError să poată plasa focus-ul pe el.

Exemple

Pentru a vedea nişte exemple în privinţa modului în care pot fi tratate erorile specifice şi cele nepredictibile folosind această schemă de tratare a erorilor, rulaţi aplicaţia MYAPP.APP (este inclusă în fişierul ataşat expunerii). Din meniul File alegeţi opţiunea Error Form 1 şi faceţi un click pe butonul "This will cause an error". Metoda Click a acestui buton are două erori. Dacă faceţi click pe butonul "Continue" din caseta cu mesajul de eroare, va surveni cea de-a doua eroare şi caseta de dialog va apare din nou. Dacă faceţi click pe "Cancel", veţi împiedica executarea codului cu cea de-a doua eroare iar aplicaţia va funcţiona în continuare şi formularul va rămâne deschis. Click-ul pe butonul "Quit" va închide aplicaţia în mod controlat, refăcând bara de meniuri, închizând toate ferestrele şi restaurând mediul la starea în care era înainte de lansarea aplicaţiei. Click-ul pe butonul "Retry" va determina apariţia aceluiaşi mesaj de eroare, deoarece problema nu a fost rezolvată.

Pentru a vedea cum pot fi tratate erorile specifice, alegeţi formularul CUSTOMER din meniul File. Cu formularul deschis, alegeţi "New" din meniul File, scrieţi "ALFKI" pentru Customer ID, apoi click pe comanda Save din meniul File. Veţi obţine o eroare, datorită faptului că există deja înregistrarea cu ID-ul introdus; câmpul CUSTOMER_ID este cheie primară în tabelă. Ca atare, survine eroarea "Primary key violation" şi ea este tratată în modul ilustrat. Pentru a vedea rezultatul încălcării unei reguli de validare la nivel de câmp, introduceţi "TEST" ca nume de companie; acest câmp are o regulă de validare care interzice această valoare. Când încercaţi să părăsiţi câmpul Company veţi vedea un mesaj de eroare. Dar focus-ul se va muta la câmpul următor. În câmpul City introduceţi "Regina", apoi alegeţi Save din meniul File. Mai întâi veţi obţine un mesaj de eroare, generat de regula de validare la nivel de câmp, apoi focus-ul va fi mutat în caseta de text Company. Ştergeţi valoarea introdusă în câmp şi scrieţi altceva, apoi selectaţi din nou comanda Save. De data aceasta veţi obţine un mesaj de eroare generat de regula de validare a tabelei, care interzice valoarea "Regina" în câmpul City. Alegeţi comanda Revert pentru a elimina înregistrarea introdusă.

Pentru a vedea cum sunt tratate erorile generate în DataEnvironment, redenumiţi fişierul CUSTOMER.DBF în CUST.DBF în Windows Explorer, apoi rulaţi din nou MYAPP.APP şi deschideţi formularul CUSTOMER. După ce răspundeţi la mesajul de eroare, formularul se închide. Este momentul să redenumiţi din nou tabela CUST.DBF.

Pentru a vedea bug-ul din codul generat de RI Builder, închideţi aplicaţia, deschideţi baza de date TESTDATA, deschideţi vederea CUST_ORDERS şi daţi un BROWSE pe ea. Vederea afişează informaţii din CUSTOMERS, ORDERS şi ORDITEMS. Observaţi că pentru id-ul ALFKI sunt mai multe înregistrări subordonate, dar prima (ORDER_ID=10062) nu are înregistrări în tabela ORDITEMS (LINE_NO este .NULL.). Rulaţi MYAPP.APP, deschideţi formularul CUSTOMER, alegeţi Delete din meniul File, apoi răspundeţi Yes pentru a confirma ştergerea. Va apare un mesaj de eroare, generat de trigger, din cauza restricţionării ştergerii dintre ORDERS şi ORDITEMS. Dar, după ce răspundeţi de câteva ori la acest mesaj, înregistrarea ALFKI se şterge. Închideţi aplicaţia, deschideţi tabela CUSTOMERS şi vă veţi convinge că este aşa. Acum scrieţi comanda USE ORDERS ORDER CUST_ID şi veţi vedea că sunt o mulţime de înregistrări ale clientului ALFKI. Bineînţeles, toate sunt orfane.

Pentru a elimina problema, procedaţi în felul următor:

  • CLOSE ALL
  • Dezarhivaţi fişierul DATA.ZIP (datele s-au stricat şi trebuie puse la loc)
  • OPEN DATABASE TESTDATA EXCLUSIVE
  • MODIFY PROCEDURES
  • Mutaţi linia care atribuie valoarea variabilei llRetValue după linia cu UNLOCK, în modul descris în Listingul 2. Salvaţi şi închideţi fereastra.
  • MODIFY PROJECT MYAPP
  • Deschideţi clasa SFMaintForm din biblioteca SFForms, deschideţi fereastra de cod a metodei ErrTriggerFailed şi scoateţi comentariul liniei care atribuie valoarea variabilei pnError. Salvaţi şi închideţi.
  • Reconstruiţi aplicaţia, cu ajutorul butonului Build.
  • Deschideţi formularul Customer, alegeţi comanda Delete din meniul File şi confirmaţi ştergerea.

De această dată veţi obţine un singur mesaj de eroare şi înregistrarea ALFKI rămâne neatinsă.

Alte aspecte care trebuie verificate:

  • Pentru a vedea cum se stochează erorile, deschideţi tabela ERRORLOG şi daţi un BROWSE pe ea. Aruncaţi o privire şi în câmpul memo numit MEMVARS - el conţine valoarea fiecărei variabile a fiecărei rutine din stiva de apelare la momentul apariţiei erorii.
  • Modificaţi poziţia DEVELOPER în fişierul APPLIC.INI, scriind Yes acolo. Această valoare este folosită de STARTUP.PRG pentru a seta proprietatea lDeveloper a obiectul oError. Salvaţi şi închideţi, apoi rulaţi MYAPP.APP. Alegeţi comanda Error Form 1 din meniul File şi veţi vedea că mesajul de eroare are şi opţiunea Debug. Este folositoare pentru a depana o eroare în timpul funcţionării aplicaţiei. Fereastra Trace afişează codul metodei ErrorHandler a clasei SFErrorMgr, datorită faptului că în această rutină se execută comanda SET STEP ON. Pentru a vă întoarce la codul care a generat eroarea, este suficient să faceţi click pe butonul "Step out" din bara de butoane a ferestrei Debugger până când ajungeţi unde doriţi.
  • Butonul "This should cause an error but doesn't" (Acesta ar trebui să genereze o eroare dar nu o face) din formularul Error Form 1 face exact ce spune: click-ul pe el nu are nici un rezultat. Dacă priviţi metoda Click a sa, veţi vedea un lucru oarecum surprinzător, pentru că sunt două erori în cod. Dar aruncaţi o privire şi pe metoda Error şi veţi observa că ea apelează - spre deosebire de alte obiecte - metoda Error a părintelui său. Aceasta este o problemă: butonul este situat pe un PageFrame, şi dat fiind faptul că ea este o clasă de bază VFP, nu are cod în metoda Error, ceea ce înseamnă că nu se întâmplă nimic dacă apare o eroare. La fel ca şi ON ERROR, aceasta este o metodă excelentă de a face aplicaţii fără mesaje de eroare. (Nu fără erori, ci fără mesaje de eroare, hi, hi, hi). Din acest motiv, toate clasele de bază din biblioteca SFCRTLS.VCX caută în sens ierarhic primul părinte care are cod în metoda Error.

Diverse informaţii despre erori

Iată nişte lucruri pe care ar trebui să le ştiţi despre tratarea erorilor în Visual FoxPro:

  • În Visual FoxPro 5 şi 6 se apelează metoda Error a controlului care este "bound" la câmp dacă este încălcată regula de validare a câmpului. Acest lucru înseamnă că puteţi controla mesajul care este afişat şi modul în care se face afişarea sa. În Visual FoxPro 3 acest tip de eroare nu poate fi interceptat. În acest caz, VFP afişează un mesaj turbat sau proprietatea RuleText a câmpului, dacă aţi introdus vreuna într-o casetă MESSAGEBOX() asupra căreia nu aveţi control.
  • Chiar dacă variabilele definite LOCAL nu sunt vizibile decât în procedura sau metoda în care au fost definite, comanda LIST MEMORY poate vedea toate variabilele din toate rutinele stivei de execuţie. În acest fel se poate face o "poză" a stării sistemului în momentul apariţiei unei erori. Metoda LogError a clasei SFErrorMgr demonstrează cum se poate face acest lucru.
  • Pentru păstrarea performanţelor, este posibili să nu doriţi navigarea în sens ierarhic din pas în pas, la apariţia unei erori. Aţi putea face un salt direct la metoda Error a formularului sau chiar la metoda SFErrorMgr.ErrorHandler, dacă doriţi. După părerea mea, în cazul apariţiei unei erori, performanţa este ultimul lucru care trebuie să mă îngrijoreze, aşa că prefer să păstrez clar design-ul claselor, folosind mecanismul descris în această expunere.
  • Comanda ERROR este interesantă: ea determină apelarea rutinei de tratare a erorilor. Este comod să testaţi erorile "uşoare" din VFP. De exemplu, dacă FILE() întoarce .F., indicând că un fişier lipseşte, aţi putea să folosiţi comanda ERROR pentru a testa modul în care se fac logarea şi afişarea erorii. Eu personal fac foarte lucrul acesta foarte rar; de ce să tratez erorile în mod diferit, când oricum am pus la punct o rutină de tratare a lor? De asemenea, funcţie de locul unde folosiţi comanda ERROR, puteţi să obţineţi rezultate imprecise privitoare la numărul liniei şi numele metodei, pentru că ea va întoarce aceste valori relative la locul unde a fost apelată, nu la locul unde a apărut eroarea.
  • Comanda LIST STATUS nu este foarte utilă, pentru că vede numai sesiunea de date curentă (cum ar fi tabelele deschise şi valorile comenzilor SET). Dacă rutina de tratare a erorii se găseşte în sesiunea de date implicită, ea nu va vedea ce se întâmplă în sesiunea de date privată a formularului care a generat eroarea. Dacă este important pentru dvs, aţi putea să comutaţi la sesiunea de date a formularului respectiv înainte de folosirea acestei comenzi, sau aţi putea instanţia din nou SFErrorMgr în proprietatea oError a formularului, pentru a-l obţine în aceeaşi sesiune de date ca şi formularul.
  • Dacă apare o eroare în codul apelat din metoda unui obiect, va fi apelată metoda Error a formularului, nu rutina ON ERROR. Acest lucru înseamnă că va trebui să folosiţi două metode de tratare a erorilor din codul dvs (este vorba de fişierele .PRG şi .MPR), depinzând de modul în care a fost apelat programul generator de erori. Acesta este un alt motiv pentru a renunţa la bibliotecile stocate în fişiere .PRG şi trecerea la biblioteci obiect.
  • Comanda ON ERROR nu interceptează erorile din rapoarte şi cele din clauzele SKIP FOR din meniuri. Aceste erori nu pot fi interceptate, aşa că asiguraţi-vă că rapoartele şi meniurile nu au erori. Pentru a verifica acest lucru, editaţi fişierul APPLIC.INI şi modificaţi valoarea "NoSuchVariable", scriin No acolo. Salvaţi şi rulaţi MYAPP. Click pe meniu şi veţi vedea că apare un mesaj de eroare.

Concluzie

În ultimele două expuneri am prezentat o schemă de tratare a erorilor care ia ce este bun din ambele situaţii: tratarea locală pentru majoritatea erorilor şi servicii globale pentru rest. Această schemă a fost folosită cu succes în multe aplicaţii. Sper să vă fie de folos şi dvs, în aplicaţiile proprii.

Descărcaţi Erori4.zip.


    

 Google Ads Minimize

    

Copyright 2002-2013 Profox   Terms Of Use  Privacy Statement