Bagikan melalui


Semua yang ingin Anda ketahui tentang pengecualian

Penanganan kesalahan hanyalah bagian dari kehidupan dalam hal menulis kode. Kami sering dapat memeriksa dan memvalidasi kondisi untuk perilaku yang diharapkan. Ketika hal tak terduga terjadi, kita beralih ke penanganan pengecualian. Anda dapat dengan mudah menangani pengecualian yang dihasilkan oleh kode orang lain atau Anda dapat menghasilkan pengecualian Anda sendiri untuk ditangani orang lain.

Catatan

Versi asli artikel ini muncul di blog yang ditulis oleh @KevinMarquette. Tim PowerShell berterima kasih kepada Kevin karena telah membagikan konten ini kepada kami. Silakan lihat blognya di PowerShellExplained.com.

Istilah dasar

Kita perlu membahas beberapa istilah dasar sebelum kita melompat ke yang satu ini.

Pengecualian

Pengecualian seperti peristiwa yang dibuat saat penanganan kesalahan normal tidak dapat menangani masalah. Mencoba membagi angka dengan nol atau kehabisan memori adalah contoh sesuatu yang menciptakan pengecualian. Terkadang pembuat kode yang Anda gunakan membuat pengecualian untuk masalah tertentu saat terjadi.

Lempar dan Tangkap

Ketika pengecualian terjadi, kami mengatakan bahwa pengecualian dilemparkan. Untuk menangani pengecualian yang dilemparkan, Anda perlu menangkapnya. Jika pengecualian dilemparkan dan tidak tertangkap oleh sesuatu, skrip berhenti dijalankan.

Tumpukan panggilan

Tumpukan panggilan adalah daftar fungsi yang telah saling memanggil. Ketika fungsi dipanggil, fungsi akan ditambahkan ke tumpukan atau bagian atas daftar. Ketika fungsi keluar atau kembali, fungsi dihapus dari tumpukan.

Ketika pengecualian dilemparkan, tumpukan panggilan tersebut diperiksa agar penangan pengecualian menangkapnya.

Mengakhiri dan tidak menghentikan kesalahan

Pengecualian umumnya adalah kesalahan penghentian. Pengecualian yang dilemparkan akan ditangkap atau mengakhiri eksekusi saat ini. Secara default, kesalahan non-penghentian dihasilkan oleh Write-Error dan menambahkan kesalahan ke aliran output tanpa melemparkan pengecualian.

Saya menunjukkan ini karena Write-Error dan kesalahan non-penghentian lainnya tidak memicu catch.

Menelan pengecualian

Ini adalah ketika Anda menangkap kesalahan hanya untuk menekannya. Lakukan ini dengan hati-hati karena dapat membuat masalah pemecahan masalah menjadi sangat sulit.

Sintaks perintah dasar

Berikut adalah gambaran umum singkat sintaks penanganan pengecualian dasar yang digunakan dalam PowerShell.

Lempar

Untuk membuat peristiwa pengecualian kami sendiri, kami melemparkan pengecualian dengan throw kata kunci.

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

Ini membuat pengecualian runtime yang merupakan kesalahan penghentian. Ini ditangani oleh catch dalam fungsi panggilan atau keluar dari skrip dengan pesan seperti ini.

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

Saya menyebutkan bahwa Write-Error tidak memunculkan kesalahan penghentian secara default. Jika Anda menentukan -ErrorAction Stop, Write-Error menghasilkan kesalahan penghentian yang dapat ditangani dengan catch.

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

Terima kasih kepada Lee Dailey untuk mengingatkan tentang menggunakan -ErrorAction Stop cara ini.

Cmdlet -ErrorAction Berhenti

Jika Anda menentukan -ErrorAction Stop pada fungsi atau cmdlet tingkat lanjut, itu mengubah semua Write-Error pernyataan menjadi mengakhiri kesalahan yang menghentikan eksekusi atau yang dapat ditangani oleh catch.

Start-Something -ErrorAction Stop

Untuk informasi selengkapnya tentang parameter ErrorAction , lihat about_CommonParameters. Untuk informasi selengkapnya tentang variabel, $ErrorActionPreference lihat about_Preference_Variables.

