ZpětObsahDalší

Neplačte nad rozlitým kakaem!


Pokud se něco nepodařilo -- a stojí za to zdůraznit, že v systému Cocoa se to, díky intuitivním knihovnám, stává poměrně zřídkakdy, a velmi často programy pracují hned na prvý pokus -- pořád se nic neděje: prostě použijeme ladicí systém. Jako obvykle v API Cocoa, i při ladění máme k dispozici nadprůměrné služby; pro úsporu místa si zde ukážeme jen základy a pár "lahůdek".

Jen v rychlosti se podíváme na čtyři odstavce:

Hlášení syntaktických chyb

Jelikož ProjectBuilder sám neobsahuje překladač, ale namísto toho volá překladače externí, zdá se na první pohled že s hlášením syntaktických chyb bude trochu problém, nebo přinejmenším že jejich hledání nebude tak snadné, jak je v integrovaných vývojových prostředích zvykem.

Opak je pravdou: ProjectBuilder dokáže s naprostou většinou překladačů spolupracovat tak, že chyby a varování jsou hlášeny obdobným způsobem, jakým se zobrazují nalezené texty při prohledávání projektu, se kterým jsme se seznámili minule: dokonce i více chyb na jediném řádku je korektně označeno. Stačí klepnout myší na hlášení chyby, a odpovídající řádek se ihned najde a zobrazí v editoru:

Debug1

Povšimněme si takétoho, jak překladač GNU C dokáže kontrolovat shodu formátovacího řetězce ve funkci printf a skutečných parametrů! Ačkoli samozřejmě i tato služba má svá omezení (asi nejhorším z nich je, že "nezná" objektový formátovací znak '%@', takže pro něj nemůže shodu kontrolovat), jedná se o výraznou pomoc a výhodu.

ProjectBuilder samozřejmě využívá toho, že sám obsahuje editor zdrojového kódu, a díky tomu si udržuje přehled o umístění chybových řádků i po případných úpravách: jestliže např. při opravě první chyby přidáme deset nových řádků, pro ProjectBuilder to není žádný problém, a druhou chybu označí ve zdrojovém textu korektně -- přestože je již na zcela jiném řádku, než kde ji původně hlásil překladač.

Pokud bychom snad použili nějaký natolik nestandardní překladač, že by ProjectBuilder nebyl schopen "se s ním domluvit" o varováních a o chybách, nabízí ProjectBuilder kompletní opis všech výpisů, jež překladač vygeneroval. Chceme-li, můžeme se na ně samozřejmě ze zájmu podívat i u běžných překladačů -- uvidíme v nich tytéž informace jako v okně se seznamem chyb, jen méně přehledné:

Debug2

Možná za stručnou zmínku stojí to, že toto okno je zcela standardním textovým oknem, takže nejenže v něm můžeme označit text a přenést jej do schránky, ale lze v něm např. i standardním způsobem vyhledávat. V ProjectBuilderu je to samozřejmost, ale v jiných prostředích, se kterými můžete mít zkušenosti, přečasto takovéto samozřejmosti samozřejmé nejsou...

Základy práce s debuggerem

Na první pohled se ladění v ProjectBuilderu nijak zásadně neliší od ladění v jiných integrovaných prostředích: prostě spustíme ladicí session, a tam máme v samostatném okénku k dispozici tlačítka pro základní akce (step in, step over, run, zobrazení hodnoty proměnné, zobrazení zásobníku,...). Okna editoru navíc dostanou při levém okraji pruh, do něhož můžeme umísťovat breakpointy, a ve kterém je vidět, na kterém řádku programu právě laděný program je.

Debug3

Povšimněte si, že na příkladu zrovna ladíme kód v Javě: stejně, jako můžeme psát zdrojové kódy v čemkoli pro co je v systému k dispozici překladač -- aniž bychom se museli v sebemenším vzdát výhod ProjectBuilderu -- můžeme výslednou aplikaci ladit v čemkoli, pro co je k dispozici debugger. To jsou nejen všechny překládané jazyky (jimž rozumí standardní gdb -- viz níže), ale stejně dobře i interpretovaná Java...

Ladíme-li program, který pracuje se standardním výstupem, objeví se výpisy přímo v okně debuggeru. Laděné GUI aplikace samozřejmě zobrazují svá okna normálně; pro ty, kdo jsou zvyklí na nepříliš kvalitní manager oken z MS Windows možná stojí za to zdůraznit samozřejmost, že i s okny právě laděného (a tedy potenciálně neběžícího, přerušeného) programu lze do značné míry pracovat: můžeme je podle potřeby volně přemísťovat po obrazovce (nebo třeba mimo ni), a můžeme je bez omezení "vytahovat" do popředí nebo naopak "ukrýt" na pozadí za všechna ostatní okna.

