---------------Fragmenty w >...< pogrubione--------------- 5E Rafaî Wiosna Eeeee... Niewiarygodne. Jak Wam sië udaîo wytrzymaê cztery poprzednie kursy i dotrzeê do piâtego? Wiem, ûe Naczelny zadaje sobie to pytanie codziennie wieczorem, ale nic to -- niestrudzony w walce bëdë kontynuowaî. Tradycyjnie zachëcam do zapodania moduîu w tîo. Tym razem polecam kawaîki Jogeira Liljedahla, które moûna znaleúê na pîytce Aminet 4 November 1994. Szczególnie "Full Moon Endpart" i "Face Another Day" -- sâ czadowe! Obsîuga bîâduf Pamiëtasz BASIC-owe ON ERROR GOSUB (w bardziej zaawansowanych interpretacjach tego jëzyka, np. na Amstradzie). Byîa to doôê efektywna metoda zapobiegania zatrzymaniu biegu programu z efektownym komunikatem "Integer out of range" lub innym tego typu burakiem. Nikt jednak na Amstradzie nie otwieraî bibliotek, ekranów, okienek, nie rezerwowaî dynamicznie pamiëci. Wszystko byîo jasne i proste, nie byîo innych programów dziaîajâcych w tle, które mogîy zabraê jakiô zasób systemowy. Nie tak jak na Amidze -- w jednej chwili moûe byê tyle wolnej pamiëci, ile programowi potrzeba, za parë sekund juû nie. Na të i podobne zmiany klimatu naleûy odpowiednio zareagowaê, wyôwietlajâc ponaglenie, odmawiajâc pracy albo w podbramkowej sytuacji wyôwietliê migajâcy napis a la guru meditation. E ma pewien mechanizm obsîugi bîëdów bardzo uîatwiajâcy programowanie. Kaûda procedura moûe mieê tzw. error-handler, czyli pomocniczâ podprocedurë, pozwalajâcâ reagowaê w odpowiedni sposób na wszelkie bîëdy. Przeôledúmy przykîad: ENUM BLAD_OK,BLAD_ZLYSYSTEM,BLAD_BRAKPAMIECI,BLAD_OPNFONT MODULE 'diskfont','graphics/text' DEF diskfontbase,mojfont,tmp DEF mojtextattr:PTR TO textattr CONST MINSYSTEM=39 /* 37 jeûeli masz system 2.0 */ RAISE BLAD_BRAKPAMIECI IF New()=NIL PROC main() HANDLE IF (diskfontbase:=OpenLibrary('diskfont.library',MINSYSTEM)) WriteF('O! Widzë, ûe masz system V\d.\n',MINSYSTEM) ELSE Raise(BLAD_ZLYSYSTEM) ENDIF mojtextattr:=New(SIZEOF textattr) mojtextattr.name:='scala.font' mojtextattr.ysize:=13 mojtextattr.style:=FSF_BOLD+FSF_ITALIC mojtextattr.flags:=FPF_DESIGNED IF (mojfont:=OpenDiskFont(mojtextattr))=NIL THEN Raise(BLAD_OPNFONT) WriteF('Otworzyîem czcionkë \s o wielkoôci \d.\n',mojtextattr.name,mojtextattr.ysize) /* ... ... tu uûywasz ôwieûo otworzonej czcionki ... */ /* tmp:=New(13857574) */ Raise(BLAD_OK) EXCEPT IF mojfont THEN CloseFont(mojfont) IF diskfontbase THEN CloseLibrary(diskfontbase) IF exception SELECT exception CASE BLAD_ZLYSYSTEM WriteF('Bîâd: musisz mieê system conajmniej V\d.\n',MINSYSTEM) CASE BLAD_BRAKPAMIECI WriteF('Bîad: za maîo wolnej pamiëci.\n') CASE BLAD_OPNFONT WriteF('Bîâd: nie mogë otworzyê czcionki \s \d.\n',mojtextattr.name,mojtextattr.ysize) DEFAULT WriteF('Nieznany bîâd, dec:\d hex:\h txt:\s.\n',exception,exception,[exception,0]) ENDSELECT ENDIF ENDPROC Ûeby zaobserwowaê, jak program reaguje na bîëdy, proponujë zmianë staîej MINSYSTEM z 39 (system 3.0) na 40 (system 3.1) lub 99 ("nie ma takiego numeru"). Drugâ rzeczâ, którâ moûesz zmieniê w tym przykîadzie, jest nazwa czcionki -- wpisz takâ, której nie masz. Nastëpnie moûesz odkomentowaê linië z New(13857574) -- Ci, którzy majâ ponad 13 MB ciâgîej pamiëci, niech sami sobie wpiszâ odpowiednio wiëkszâ liczbë... W pierwszej linii natykamy sië na waûnâ rzecz, zwiâzanâ z obsîugâ bîëdów. Definiujë tam ich numery, uûywane póúniej przez program. Komenda ENUM pozwala przyporzâdkowaê pewnym staîym kolejne numery. Przykîad: ENUM AA,BB,CC,DD=7,EE ENUM FF,GG Da w wyniku staîe o wartoôciach: AA=0, BB=1, CC=2, DD=7, EE=8, FF=0, GG=1. Pierwsza staîa po deklaracji ENUM zawsze ma wartoôê zero. Tîumaczy to, dlaczego FF jest "warte" akurat tyle. Tak wiëc w pierwszej linii definiujë staîe uûywane przez program do numerowania bîëdów. Pierwszy, BLAD_OK, bëdzie miaî wartoôê 0, drugi -- BLAD_ZLYSYSTEM -- 1 itd. Zwróê uwagë, ûe procedura main() zostaîa zadeklarowana trochë nietypowo, dodano bowiem do deklaracji PROC main() tekst 'HANDLE'. Oznacza to, ûe wszelkimi sytuacjami nietypowymi bëdzie zawiadowaê specjalny handler bîëdów. Ogólna budowa takiej procedury jest nastëpujâca: PROC nazwa() HANDLE ... ... kod Twojego programu ... EXCEPT ... ... handler bîëdów ... ENDPROC Gdy ûadne bîëdy nie wystâpiâ, procedura wykona sië normalnie aû do dyrektywy >EXCEPT<, która spowoduje natychmiastowe zakoïczenie wykonywania main(), tak jakby tam byîo ENDPROC. Natomiast jeûeli wystâpi jakiô bîâd, program przeskoczy od razu za dyretywë EXCEPT, niezaleûnie od tego, z którego miejsca bëdzie to robiî. Moûe to byê pëtla REPEAT..UNTIL, FOR..NEXT, moûe to byê nawet wnëtrze konstrukcji IF..THEN..ENDIF. Powstaje jednak wâtpliwoôê: jak to sië dzieje, ûe program skacze do handlera bîëdów. Otóû nic nie ma za darmo -- trzeba samemu sprawdziê wyniki dziaîania programu, a szczególnie wartoôci zwracane przez funkcje systemowe (gdyû to gîównie problemy w uzyskaniu od systemu tego, czego chcemy) sâ na poczâtku najczëstsze (np. nie bëdzie moûna otworzyê jakiejô biblioteki, czcionki czy zasobu -- dobry program powinien o tym uûytkownika poinformowaê, a nie sië wyîoûyê). W podanym przykîadzie takâ funkcjâ systemowâ jest wywoîanie OpenDiskFont() z biblioteki diskfont.library, jeûeli zwróci nam ona zero (inaczej: NIL) oznacza to niemoûnoôê otworzenia czcionki, którâ okreôliliômy w strukturze, bëdâcej argumentem funkcji (patrz przykîad). Jeûeli taka sytuacja wystâpi, naleûy wywoîaê komendë >Raise(numerbledu)<. To wîaônie ona powoduje bezwarunkowy skok "za" linië EXCEPT. Argument, czyli numer bîëdu, kopiowany jest tam do zmiennej exception (jest to wewnëtrzna zmienna E i nie naleûy jej definiowaê w DEF-ach). We wnëtrzu handlera moûna zrobiê praktycznie wszystko, co nam sië podoba. Ja uûywam go zawsze do oddania systemowi wszystkiego, czego od niego zaûâdaîem na poczâtku programu (zamykam otwarte biblioteki, ekrany, okna, porty itp). Robië to w sposób wyjaôniony w trzecim odcinku E, zresztâ uûyty w przykîadzie. Dopiero potem sprawdzam numer bîëdu i odpowiednio informujë uûytkownika. Moûna to zrobiê na dwa sposoby -- albo wypisaê coô komendâ WriteF(), albo wystosowaê adekwatny do sytuacji requester (zalecane, jeûeli program uruchamiany bëdzie z ikonki, a nie z Cli). Jak to zrobisz, to juû Twoja sprawa. Spójrz jednak jeszcze raz na przykîad, dokîadnie na linië IF exception. Jak juû wiesz, taka definicja warunku jest równoznacza warunkowi exception<>0. Zwykle tak sië skîada, ûe jest to prawdâ (o bîëdzie numer 0 za chwilë). Uûyîem potem konstrukcji SELECT..ENDSELECT znanej z C i Pascala. Ma ona postaê: SELECT zmienna CASE wartoôê ... wykonaj, jeûeli zmienna=waroôê CASE innawartoôê ... wykonaj, jeûeli zmienna=innawartoôê CASE jeszczeinnawartoôê ... wykonaj, jeûeli zmienna=jeszczeinnawaroôê DEFAULT ... wykonaj, jeûeli zmienna ma inne waroôci niû te zdefiniowane w CASE-ach ENDSELECT Konstrukcja ta nadaje sië znakomicie do reakcji na bîëdy. Jako zmiennâ stosujemy exception, a w CASE-ach wpisujemy kolejne staîe bîëdów, te zdefiniowane na poczâtku w EVAL-u. Dyrektywa DEFAULT pozwala zareagowaê, jeûeli zmienna ma inne wartoôci niû te, które sprawdzamy wczeôniej w CASE-ach. W obsîudze warto zostawiê furtkë na jakieô dziwne, nieznane numery exception (moûe po prostu bîëdy, które gdzieô tam w programie Raise()-ujemy), gdyû inaczej nasz program moûe sië wykrzaczyê w sytuacji, z której moûna wyjôê "z twarzâ". (Prawie tak jak robi to MkS_Vir na pececie -- niekiedy wychodzi z komunikatem "Bîâd TX-033", którego znaczenie jest w stanie pojâê tylko autor. Niech nasze programy przynajmniej wypisujâ "Nieznany bîâd 237, skontaktuj sië z Autorem!!!"). Wspomniaîem wczeôniej o bîëdzie o numerze 0. Otóû uûywam go do zasygnalizowania handlerowi, ûe wykonywanie programu przebiegîo bez zakîóceï. Dlaczego wiëc skaczë do handlera? Dlatego, ûeby zwróciê zasoby systemu. W ten sposób oszczëdzam sobie trochë pisania, w prostych programach jest to zbawienne. Zauwaû, jak przykîadowy program zareaguje na komendë Raise(BLAD_OK). Zamknie czcionkë i otworzonâ bibliotekë i... spokojnie zakoïczy dziaîanie, gdyû caîa konstrukcja od IF exception do ENDIF zostanie przeskoczona! Chytre, nie? Nie, nie zapomniaîem o tej dyrektywie >RAISE< przed PROC main(). Otóû E ma bardzo miîy mechanizm pozwalajâcy na naleûyte reagowanie na bîëdy wynikacjâce z dziaîania jednej lub kilku waûnych funkcji. W przykîadze rozkazaîem kompilatorowi, ûeby za kaûdym wywoîaniem funkcji New(), rezerwujâcej pamiëê, sprawdzaî, czy jej wynikiem jest NIL (brak pamiëci). Jeûeli tak, to automatycznie bëdzie wywoîany handler ze zmiennâ exception równâ BLAD_BRAKPAMIËCI. Dziëki temu odpada nam ûmudne sprawdzanie za kaûdym razem, gdy wywoîujemy New(), czy przypadkiem nie zabrakîo pamiëci -- kompilator sam zadba o to, ûeby to robiê! Ogólnâ postaciâ RAISE jest: RAISE numerbîëdu IF Funkcja()=zîawaroôê Moûna to zinterpretowaê nastëpujâco: jeûeli wartoôciâ zwróconâ przez Funkcja() bëdzie zîa wartoôê, to wykonaj komendë Raise(numerbledu). Na koniec rada dla poczâtkujâcych: korzystaê z handlera powinieneô tylko wtedy, gdy wystâpiâ bîëdy uniemoûliwiajâce wykonywanie procedury. W wypadku main() uniemoûliwiajâce dalsze wykonywanie programu. Angaûowanie handlera do sytuacji, gdy np. uûytkownik wpisze zîâ wartoôê, nie jest wskazane (mimo ûe jest to moûliwe) i powinno byê zaîatwione raczej w pëtli, np. REPEAT..UNTIL, która bëdzie wykonywana tak dîugo, aû uûytkownik nie wpisze wartoôci dobrej lub zrezygnuje z tego. * Zakoïczë juû. W nastëpnym odcinku postaram sië rozwinâê jeszcze obsîugë bîëdów -- zagnieûdûanie sië handlerów, Throw(), ReThrow(), SELECT val OF var..., czyli bardziej zaawansowane techniki obsîugi sytuacji wyjâtkowych. Zostawiam Was na miesiâc, pamiëtajcie jednak, ûeby coô w miëdzyczasie dîubaê, pisaê proste programiki itp. Praktyka czyni mistrza.