Coba/Tangkap

Cara kerja penanganan pengecualian di PowerShell (dan banyak bahasa lainnya) adalah Anda terlebih dahulu try menjadi bagian kode dan jika melemparkan kesalahan, Anda bisa catch melakukannya. Berikut adalah sampel cepat.

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 $_
}

catch Skrip hanya berjalan jika ada kesalahan yang mengakhiri. Jika dijalankan try dengan benar, maka melewati .catch Anda dapat mengakses informasi pengecualian di catch blok menggunakan $_ variabel .

Coba/Akhirnya

Terkadang Anda tidak perlu menangani kesalahan tetapi masih memerlukan beberapa kode untuk dijalankan jika terjadi pengecualian atau tidak. finally Skrip melakukan hal itu.

Lihat contoh ini:

$command = [System.Data.SqlClient.SqlCommand]::New(queryString, connection)
$command.Connection.Open()
$command.ExecuteNonQuery()
$command.Connection.Close()

Setiap kali Anda membuka atau menyambungkan ke sumber daya, Anda harus menutupnya. Jika melemparkan ExecuteNonQuery() pengecualian, koneksi tidak ditutup. Berikut adalah kode yang sama di dalam try/finally blok.

$command = [System.Data.SqlClient.SqlCommand]::New(queryString, connection)
try
{
    $command.Connection.Open()
    $command.ExecuteNonQuery()
}
finally
{
    $command.Connection.Close()
}

Dalam contoh ini, koneksi ditutup jika ada kesalahan. Ini juga ditutup jika tidak ada kesalahan. finally Skrip berjalan setiap saat.

Karena Anda tidak menangkap pengecualian, itu masih akan disebarkan ke tumpukan panggilan.

Coba/Tangkap/Akhirnya

Ini sangat valid untuk digunakan catch dan finally bersama-sama. Sebagian besar waktu Anda akan menggunakan satu atau yang lain, tetapi Anda mungkin menemukan skenario di mana Anda menggunakan keduanya.

$PSItem

Sekarang kita punya dasar-dasar keluar dari jalan, kita bisa menggali sedikit lebih dalam.

catch Di dalam blok, ada variabel otomatis ($PSItem atau $_) jenis ErrorRecord yang berisi detail tentang pengecualian. Berikut adalah gambaran umum singkat tentang beberapa properti utama.

Untuk contoh ini, saya menggunakan jalur ReadAllText yang tidak valid untuk menghasilkan pengecualian ini.

[System.IO.File]::ReadAllText( '\\test\no\filefound.log')

PSItem.ToString()

Ini memberi Anda pesan terbersih untuk digunakan dalam pengelogan dan output umum. ToString() secara otomatis dipanggil jika $PSItem ditempatkan di dalam string.

catch
{
    Write-Output "Ran into an issue: $($PSItem.ToString())"
}

catch
{
    Write-Output "Ran into an issue: $PSItem"
}

$PSItem.InvocationInfo

Properti ini berisi informasi tambahan yang dikumpulkan oleh PowerShell tentang fungsi atau skrip tempat pengecualian dilemparkan. Berikut adalah InvocationInfo dari pengecualian sampel yang saya buat.

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

Detail penting di sini menunjukkan ScriptName, Line kode dan ScriptLineNumber tempat pemanggilan dimulai.

$PSItem.ScriptStackTrace

Properti ini menunjukkan urutan panggilan fungsi yang membawa Anda ke kode tempat pengecualian dibuat.

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

Saya hanya melakukan panggilan ke fungsi dalam skrip yang sama tetapi ini akan melacak panggilan jika beberapa skrip terlibat.

$PSItem.Exception

Ini adalah pengecualian aktual yang dilemparkan.

$PSItem.Exception.Message

Ini adalah pesan umum yang menjelaskan pengecualian dan merupakan titik awal yang baik saat pemecahan masalah. Sebagian besar pengecualian memiliki pesan default tetapi juga dapat diatur ke sesuatu yang kustom saat pengecualian dilemparkan.

PS> $PSItem.Exception.Message

Exception calling "ReadAllText" with "1" argument(s): "The network path was not found."

Ini juga merupakan pesan yang dikembalikan saat memanggil $PSItem.ToString() jika tidak ada satu set pada ErrorRecord.

