Udostępnij za pośrednictwem


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 prowadzonym 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 powstaje, gdy standardowe mechanizmy obsługi błędów nie mogą 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ą.

Rzucanie i Łapanie

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 wywołują siebie nawzajem. Po wywołaniu funkcji zostanie ona dodana do stosu lub górnej części listy. Po zakończeniu działania lub powrocie funkcji, jest ona usuwana 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ątek jest zazwyczaj błędem krytycznym. Wyjątek, który został zgłoszony, zostaje przechwycony lub kończy bieżące wykonanie. Domyślnie błąd niepowodujący zakończenia jest generowany przez Write-Error i dodaje błąd do strumienia wyjściowego bez zgłaszania wyjątku.

Zwracam to uwagę, ponieważ Write-Error i inne błędy niepowodujące zakończenia nie wyzwalają catch.

Połykanie wyjątku

Dzieje się tak, gdy przechwycisz błąd, aby go stłumić. 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.

Rzucać

Aby utworzyć własne zdarzenie wyjątku, zgłaszamy wyjątek używając słowa kluczowego throw.

function Start-Something
{
    throw "Bad thing happened"
}

Spowoduje to utworzenie wyjątku środowiska uruchomieniowego, który jest krytycznym błędem. Jest on obsługiwany przez catch w funkcji wywołującej 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 Zatrzymaj

Wspomniałem, że Write-Error domyślnie nie zgłasza błędu zakończenia. Jeśli określisz -ErrorAction Stop, Write-Error generuje błąd zakończenia, który można obsłużyć za pomocą catch.

Write-Error -Message "Houston, we have a problem." -ErrorAction Stop

Dziękuję Lee Dailey za przypomnienie o używaniu -ErrorAction Stop w ten sposób.

Polecenie cmdlet -ErrorAction Zatrzymaj

Jeśli określisz -ErrorAction Stop dla dowolnej funkcji zaawansowanej lub polecenia cmdlet, wszystkie instrukcje Write-Error zostaną zamienione na błędy krytyczne, które przerywają wykonanie lub mogą być obsługiwane przez catch.

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 (próba/obsługa błędu)

Sposób, w jaki obsługa wyjątków działa w programie PowerShell (i wielu innych językach), polega na tym, że najpierw wykonujesz fragment kodu oznaczony jako try, a jeśli występuje błąd, możesz go zarządzać sekcją oznaczoną jako catch. 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 try działa poprawnie, pominie on catch. Dostęp do informacji o wyjątku można uzyskać w bloku catch 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.

Przyjrzyj się temu przykładowi:

$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. Jeśli ExecuteNonQuery() zgłosi wyjątek, połączenie nie zostanie zamknięte. Oto ten sam kod w bloku try/finally.

$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. Jeśli nie ma błędu, również jest zamykany. Skrypt finally jest uruchamiany za każdym razem.

Ponieważ nie przechwytujesz wyjątku, nadal jest propagowany w górę stosu wywołań.

Wypróbuj/Łap/W końcu

Całkowicie poprawne jest używanie 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 za sobą, możemy nieco głębiej się wgłębić.

Wewnątrz bloku catch znajduje się zmienna automatyczna ($PSItem lub $_) typu ErrorRecord zawierająca 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 w ReadAllText w celu 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 wynikach. ToString() jest wywoływana automatycznie, jeśli $PSItem jest umieszczana 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 InvocationInfo z utworzonego przykładowego wyjątku.

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, Line kodu i ScriptLineNumber, w którym rozpoczęto wywołanie.

$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

Wykonuję tylko wywołania funkcji w tym samym skrypcie, ale śledziłoby to wywołania, gdyby było zaangażowanych wiele skryptów.

$PSItem.Exception

Jest to rzeczywisty wyjątek, który został wyrzucony.

$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(), 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.

Wrócę do tego później, kiedy będę mówił o ponownym zgłaszaniu wyjątków.

$PSItem.Exception.StackTrace

Jest to StackTrace dla wyjątku. Powyżej pokazano ScriptStackTrace, ale to dotyczy 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 )

Otrzymujesz ten ślad stosu tylko wtedy, gdy zdarzenie zostanie wyrzucone z kodu zarządzanego. Wywołuję funkcję .NET Framework bezpośrednio, więc to wszystko, co możemy zobaczyć w tym przykładzie. Ogólnie rzecz biorąc, gdy analizujesz ślad stosu, szukasz miejsca zakończenia twojego 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ć przechwytywane wyjątki. 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 bloku catch, aż zostanie znaleziony taki, który pasuje do twojego wyjątku. Należy pamiętać, że wyjątki mogą dziedziczyć z innych wyjątków. W powyższym przykładzie FileNotFoundException dziedziczy z IOException. Więc jeśli IOException był pierwszy, to właśnie on zostałby wywołany. Wywoływany jest tylko jeden blok catch, nawet jeśli istnieje wiele dopasowań.

Jeśli mielibyśmy System.IO.PathTooLongException, to IOException pasowałoby, ale gdybyśmy mieli InsufficientMemoryException, to nic by tego nie złapało i będzie się propagować po stosie.

Przechwyć wiele typów jednocześnie

Istnieje możliwość przechwycenia wielu typów wyjątków przy użyciu tej samej instrukcji catch.

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ękujemy Redditorowi 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 za pomocą ciągu:

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łoszone. 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 podejścia New-Object.

