Wednesday, April 24, 2019 04:48

Breakpoint-uri și variabile locale

Breakpoint-urile sunt, fără îndoială, cea mai utilizată opțiune în procesul de depanare. După cum sugerează și numele în limba engleză a acestora, ele sunt literalmente un punct în care execuția programelor voatre se va „întrerupe”, sau mai precis, va fi pusă pe pauză. Ori de câte ori execuția unui program se află în această stare întreruptă, care nu este nici timpul de proiectare (design time), dar nu este nici timpul de execuție (runtime) (ați putea spune că este o stare intermediară între aceste două stări), spunem că programul este în timp de depanare sau în modul de depanare (debug time).

Deci, de ce am vrea să întrerupem execuția programelor noastre? De ce trebuie să intrăm în acest mod de depanare? Ei bine, mai întâi de toate, prin setarea unui breakpoint la o anumită linie de cod, putem verifica dacă linia respectivă este executată sau nu, cum ar fi de exemplu într-o instrucțiune If-Else, pentru a vedea care din condiții este îndeplinită. Să facem asta! Să luăm în considerare următorul cod:

Cea mai ușoară modalitate de a crea un breakpoint este să efectuați clic pe bara verticală din partea stângă a ecranului principal din Visual Studio, în partea stângă a liniei de cod pe care doriți să setați breakpoint-ul. Când efectuați clic pe ea, ar trebui să vedeți un cerc roșu apărând, care arată cam așa:

Setarea unui breakpoint in Visual Studio

Un alt mod de a face același lucru este plasarea cursorului pe linia unde doriți să adăugați breakpoint-ul și să mergeți la meniul Debug, alegând opțiunea Toggle Breakpoint, sau să apăsați pur și simplu tasta F9 la tastatură. Pentru a dezactiva un breakpoint, puteți pur și simplu să faceți clic pe cercul roșu cu mouse-ul, sau să apăsați din nou F9 în timp ce cursorul se află pe linia de cod specificată.

Executați programul, așa cum ați făcut de nenumărate ori. Veți observa că nu se întâmplă nimic nou. Rezultatul arată astfel:

runtime

Motivul pentru care nu s-a întâmplat nimic este acela că, evident, dacă am inițializat variabila string cu valoarea „Marți”, când verificăm dacă este „Luni”, această condiție va fi egală cu False, iar codul acelei condiții nu va fi rulat. Faceți din nou clic pe breakpoint pentru a îl șterge și, în schimb, plasați un nou breakpoint aici:

Rulați din nou programul.

Hei! S-a întâmplat ceva ciudat! Fereastra consolei a fost creată, dar a fost imediat minimizată, iar Visual Studio ne-a arătat din nou editorul de cod. În plus, butonul nostru Start (cel care arată ca un „Play”) are acum textul „Continue”, noul breakpoint acum pare să aibă o săgeată galbenă pe el, iar linia de cod este evidențiată și ea cu un fundal galben:

Atingerea unui breakpoint in Visual Studio

Dacă mergem din nou la fereastra consolei, vedem că este goală. Nimic nu a fost printat și că nu răspunde la interacțiunile noastre, indiferent câte taste apăsăm. Asta înseamnă că partea Console.Read(); nu-și face treaba! Și de ce nu a fost afișat „Astăzi nu este prima zi a săptămânii!”?

Faptul că cercul roșu are o săgeată galbenă pe el indică faptul că acesta este punctul în care execuția a fost întreruptă sau, după cum spun programatorii, „unde a fost lovit breakpoint-ul”. În cuvinte simple, din moment ce ziuaSaptamanii a fost inițializată ca „Marți”, atunci când am verificat dacă valoarea sa este „Luni”, această condiție a returnat Fals și execuția a fost transferată la partea Else. Și din moment ce am plasat un breakpoint în acea parte a codului, când execuția a fost transferată, breakpoint-ul a fost lovit și a întrerupt executarea programului. Suntem acum în modul de depanare (debugging mode). Deoarece fraza nu a fost încă tipărită pe ecran, aceasta înseamnă și că atunci când se atinge un breakpoint, săgeata galbenă indică locul unde execuția a fost întreruptă, cu alte cuvinte, care este următorul cod care urmează să fie executat când execuția programului va fi reluată. Acesta este motivul pentru care fraza nu a fost tipărită la consolă, acea instrucțiune va fi prima executată atunci când execuția va fi reluată. De asemenea, motivul pentru care Console.Read(); nu a fost executată este similar: acea parte a codului nu a fost încă atinsă, urmează să fie executată.

