۶

Exception Handling و Debugging 🐛🔧

وقتشه یاد بگیریم چطور با خطاها مقابله کنیم!

Exception Handling: مدیریت خطاها 🛡️

هر برنامه‌ای ممکنه با خطا مواجه بشه! Exception Handling به ما کمک می‌کنه این خطاها رو مدیریت کنیم تا برنامه کرش نکنه. فکر کنید Exception ها مثل سیستم ایمنی ماشین هستند - وقتی مشکلی پیش میاد، برنامه رو محافظت می‌کنند.

ساختار try-catch-finally

  • try: کدی که ممکن است خطا داشته باشد
  • catch: مدیریت خطاهای خاص
  • finally: کدی که همیشه اجرا می‌شود (حتی در صورت خطا)
مدیریت خطا با try-catch-finally
using System;

class Program
{
    static void Main()
    {
        try
        {
            // کدی که ممکنه خطا داشته باشه
            Console.Write("یک عدد وارد کنید: ");
            string input = Console.ReadLine();
            int number = int.Parse(input); // اگر متن وارد شده عدد نباشه، خطا می‌ده
            
            int result = 100 / number; // اگر عدد صفر باشه، خطا می‌ده
            Console.WriteLine($"نتیجه: {result}");
        }
        catch (FormatException ex)
        {
            // خطای تبدیل متن به عدد
            Console.WriteLine("❌ خطا: لطفاً یک عدد معتبر وارد کنید!");
            Console.WriteLine($"جزئیات: {ex.Message}");
        }
        catch (DivideByZeroException ex)
        {
            // خطای تقسیم بر صفر
            Console.WriteLine("❌ خطا: نمی‌توان بر صفر تقسیم کرد!");
        }
        catch (OverflowException ex)
         {
            // خطای سرریز عدد
            Console.WriteLine("❌ خطا: عدد وارد شده خیلی بزرگ است!");
        }
        catch (Exception ex)
        {
            // سایر خطاها (این باید آخرین catch باشد)
            Console.WriteLine($"❌ خطای غیرمنتظره: {ex.Message}");
        }
        finally
        {
            // این بخش همیشه اجرا می‌شود
            Console.WriteLine("✅ پایان عملیات - منابع پاک شدند.");
        }
    }
}

انواع رایج Exception ها 📋

آشنایی با انواع مختلف Exception ها به شما کمک می‌کند بهتر آنها را مدیریت کنید:

خطاهای ورودی

  • FormatException - تبدیل نادرست
  • ArgumentException - پارامتر نامعتبر
  • ArgumentNullException - پارامتر null

خطاهای ریاضی

  • DivideByZeroException - تقسیم بر صفر
  • OverflowException - سرریز عدد
  • ArithmeticException - خطای ریاضی

خطاهای حافظه

  • NullReferenceException - دسترسی به null
  • IndexOutOfRangeException - ایندکس نامعتبر
  • OutOfMemoryException - کمبود حافظه

خطاهای سیستم

  • FileNotFoundException - فایل پیدا نشد
  • UnauthorizedAccessException - دسترسی غیرمجاز
  • TimeoutException - زمان انتظار تمام شد
مثال کاربردی - مدیریت فایل
using System;
using System.IO;

class FileManager
{
    public static void ReadFile(string filePath)
    {
        try
        {
            string content = File.ReadAllText(filePath);
            Console.WriteLine("محتوای فایل:");
            Console.WriteLine(content);
        }
        catch (FileNotFoundException)
        {
            Console.WriteLine($"❌ فایل '{filePath}' پیدا نشد!");
        }
        catch (UnauthorizedAccessException)
        {
            Console.WriteLine("❌ شما اجازه دسترسی به این فایل را ندارید!");
        }
        catch (DirectoryNotFoundException)
        {
            Console.WriteLine("❌ مسیر مشخص شده وجود ندارد!");
        }
        catch (IOException ex)
        {
            Console.WriteLine($"❌ خطا در خواندن فایل: {ex.Message}");
        }
    }
    
    static void Main()
    {
        ReadFile("test.txt");
        ReadFile("C:\\NonExistent\\file.txt");
    }
}

Exception های سفارشی 🎯