$PSItem.Exception.InnerException

Pengecualian dapat berisi pengecualian dalam. Ini sering terjadi ketika kode yang Anda panggil menangkap pengecualian dan melemparkan pengecualian yang berbeda. Pengecualian asli ditempatkan di dalam pengecualian baru.

PS> $PSItem.Exception.InnerExceptionMessage
The network path was not found.

Saya akan mengunjungi kembali ini nanti ketika saya berbicara tentang melemparkan kembali pengecualian.

$PSItem.Exception.StackTrace

Ini adalah StackTrace untuk pengecualian. Saya menunjukkan di ScriptStackTrace atas, tetapi yang ini untuk panggilan ke kode terkelola.

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 )

Anda hanya mendapatkan jejak tumpukan ini ketika peristiwa dilemparkan dari kode terkelola. Saya memanggil fungsi .NET framework secara langsung sehingga hanya itu yang dapat kita lihat dalam contoh ini. Umumnya ketika Anda melihat jejak tumpukan, Anda mencari di mana kode Anda berhenti dan panggilan sistem dimulai.

Bekerja dengan pengecualian

Ada lebih banyak pengecualian daripada sintaks dasar dan properti pengecualian.

Menangkap pengecualian yang diketik

Anda dapat selektif dengan pengecualian yang Anda tangkap. Pengecualian memiliki jenis dan Anda dapat menentukan jenis pengecualian yang ingin Anda tangkap.

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"
}

Jenis pengecualian diperiksa untuk setiap catch blok hingga ditemukan yang cocok dengan pengecualian Anda. Penting untuk disadari bahwa pengecualian dapat diwarisi dari pengecualian lain. Dalam contoh di atas, FileNotFoundException mewarisi dari IOException. Jadi jika yang IOException pertama, maka itu akan dipanggil sebagai gantinya. Hanya satu blok tangkapan yang dipanggil bahkan jika ada beberapa kecocokan.

Jika kita memiliki System.IO.PathTooLongException, IOException akan cocok tetapi jika kita memiliki InsufficientMemoryException maka tidak ada yang akan menangkapnya dan itu akan menyebarkan tumpukan.

Menangkap beberapa jenis sekaligus

Dimungkinkan untuk menangkap beberapa jenis pengecualian dengan pernyataan yang sama 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]"
}

Terima kasih Redditor u/Sheppard_Ra telah menyarankan penambahan ini.

Melemparkan pengecualian yang diketik

Anda dapat melemparkan pengecualian yang diketik di PowerShell. Alih-alih memanggil throw dengan string:

throw "Could not find: $path"

Gunakan akselerator pengecualian seperti ini:

throw [System.IO.FileNotFoundException] "Could not find: $path"

Tetapi Anda harus menentukan pesan ketika Anda melakukannya dengan cara itu.

Anda juga dapat membuat instans baru pengecualian yang akan dilemparkan. Pesan bersifat opsional ketika Anda melakukan ini karena sistem memiliki pesan default untuk semua pengecualian bawaan.

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

Jika Anda tidak menggunakan PowerShell 5.0 atau yang lebih tinggi, Anda harus menggunakan pendekatan yang lebih New-Object lama.

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

Dengan menggunakan pengecualian yang diketik, Anda (atau orang lain) dapat menangkap pengecualian berdasarkan jenis seperti yang disebutkan di bagian sebelumnya.

Write-Error -Exception

Kita dapat menambahkan pengecualian yang diketik ini ke Write-Error dan kita masih catch dapat kesalahan berdasarkan jenis pengecualian. Gunakan Write-Error seperti dalam contoh ini:

# 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

Lalu kita bisa menangkapnya seperti ini:

catch [System.IO.FileNotFoundException]
{
    Write-Log $PSItem.ToString()
}

Daftar besar pengecualian .NET

Saya menyusun daftar master dengan bantuan komunitas Reddit r/PowerShell yang berisi ratusan pengecualian .NET untuk melengkapi posting ini.

Saya mulai dengan mencari daftar itu untuk pengecualian yang terasa cocok untuk situasi saya. Anda harus mencoba menggunakan pengecualian di namespace dasar System .

