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 Stopparametru 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 catchelement .

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 ($PSItemlub $_) 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ą ScriptNametutaj , 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.PathTooLongExceptionelement , 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]"
}

/u/Sheppard_Ra Dziękujemy 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ą za pomocą społeczności reddit/r/programu 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.FileNotFoundExceptionprogramu , 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/catchelementu , 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łapki

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.