گاهی نیاز داریم Exception های سفارشی بسازیم تا خطاهای خاص برنامه‌مان را بهتر مدیریت کنیم. این کار باعث می‌شود کد ما واضح‌تر و قابل فهم‌تر باشد.

چرا Exception سفارشی بسازیم؟

  • وضوح بیشتر: نام Exception مشخص می‌کند چه مشکلی رخ داده
  • مدیریت بهتر: می‌توانیم هر نوع خطا را جداگانه مدیریت کنیم
  • اطلاعات اضافی: می‌توانیم ویژگی‌های خاص اضافه کنیم
  • حرفه‌ای‌تر: کد ما استاندارد و قابل نگهداری می‌شود
Exception های سفارشی - مثال کامل
using System;

// Exception سفارشی برای سن نامعتبر
public class InvalidAgeException : Exception
{
    public int AttemptedAge { get; }
    
    public InvalidAgeException(int age) 
        : base($"سن نامعتبر: {age}. سن باید بین 0 تا 150 باشد.")
    {
        AttemptedAge = age;
    }
    
    public InvalidAgeException(int age, string message) : base(message)
    {
        AttemptedAge = age;
    }
}

// Exception سفارشی برای ایمیل نامعتبر
public class InvalidEmailException : Exception
{
    public string AttemptedEmail { get; }
    
    public InvalidEmailException(string email) 
        : base($"فرمت ایمیل نامعتبر: {email}")
    {
        AttemptedEmail = email;
    }
}

public class Person
{
    private int _age;
    private string _email;
    
    public int Age
    {
        get { return _age; }
        set
        {
            if (value < 0 || value > 150)
            {
                throw new InvalidAgeException(value);
            }
            _age = value;
        }
    }
    
    public string Email
    {
        get { return _email; }
        set
        {
            if (string.IsNullOrEmpty(value) || !value.Contains("@"))
            {
                throw new InvalidEmailException(value ?? "null");
            }
            _email = value;
        }
    }
    
    public string Name { get; set; }
}

class Program
{
    static void Main()
    {
        try
        {
            Person person = new Person();
            person.Name = "علی";
            
            Console.Write("سن را وارد کنید: ");
            int age = int.Parse(Console.ReadLine());
            person.Age = age;
            
            Console.Write("ایمیل را وارد کنید: ");
            string email = Console.ReadLine();
            person.Email = email;
            
            Console.WriteLine($"✅ اطلاعات ثبت شد: {person.Name}, {person.Age} سال, {person.Email}");
        }
        catch (InvalidAgeException ex)
        {
            Console.WriteLine($"❌ خطای سن: {ex.Message}");
            Console.WriteLine($"سن وارد شده: {ex.AttemptedAge}");
        }
        catch (InvalidEmailException ex)
        {
            Console.WriteLine($"❌ خطای ایمیل: {ex.Message}");
            Console.WriteLine($"ایمیل وارد شده: {ex.AttemptedEmail}");
        }
        catch (FormatException)
        {
            Console.WriteLine("❌ لطفاً یک عدد معتبر برای سن وارد کنید!");
        }
    }
}

تکنیک‌های Debugging 🔍

Debugging هنر پیدا کردن و رفع مشکلات در کد است. با تکنیک‌های مختلف می‌توانیم سریع‌تر مشکلات را شناسایی کنیم.

Console Debugging

  • Console.WriteLine() - نمایش مقادیر
  • Console.ReadKey() - توقف برنامه
  • • رنگ‌بندی پیام‌ها

Debug Class

  • Debug.WriteLine() - فقط در Debug mode
  • Debug.Assert() - بررسی شرایط
  • Debugger.Break() - توقف در debugger
تکنیک‌های Debugging عملی
using System;
using System.Diagnostics;

class Calculator
{
    public static double Divide(double a, double b)
    {
        // Debug: نمایش مقادیر ورودی
        Debug.WriteLine($"🔍 Divide called with a={a}, b={b}");
        
        // Assert: بررسی شرط
        Debug.Assert(b != 0, "مقسوم علیه نمی‌تواند صفر باشد!");
        
        if (b == 0)
        {
            Debug.WriteLine("❌ تقسیم بر صفر تشخیص داده شد!");
            throw new DivideByZeroException("نمی‌توان بر صفر تقسیم کرد");
        }
        
        double result = a / b;
        Debug.WriteLine($"✅ نتیجه محاسبه شد: {result}");
        
        return result;
    }
}

