1. Çağırış steki (Call Stack) ilə tanışlıq
Keçən dərsdə biz çağırış steki barədə qısa danışdıq, indi isə bunu bir az daha detallı öyrənəcəyik.
Təsəvvür elə: istifadəçi tətbiqdə bir düyməni sıxır. Bu düymə OnClick() metodunu ("Düymə sıxıldı") çağırır. O isə öz növbəsində LoadData() (datanı yükləmək), o da — ReadFromFile() (fayldan oxumaq) metodunu çağırır.
Birdən ReadFromFile() içində error baş verir — fayl tapılmır. Kim günahkardır?
Bunu başa düşmək üçün proqram "izləri ilə geri gedir": ReadFromFile() → LoadData() → OnClick(). Bu yol çağırış steki adlanır — elə bil boşqab yığınıdır, ən üstdəki birinci düşür.
Proqram bu yığınla aşağı gedir, ta ki uyğun catch tapana qədər, amma bu zaman yolda olan bütün finally bloklarını icra edir — hər şey bağlansın, sərbəst buraxılsın, təmizlənsin deyə.
Proqramlaşdırmada çağırış steki — kim kimi çağırıb, onu yadda saxlayan siyahıdır ki, birdən nəsə olsa, bu "sorgu yolunu" geri gedib, problemin səbəbini tapa biləsən.
Bu necə işləyir
Proqram işləyəndə, hər metod (və ya funksiya) çağırışı stekə yeni bir "sətir" əlavə edir (siyahıya). Əgər işləmə zamanı exception baş verirsə, .NET-in Exception klası bu stek barədə məlumatı saxlayır: hansı metodlar, hansı ardıcıllıqla çağırılıb və error-a səbəb olub.
2. Çağırış steki nəyə lazımdır
Çağırış steki — çətin error-ları debug edəndə ən yaxşı dostundur.
- O, yalnız nə baş verdi deyil, həm də harada dəqiq və kim günahkardır göstərir.
- Bəzən stekə baxanda təəccüblənirsən ki, proqram necə bu vəziyyətə gəlib çıxıb (xüsusən kimsə səhvən argumentə başqa dəyər veribsə).
Tipik hekayə: Tutaq ki, sənin böyük bir layihən var, metodlar bir-birini onlarla səviyyədə çağırır. Birdən NullReferenceException çıxır, amma başa düşmürsən, proqram ora necə gəlib. Steki açırsan — bütün çağırış zəncirini görürsən və artıq haradan başlamaq lazım olduğunu anlamaq asanlaşır.
Nümunə:
class MyClass
{
public void MethodA() { MethodB(); }
public void MethodB() { MethodC(); }
public void MethodC() {
throw new Exception("Xəta!");
}
public void Main()
{
try
{
MethodA();
}
catch (Exception ex)
{
Console.WriteLine(ex.StackTrace);
}
}
}
3. Öz exception-larını necə yaratmaq olar
Bəzən standart exception-lar bəs etmir
.NET-də bir dəstə standart exception var (ArgumentNullException, InvalidOperationException və s.), amma bəzən bu kifayət etmir:
- Sənin tətbiqində öz "oyun qaydaların" var: məsələn, istifadəçi bir dəfəyə 10-dan çox məhsul ala bilməz, ya da biznes-loqikanda mənfi məbləğ olmamalıdır.
- Proqram loqikasının error-larını "sistem" error-larından ayırmaq istəyirsən.
Burada "özününkünü" yaratmaq istəyi yaranır — axı olar!
using System;
// Öz exception-u: istifadəçi tapılmadı
public class UserNotFoundException : Exception
{
// əsas konstruktor
public UserNotFoundException() : base("İstifadəçi tapılmadı.") { }
// mesajla konstruktor
public UserNotFoundException(string message) : base(message) { }
// mesaj və daxili exception ilə konstruktor
public UserNotFoundException(string message, Exception inner) : base(message, inner) { }
}
Konstruktorlar barədə qısa
- Paramsız — standart mesaj qoyacaq
- İstənilən öz mesajınla — bəzən detallı info vermək istəyirsən
- Daxili exception (inner) ilə — error başqa error-un içində baş veribsə, vacib məlumat itməsin deyə.
Öz exception-un necə istifadə olunur
Tutaq ki, tapşırıqlar idarəetmə tətbiqimizdə istifadəçi axtarış metodunu modelləşdiririk:
using System;
public class UserService
{
public string FindUserNameById(int userId)
{
// "İstifadəçi axtarılır", tapılmasa — exception atırıq
if (userId != 42)
throw new UserNotFoundException($"İstifadəçi id {userId} ilə tapılmadı.");
return "Maksim";
}
}
Əsas proqramda:
// Main-də
var service = new UserService();
try
{
string name = service.FindUserNameById(17);
Console.WriteLine("İstifadəçi adı: " + name);
}
catch (UserNotFoundException ex)
{
Console.WriteLine("İstifadəçi axtarış problemi: " + ex.Message);
// Burada da stek ex.StackTrace ilə əlçatandır
}
Əgər id 42-dən fərqlidirsə, belə çıxacaq:
İstifadəçi axtarış problemi: İstifadəçi id 17 ilə tapılmadı.
4. Öz exception-larını yaratmağa səbəblər
Loqlaşdırma və error-ların bölünməsi
Tutaq ki, tətbiqində bir dəstə fərqli error var və onları fərqli cür işləmək lazımdır. Məsələn, database error-larını "fatal" kimi loqlaşdırırsan, istifadəçi error-larını qırmızı yazı ilə göstərirsən, şəbəkə error-larında isə əməliyyatı təkrar edirsən. Exception tipinə görə qruplaşdırmaq — bunu etmək üçün əla üsuldur.
OOP və irsiyyət
Öz predmeti sahənin error-ları üçün ierarxiya qura bilərsən:
public class MyAppException : Exception { ... }
public class OrderException : MyAppException { ... }
public class ProductException : MyAppException { ... }
public class TooManyItemsInOrderException : OrderException { ... }
İndi MyAppException tutanda bütün predmeti sahənin error-larını işləyə bilərsən, amma "çox böyük sifariş" üçün xüsusi reaksiya istəyirsənsə — ən konkret halı tutursan.
5. Öz exception-larını yaradanda nələri yadda saxlamaq lazımdır
- Exception-u sadəcə exception üçün atma
Öz exception-larını yalnız bu hallarda yarat:- Bu, kodu daha anlaşıqlı edir
- Çağıran kodun onları tutmaq ehtimalı var
- Çağıran koda daha çox info vermək istəyirsən (field/property ilə)
- Yaxşı praktika: Serializasiya
.NET-də standart exception-lar serializasiyanı dəstəkləyir (məsələn, şəbəkə ilə ötürmək üçün). Sadə tətbiqlərdə serializasiya nadir hallarda lazımdır, amma "advanced" hallarda — [Serializable] atributunu əlavə et və serializasiya üçün konstruktor yaz (bax rəsmi sənəd). Tədris nümunələrində bunu etməyə ehtiyac yoxdur, amma işdə — team lead-dən soruş :)
Serializasiya — obyektin saxlanması və ya ötürülməsi üçün (məsələn, fayla və ya şəbəkə ilə) uyğun formata çevrilməsi prosesidir. Serializasiya mövzusunu sonra daha ətraflı öyrənəcəyik.
6. Çağırış stekinin xüsusiyyətləri: harada çaşqınlıq ola bilər
Çağırış steki yalnız exception baş verən nöqtəyə qədər olan yolu göstərir. Əgər exception-u tutub, "daxili" exception-u göstərmədən yeni exception atırsansa (yəni Exception(string, Exception inner) konstruktorundan istifadə etmirsənsə), error-un ilkin səbəbini itirə bilərsən. Buna "stekin gizlədilməsi" deyirlər.
Pis nümunə:
try
{
// Burada nəsə error baş verir
}
catch (Exception ex)
{
throw new Exception("Naməlum xəta baş verdi."); // Keçmiş error-un steki itir!
}
Yaxşı nümunə:
try
{
// Burada nəsə error baş verir
}
catch (Exception ex)
{
throw new Exception("Naməlum xəta baş verdi.", ex); // Orijinal steki saxlayırıq!
}
Onda StackTrace-də həm ilk error-a yol, həm də yeni mesajın qalacaq.
7. Praktik məsləhətlər və tez-tez edilən səhvlər
- Adi loqika üçün exception atmaq lazım deyil (məsələn, "dövrədən çıxmaq" üçün — bunun daha elegant yolları var!).
- Lazım olan exception tipini tut — həmişə Exception tutmaq yaxşı fikir deyil (bəzən "udduğun" error-u tutmamalı idin).
- Çətin error-larda həmişə steki loqlaşdır. Bu, bug axtaranda bir dəstə əsəbi xilas edəcək.
- Daxili exception-lardan istifadə et — bu, error-un ilkin səbəbini itirməməyə kömək edir.
- Öz exception-larını aydın təsvir et — kodu bir il sonra oxuyan başa düşsün ki, bu error niyə çıxıb.
GO TO FULL VERSION