Pengecualian adalah objek

Jika Anda mulai menggunakan banyak pengecualian yang diketik, ingatlah bahwa itu adalah objek. Pengecualian yang berbeda memiliki konstruktor dan properti yang berbeda. Jika kita melihat dokumentasi FileNotFoundException untuk System.IO.FileNotFoundException, kita melihat bahwa kita dapat meneruskan pesan dan jalur file.

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

Dan memiliki FileName properti yang mengekspos jalur file tersebut.

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

Anda harus berkonsultasi dengan dokumentasi .NET untuk konstruktor dan properti objek lainnya.

Melemparkan kembali pengecualian

Jika semua yang akan Anda lakukan di blok Anda catch adalah throw pengecualian yang sama, maka jangan catch . Anda seharusnya hanya catch pengecualian yang Anda rencanakan untuk menangani atau melakukan beberapa tindakan saat itu terjadi.

Ada kalanya Anda ingin melakukan tindakan pada pengecualian tetapi melemparkan kembali pengecualian sehingga sesuatu di hilir dapat menanganinya. Kita dapat menulis pesan atau mencatat masalah yang dekat dengan tempat kami menemukannya tetapi menangani masalah lebih jauh ke tumpukan.

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

Cukup menarik, kita dapat memanggil throw dari dalam catch dan melemparkan kembali pengecualian saat ini.

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

Kami ingin melemparkan kembali pengecualian untuk mempertahankan informasi eksekusi asli seperti skrip sumber dan nomor baris. Jika kita melemparkan pengecualian baru pada saat ini, pengecualian akan bersembunyi di mana pengecualian dimulai.

Melemparkan kembali pengecualian baru

Jika Anda menangkap pengecualian tetapi Anda ingin melemparkan yang berbeda, maka Anda harus menyarangkan pengecualian asli di dalam yang baru. Ini memungkinkan seseorang menurunkan tumpukan untuk mengaksesnya sebagai $PSItem.Exception.InnerException.

catch
{
    throw [System.MissingFieldException]::new('Could not access field',$PSItem.Exception)
}

$PSCmdlet.ThrowTerminatingError()

Satu hal yang tidak saya sukai tentang penggunaan throw untuk pengecualian mentah adalah bahwa pesan kesalahan menunjuk pada throw pernyataan dan menunjukkan bahwa baris adalah tempat masalahnya.

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.

Memiliki pesan kesalahan memberi tahu saya bahwa skrip saya rusak karena saya memanggil throw baris 31 adalah pesan buruk bagi pengguna skrip Anda untuk melihat. Ini tidak memberi tahu mereka sesuatu yang berguna.

Dexter Dhami menunjukkan bahwa saya dapat menggunakan ThrowTerminatingError() untuk memperbaiki itu.

$PSCmdlet.ThrowTerminatingError(
    [System.Management.Automation.ErrorRecord]::new(
        ([System.IO.FileNotFoundException]"Could not find $Path"),
        'My.ID',
        [System.Management.Automation.ErrorCategory]::OpenError,
        $MyObject
    )
)

Jika kita berasumsi bahwa ThrowTerminatingError() itu dipanggil di dalam fungsi yang disebut Get-Resource, maka ini adalah kesalahan yang akan kita lihat.

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

Apakah Anda melihat bagaimana hal itu menunjuk ke Get-Resource fungsi sebagai sumber masalah? Itu memberi tahu pengguna sesuatu yang berguna.

Karena $PSItem adalah ErrorRecord, kita juga dapat menggunakan ThrowTerminatingError cara ini untuk melemparkan kembali.

catch
{
    $PSCmdlet.ThrowTerminatingError($PSItem)
}

Ini mengubah sumber kesalahan ke Cmdlet dan menyembunyikan internal fungsi Anda dari pengguna Cmdlet Anda.

Coba dapat membuat kesalahan penghentian

Kirk Munro menunjukkan bahwa beberapa pengecualian hanya mengakhiri kesalahan saat dijalankan di dalam try/catch blok. Berikut adalah contoh yang ia berikan kepada saya yang menghasilkan pengecualian runtime dibagi dengan nol.

function Start-Something { 1/(1-1) }