class DebugExample
{
    static void Main()
    {
        // فعال کردن Debug listener برای نمایش در Console
        Debug.Listeners.Add(new ConsoleTraceListener());
        
        try
        {
            Console.WriteLine("=== شروع برنامه ===");
            
            // تست عملیات عادی
            Console.WriteLine($"10 ÷ 2 = {Calculator.Divide(10, 2)}");
            
            // تست با صفر (باعث خطا می‌شود)
            Console.WriteLine($"10 ÷ 0 = {Calculator.Divide(10, 0)}");
        }
        catch (DivideByZeroException ex)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine($"❌ خطا: {ex.Message}");
            Console.ResetColor();
            
            // Log کردن خطا با جزئیات
            Debug.WriteLine($"Exception Details: {ex}");
        }
        finally
        {
            Console.WriteLine("=== پایان برنامه ===");
        }
        
        Console.WriteLine("\nPress any key to exit...");
        Console.ReadKey();
    }
}

💡 نکات مهم Debugging

  • • همیشه پیام‌های Debug را واضح و مفید بنویسید
  • • از رنگ‌های مختلف برای انواع پیام‌ها استفاده کنید
  • • Debug.WriteLine فقط در حالت Debug کار می‌کند
  • • در نسخه نهایی، کدهای Debug حذف می‌شوند

👤 مثال اضافی: کلاس Person با Debugging

در این مثال، یک کلاس Person ایجاد می‌کنیم که شامل Exception Handling و Debugging است.

// کلاس Person با اعتبارسنجی سن
public class Person
{
    private int _age;
    
    public int Age
    {
        get { return _age; }
        set
        {
            if (value < 0 || value > 150)
            {
                throw new InvalidAgeException($"سن نامعتبر: {value}");
            }
            _age = value;
        }
    }
    
    public string Name { get; set; }
}

class Program
{
    static void Main()
    {
        try
        {
            Person person = new Person();
            person.Name = "علی";
            
            // استفاده از Debug برای نمایش اطلاعات
            Debug.WriteLine($"در حال تنظیم سن برای {person.Name}");
            
            Console.Write("سن را وارد کنید: ");
            int age = int.Parse(Console.ReadLine());
            
            person.Age = age; // ممکن است Exception سفارشی پرتاب کند
            
            Console.WriteLine($"سلام {person.Name}، شما {person.Age} سال دارید.");
        }
        catch (InvalidAgeException ex)
        {
            Console.WriteLine($"خطای سن: {ex.Message}");
            // Log کردن خطا برای بررسی بعدی
            Debug.WriteLine($"خطای سن رخ داد: {ex}");
        }
        catch (FormatException)
        {
            Console.WriteLine("لطفاً یک عدد معتبر وارد کنید!");
        }
        
        // نمایش اطلاعات سیستم برای Debugging
        Console.WriteLine($"\nاطلاعات سیستم:");
        Console.WriteLine($"زمان اجرا: {DateTime.Now}");
        Console.WriteLine($"نسخه .NET: {Environment.Version}");
    }
}

تمرین‌های عملی! 🧠💪

تمرین ۱: سیستم مدیریت حساب بانکی 🏦

یک کلاس BankAccount بسازید که شامل موارد زیر باشد:

  • Exception های سفارشی: InsufficientFundsException و InvalidAmountException
  • متدهای Deposit و Withdraw با مدیریت خطا
  • محدودیت حداقل موجودی و حداکثر مبلغ تراکنش
  • سیستم لاگ برای تمام تراکنش‌ها

جواب تمرین ۱

using System;
using System.Collections.Generic;
using System.Diagnostics;

// Exception های سفارشی
public class InsufficientFundsException : Exception
{
    public decimal RequestedAmount { get; }
    public decimal AvailableBalance { get; }
    
    public InsufficientFundsException(decimal requested, decimal available) 
        : base($"موجودی کافی نیست. مبلغ درخواستی: {requested:C}, موجودی: {available:C}")
    {
        RequestedAmount = requested;
        AvailableBalance = available;
    }
}

