P R O G R A M U J E M EDelfinárium / 2. èas : Ladenie aplikácií ILadenie patrí k neoddelite¾nej súèasti vývojového cyklu aplikácie. Najrôznejším „muchám“sa nikdy nemožno vyhnú , a tak sú ladiace nástroje jednou z <strong>na</strong>jdôležitejších súèastínielen Delphi, ale aj vývojových nástrojov všeobecne. Hoci sa <strong>na</strong> prvý poh¾ad môžezda , že pojem ladenie vo vz ahu k Delphi z<strong>na</strong>mená iba použitie klávesov F7 a F8, nie jeto pravda. Ve¾mi dôležitým nástrojom ladenia sú výnimky, ktorých správne použitie môželadenie aplikácie výrazne u¾ahèi . Okrem toho je potrebné sledova varovania kompilátora,pretože tie môžu neraz vies k odhaleniu chyby, ktorú sme ešte ani nestihli objavi .Ako už <strong>na</strong>z<strong>na</strong>èil <strong>na</strong>dpis tohto èlánku, budeme sa venova ladeniu v Delphi. Aby smeneopomenuli používate¾ov starších verzií Delphi, zaèneme <strong>na</strong>j<strong>sk</strong>ôr opisom ladiacichmožností v Delphi 3. Preberieme si tie <strong>na</strong>jzákladnejšie princípy a postupy ladenia, ktoréje možné (aj keï s urèitými obme<strong>na</strong>mi) aplikova aj <strong>na</strong> Delphi 5. Žia¾, používate¾ov Delphi5 budem musie <strong>sk</strong>lama , pretože opis ladiacich nástrojov špecifických pre toto vývojovéprostredie príde <strong>na</strong> rad až o mesiac. Verím však, že nájdu ve¾a pouèného aj v tomtoèlánku, a prosím ich o trpezlivos a zhovievavos .Obr. 1Obr. 2Základy ladeniaSkôr ako sa pustíme do práce, uvedieme nieko¾ko základných pravidiel, ktoré by ste malipri ladení aplikácií dodržiava . Najzákladnejšie je vypnutie optimalizácie (po òom trebabezpodmieneène použi príkaz Build all, aby vypnutie optimalizácie ovplyvnilo všetkysúbory projektu). Keby sme to neurobili, neboli by sme v niektorých prípadoch schopnísledova stav urèitej premennej. Namiesto toho by sme iba uvideli hlásenie „Variable i<strong>na</strong>ccessiblehere due to optimization“. Optimalizaèný kompilátor totiž používa postupy,ktoré môžu kód upravi tak, že debugger sa k premennej jednoducho nedostane. Mimochodom,v nápovedi nájdete upokojujúcu vetu, že takýto zásah (teda optimalizácia)v nijakom prípade neovplyvní funkènos vášho kódu. Žia¾, nie je to celkom pravda, zapnutáoptimalizácia totiž môže neraz zapríèini vznik problémov. Z toho dôvodu mnoho¾udí (vrátane mòa) optimalizáciu radšej prezieravo vypne a zabudne <strong>na</strong> òu. Samo- z-rejme, môže to z<strong>na</strong>me<strong>na</strong> urèitý pokles výkonu, ja si však dovo¾ujem tvrdi , že iba teoretický.Treba si uvedomi , že optimalizácia sa prejaví iba <strong>na</strong> zložitejšom kóde, ktorý zprevažnej väèšiny pozostáva z èistého Pascalu, resp. iného programovacieho jazyka (optimalizáciuumožòujú aj iné vývojové prostredia). Predstavte si <strong>na</strong>príklad, že <strong>na</strong>píšete procedúru,ktorá manipuluje s dátami, používajúc známe metódy komponentu TTable, ako<strong>na</strong>príklad First, Next, Prior a pod. Ak bude optimalizácia zapnutá, program ušetrí pár strojovýchcyklov. Problém je v tom, že volanie rutín ako Prior èi First výslednú procedúru ajtak spomalí, takže tých nieko¾ko usporených taktov je nám, ¾udovo povedané, <strong>na</strong>niè. Kebyste pracovali <strong>na</strong> triediacom algoritme, to už by bola situácia iná. Ja však predpokladám,že väèši<strong>na</strong> Delphi vývojárov svoje aplikácie <strong>sk</strong>ladá z už hotových súèastí a v tom prípadeoptimalizácia nehrá ve¾kú úlohu. Aby som to zhrnul: netvrdím, že optimalizácia je zbytoèná,<strong>sk</strong>ôr sa domnievam, že väèši<strong>na</strong> z vás nebude vyvíja také typy aplikácií, kde bykompilátorom optimalizovaný kód hral úlohu (zámerne píšem „kompilátorom optimalizovaný“,pretože optimalizova môže aj èlovek a výsledok je, pochopite¾ne, ove¾a lepší).Dovo¾ujem si vám teda odporúèa , aby ste optimalizáciu vypli a zabudli <strong>na</strong> òu.Vrá me sa však k pôvodnej téme. Ïalším pravidlom pri ladení programov je, že Delphi bymalo ma prístup k zdrojovým kódom len tých jednotiek, ktoré budeme poèas práce <strong>na</strong> programemodifikova . Pre lepšie pochopenie uvediem príklad: predstavme si, že sme si prezrelidemo súborového ma<strong>na</strong>žéra, ktorý sa dodáva s Delphi. Tento demoprogram používa jednotkuplnú užitoèných funkcií, ktoré by sme radi využili vo vlastných programoch, preto jednotkupridáme do nášho projektu. Všetko síce bude fungova bezchybne, no nie príliš efektívne.Príkaz Build all spôsobí, že Delphi prekompiluje všetky súbory nášho projektu vrátanetejto jednotky, èo je plytvanie èasom. Okrem toho sa môže sta , že poèas krokovania programunedopatrením stlaèíte nesprávny kláves, v dôsledku èoho sa dostanete do tela niektorejprocedúry èi funkcie patriacej do tejto novej jednotky. V tom prípade sa budete musiezdåhavo „prekrokova “ až <strong>na</strong> koniec tejto procedúry èi funkcie, èo veru nikoho nepoteší.Možno budete teraz odporova , že jednotku zo svojho projektu jednoducho nemôžetevynecha , pretože ju potrebujete. Nuž, vynecha ju ani nie je potrebné, problém sa dá vyriešitakto: jednotku prekopírujeme do adresára s <strong>na</strong>ším projektom, projekt prekompilujeme ajednotku (teda súbor s príponou PAS) vymažeme. V adresári s <strong>na</strong>ším projektom ostane ibasúbor DCU, s ktorým už žiadne problémy nebudú, keïže opätovná kompilácia mu nehrozí.Možno sa teraz pýtate, preèo som odporúèal súbor prekopírova , a teda ho zdvoji a zbytoènetak plytva miestom. Odpoveï je ve¾mi jednoduchá: ak budete váš projekt zálohova ,bude všetko v jednom adresári a nebudete musie „beha “ hore-dolu po rôznych zákutiachvášho hard di<strong>sk</strong>u a pridáva do výsledného archívu súbory. Okrem toho sa môže sta , že sarozhodnete urèitý adresár vymaza a pod¾a Murphyho zákonov to bude práve ten adresár,ktorý obsahuje dôležité jednotky. Možno sa vám tieto upozornenia zdajú irelevantné, majúvšak svoje opodstatnenie. Ak si <strong>na</strong>príklad <strong>na</strong>inštalujete RX Library a pri krokovaní náhodoustlaèíte nesprávny kláves, budete sa musie „prekrokova “ celou plejádou volaní, o ktorýchexistencii nemáte a ani nemusíte ma ani potuchy, nehovoriac už o tom, že po vybranípríkazu Build all bude spolu s vaším projektom prekompilovaná aj RX Library, èo je nieko¾kotisíc riadkov. Ak teda budete ma k dispozícii túto knižnicu (èi akéko¾vek iné knižnice) aj sozdrojovým kódom, prekompilujte ho a ponechajte iba súbor DCU (súbory PAS môžete<strong>na</strong>príklad <strong>sk</strong>omprimova a ponecha v tom istom adresári).Postup spomenutý v predchádzajúcom odseku by som neodporúèal použi pri súboroch,ktoré sú priamou súèas ou vášho projektu (mám tým <strong>na</strong> mysli jednotky, ktoré sme neprebrali,ale sami vytvorili). Teoreticky by to síce možné bolo, pod¾a môjho názoru by to všakprinieslo viac problémov ako úžitku. Keby sme <strong>na</strong>príklad presunuli zdrojový kód jednotky, poèase by sme mohli zisti , že ešte nefunguje tak, ako má, a v dôsledku toho by ste zdrojovýkód museli prekopírova spä , upravi , <strong>sk</strong>ompilova a zdrojový súbor opä presunú .Ïalším argumentom, preèo tento postup neuplatòova , je fakt, že to jednoducho nie jepotrebné: Delphi totiž vždy prekompiluje iba tie súbory, ktoré sa zmenili, prípadne súbory,ktoré so zmenenými súbormi súvisia. Na tomto mieste však treba doda , že v niektorých prípadochto môže vies k závažným problémom, pretože systém obèas zmeny nezistí a programpotom funguje nesprávne, hoci chyba už bola opravená. Inými slovami, vy ste sícezdrojový súbor zmenili, no kompilátor to nezistil, a preto ho neprekompiloval. Ak si tedachcete by úplne istí, že vami urobené zmeny sa <strong>sk</strong>utoène premietli do výsledného EXEsúboru, použite príkaz Build all. Naš astie takéto „preš¾apy“ robí kompilátor iba málokedy,takže sa netreba zdržiava neustálym používaním uvedeného príkazu.A ešte jed<strong>na</strong> ve¾mi dôležitá rada, ktorú ve¾a programátorov s ob¾ubou ignoruje: komentujtesvoje zdrojové súbory! Žia¾, smutným faktom je, že komentáre sa nikomu písanechce, a keï sa dotyèný pozrie <strong>na</strong> svoj kód o rok ne<strong>sk</strong>ôr, nevychádza z údivu. Ak je tomožné, hneï po dokonèení dôležitej èasti vášho programu si zapíšte základný princíp, <strong>na</strong>ktorom je tá-ktorá èas programu založená. V niektorých prípadoch to pomôže viac akookomentovanie každého riadka kódu.Obr. 3Na záver úvodnej èasti tu mám pre používate¾ov Delphi 3 jeden malý trik. Viete o tom,že Delphi už od verzie 2 obsahuje okno CPU Window? Toto okienko obsahuje disassemblovanývýpis práve vykonávaného kódu plus nieko¾ko ïalších zaujímavých informácií.Žia¾, firma Inprise/Borland ho pred zvedavými zrakmi „delphistov“ z neznámych príèinvedome ukryla. Naš astie je možné toto okno pomerne jednoducho aktivova aplikovaním<strong>na</strong>sledujúceho postupu: spustite Registry Editor a nájdite k¾úè HKEY_CURRENT_USER\Software\Borland\Delphi\3.0\Debugging. Potom do tohto k¾úèa pridajte novú re azcovúhodnotu s názvom E<strong>na</strong>bleCPU a jej hodnotu <strong>na</strong>stavte <strong>na</strong> 1. Po opätovnom spustení Delphi3 sa vám v menu View objaví položka CPU Window.Ladenie v Delphi 3Väèši<strong>na</strong> z vás už urèite poèula o bodoch prerušenia (breakpoints), ktoré sú azda tým<strong>na</strong>jzákladnejším ladiacim mechanizmom všetkých vývojových prostredí, Delphi nevynímajúc.Sú to urèité miesta programu, v ktorých sa normálny beh aplikácie zastaví a kontrolupreberá ladiaci program (v <strong>na</strong>šom prípade je ladiacim programom samotné Delphi, noladenie je možné aj pomocou špecializovaných ladiacich programov, ako <strong>na</strong>príkladBorland Turbo Debugger èi NuMega SoftICE). Bod prerušenia sa dá <strong>na</strong>stavi stlaèenímklávesu F5, prípadne stlaèením malej modrej bodky v ¾avej èasti ok<strong>na</strong> editora zdrojového12/2000 PC REVUE 117
P R O G R A M U J E M EZvyšné dve procedúry, z ktorých bude uvedená ruti<strong>na</strong> volaná, vyzerajú takto:procedure TForm1.FirstBtnClick(Sender: TObject);beginDummyProc(‘select orders from animals’);end;procedure TForm1.SecondBtnClick(Sender: TObject);beginDummyProc(‘select * from animals’);end;Obr. 4kódu. Treba poz<strong>na</strong>me<strong>na</strong> , že táto bodka sa objaví iba po prekompilovaní projektu. Takistoje potrebné zdôrazni , že nie <strong>na</strong> každý riadok kódu je možné umiestni bod prerušenia.Pozorne si preštudujte obrázok è. 1. Bod prerušenia je možné zaradi iba <strong>na</strong> ten riadok,ktorý je v ¾avej èasti ok<strong>na</strong> oz<strong>na</strong>èený bodkou. Urobme jeden malý pokus: zaraïme bodprerušenia <strong>na</strong> riadok zaèí<strong>na</strong>júci sa príkazom with. Pri pokuse o spustenie programu sanám objaví dialógové okno, podobné tomu <strong>na</strong> obrázku è. 2. Delphi nás upozoròuje, žepre daný riadok nebol vygenerovaný nijaký kód, resp. že tento kód bol odstránený optimalizaènýmsystémom. My však môžeme druhú možnos s istotou vylúèi , pretože optimalizáciumáme vypnutú. Jediným vysvetlením teda je, že pre daný riadok prekladaènevygeneroval nijaký kód. V <strong>na</strong>sledujúcom odseku vysvetlíme, èo je príèinou tohto javu.Iste všetci viete, že programovanie v Delphi èi iných programovacích jazykoch pozostávaz dvoch hlavných fáz: <strong>na</strong>písanie programu a preklad programu (ten sa ešte delí <strong>na</strong> fázu kompilaènúa linkovaciu, to je však momentálne nepodstatné). Program <strong>na</strong>písaný vo vyššom programovacomjazyku sa teda preloží do strojového kódu. Na základe tohto tvrdenia by sme samohli domnieva , že každý riadok pascalov<strong>sk</strong>ého kódu sa preloží do nieko¾kých strojovýchinštrukcií. Inými slovami, mohli by sme sa <strong>na</strong>zdáva , že každý riadok má svoj „strojový ekvivalent“.To však nie je pravda. Mnohé k¾úèové slová používané v Delphi (<strong>na</strong>pr. with èi implementation)slúžia iba ako oporné body pre kompilátor, do strojového kódu sa neprekladajú. Zámernepíšem „mnohé“, pretože v niektorých prípadoch sa do strojového kódu prekladajú príkazy, oktorých by si to èlovek nemyslel. Pozrite sa ešte raz <strong>na</strong> obrázok è. 1: <strong>na</strong> zaèiatku procedúry je ved¾apríkazu begin bodka. Pokroèilejší z vás iste vedia, že kompilátor <strong>na</strong> tento príkaz zareagujevytvorením tzv. štandardného rámca zásobníka (standard stack frame), èo je vo väèšine prípadovinštrukcia PUSH EBP, prípadne nieko¾ko ïalších inštrukcií PUSH. To už sú však nepodstatné detaily,dôležité je uvedomi si, že nie <strong>na</strong> každý riadok je možné zaradi bod prerušenia.Ukážkové programy, s ktorými budeme pracova , budú ve¾mi jednoduché a zámernechybne <strong>na</strong>písané, pretože keby fungovali správne, princípy ladenia by sme si <strong>na</strong> nich veruukáza nemohli. Dajme sa teda do práce.Jadrom nášho prvého programu bude procedúra, ktorá vykoná dopyt SQL, prièomsamotný SQL príkaz dostane ako parameter. Vola ju budú dve rutiny, prvá sa aktivuje postlaèení prvého tlaèidla (<strong>na</strong>zvaného FirstBtn) a druhá po stlaèení druhého tlaèidla (<strong>na</strong>zvanéhoSecondBtn). Komponent TQuery, ktorý bude ma za úlohu spracova dopyt SQL,som <strong>na</strong>zval Query. Po stlaèení prvého tlaèidla <strong>na</strong>stane chyba, po stlaèení druhého celýproces prebehne správne. Uvedené procedúry majú <strong>na</strong>sledujúci tvar:procedure TForm1.DummyProc(SQLString:string);begintrywith query dobeginClose;SQL.Clear;SQL.Add(SQLString);Open;end;//withexceptraiseend;//exceptShowMessage(‘OK’);end;Ako vidíme, táto procedúra je založená <strong>na</strong> ve¾mi jednoduchom princípe: ak sa poèas jejvykonávania vy<strong>sk</strong>ytne chyba (výnimka), program <strong>sk</strong>oèí <strong>na</strong> príkaz raise a procedúra Show-Message sa nikdy nevykoná. Samotné <strong>na</strong>èítanie SQL príkazu prebehne tak, že sa <strong>na</strong>jprvpredchádzajúci príkaz vymaže metódou Clear a nový príkaz sa pridá pomocou metódyAdd (nezabúdajte, že keby sme nepoužili metódu Clear, poèet riadkov SQL <strong>sk</strong>riptu by saneustále zvyšoval).Program spustíme a otestujeme. Jeho cie¾om je demonštrova situáciu, keï je jed<strong>na</strong> a táistá ruti<strong>na</strong> volaná z viacerých miest programu. Chybu spôsobí prvá procedúra, ktorá rutineDummyProc odovzdá príkaz odkazujúci <strong>na</strong> neexistujúce pole. Samotná ruti<strong>na</strong> DummyProcmôže by <strong>na</strong>písaná správne, no v dôsledku nesprávnych parametrov sa môžu vy<strong>sk</strong>ytnúproblémy. V takom prípade je potrebné zisti , ktorá procedúra (alebo procedúry) volaniutejto rutiny predchádzala, a teda jej mohla odovzda nesprávne parametre. Presne <strong>na</strong> tentoúèel slúži okno Call Stack: ukazuje nám zoz<strong>na</strong>m volaných rutín, prièom <strong>na</strong>j<strong>sk</strong>ôr volaná ruti<strong>na</strong>je <strong>na</strong> poslednom mieste zoz<strong>na</strong>mu a <strong>na</strong>posledy volaná ruti<strong>na</strong> je <strong>na</strong> prvom mieste zoz<strong>na</strong>mu.Zatia¾ teda vieme, že ruti<strong>na</strong> DummyProc v jednom prípade dostane dobrý a v druhom prípadezlý parameter. Keïže zlý parameter spôsobí vyvolanie výnimky, program zaène vykonávapríkaz raise. Z toho dôvodu <strong>na</strong> tento príkaz umiestnime bod prerušenia a otvoríme oknoCall Stack. Program spustíme a stlaèíme prvé tlaèidlo (teda tlaèidlo FirstBtn). Ako vidíme, výnimka<strong>na</strong>stala bezprostredne za volaním procedúry DummyProc, okno Call Stack nám všakzatia¾ neukazuje potrebné informácie, pretože sa ešte nezaèala vykonáva obslužná ruti<strong>na</strong>výnimky v procedúre DummyProc. Stlaèíme teda kláves F9 a program <strong>sk</strong>oèí <strong>na</strong> príkaz raise.Okno Call Stack bude vyzera podobne ako jeho „kolega“ <strong>na</strong> obrázku è. 3. Teraz už vieme,aké bolo poradie volania jednotlivých funkcií, a teda vieme zisti , odkia¾ ruti<strong>na</strong> DummyProcdostala nesprávny parameter.Keïže náš ukážkový program je ve¾mi jednoduchý, vieme chybu rýchlo odstránia program opätovne prekompilova . Môže však <strong>na</strong>sta situácia, keï sú vaše prsty rýchlejšieako mozog a stlaèia F9 <strong>sk</strong>ôr, ako si uvedomíte, že ste síce chybu opravili, ale inú,možno menšiu chybu ste opravi zabudli. Predstavte si, že ste SQL príkaz v prvej procedúreopravili <strong>na</strong> správny, slovko Orders ste teda <strong>na</strong>hradili hviezdièkou. Nedopatrenímste sa však „uklepli“ a medzi slová Orders a From ste zabudli da medzeru. Kým sa svojuchybu s<strong>na</strong>žíte o¾utova , program sa rozbehne. Èo teraz? Prvou možnos ou je programObr. 5ukonèi , opravi a prekompilova . V <strong>na</strong>šom prípade bude potom všetko pracovasprávne, štruktúra aplikácie je jednoduchá a nie je problém chybu v priebehu nieko¾kýchsekúnd lokalizova . V zložitejších programoch to však nemusí plati , oprava chyby môževies k odhaleniu ïalších <strong>sk</strong>rytých chýb. Aby sme neplytvali èas opakovanou kompiláciou,ukážeme si, ako zmeni obsah premennej poèas behu programu.Keï si pozorne preštudujete procedúru DummyProc, možno dospejete k záveru, že<strong>na</strong>jzrelším kandidátom <strong>na</strong> úpravu bude premenná SQLString, parameter tejto procedúry.Ale pozor, parametre by ste nemali nikdy meni , pretože to môže vies k pádu celéhoDelphi. Jedným z možných riešení by bolo zavies lokálnu premennú, ktorá by prebralahodnotu premennej SQLString, prièom ruti<strong>na</strong> by ïalej pracovala už iba z touto premennou.To však nie je potrebné, pretože my môžeme pracova priamo s vlastnos ou SQLkomponentu TQuery, ktorá je typu TStrings. Aby sme si boli istí, že po <strong>na</strong>šej „umelej“zmene sa SQL <strong>sk</strong>ript už nezmení, zaradíme bod prerušenia <strong>na</strong> príkaz Open. Ak by smetotiž umiestnili bod prerušenia <strong>na</strong>príklad <strong>na</strong> príkaz Add èi <strong>na</strong> iný príkaz, ktorý mu predchádza,nemohli by sme jednoducho zmeni niè, pretože až príkaz Add spôsobí pridaniechybného SQL príkazu, ktorý potom musíme opravi . Mohlo by sa sta aj to, že síce chybnýSQL príkaz opravíte, bod prerušenia je však zaradený práve <strong>na</strong> ten príkaz, ktorý vlastnos SQLkomponentu TQuery nejakým spôsobom modifikuje, v dôsledku èoho bude SQL príkaz opäzlý. Ak však bude bod prerušenia „sídli “ <strong>na</strong> príkaze Open, máte úplnú istotu, že sa vlastnosSQL už nezmení, a teda vami urobené zmeny èi opravy nebudú ignorované.Zmeni hodnoty premenných poèas behu programu je možné prostredníctvom príkazuEvaluate/Modify, ktorý vyvoláme z kontextového menu. Do rozba¾ovacieho zoz<strong>na</strong>mu<strong>na</strong>píšeme výraz query.SQL[0] a stlaèíme tlaèidlo Evaluate. Na obrázku è. 4 vidíme, že vlast-118 PC REVUE 12/2000