Kemudian panggil seperti ini untuk melihatnya menghasilkan kesalahan dan masih menghasilkan pesan.

&{ Start-Something; Write-Output "We did it. Send Email" }

Tetapi dengan menempatkan kode yang sama di dalam try/catch, kita melihat sesuatu yang lain terjadi.

try
{
    &{ Start-Something; Write-Output "We did it. Send Email" }
}
catch
{
    Write-Output "Notify Admin to fix error and send email"
}

Kami melihat kesalahan menjadi kesalahan yang mengakhiri dan tidak menghasilkan pesan pertama. Apa yang tidak saya sukai dari yang satu ini adalah Anda dapat memiliki kode ini dalam fungsi dan bertindak secara berbeda jika seseorang menggunakan try/catch.

Saya belum mengalami masalah dengan ini sendiri tetapi itu adalah kasus sudut yang harus diperhatikan.

$PSCmdlet.ThrowTerminatingError() di dalam try/catch

Salah satu nuansanya $PSCmdlet.ThrowTerminatingError() adalah menciptakan kesalahan yang mengakhiri dalam Cmdlet Anda tetapi berubah menjadi kesalahan yang tidak mengakhiri setelah meninggalkan Cmdlet Anda. Ini meninggalkan beban pada pemanggil fungsi Anda untuk memutuskan cara menangani kesalahan. Mereka dapat mengubahnya kembali menjadi kesalahan yang mengakhiri dengan menggunakan -ErrorAction Stop atau memanggilnya dari dalam try{...}catch{...}.

Templat fungsi publik

Satu terakhir mengambil cara saya dengan percakapan saya dengan Kirk Munro adalah bahwa ia menempatkan sekitar try{...}catch{...} setiap begin, process dan end blok di semua fungsi lanjutannya. Dalam blok tangkapan generik, ia memiliki satu baris menggunakan $PSCmdlet.ThrowTerminatingError($PSItem) untuk menangani semua pengecualian meninggalkan fungsinya.

function Start-Something
{
    [CmdletBinding()]
    param()

    process
    {
        try
        {
            ...
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError($PSItem)
        }
    }
}

Karena semuanya dalam pernyataan try dalam fungsinya, semuanya bertindak secara konsisten. Ini juga memberikan kesalahan bersih kepada pengguna akhir yang menyembunyikan kode internal dari kesalahan yang dihasilkan.

Jerat

Saya berfokus pada try/catch aspek pengecualian. Tapi ada satu fitur warisan yang perlu saya sebutkan sebelum kita membungkus ini.

ditempatkan trap dalam skrip atau fungsi untuk menangkap semua pengecualian yang terjadi dalam cakupan tersebut. Ketika pengecualian terjadi, kode dalam trap dijalankan dan kemudian kode normal berlanjut. Jika beberapa pengecualian terjadi, maka perangkap dipanggil berulang-berulang.

trap
{
    Write-Log $PSItem.ToString()
}

throw [System.Exception]::new('first')
throw [System.Exception]::new('second')
throw [System.Exception]::new('third')

Saya pribadi tidak pernah mengadopsi pendekatan ini tetapi saya dapat melihat nilai dalam skrip admin atau pengontrol yang mencatat pengecualian apa pun dan semua, kemudian masih terus dijalankan.

Menutup komentar

Menambahkan penanganan pengecualian yang tepat ke skrip Anda tidak hanya membuatnya lebih stabil, tetapi juga memudahkan Anda untuk memecahkan masalah pengecualian tersebut.

Saya menghabiskan banyak waktu berbicara throw karena itu adalah konsep inti ketika berbicara tentang penanganan pengecualian. PowerShell juga memberi kami Write-Error yang menangani semua situasi di mana Anda akan menggunakan throw. Jadi jangan berpikir bahwa Anda perlu menggunakan throw setelah membaca ini.

Sekarang setelah saya meluangkan waktu untuk menulis tentang penanganan pengecualian dalam detail ini, saya akan beralih ke menggunakan Write-Error -Stop untuk menghasilkan kesalahan dalam kode saya. Saya juga akan mengambil saran Kirk dan membuat ThrowTerminatingError handler pengecualian goto saya untuk setiap fungsi.