Jaký debugger vlastně používáme?

Jak to tedy vlastně je -- obsahuje snad ProjectBuilder debuggery pro všechny možné jazyky? Ale kdež, takové nešikovně statické řešení by se snad dalo čekat v DOSu nebo ve Windows, ale ne v rozumně navrženém... vlastně NeXTStepu, protože ProjectBuilder, ačkoli už dlouho vylepšovaný a rozvíjený firmou Apple, je přímým dědictvím po firmě NeXT.

Je to vyřešeno stejně šikovně a efektivně, jako s překladači: ProjectBuilder vůbec neobsahuj žádný debugger; namísto toho má v sobě jen rutiny grafického uživatelského rozhraní, a pro skutečně výkonné služby (tj. "co se stane, když zmáčkneme to tlačítko step over") používá externí debuggery. Základem je standardní špičkový editor gdb, který dokáže obsloužit téměř libovolný překládaný jazyk; pro Javu slouží samostatný JavaDebug.

Výhody a nevýhody gdb

Tradiční nevýhodou gdb (již ProjectBuilder do značné míry odstraňuje) je řádkové uživatelské rozhraní: dnešní programátoři jsou zhýčkaní, a nechce se jim psát do příkazové řádky 'c', mohou-li klepnout myší na tlačítko continue. Gdb však na druhou stranu nabízí nesmírně silnou sadu služeb; my si zde ukážeme jen několik málo z nich.

Pro zobrazování dat a hodnot slouží zhýčkaným programátorům tři ikonky v pravém horním rohu okna debuggeru -- všechny tři vypadají jako okénko s malou červeneou šipkou, druhá navíc obsahuje hvězdičku a třetí krychli, jež je v Cocoa logem objektu. Podle toho také fungují: programátor prostě označí ve zdrojovém textu libovolnou proměnnou nebo výraz, a klepne na kterékoli z nich: prvé tlačítko zobrazí hodnotu výrazu, druhé obsah ukazatele, jehož hodnotu udává výraz, a třetí zobrazí objekt, jehož adresu určuje výraz.

Zde bychom se měli pozastavit nad dvěma věcmi: předně, gdb dovoluje zobrazit skutečně jakýkoli výraz! Jeho součástí klidně může být jakákoli konstrukce z právě platného jazyka, ale i voláni kterékoli knihovní služby, nebo dokonce volání libovolné metody jež je součástí laděného programu! To je nesmírně silný prostředek pro ladění: uvědomme si, že tak můžeme snadno ihned přímo v gdb vyzkoušet jakoukoli novou ideu, aniž bychom kvůli tomu museli přepisovat a znovu překládat program.

Za druhé, pro ty, kdo nemají zkušenosti s objektovým prostředím je vhodné blíže vysvětlit možnost zobrazení objektu. Ani zdaleka se nejedná o možnost vypsat obsah jeho proměnných -- to samozřejmě gdb umí také, ovšem abychom to mohli udělat, musíme znát předem třídu objektu a mít k dispozici na hlavičkových souborech její deklaraci. V dynamickém objektovém prostředí nám gdb nabízí daleko šikovnější možnost: každý objekt totiž díky standardní společné nadtřídě všech objektů (jíž je v Objective C třída NSObject, v Javě standardní třída Object) obsahuje polymorfní metodu, která zobrazí informace o objektu. Můžeme tedy libovolnému objektu v Objective C poslat zprávu description (toString v Javě), a dozvíme se, co je objekt zač, a co obsahuje. Samozřejmě to platí stejně dobře pro skalární objekty jako pro kontejnery -- v jejich případě se informace o objektech korektně zobrazují rekursivně.

Gdb tuto vlastnost objektů "zná", a nabízí programátorům možnost si kdykoli snadno zobrazit obsah zvoleného objektu pomocí řádkového příkazu po ("print object") -- nebo, samozřejmě, prostřednictvím tlačítka v pravém horním rohu okna, jež ostatně nedělá nic jiného, než že vnitřně vyvolá právě příkaz po. Ukažme si jednoduchý příklad: příkaz programu, na nemž máme breakpoint, načte kontejner (konkrétně slovníkovou tabulku, obsahující dvojice klíč/hodnota) ze souboru. Další příkaz pak vybere hodotu pro klíč "Allowable_SubprojectTypes", a -- předpokládáme, že touto hodnotou je opět kontejner, konkrétně pole objektů -- získá poslední prvek tohoto pole.

