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.