|
|||
Forrige < |
Innhold ^
|
Neste >
|
open
-metoden
returnerer en spesifikk verdi for å si at den feilet. Denne verdien blir da ført tilbake
gjennom lagene av metodekall til noen vil ta ansvaret for den.
Problemet med denne måten er at håndteringen av alle disse feilkodene kan bli slitsomt.
Hvis en funksjon kaller open
, så read
, og til slutt close
,
og hver kan returnere en feil-indikasjon, hvordan kan funksjonen skille disse feil-kodene
i den verdien den returnerer til dens kaller?
Til en stor grad løser unntak dette problemet. Unntak lar deg pakke inn informasjon om en feil
i et objekt. Dette unntaksobjektet blir da ført tilbake opp kallstakken automatisk til
kjøretidssystemet finner kode som uttrykkelig deklarerer at den vet hvor den skal håndtere
den type unntak.
Exception
,
eller en av klassen Exception
s mange subklasser. Ruby kommer ferdig med et ryddig hierarki av
unntak, vist i figur 8.1 på side 91. Som vi vil se senere gjør dette hierarkiet behandling av unntak
betraktelig lettere.
Figur 8.1: Ruby sitt unntakshierarki |
Exception
-klassene, eller du kan lage din egen.
Hvis du lager din egen, kan du ønske å gjøre det til en subklasse av
StandardError
, eller et av dens barn. Hvis du ikke
gjør det vil ikke ditt unntak bli fanget som standard.
Hvert Exception
-objekt har en tekstbeskjed og
en sporing av kallstakken. Hvis du definerer dine egne unntak, kan du legge til tilleggsinformasjon.
opFile = File.open(opName, "w") while data = socket.read(512) opFile.write(data) end |
begin
/end
-blokk, og bruke rescue
-klausuler for å fortelle Ruby hvilke typer
unntak som vi vil håndtere. I dette tilfellet er vi interessert i å fange
SystemCallError
-unntak (og implisitt alle unntak som er subklasser av
SystemCallError
), så det er hva som kommer fram på
rescue
-linjen. I feilbehandlingsblokken rapporterer vi feilen, lukker og sletter
output-filer, og så gjenheve unntaket.
opFile = File.open(opName, "w") begin # Unntak hevet av denne kodebiten # fanges opp av den påfølgende rescue-klausulen while data = socket.read(512) opFile.write(data) end rescue SystemCallError $stderr.print "IO failed: " + $! opFile.close File.delete(opName) raise end |
Exception
-objektet som innekapsler unntaket i den globale variabelen $!
(utropstegnet gjenspeiler antagelig
vår overraskelse over at vår kode kunne feile). I det forrige eksemplet
bruke vi denne variabelen til å formatere vår feilmelding.
Etter å ha lukket og slettet filen, kaller vi raise
uten noen parametere,
som gjenreiser unntaket i $!
. Dette er en brukbar teknikk som tillater deg
å skrive kode som filterer unntak, og sender du ikke kan håndtere til høyere nivå.
Dette er nesten som å implementere et arve-hierarki for feil-prosessering.
Du kan ha flere rescue
-klausuler i en begin
-blokk, og hver
rescue
-klausul kan spesifisere flere unntakstyper som skal fanges. I enden av hver
redningsklausul kan du gi Ruby navnet til en lokal variabel som skal motta
unntaket. Mange syntes dette er mer leselig enn å bruke $!
overalt.
begin eval string rescue SyntaxError, NameError => boom print "String doesn't compile: " + boom rescue StandardError => bang print "Error running script: " + bang end |
case
-utsagnet. For hver rescue
-klausul i begin
-blokken,
sammenligner Ruby det hevede unntaket med hver av parameterene etter tur.
Hvis det hevede unntaket passer med en parameter, utfører Ruby kroppen
til rescue
og avslutter sitt søk. Denne matchen er utført
ved å bruke $!.kind_of?(parameter)
, og så vil få suksess
hvis parameteren er av samme klasse som unntaket, eller er en forelderklasse til unntaket.
Hvis du skriver en rescue
-klausul uten noen parameter-liste,
antas parameteren til å være StandardError
.
Hvis ingen rescue
-klausul passer, eller hvis et unntak er hevet på utsiden av
en begin
/end
-blokk, beveger Ruby seg oppi kallstakken og ser etter
unntakshåndteringskode i kalleren, så i kallerens kaller, og så videre.
Selv om parameterene til rescue
-klausulen typisk er navn til
Exception
-klasser, kan de også faktisk være tilfeldige uttrykk (inkluderende metodekall) som returnerer en Exception
-klasse.
Exception
class.
ensure
-klausulen gjør akkurat dette. ensure
går etter den siste
rescue
-klausulen og inneholder et knippe kode som alltid vil bli utført når
blokken terminerer. Det gjør ingenting om blokken avslutter normalt, hvis den hever og redder
et unntak, eller hvis den blir terminert av et ufanget unntak---ensure
-blokken
vil bli kjørt.
f = File.open("testfile") begin # .. prosesser rescue # .. håndter feil ensure f.close unless f.nil? end |
else
-klausulen er en lignende, om enn en mindre nyttig, konstruksjon.
Hvis den er tilstede kommer den etter rescue
-klausulen(e) og før en eventuell
ensure
. Kroppen til en else
-klausul blir bare kjørt hvis ingen unntak blir hevet
av hovedkroppen til koden.
f = File.open("testfile") begin # .. prosesser rescue # .. håndter feil else puts "Congratulations-- no errors!" ensure f.close unless f.nil? end |
retry
-utsagnet inne en
rescue
-klausul for å repetere hele begin
/end
-blokken.
Det er helt klart store muligheter for å skyte seg selv i foten med uendelige løkker her, så dette er ting som bør benyttes
med varsomhet (og med en finger som hviler lett på avbruddstasten.)
Som et eksempel på kode som prøver igjen ved unntak,
ta en titt på det følgende,
tilpasset fra Minero Aoki's net/smtp.rb
-bibliotek.
@esmtp = true begin # First try an extended login. If it fails because the # server doesn't support it, fall back to a normal login if @esmtp then @command.ehlo(helodom) else @command.helo(helodom) end rescue ProtocolError if @esmtp then @esmtp = false retry else raise end end |
EHLO
-kommandoen,
som ikke er universelt støttet. Hvis tilkoblingsforsøket feiler, setter koden
@esmtp
-variabelen til false
og prøver igjen å koble seg til. Hvis
dette feiler igjen blir unntaket gjenhevet opp til kalleren.
Kernel::raise
-metoden.
raise raise "bad mp3 encoding" raise InterfaceException, "Keyboard failure", caller |
RuntimeError
hvis det ikke er et nåværende unntak).
Dette blir benyttet i unntakshåndterere som trenger å samhandle med et unntak
før det sendes videre.
Den andre formen lager et nytt RuntimeError
-unntak, ved å sette
dens beskjed til den gitte strengen. Dette unntaket blir så hevet oppover i kallstakken som vanlig.
Den tredje formen bruker det første argumentet til å lage et unntak og så setter den
assosierte beskjeden til det andre argumentet og stakksporingen til det tredje argumentet.
Typisk vil det første argumentet være enten navnet på klassen i Exception
-hierarkiet
eller en referanse til en objekt-instans til en av disse klassene. [Teknisk kan dette
argumentet være hvilket som helst objekt som svarer på beskjeden exception
ved å returnere
et objekt slik at object.kind_of?(Exception)
er sann]. Stakksporingen er normalt
produsert ved å bruke
Kernel::caller
-metoden.
Her er noen typiske eksempler på raise
i aksjon.
raise raise "Missing name" if name.nil? if i >= myNames.size raise IndexError, "#{i} >= size (#{myNames.size})" end raise ArgumentError, "Name too big", caller |
raise ArgumentError, "Name too big", caller[1..-1] |
class RetryException < RuntimeError attr :okToRetry def initialize(okToRetry) @okToRetry = okToRetry end end |
def readData(socket) data = socket.read(512) if data.nil? raise RetryException.new(true), "transient read error" end # .. normal prosessering end |
begin stuff = readData(socket) # .. prosesser ting rescue RetryException => detail retry if detail.okToRetry raise end |
raise
og rescue
er perfekt for å avslutte kjøring
når ting går galt, er det noen ganger fint å være i stand til å hoppe ut av en dypt nøstet
konstruksjon under vanlig prosessering. Dette er hvor catch
og throw
er hendige.
catch (:done) do while gets throw :done unless fields = split(/\t/) songList.add(Song.new(*fields)) end songList.play end |
catch
definerer en blokk som er merket med det gitte navnet (som kan være
et Symbol
eller en String
). Denne blokken blir kjøres helt
normalt til en throw
viser seg.
Når Ruby finner en throw
suser den tilbake oppover i kallstakken
og leter etter en catch
med et matchende symbol. Når den finner det,
pakker Ruby opp kallstakken tilbake til det punktet og terminerer blokken. Hvis
throw
blir kalt med det valgfrie andre parameteren blir denne verdien
returnert som verdien til catch
. Så i det tidligere eksempelet, hvis inputen
ikke inneholder korrekt formaterte linjer, vil throw
hoppe til slutten på den
korresponderende catch
, og ikke bare terminere while
-løkken, men også
hoppe over spillingen av sanglisten.
Det følgende eksemplet bruker en throw
til å terminere interaksjonen med bruker
hvis ``!'' blir skrevet som respons på hvilket som helst prompt.
def promptAndGet(prompt) print prompt res = readline.chomp throw :quitRequested if res == "!" return res end catch :quitRequested do name = promptAndGet("Name: ") age = promptAndGet("Age: ") sex = promptAndGet("Sex: ") # .. # process information end |
throw
ikke nødt til å være innenfor
det statiske skopet til catch
.
( In progress translation to Norwegian by NorwayRUG. $Revision: 1.8 $ )
$Log: tut_exceptions.xml,v $ Revision 1.8 2003/08/31 11:58:58 kent Fjerner masse KapitaliseringsMani i overskrifter. Revision 1.7 2002/08/03 10:07:58 kent Første kjappe gjennomgang ferdig. Revision 1.6 2002/08/03 09:36:37 kent Småfiks frem til "Play it again".
Forrige < |
Innhold ^
|
Neste >
|