public class InvalidAmountException : Exception
{
    public decimal AttemptedAmount { get; }
    
    public InvalidAmountException(decimal amount) 
        : base($"مبلغ نامعتبر: {amount:C}. مبلغ باید مثبت و کمتر از 10,000,000 تومان باشد.")
    {
        AttemptedAmount = amount;
    }
}

public class BankAccount
{
    private decimal _balance;
    private List _transactionLog;
    
    public string AccountNumber { get; }
    public string OwnerName { get; }
    public decimal Balance => _balance;
    public decimal MinimumBalance { get; } = 50000; // حداقل موجودی
    public decimal MaxTransactionAmount { get; } = 10000000; // حداکثر تراکنش
    
    public BankAccount(string accountNumber, string ownerName, decimal initialBalance)
    {
        AccountNumber = accountNumber;
        OwnerName = ownerName;
        _balance = initialBalance;
        _transactionLog = new List();
        
        LogTransaction($"حساب ایجاد شد با موجودی اولیه: {initialBalance:C}");
    }
    
    public void Deposit(decimal amount)
    {
        ValidateAmount(amount);
        
        _balance += amount;
        LogTransaction($"واریز: {amount:C} - موجودی جدید: {_balance:C}");
        
        Console.WriteLine($"✅ مبلغ {amount:C} واریز شد. موجودی فعلی: {_balance:C}");
    }
    
    public void Withdraw(decimal amount)
    {
        ValidateAmount(amount);
        
        if (_balance - amount < MinimumBalance)
        {
            throw new InsufficientFundsException(amount, _balance - MinimumBalance);
        }
        
        _balance -= amount;
        LogTransaction($"برداشت: {amount:C} - موجودی جدید: {_balance:C}");
        
        Console.WriteLine($"✅ مبلغ {amount:C} برداشت شد. موجودی فعلی: {_balance:C}");
    }
    
    private void ValidateAmount(decimal amount)
    {
        if (amount <= 0 || amount > MaxTransactionAmount)
        {
            throw new InvalidAmountException(amount);
        }
    }
    
    private void LogTransaction(string transaction)
    {
        string logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {transaction}";
        _transactionLog.Add(logEntry);
        Debug.WriteLine(logEntry);
    }
    
    public void ShowTransactionHistory()
    {
        Console.WriteLine($"\n📋 تاریخچه تراکنش‌های حساب {AccountNumber}:");
        foreach (var transaction in _transactionLog)
        {
            Console.WriteLine(transaction);
        }
    }
}

class Program
{
    static void Main()
    {
        try
        {
            BankAccount account = new BankAccount("123456789", "علی احمدی", 1000000);
            
            Console.WriteLine($"حساب {account.OwnerName} ایجاد شد.");
            Console.WriteLine($"موجودی اولیه: {account.Balance:C}\n");
            
            // تست واریز
            account.Deposit(500000);
            
            // تست برداشت موفق
            account.Withdraw(300000);
            
            // تست برداشت ناموفق (موجودی کافی نیست)
            account.Withdraw(1500000);
        }
        catch (InsufficientFundsException ex)
        {
            Console.WriteLine($"❌ {ex.Message}");
            Console.WriteLine($"مبلغ قابل برداشت: {ex.AvailableBalance:C}");
        }
        catch (InvalidAmountException ex)
        {
            Console.WriteLine($"❌ {ex.Message}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"❌ خطای غیرمنتظره: {ex.Message}");
        }
    }
}

تمرین ۲: سیستم مدیریت فایل با Exception Handling 📁

یک کلاس FileManager بسازید که بتواند:

  • فایل‌ها را بخواند، بنویسد و کپی کند
  • تمام خطاهای مربوط به فایل را مدیریت کند
  • سیستم لاگ برای عملیات‌ها داشته باشد
  • از تکنیک‌های Debugging استفاده کند

جواب تمرین ۲

using System;
using System.IO;
using System.Diagnostics;

public class FileOperationException : Exception
{
    public string FileName { get; }
    public string Operation { get; }
    
