Wszystko, co chciałeś wiedzieć o wyjątkach
Obsługa błędów jest tylko częścią życia, jeśli chodzi o pisanie kodu. Często możemy sprawdzić i zweryfikować warunki oczekiwanego zachowania. Gdy wystąpi nieoczekiwana sytuacja, zwracamy się do obsługi wyjątków. Możesz łatwo obsługiwać wyjątki generowane przez kod innych osób lub wygenerować własne wyjątki dla innych osób do obsługi.
Uwaga
Oryginalna wersja tego artykułu pojawiła się na blogu napisanym przez @KevinMarquette. Zespół programu PowerShell dziękuje Kevinowi za udostępnienie tej zawartości nam. Zapoznaj się ze swoim blogiem na PowerShellExplained.com.
Podstawowa terminologia
Musimy omówić kilka podstawowych terminów, zanim przejdziemy do tego.
Wyjątek
Wyjątek jest jak zdarzenie, które jest tworzone, gdy normalna obsługa błędów nie może poradzić sobie z problemem. Próba podzielenia liczby przez zero lub brak pamięci to przykłady elementów, które tworzą wyjątek. Czasami autor kodu, którego używasz, tworzy wyjątki dla niektórych problemów, gdy występują.
Zgłaszanie i przechwytywanie
Gdy wystąpi wyjątek, mówimy, że zgłaszany jest wyjątek. Aby obsłużyć zgłoszony wyjątek, należy go przechwycić. Jeśli wyjątek zostanie zgłoszony i nie zostanie przechwycony przez coś, skrypt przestanie działać.
Stos wywołań
Stos wywołań to lista funkcji, które zostały ze sobą wywoływane. Po wywołaniu funkcji zostanie ona dodana do stosu lub górnej części listy. Po zakończeniu lub powrocie funkcji zostanie ona usunięta ze stosu.
Gdy zgłaszany jest wyjątek, ten stos wywołań jest sprawdzany w celu przechwycenia go przez program obsługi wyjątków.
Błędy zakończenia i niepowodujące zakończenia
Wyjątkiem jest zazwyczaj błąd zakończenia. Zgłoszony wyjątek jest przechwycony lub kończy bieżące wykonanie. Domyślnie generowany jest Write-Error
błąd niepowodujący zakończenia i dodaje błąd do strumienia wyjściowego bez zgłaszania wyjątku.
Zwracam to uwagę, ponieważ Write-Error
i inne błędy niezwiązane z kończeniem nie wyzwalają catch
.
Połykanie wyjątku
Dzieje się tak, gdy przechwycisz błąd, aby go pominąć. Zrób to z ostrożnością, ponieważ może to sprawić, że rozwiązywanie problemów jest bardzo trudne.
Podstawowa składnia poleceń
Poniżej przedstawiono krótkie omówienie podstawowej składni obsługi wyjątków używanej w programie PowerShell.
Throw
Aby utworzyć własne zdarzenie wyjątku, zgłaszamy wyjątek ze throw
słowem kluczowym .
function Start-Something
{
throw "Bad thing happened"
}
Spowoduje to utworzenie wyjątku środowiska uruchomieniowego, który jest błędem zakończenia. Jest ona obsługiwana przez catch
funkcję wywołującą lub zamyka skrypt z komunikatem takim jak ten.
PS> Start-Something
Bad thing happened
At line:1 char:1
+ throw "Bad thing happened"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Bad thing happened:String) [], RuntimeException
+ FullyQualifiedErrorId : Bad thing happened
Write-Error -ErrorAction Stop
Wspomniałem, że Write-Error
domyślnie nie zgłasza błędu zakończenia. W przypadku określenia -ErrorAction Stop
parametru Write-Error
program generuje błąd zakończenia, który można obsłużyć za pomocą elementu catch
.
Write-Error -Message "Houston, we have a problem." -ErrorAction Stop
Dziękuję Lee Dailey za przypomnienie o użyciu -ErrorAction Stop
w ten sposób.
Polecenie cmdlet - ErrorAction Stop
Jeśli określisz -ErrorAction Stop
dowolną zaawansowaną funkcję lub polecenie cmdlet, wszystkie Write-Error
instrukcje zostaną przekształcone w błędy zakończenia, które zatrzymają wykonywanie lub które mogą być obsługiwane przez catch
element .
Start-Something -ErrorAction Stop
Aby uzyskać więcej informacji na temat parametru ErrorAction , zobacz about_CommonParameters. Aby uzyskać więcej informacji na temat zmiennej $ErrorActionPreference
, zobacz about_Preference_Variables.
Try/Catch
Sposób, w jaki obsługa wyjątków działa w programie PowerShell (i wielu innych językach), jest to, że najpierw try
wykonujesz sekcję kodu i jeśli zgłasza błąd, możesz catch
to zrobić. Oto szybki przykład.
try
{
Start-Something
}
catch
{
Write-Output "Something threw an exception"
Write-Output $_
}
try
{
Start-Something -ErrorAction Stop
}
catch
{
Write-Output "Something threw an exception or used Write-Error"
Write-Output $_
}
Skrypt catch
jest uruchamiany tylko w przypadku wystąpienia błędu zakończenia. Jeśli polecenie try
zostanie wykonane poprawnie, spowoduje to pominięcie elementu catch
. Dostęp do informacji o wyjątku catch
w bloku można uzyskać przy użyciu zmiennej $_
.
Spróbuj/Na koniec
Czasami nie trzeba obsługiwać błędu, ale nadal trzeba wykonać jakiś kod, jeśli wystąpi wyjątek. Skrypt finally
dokładnie to robi.
Zapoznaj się z poniższym przykładem:
$command = [System.Data.SqlClient.SqlCommand]::New(queryString, connection)
$command.Connection.Open()
$command.ExecuteNonQuery()
$command.Connection.Close()
Za każdym razem, gdy otworzysz zasób lub połączysz się z nim, zamknij go. ExecuteNonQuery()
Jeśli zgłasza wyjątek, połączenie nie zostanie zamknięte. Oto ten sam kod wewnątrz try/finally
bloku.
$command = [System.Data.SqlClient.SqlCommand]::New(queryString, connection)
try
{
$command.Connection.Open()
$command.ExecuteNonQuery()
}
finally
{
$command.Connection.Close()
}
W tym przykładzie połączenie zostanie zamknięte, jeśli wystąpi błąd. Jest również zamykany, jeśli nie ma błędu. Skrypt finally
jest uruchamiany za każdym razem.
Ponieważ nie przechwytujesz wyjątku, nadal jest propagowany stos wywołań.
Try/Catch/Finally
Doskonale ważne jest, aby używać catch
i finally
razem. W większości przypadków będziesz używać jednego lub drugiego, ale możesz znaleźć scenariusze, w których używasz obu tych metod.
$PSItem
Teraz, gdy mamy podstawy z drogi, możemy kopać nieco głębiej.
catch
Wewnątrz bloku znajduje się zmienna automatyczna ($PSItem
lub $_
) typuErrorRecord
, która zawiera szczegółowe informacje o wyjątku. Oto krótkie omówienie niektórych kluczowych właściwości.
W tych przykładach użyto nieprawidłowej ścieżki do ReadAllText
wygenerowania tego wyjątku.
[System.IO.File]::ReadAllText( '\\test\no\filefound.log')
PSItem.ToString()
Zapewnia to najczystszy komunikat do użycia w rejestrowaniu i ogólnych danych wyjściowych. ToString()
jest wywoływana automatycznie, jeśli $PSItem
znajduje się wewnątrz ciągu.
catch
{
Write-Output "Ran into an issue: $($PSItem.ToString())"
}
catch
{
Write-Output "Ran into an issue: $PSItem"
}
$PSItem.InvocationInfo
Ta właściwość zawiera dodatkowe informacje zebrane przez program PowerShell dotyczące funkcji lub skryptu, w którym zgłoszono wyjątek. Oto przykładowy InvocationInfo
wyjątek utworzony przeze mnie.
PS> $PSItem.InvocationInfo | Format-List *
MyCommand : Get-Resource
BoundParameters : {}
UnboundArguments : {}
ScriptLineNumber : 5
OffsetInLine : 5
ScriptName : C:\blog\throwerror.ps1
Line : Get-Resource
PositionMessage : At C:\blog\throwerror.ps1:5 char:5
+ Get-Resource
+ ~~~~~~~~~~~~
PSScriptRoot : C:\blog
PSCommandPath : C:\blog\throwerror.ps1
InvocationName : Get-Resource
Ważne szczegóły pokazują ScriptName
tutaj , Line
kod i ScriptLineNumber
miejsce rozpoczęcia wywołania.
$PSItem.ScriptStackTrace
Ta właściwość pokazuje kolejność wywołań funkcji, które dotarły do kodu, w którym wygenerowano wyjątek.
PS> $PSItem.ScriptStackTrace
at Get-Resource, C:\blog\throwerror.ps1: line 13
at Start-Something, C:\blog\throwerror.ps1: line 5
at <ScriptBlock>, C:\blog\throwerror.ps1: line 18
Wykonujem tylko wywołania funkcji w tym samym skrycie, ale będzie to śledzić wywołania, jeśli wiele skryptów było zaangażowanych.
$PSItem.Exception
Jest to rzeczywisty wyjątek, który został zgłoszony.
$PSItem.Exception.Message
Jest to ogólny komunikat opisujący wyjątek i jest dobrym punktem wyjścia podczas rozwiązywania problemów. Większość wyjątków ma komunikat domyślny, ale może być również ustawiona na coś niestandardowego, gdy wyjątek jest zgłaszany.
PS> $PSItem.Exception.Message
Exception calling "ReadAllText" with "1" argument(s): "The network path was not found."
Jest to również komunikat zwracany podczas wywoływania $PSItem.ToString()
metody , jeśli nie ustawiono go na .ErrorRecord
$PSItem.Exception.InnerException
Wyjątki mogą zawierać wyjątki wewnętrzne. Jest to często przypadek, gdy wywoływany kod przechwytuje wyjątek i zgłasza inny wyjątek. Oryginalny wyjątek jest umieszczany wewnątrz nowego wyjątku.
PS> $PSItem.Exception.InnerExceptionMessage
The network path was not found.
Ponawiam to później, kiedy mówię o ponownym zgłaszaniu wyjątków.
$PSItem.Exception.StackTrace
Jest StackTrace
to wyjątek. Pokazano ScriptStackTrace
powyżej, ale jest to dla wywołań kodu zarządzanego.
at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean
useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs,
String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32
bufferSize, FileOptions options, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean
checkHost)
at System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks,
Int32 bufferSize, Boolean checkHost)
at System.IO.File.InternalReadAllText(String path, Encoding encoding, Boolean checkHost)
at CallSite.Target(Closure , CallSite , Type , String )
Ten ślad stosu jest pobierany tylko wtedy, gdy zdarzenie zostanie wyrzucone z kodu zarządzanego. Wywołujem funkcję .NET Framework bezpośrednio, aby wszystko, co widzimy w tym przykładzie. Ogólnie rzecz biorąc, gdy patrzysz na ślad stosu, szukasz miejsca zatrzymania kodu i rozpoczęcia wywołań systemowych.
Praca z wyjątkami
Istnieje więcej wyjątków niż podstawowa składnia i właściwości wyjątku.
Przechwytywanie wpisanych wyjątków
Można selektywnie wybierać wyjątki przechwytujące. Wyjątki mają typ i można określić typ wyjątku, który chcesz przechwycić.
try
{
Start-Something -Path $path
}
catch [System.IO.FileNotFoundException]
{
Write-Output "Could not find $path"
}
catch [System.IO.IOException]
{
Write-Output "IO error with the file: $path"
}
Typ wyjątku jest sprawdzany dla każdego catch
bloku, dopóki jeden nie zostanie znaleziony, który pasuje do wyjątku.
Należy pamiętać, że wyjątki mogą dziedziczyć z innych wyjątków. W powyższym FileNotFoundException
przykładzie dziedziczy z elementu IOException
. Więc jeśli IOException
był pierwszy, to zostanie wywołany zamiast. Wywoływany jest tylko jeden blok catch, nawet jeśli istnieje wiele dopasowań.
Gdybyśmy mieli System.IO.PathTooLongException
element , IOException
pasowałby, ale gdybyśmy mieli wtedy InsufficientMemoryException
nic by go złapać i będzie propagować stos.
Przechwyć wiele typów jednocześnie
Istnieje możliwość przechwycenia wielu typów wyjątków za pomocą tej samej catch
instrukcji.
try
{
Start-Something -Path $path -ErrorAction Stop
}
catch [System.IO.DirectoryNotFoundException],[System.IO.FileNotFoundException]
{
Write-Output "The path or file was not found: [$path]"
}
catch [System.IO.IOException]
{
Write-Output "IO error with the file: [$path]"
}
Dziękuję Redditor u/Sheppard_Ra
za sugerowanie tego dodatku.
Zgłaszanie wpisanych wyjątków
W programie PowerShell można zgłaszać wyjątki wpisane. Zamiast wywoływać throw
ciąg:
throw "Could not find: $path"
Użyj akceleratora wyjątków w następujący sposób:
throw [System.IO.FileNotFoundException] "Could not find: $path"
Ale musisz określić komunikat, gdy to zrobisz w ten sposób.
Można również utworzyć nowe wystąpienie wyjątku, które ma zostać zgłoszony. Komunikat jest opcjonalny, gdy to zrobisz, ponieważ system ma komunikaty domyślne dla wszystkich wbudowanych wyjątków.
throw [System.IO.FileNotFoundException]::new()
throw [System.IO.FileNotFoundException]::new("Could not find path: $path")
Jeśli nie używasz programu PowerShell 5.0 lub nowszego, musisz użyć starszego New-Object
podejścia.
throw (New-Object -TypeName System.IO.FileNotFoundException )
throw (New-Object -TypeName System.IO.FileNotFoundException -ArgumentList "Could not find path: $path")
Korzystając z wpisanego wyjątku, użytkownik (lub inne) może przechwycić wyjątek według typu, jak wspomniano w poprzedniej sekcji.
Write-Error -Exception
Możemy dodać te typizowane wyjątki i Write-Error
nadal możemy błędy catch
według typu wyjątku. Użyj metody Write-Error
podobnej do w poniższych przykładach:
# with normal message
Write-Error -Message "Could not find path: $path" -Exception ([System.IO.FileNotFoundException]::new()) -ErrorAction Stop
# With message inside new exception
Write-Error -Exception ([System.IO.FileNotFoundException]::new("Could not find path: $path")) -ErrorAction Stop
# Pre PS 5.0
Write-Error -Exception ([System.IO.FileNotFoundException]"Could not find path: $path") -ErrorAction Stop
Write-Error -Message "Could not find path: $path" -Exception (New-Object -TypeName System.IO.FileNotFoundException) -ErrorAction Stop
Następnie możemy go złapać w następujący sposób:
catch [System.IO.FileNotFoundException]
{
Write-Log $PSItem.ToString()
}
Duża lista wyjątków platformy .NET
Skompilowałem listę główną z pomocą społeczności Reddit r/PowerShell
, która zawiera setki wyjątków platformy .NET, aby uzupełnić ten wpis.
Zaczynam od wyszukiwania tej listy wyjątków, które czują, że będą one dobrym rozwiązaniem dla mojej sytuacji. Należy spróbować użyć wyjątków w podstawowej System
przestrzeni nazw.
Wyjątki to obiekty
Jeśli zaczniesz używać wielu typowych wyjątków, pamiętaj, że są to obiekty. Różne wyjątki mają różne konstruktory i właściwości. Jeśli przyjrzymy się dokumentacji FileNotFoundException dla System.IO.FileNotFoundException
programu , zobaczymy, że możemy przekazać komunikat i ścieżkę pliku.
[System.IO.FileNotFoundException]::new("Could not find file", $path)
Ma ona FileName
właściwość, która uwidacznia ścieżkę pliku.
catch [System.IO.FileNotFoundException]
{
Write-Output $PSItem.Exception.FileName
}
Należy zapoznać się z dokumentacją platformy .NET dotyczącą innych konstruktorów i właściwości obiektów.
Ponowne zgłaszanie wyjątku
Jeśli wszystko, co zamierzasz zrobić w catch
bloku, jest throw
tym samym wyjątkiem, nie rób catch
tego. W takim przypadku należy wykonać tylko catch
wyjątek, który planujesz obsłużyć lub wykonać jakąś akcję.
Istnieją czasy, w których chcesz wykonać akcję dla wyjątku, ale ponownie zgłosić wyjątek, aby coś podrzędnego może sobie z tym poradzić. Możemy napisać komunikat lub zarejestrować problem w pobliżu miejsca jego odnalezienia, ale obsłużyć problem dalej w górę stosu.
catch
{
Write-Log $PSItem.ToString()
throw $PSItem
}
Co ciekawe, możemy wywołać metodę throw
z wewnątrz catch
elementu i ponownie zgłasza bieżący wyjątek.
catch
{
Write-Log $PSItem.ToString()
throw
}
Chcemy ponownie zgłosić wyjątek, aby zachować oryginalne informacje o wykonaniu, takie jak skrypt źródłowy i numer wiersza. Jeśli w tym momencie zgłosimy nowy wyjątek, spowoduje to ukrycie miejsca rozpoczęcia wyjątku.
Ponowne zgłaszanie nowego wyjątku
Jeśli przechwycisz wyjątek, ale chcesz zgłosić inny wyjątek, należy zagnieżdżać oryginalny wyjątek wewnątrz nowego. Dzięki temu ktoś w dół stosu może uzyskać do niego dostęp jako .$PSItem.Exception.InnerException
catch
{
throw [System.MissingFieldException]::new('Could not access field',$PSItem.Exception)
}
$PSCmdlet.ThrowTerminatingError()
Jedyną rzeczą, której nie lubię używać throw
dla nieprzetworzonych wyjątków, jest to, że komunikat o błędzie wskazuje na throw
instrukcję i wskazuje, że wiersz polega na tym, że problem jest.
Unable to find the specified file.
At line:31 char:9
+ throw [System.IO.FileNotFoundException]::new()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], FileNotFoundException
+ FullyQualifiedErrorId : Unable to find the specified file.
Komunikat o błędzie informuje mnie, że mój skrypt jest uszkodzony, ponieważ wywołany throw
w wierszu 31 jest złym komunikatem dla użytkowników skryptu, aby zobaczyć. Nie mówi im nic przydatnego.
Dexter Dhami zwrócił uwagę, że mogę użyć ThrowTerminatingError()
, aby to poprawić.
$PSCmdlet.ThrowTerminatingError(
[System.Management.Automation.ErrorRecord]::new(
([System.IO.FileNotFoundException]"Could not find $Path"),
'My.ID',
[System.Management.Automation.ErrorCategory]::OpenError,
$MyObject
)
)
Jeśli zakładamy, że ThrowTerminatingError()
została wywołana wewnątrz funkcji o nazwie Get-Resource
, jest to błąd, który zobaczymy.
Get-Resource : Could not find C:\Program Files (x86)\Reference
Assemblies\Microsoft\Framework\.NETPortable\v4.6\System.IO.xml
At line:6 char:5
+ Get-Resource -Path $Path
+ ~~~~~~~~~~~~
+ CategoryInfo : OpenError: (:) [Get-Resource], FileNotFoundException
+ FullyQualifiedErrorId : My.ID,Get-Resource
Czy widzisz, jak wskazuje Get-Resource
ona funkcję jako źródło problemu? To informuje użytkownika o czymś przydatnym.
Ponieważ $PSItem
element jest elementem ErrorRecord
, możemy również użyć ThrowTerminatingError
tego sposobu, aby ponownie zgłosić.
catch
{
$PSCmdlet.ThrowTerminatingError($PSItem)
}
Spowoduje to zmianę źródła błędu polecenia cmdlet i ukrycie wewnętrznych elementów funkcji od użytkowników polecenia cmdlet.
Spróbuj utworzyć błędy zakończenia
Kirk Munro zwraca uwagę, że niektóre wyjątki kończą błędy tylko w przypadku wykonywania wewnątrz try/catch
bloku. Oto przykład, który dał mi, który generuje podział przez zero wyjątku środowiska uruchomieniowego.
function Start-Something { 1/(1-1) }
Następnie wywołaj go w ten sposób, aby zobaczyć, że generuje błąd i nadal generuje komunikat.
&{ Start-Something; Write-Output "We did it. Send Email" }
Jednak umieszczając ten sam kod wewnątrz try/catch
elementu , widzimy, że coś innego się stanie.
try
{
&{ Start-Something; Write-Output "We did it. Send Email" }
}
catch
{
Write-Output "Notify Admin to fix error and send email"
}
Widzimy, że błąd staje się błędem zakończenia i nie generuje pierwszego komunikatu. To, czego nie lubię, to to, że możesz mieć ten kod w funkcji i działa inaczej, jeśli ktoś używa elementu try/catch
.
Nie wpadłem na problemy z tym samym, ale jest to narożny przypadek, aby być świadomym.
$PSCmdlet.ThrowTerminatingError() wewnątrz funkcji try/catch
Jednym z niuansów $PSCmdlet.ThrowTerminatingError()
jest to, że tworzy błąd zakończenia w poleceniu cmdlet, ale zamienia się w błąd niepowodujący zakończenia po opuszczeniu polecenia cmdlet. Pozostawia to obciążenie obiektu wywołującego funkcji, aby zdecydować, jak obsłużyć błąd. Mogą przekształcić go z powrotem w błąd zakończenia, używając -ErrorAction Stop
lub wywołując go z poziomu try{...}catch{...}
elementu .
Szablony funkcji publicznych
Po raz ostatni miałem sposób z moją rozmową z Kirk Munro było to, że umieszcza try{...}catch{...}
wokół każdego begin
, process
i end
bloku we wszystkich jego zaawansowanych funkcjach. W tych ogólnych blokach catch ma jedną linię używaną $PSCmdlet.ThrowTerminatingError($PSItem)
do radzenia sobie ze wszystkimi wyjątkami pozostawiając swoje funkcje.
function Start-Something
{
[CmdletBinding()]
param()
process
{
try
{
...
}
catch
{
$PSCmdlet.ThrowTerminatingError($PSItem)
}
}
}
Ponieważ wszystko jest w oświadczeniu try
w jego funkcjach, wszystko działa konsekwentnie. Daje to również czyste błędy użytkownikowi końcowemu, który ukrywa kod wewnętrzny z wygenerowanego błędu.
Pułapka
Skupiłem się na try/catch
aspekcie wyjątków. Ale jest jedna starsza funkcja, o której muszę wspomnieć, zanim to podsuniemy.
Element jest trap
umieszczany w skrypcie lub funkcji w celu przechwycenia wszystkich wyjątków występujących w tym zakresie. W przypadku wystąpienia wyjątku kod w obiekcie trap
jest wykonywany, a następnie będzie kontynuowany normalny kod. Jeśli wystąpi wiele wyjątków, pułapka jest wywoływana przez i wyprzeda.
trap
{
Write-Log $PSItem.ToString()
}
throw [System.Exception]::new('first')
throw [System.Exception]::new('second')
throw [System.Exception]::new('third')
Osobiście nigdy nie przyjąłem tego podejścia, ale widzę wartość w skryptach administratora lub kontrolera, które rejestrują wszystkie wyjątki, a następnie nadal są wykonywane.
Uwagi końcowe
Dodanie prawidłowej obsługi wyjątków do skryptów nie tylko sprawia, że są one bardziej stabilne, ale także ułatwia rozwiązywanie problemów z tymi wyjątkami.
Spędziłem dużo czasu na rozmowie throw
, ponieważ jest to podstawowa koncepcja podczas rozmowy o obsłudze wyjątków. Program PowerShell dał nam Write-Error
również obsługę wszystkich sytuacji, w których należy użyć polecenia throw
. Więc nie sądzę, że musisz używać throw
po przeczytaniu tego.
Teraz, po upływie czasu, aby napisać o obsłudze wyjątków w tym szczegółach, zamierzam przełączyć się na użycie Write-Error -Stop
, aby wygenerować błędy w kodzie. Zamierzam również podjąć porady Kirka i sprawić, że ThrowTerminatingError
moja procedura obsługi wyjątków goto dla każdej funkcji.