Faptul că programul nostru se află într-o stare întreruptă explică și de ce fereastra consolei nu răspunde la interacțiunile noastre.

Deci, ce putem face în această stare de depanare? OK, am văzut că putem testa dacă o anumită linie de cod este executată sau nu. Dar, atât?

Nu. Deplasați cursorul mouse-ului peste variabila ziuaSaptamanii:

Visual Studio va afișa un pop-up mic cu numele variabilei noastre și valoarea sa actuală. Acesta este un lucru bun! Putem vedea care sunt valorile variabilelor noastre, în timp real! Plasați cursorul mouse-ului peste operatorul de verificare a egalității, ==. Veți vedea acest lucru:

Verificarea conditiilor in modul de depanare

Un alt lucru minunat! Visual Studio ne permite să aflăm rezultatul verificărilor noastre condiționate! Aceasta indică faptul că expresia ziuaSaptamanii == "Luni" a fost Falsă atunci când execuția a ajuns la ea, ceea ce explică de ce a fost executată partea Else.

Deci, numai pentru aceste lucruri și breakpoint-urile ar fi minunate! Dar sunt încă multe alte lucruri despre care nu am învățat încă! Setați un alt breakpoint pe linia Console.Read();. Apoi, faceți clic pe butonul Continue (cel care a fost inițial Start). Veți vedea acest lucru:

Execuția a fost reluată acum, dar a fost imediat oprită la următoarea linie, pentru că a întâmpinat al doilea breakpoint. Acest lucru demonstrează încă o dată, dacă mai era încă necesar, că executarea programelor este liniară, de sus în jos. Oricum, acesta nu este motivul pentru care v-am spus să urmați acești pași. Duceți-vă la fereastra de consolă din nou și veți vedea că acum aceasta arată astfel:

Dar încercarea de a apăsa orice tastă ar fi în continuare ignorată. Linia care a fost tipărită a apărut deoarece, când ați efectuat clic pe Continue, Visual Studio a executat următoarele coduri care au urmat după punctul în care execuția a fost pusă pe pauză, până când noul breakpoint a fost lovit. Cu alte cuvinte, a executat linia Console.WriteLine("Astăzi este nu este prima zi a săptămânii!");.

Apăsați Continue din nou și veți vedea fereastra de consolă va fi restaurată și niciunul din breakpoint-urile noastre nu va mai avea săgeți galbene pe ele, în timp ce butonul Continue a redevenit dezactivat. Toate acestea indică faptul ca suntem înapoi în modul runtime. Dacă doriți, puteți verifica acest lucru apăsând orice tastă, urmată de Enter. Linia Console.Read(); ar trebui să-și facă acum treaba și fereastra se va închide. Opriți execuția.

Ștergeți breakpoint-urile accesând meniul Debug și selectând Delete All Breakpoints. Puteți întotdeauna utiliza această opțiune pentru a șterge numeroasele breakpoint-uri, însă trebuie să știți că această opțiune va șterge toate breakpoint-urile din întregul proiect. Așadar, fiți atenți dacă aveți unele setate în alte fișiere.

Acum, setați un nou breakpoint la prima linie din blocul metodei noastre Main(). Porniți din nou programul. Execuția va fi oprită din nou și vom intra din nou în modul de depanare:

Plasați din nou cursorul mouse-ului peste variabila ziuaSaptamanii. Visual Studio ne va indica că valoarea sa este nulă:

Știm deja acum că acest lucru se întâmplă deoarece linia în care atribuim o valoare variabilei respective nu este încă executată, deoarece breakpoint-ul a întrerupt execuția înainte ca acesta să aibă șansa de a executa respectiva linie. Acum știm că acesta este punctul unde execuția a ajuns, și aceasta este următoarea linie care va fi executată când execuția va fi reluată. Putem vedea că variabila noastră este nulă și în fila Locals din partea de jos, așa cum am explicat în lecția anterioară:

Acum, pentru a avansa execuția cu încă o linie, știm că am putea stabili un nou breakpoint pe linia următoare. Dar dacă vrem să întrerupem execuția linie cu linie pentru 10 linii, acest lucru ar deveni în curând enervant, să setăm și să ștergem breakpoint-uri tot timpul. În schimb, mergeți acum la meniul Debug și alegeți Step Into, sau apăsați tasta F11 pe tastatură. Același lucru poate fi obținut făcând clic pe acest buton din bara de instrumente din partea de sus a Visual Studio:

Ceea ce veți observa este faptul că săgeata galbenă de deasupra breakpoint-ului va sări automat la următoarea linie:

Visual Studio Step Into

Dacă vom plasa din nou cursorul mouse-ului deasupra variabilei ziuaSaptamanii, vom vedea acum că are atribuită valoarea „Marți”. Aceasta înseamnă că execuția a fost reluată, a fost executată linia în care variabila a primit valoarea și apoi a fost întreruptă din nou, avansând o singură linie. Nu este necesar să setați manual un breakpoint pe linia următoare, când putem executa următoarele linii una câte una, folosind acest buton.

Apăsați din nou butonul Step Into:

Dacă v-ați așteptat ca execuția să avanseze pe următoarea linie, ați greșit parțial. Numai parțial, pentru că într-adevăr a sărit pe următoarea linie, dar pe următoarea linie care urma să fie executată în conformitate cu logica programului nostru. Deoarece linia care a fost executată a fost o verificare condițională, rezultatul său a fost calculat și a avansat execuția la primul rând care a urmat în funcție de acest rezultat condițional. Din moment ce am verificat dacă ziuaSaptamanii este „Luni”, dar valoarea sa a fost „Marți”, execuția a sărit la blocul Else, nu la cel If. Drept urmare, realizăm că putem folosi acest buton pentru a urmări calea de execuție a programelor noastre, pentru a vedea ce linii sunt executate, care sunt ignorate etc.

Acum, efectuați clic pe săgeata galbenă de pe bara verticală din stânga, cea care indică locul unde execuția este întreruptă, și trageți-o pe linia unde este setat breakpoint-ul. Apăsați din nou butonul Step Into. Puteți observa că putem să mutăm de fapt execuția la orice linie dorim (în interiorul metodei curente, totuși!), inclusiv înapoi la început (rularea liniilor din nou) sau chiar în locuri care în mod normal ar fi ignorate de compilator. Da! Putem chiar să o tragem în blocul condiției If, chiar dacă expresia condițională ar returna False, iar blocul Else ar fi executat în mod normal:

În mod normal nu ar trebui să faceți acest lucru, dar există cazuri în care doriți să testați cum se comportă unele coduri care nu ar fi lovite în condițiile curente. Cine știe? În cele din urmă, puteți chiar trage săgeata în jos, făcând compilatorul să sară executarea unor linii. Putem chiar să o tragem la acolada de închidere a metodei Main(), sărind peste execuția întregului cod rămas în interiorul blocului său! Această caracteristică s-ar putea dovedi foarte utilă în anumite situații. Nu trebuie decât să mă gândesc la un scenariu în care acest lucru ar putea fi extrem de important: dacă lucrați într-o centrală nucleară și depanați codurile care ar declanșa închiderea nucleului, l-ați închide cu adevărat când execuția ar ajunge la acea parte? Nu, trebuie doar să testați că sunt îndeplinite condițiile care declanșează închiderea și că execuția ajunge acolo. După aceea, putem trece cu ușurință peste codul care l-ar închide cu adevărat.

Trageți și plasați săgeata galbenă a execuției la prima linie a blocului metodei Main() din nou. Apăsați Step Into. Plasați din nou cursorul mouse-ului deasupra variabilei ziuaSaptamanii. Valoarea sa va fi „Marți”, așa cum era de așteptat. Cu toate acestea, faceți clic pe fereastra de tip pop-up care afișează valoarea sa. Ar trebui să intre brusc în modul de editare:

Editarea in timp real a valorilor variabilelor, in mod de depanare

Modificați valoarea în „Luni” și apăsați pe Enter. Va fi o surpriză pentru voi, dar chiar dacă ați atribuit în cod valoarea „Marți” variabilei voastre, dacă plasați acum mouse-ul deasupra sa, aceasta are valoarea „Luni”. Deci, oau!, Visual Studio chiar ne permite să schimbăm valorile variabilelor noastre în timp real, în timp ce execuția este pauzată, fără modificarea codurilor noastre. Astfel, putem verifica foarte ușor care sunt calculele atunci când variabilele noastre au valori diferite. Dacă am apăsa Step Into în acest moment, vom vedea că evaluarea condiției este destul de diferită:

Blocul If a fost executat acum, pentru că am schimbat valoarea variabilei noastre în „Luni”, iar acum verificarea condițională returnează True, executând astfel blocul respectiv de cod.

După cum puteți observa, există câteva butoane alături de Step Into, și anume: Show Next Statement, Step Into, Step Over, Step Out. Să analizăm un pic mai profund ceea ce face fiecare.

Show Next Statement este destul de simplu. Acesta evidențiază următoarea afirmație care va fi executată când execuția va fi reluată.

Pentru Step Into, Step Over și Step Out, să schimbăm un pic codul:

Plasați un nou breakpoint la prima linie de cod din interiorul blocului metodei Main() și porniți execuția. Apăsați Step Into o dată și veți vedea execuția avansând la următoarea linie de cod, ceea ce era de așteptat. Acum, apăsați Step Into din nou și veți observa ceva diferit, dar logic: deoarece aceasta este linia unde apelăm metoda PrimaMeaMetoda(), execuția este transferată la această nouă metodă, iar debuggerul urmează acea cale. Pentru a evidenția faptul că execuția a părăsit metoda principală pentru că a trebuit să meargă „mai adânc”, în interiorul corpului metodei pe care o apelează, veți observa, de asemenea, că apelul metodei are acum un fundal gri închis:

Debugger Step Into

Dacă continuăm să apăsăm butonul Step Into, linia de execuție curentă va avansa până când va ajunge la apelul către a doua metodă, unde din nou va fi transferată:

Continuând să apăsăm butonul Step Into vom vedea execuția avansând prin blocul său, apoi reveniți la prima metodă:

Continuați să îl apăsați și veți vedea că execuția se va întoarce la codul din interiorul metodei Main(), unde a fost înainte de apelarea primei metodei:

Bun! Acum știm că folosind Step Into va transfera linia curentă executată de debugger în orice loc al programului unde conduce logica acestuia, chiar dacă aceasta înseamnă intrarea în alte metode, clase, fișiere din proiect etc. Putem folosi întotdeauna acest lucru pentru a urma o linie strictă de execuție a codului, unde putem vedea calea exactă urmată de aceasta, de la început până la sfârșit. Cu toate acestea, în exemplul nostru particular, poate că vrem doar să depanăm codurile în cadrul metodei noastre Main() și nu suntem deloc interesați de codurile din prima și a doua metodă. Aici putem folosi butonul Step Over. Reporniți execuția programului și avansați-o până când urmează apelul la prima metodă:

Știm că dacă am apăsa pe butonul Step Into, execuția ar fi acum transferată la PrimaMeaMetoda(), deci, în schimb, apăsați pe butonul Step Over. Veți vedea debuggerul avansând o linie, încă în interiorul metodei Main():

Visual Studio Step Over

Deci, într-adevăr, a trecut peste codurile din PrimaMeaMetoda() și implicit peste ADouaMeaMetoda(), apelată de aceasta. Cu toate acestea, fiți foarte conștienți de faptul că acest lucru NU înseamnă că aceste apeluri nu au fost executate! PrimaMeaMetoda() a fost într-adevăr apelată și a tipărit mesajul la consolă, apoi a apelat metoda ADouaMeaMetoda(), care și-a tipărit și ea mesajul la consolă, etc, dar când ambele blocuri ale metodelor au fost executate, execuția a revenit la Main(), unde a fost oprită din nou, la următoarea linie. Deci, Step Over nu implică faptul că va trece peste execuția metodei pe care codul o apelează, ci că doar o execută fără a pauza programul până când execuția revine la primul rând după apelul inițial al metodei. Pentru a demonstra acest lucru, putem să aruncăm o privire la ecranul consolei, unde vom vedea textul tipărit în aceste două metode:

În cele din urmă, să vedem ce face butonul Step Out. Reporniți executarea programului și utilizați funcția Step Into pentru a avansa linia executată până când vă aflați în interiorul metodei PrimaMeaMetoda(), după cum urmează:

Visual Studio Step Out

Acum, în loc să apăsați încă o dată Step Into, care va transfera execuția în blocul metodei ADouaMeaMetoda(), din cauza apelului la această metodă, apăsați butonul Step Out:

Visual Studio Step Out