    public FileOperationException(string fileName, string operation, string message) 
        : base($"خطا در {operation} فایل '{fileName}': {message}")
    {
        FileName = fileName;
        Operation = operation;
    }
}

public class FileManager
{
    private static void LogOperation(string operation, string details = "")
    {
        string logMessage = $"[{DateTime.Now:HH:mm:ss}] {operation}";
        if (!string.IsNullOrEmpty(details))
            logMessage += $" - {details}";
            
        Debug.WriteLine(logMessage);
        Console.WriteLine($"📝 {logMessage}");
    }
    
    public static string ReadFile(string filePath)
    {
        try
        {
            LogOperation("شروع خواندن فایل", filePath);
            
            if (!File.Exists(filePath))
            {
                throw new FileOperationException(filePath, "خواندن", "فایل وجود ندارد");
            }
            
            string content = File.ReadAllText(filePath);
            LogOperation("فایل با موفقیت خوانده شد", $"تعداد کاراکتر: {content.Length}");
            
            return content;
        }
        catch (UnauthorizedAccessException)
        {
            throw new FileOperationException(filePath, "خواندن", "دسترسی غیرمجاز");
        }
        catch (IOException ex)
        {
            throw new FileOperationException(filePath, "خواندن", ex.Message);
        }
    }
    
    public static void WriteFile(string filePath, string content)
    {
        try
        {
            LogOperation("شروع نوشتن فایل", filePath);
            
            // بررسی مسیر
            string directory = Path.GetDirectoryName(filePath);
            if (!Directory.Exists(directory))
            {
                Directory.CreateDirectory(directory);
                LogOperation("پوشه ایجاد شد", directory);
            }
            
            File.WriteAllText(filePath, content);
            LogOperation("فایل با موفقیت نوشته شد", $"تعداد کاراکتر: {content.Length}");
        }
        catch (UnauthorizedAccessException)
        {
            throw new FileOperationException(filePath, "نوشتن", "دسترسی غیرمجاز");
        }
        catch (IOException ex)
        {
            throw new FileOperationException(filePath, "نوشتن", ex.Message);
        }
    }
    
    public static void CopyFile(string sourcePath, string destinationPath)
    {
        try
        {
            LogOperation("شروع کپی فایل", $"{sourcePath} -> {destinationPath}");
            
            if (!File.Exists(sourcePath))
            {
                throw new FileOperationException(sourcePath, "کپی", "فایل مبدأ وجود ندارد");
            }
            
            File.Copy(sourcePath, destinationPath, true);
            LogOperation("فایل با موفقیت کپی شد");
        }
        catch (UnauthorizedAccessException)
        {
            throw new FileOperationException(destinationPath, "کپی", "دسترسی غیرمجاز");
        }
        catch (IOException ex)
        {
            throw new FileOperationException(destinationPath, "کپی", ex.Message);
        }
    }
}

class Program
{
    static void Main()
    {
        // فعال کردن Debug output
        Debug.Listeners.Add(new ConsoleTraceListener());
        
        try
        {
            Console.WriteLine("=== تست سیستم مدیریت فایل ===");
            
            // تست نوشتن فایل
            string testFile = "test.txt";
            string content = "سلام! این یک فایل تست است.\nخط دوم فایل.";
            
            FileManager.WriteFile(testFile, content);
            
            // تست خواندن فایل
            string readContent = FileManager.ReadFile(testFile);
            Console.WriteLine($"\n📄 محتوای فایل:\n{readContent}\n");
            
            // تست کپی فایل
            FileManager.CopyFile(testFile, "backup_test.txt");
            
            // تست خطا - خواندن فایل غیرموجود
            FileManager.ReadFile("nonexistent.txt");
        }
        catch (FileOperationException ex)
        {
            Console.WriteLine($"❌ {ex.Message}");
            Console.WriteLine($"فایل: {ex.FileName}, عملیات: {ex.Operation}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"❌ خطای غیرمنتظره: {ex.Message}");
        }
        finally
        {
            Console.WriteLine("\n=== پایان تست ===");
        }
    }
}

💡 نکات مهم برای حل تمرین‌ها

  • • همیشه Exception های خاص را قبل از Exception عمومی catch کنید
  • • از finally برای پاک کردن منابع استفاده کنید
  • • پیام‌های خطا را واضح و کاربرپسند بنویسید
  • • از Debug.WriteLine برای ردیابی مشکلات استفاده کنید
  • • Exception های سفارشی باید اطلاعات مفید ارائه دهند