throw (New-Object -TypeName System.IO.FileNotFoundException )
throw (New-Object -TypeName System.IO.FileNotFoundException -ArgumentList "Could not find path: $path")

Korzystając z typizowanego wyjątku, użytkownik (lub inni użytkownicy) może przechwycić wyjątek na podstawie typu, jak wspomniano w poprzedniej sekcji.

Write-Error -Exception

Możemy dodać te typizowane wyjątki do Write-Error i nadal możemy catch błędy według typu wyjątku. Użyj Write-Error, jak w następujących 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ą zawierającą setki wyjątków .NET z pomocą społeczności Reddit r/PowerShell, która uzupełnia ten wpis.

Zaczynam od wyszukiwania na tej liście wyjątków, które wydają się odpowiednie 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, zobaczymy, że możemy przekazać komunikat i ścieżkę pliku.

[System.IO.FileNotFoundException]::new("Could not find file", $path)

Ma ona właściwość FileName, która uwidacznia ścieżkę pliku.

catch [System.IO.FileNotFoundException]
{
    Write-Output $PSItem.Exception.FileName
}

Należy zapoznać się z dokumentacją platformy .NET dla innych konstruktorów i właściwości obiektów.

Ponowne zgłaszanie wyjątku

Jeśli wszystko, co zamierzasz zrobić w bloku catch, to throw ten sam wyjątek, to nie catch. Należy catch wyjątek tylko wtedy, gdy planujesz go obsłużyć lub podjąć jakieś działania, kiedy to nastąpi.

Zdarzają się sytuacje, w których chcesz wykonać akcję na wyjątku, ale ponownie zgłosić wyjątek, aby kolejna część procesu mogła sobie z nim poradzić. Możemy napisać wiadomość lub zarejestrować problem blisko miejsca jego odkrycia, ale zająć się nim na wyższym poziomie stosu.

catch
{
    Write-Log $PSItem.ToString()
    throw $PSItem
}

Co ciekawe, możemy wywołać throw z poziomu catch, a ono ponownie zgłosi 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 wyrzucanie nowego wyjątku

Jeśli przechwycisz wyjątek, ale chcesz zgłosić inny, powinieneś umieścić oryginalny wyjątek wewnątrz nowego. Dzięki temu ktoś niżej w hierarchii 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ę przy używaniu throw do surowych wyjątków, jest to, że komunikat o błędzie wskazuje na instrukcję throw i sugeruje, że to właśnie ten wiersz jest źródłem problemu.

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 informujący mnie, że mój skrypt jest uszkodzony, ponieważ wywołałem throw w wierszu 31, to zły komunikat, który widzą użytkownicy skryptu. 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 Get-Resource funkcja wskazuje na źródło problemu? To informuje użytkownika o czymś przydatnym.

Ponieważ $PSItem jest ErrorRecord, możemy również użyć ThrowTerminatingError tą metodą, aby ponownie zgłosić.

catch
{
    $PSCmdlet.ThrowTerminatingError($PSItem)
}

To zmienia źródło błędu na cmdlet i ukrywa wewnętrzne elementy funkcji przed użytkownikami cmdletu.

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 bloku try/catch. 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, 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 krytycznym i nie wyświetla pierwszego komunikatu. To, czego nie lubię, to to, że możesz mieć ten kod w ramach funkcji i zachowuje się inaczej, jeśli ktoś korzysta z try/catch.

Nie napotkałem problemów z tym, ale jest to nietypowy przypadek, na który warto zwrócić uwagę.

$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. To pozostawia wywołującemu funkcję odpowiedzialność za zdecydowanie, jak obsłużyć błąd. Mogą one przekształcić go z powrotem w błąd zakończenia, używając -ErrorAction Stop lub wywołując go z poziomu try{...}catch{...}.

Szablony funkcji publicznych

Jedną z ostatnich obserwacji z mojej rozmowy z Kirkiem Munro było to, że umieszcza on try{...}catch{...} wokół każdego bloku begin, process i end we wszystkich swoich zaawansowanych funkcjach. W tych ogólnych blokach catch ma jedną linię używającą $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 instrukcji try w jego funkcjach, wszystko działa spójnie. To również zapewnia użytkownikowi końcowemu przejrzyste błędy, które ukrywają kod wewnętrzny generowany przez błąd.

Pułapka

Skupiłem się na try/catch aspektach wyjątków. Jest jedna starsza funkcja, o której muszę wspomnieć, zanim to zakończymy.

trap jest umieszczana w skrypcie lub funkcji w celu przechwycenia wszystkich wyjątków występujących w tym zakresie. Gdy wystąpi wyjątek, kod w trap zostanie wykonany, a następnie będzie kontynuowany normalny kod. Jeśli wystąpi wiele wyjątków, pułapka jest wywoływana wielokrotnie.

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 zamykające

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 rozmawiając o throw, ponieważ jest to podstawowa koncepcja, gdy mowa o obsłudze wyjątków. Program PowerShell dał nam również Write-Error, który obsługuje wszystkie sytuacje, w których należy użyć throw. Więc nie sądzę, że musisz używać throw po przeczytaniu tego.

Teraz, gdy poświęciłem czas na napisanie o obsłudze wyjątków w takich szczegółach, zamierzam przejść do korzystania z Write-Error -Stop w celu generowania błędów w moim kodzie. Zamierzam również podjąć porady Kirka i zrobić ThrowTerminatingError mój program obsługi wyjątków goto dla każdej funkcji.