Debug4

Program nefungoval přesně jak měl, spustili jsme tedy debugger, a hned po načtení kontejneru z disku jsme si zobrazili jeho obsah (prvým příkazem po cc). Díky tomu, že obsah kontejnerů se zobrazuje korektně hierarchicky, vidíme hned v čem je problém: pole Allowable_SubprojectTypes není setříděné, ačkoli jsme počítali s tím, že setříděné bude. Hned jsme také využili toho, že gdb dokáže zobrazit výsledek libovolného výrazu, a podívali jsme se, jak bude pole vypadat po setřídění (po [[cc objectForKey:@"Allowable_SubprojectTypes"] sortedArrayUsingSelector:@selector(compare:)]).

Samozřejmě, mohli bychom ihned opravit zdrojový kód, program znovu přeložit a ladit dál... ne vždy je to ale výhodné. Aplikace, již ladíme, např. může být natolik rozsáhlá, že taková úprava zabere několik minut (nebo několik desítek minut), a my bychom raději našli další chyby, dříve, než spustíme nový překlad. Nebo si prostě jen nejsme příliš jisti tím, že setřídění je právě to, co potřebujeme, a rádi bychom jej prostě jen rychle vyzkoušeli...

Gdb dokáže i takové věci (a ještě mnohem více). Můžeme totiž požít breakpoint, který namísto pozastavení programu automaticky provede libovolnou akci, již si předepíšeme! Ukažme si -- tentokrát jen opisem okna debuggeru -- jak by to v gdb vypadalo. Nejprve si můžeme ověřit, že příkaz NSLog (viditelný na minulém obrázku zpoloviny u dolního okraje okna) vypíše nesprávný prvek (poslední v nesetříděném seznamu); stačí stisknout tlačítko continue:

...
   Library,
    Palette,
    Tool
)
(gdb) Continuing.
Sep 21 03:36:38 Test[5607] JavaPackage
(gdb)

Nyní umístíme breakpoint na příkaz NSLog -- to můžeme pohodlně udělat myší:

Debug5

pro nastavení příkazů, jež se provedou při každém průchodu programu breakpointem však již potřebujeme příkazový řádek. Není to ale tak složité -- jen zvolíme příkaz commands a určíme číslo breakpointu, a pak napíšeme seznam příkazů, jež se mají provést. První z nich může být příkaz silent, který zajistí, že breakpoint se nebude zbytečně hlásit; na posledním místě před ukončením příkazem end nezapomeneme uvést příkaz continue, který zajistí automatické pokračování programu:

...
(gdb) commands 4
>silent
>set q=[[[cc objectForKey:@"Allowable_SubprojectTypes"] sortedArrayUsingSelector:@selector(compare:)] lastObject]
>continue
>end
(gdb) r
Starting program: /tmp/Test/Test
Breakpoint 1, main (argc=1, argv=0xbffffeec) at Test_main.m:9
(gdb) Continuing.
Sep 21 03:45:03 Test[6005] Tool
(gdb)

Na předposledním řádku vidíme, že vše funguje přesně jak má: tentokrát se opravdu vypíše abecedně poslední prvek...

Gdb nabízí řadu dalších, podobně kvalitních služeb: můžeme např. na jedinou adresu umístit více breakpointů, každý platný pro jiný thread... Nemělo by však smysl je zde popisovat: především, v praxi jich využijeme poměrně málokdy, a hlavně -- účelem těchto článků je jen všeobecné seznámení s API Cocoa; podrobná referenční příručka by musela vypadat jinak.

Shrnutí

Víme již jak psát programy v Objective C (a pro ty, kdo preferují Javu, platí totéž -- navíc se nemusejí starat o poloautomatický garbage collector s jeho službami retain a (auto)release). Minule jsme se seznámili se základy práce s ProjectBuilderem, a ode dneška víme, jak hledat chyby.

Zbývá tedy něco? Vlastně drobnost: konkrétní služby knihoven, jež můžeme využívat. Součástí Mac OS X jsou standardní ANSI knihovny (přesně řečeno, máme k dispozici kompletní API BSD unixu); o těch se zde však bavit nebudeme. Namísto toho se od příštího dílu začneme seznamovat s objektovými knihovnami Cocoa.


ZpětObsahDalší

Copyright (c) Chip, O. Čada 2000