și vom vedea că execuția a fost transferată înapoi la metoda Main(). Cu alte cuvinte, dacă am folosit în loc de Step Into pentru a merge un nivel mai adânc în interiorul unui apel de funcție, când am apăsat Step Out în timp ce execuția se află în interiorul corpului blocului său, ea a fost returnată la punctul în care a fost apelată metoda respectivă. Ca o concluzie, folosirea unei combinații între Step Into și Step Out ne poate ajuta să aruncăm o privire rapidă asupra metodelor care sunt apelate în codurile noastre și să ne întoarcem repede la locul unde ne aflam, dacă nu am găsit ceea ce căutam.

Pentru a încheia această lecție, să aruncăm o privire la meniul care apare atunci când faceți clic dreapta pe un breakpoint:

Breakpoint context menu

Ne putem imagina deja ce face Delete și Disable Breakpoint. Ele sunt auto-descriptive. Ultima opțiune, Export, este și ea intuitivă: ne permite să exportăm breakpoint-urile ca fișiere XML care pot fi importate mai târziu, pentru a avea aceleași breakpoint-uri configurate din nou.

Dar, să vorbim despre celelalte trei opțiuni: Conditions, Actions și Edit Labels. Mai întâi, să schimbăm un pic codul:

După cum puteți vedea, avem doar o buclă For care numără de la 0 la 99 și afișează valoarea curentă la consolă. Setați un breakpoint la rândul în care imprimăm valoarea la consolă. Faceți clic dreapta pe el și alegeți Conditions. Vi se va afișa o nouă fereastră:

Visual Studio Breakpoint Conditional

Acum, în câmpul de text, tastați i == 15, apoi apăsați Enter. Apoi, puteți închide fereastra pop-up. Breakpoint-ul ar trebui să aibă acum o cruce albă pe el:

Visual Studio Breakpoint Avansat

Acest lucru indică faptul că breakpoint-ul este un breakpoint avansat, în acest caz, unul care va fi lovit numai atunci când se va îndeplini condiția specificată. Rulați programul. Execuția se va opri la breakpoint-ul nostru și dacă vom trece cu cursorul mouse-ului peste variabila i sau dacă mergem la fila Locals, vom vedea că valoarea curentă a lui i este de 15, exact așa cum am specificat în condiția pentru breakpoint. Ar trebui să vedeți deja cum acest lucru ar putea fi util pentru voi, pentru a putea stabili breakpoint-uri care vor întrerupe execuția programelor noastre numai atunci când anumite condiții pe care dorim să le specificăm sunt îndeplinite sau nu sunt îndeplinite, dacă se modifică valoarea unei anumite variabile, și o mulțime de alte opțiuni pe care ar trebui să le verificați singuri.

În mod similar, Actions ne permite să efectuăm o acțiune, și țineți cont că putem combina împreună condițiile și acțiunile pentru un control mai mare.

Ultimul punct de interes pentru noi în meniul contextual al breakpoint-ului este Etichetele (Labels). Faceți clic dreapta pe cercul roșu al breakpoint-ului și selectați Edit Labels din meniu. În noua fereastră care apare, tastați un mesaj descriptiv pentru ceea ce face respectivul breakpoint:

Visual Studio Eticheta Breakpoint

Acum, mergând la meniul Debug, submeniul Windows și făcând click pe Breakpoints, vom obține o listă a tuturor breakpoint-urilor din cadrul aplicației noastre, adică din toate fișierele. Dacă faceți dublu clic pe un breakpoint, veți ajunge la linia de cod unde este acesta este setat. Lucru interesant de observat este că aici putem vedea și etichetele pe care le punem altor breakpoint-uri:

Un alt lucru important este faptul că putem să filtrăm efectiv breakpoint-urile prin etichete, iar acest lucru ar putea fi crucial atunci când avem multe breakpoint-uri stabilite în întreaga noastră aplicație (amintiți-vă că putem seta aceeași etichetă pentru mai multe breakpoint-uri). Iată un exemplu în care afișăm numai breakpoint-urile care au eticheta lor setată ca „Bug 3135”:

Visual Studio Filtru Breakpoint-uri

Cu aceasta, încheiem lecția despre breakpoint-uri. În următoarea lecție vom afla despre zonele de memorie Stack și Heap, ce sunt și cum ne pot ajuta.

Comments

comments

Tags: ,

Leave a Reply