استثناها Exceptions در زبان برنامه نویسی Kotlin

استثناها Exceptions در زبان برنامه نویسی Kotlin
در این پست می‌خوانید:

مدیریت خطا یکی از بخش‌های حیاتی در توسعه نرم‌افزارهای پایدار و قابل اعتماد است. در زبان برنامه‌نویسی Kotlin، مکانیزم استثناها (Exceptions) نقش مهمی در شناسایی، کنترل و مدیریت شرایط غیرمنتظره‌ای ایفا می‌کند که ممکن است در زمان اجرای برنامه رخ دهند. آشنایی با نحوه تعریف، پرتاب و مدیریت استثناها به برنامه‌نویسان کمک می‌کند تا کدهایی ایمن‌تر، خواناتر و قابل نگهداری‌تر بنویسند. در این مقاله، به بررسی مفهوم استثناها در Kotlin، انواع آن‌ها و بهترین روش‌های استفاده از آن‌ها خواهیم پرداخت تا بتوانید خطاها را به شکلی اصولی و حرفه‌ای مدیریت کنید.

استثناها در کاتلین

برای اینکه بفهمیم دقیقاً استثناها چی‌کار می‌کنن، اول باید ببینیم از کجا اومدن. استثناها از زبان Java به Kotlin اومدن. البته ماجرای استثناها توی جاوا خودش داستانی داره که خلاصه‌ش رو برات می‌گم 👇

منشأ ماجرا استثناها 

جاوا یه مفهوم خاص داره به اسم «استثناهای بررسی‌شده» (checked exceptions) که هدفش این بود جلوی کدهای طولانی و دردسرساز برای کنترل خطاها رو بگیره.
قبل از جاوا، مثلاً توی زبان C، برای کنترل خطاها باید با دست تمام شرایط رو چک می‌کردی، مثل این مثال:

file = fopen("file.txt", "r");
if (file == NULL) {
    // handle error & return
}
// کار با فایل و چک‌کردن خطا بعد از هر عملیات

هر بار که کاری انجام می‌دادی (مثلاً خوندن از فایل یا ارتباط شبکه)، باید خودت با شرط‌ها چک می‌کردی که خطا پیش نیومده باشه. این کار هم خسته‌کننده بود، هم راحت فراموش می‌شد، هم سخت دیباگ می‌شد.

جاوا اومد گفت: «بیاید این کارا رو خود زبان برامون هندل کنه».
برای همین مفهوم checked exceptions رو معرفی کرد. مثلا موقع کار با فایل‌ها می‌گفت:

file = FileInputStream("file.txt"); // throws IOException

یعنی اگه خطایی از نوع IOException پیش بیاد، یا باید خودت توی کدت هندلش کنی (با try/catch)، یا باید اعلام کنی که این تابع ممکنه اون خطا رو “پرت” کنه.
به‌ظاهر خیلی تمیز و اصولی بود، چون دیگه مجبور نبودی برای هر خطای کوچیک کد تکراری بنویسی.

مشکلات استثنا ها چی بودند

ولی با گذشت زمان، مشکلات checked exceptions بیشتر و بیشتر شدن. مثلاً:
– بعضی API‌ها مثل `ByteArrayInputStream` خطایی رو اعلام می‌کردن (`IOException`) که عملاً هیچ‌وقت اتفاق نمی‌افتاد.
– بعضی توسعه‌دهنده‌ها ازش توی طراحی APIها سوءاستفاده می‌کردن و لیست بلندبالایی از استثناها درست می‌کردن.
– خیلی‌ها هم استثناها رو توی کد فقط می‌گرفتن و نادیده می‌گرفتن، فقط برای اینکه با یه interface خاص مطابقت پیدا کنه.

در نهایت، جامعه‌ی برنامه‌نویسی جاوا از checked exceptions دل‌چرکین شد و ازش فاصله گرفت. حتی مقاله‌هایی مثل مقاله‌ی معروف «Stephen Colebourne» در سال ۲۰۱۰ به‌شدت علیه checked exceptions نوشته شدن.

ضربه‌ی آخر وقتی خورد که Lambda ها وارد جاوا 8 شدن (سال ۲۰۱۴).
استفاده از checked exceptions توی lambda و Streamها خیلی سخت و دست‌وپاگیر بود، برای همین کلاً از طراحی نهایی حذف شد.
در واقع، checked exceptions با کد فانکشنال (توابع مرتبه بالا و غیره) خوب ترکیب نمی‌شن.
به همین دلیل هیچ زبان مدرن مثل Kotlin یا Scala دیگه checked exceptions رو پشتیبانی نکرد.

الان دیگه همگان قبول دارن که checked exceptions یه شکست در طراحی زبان بودن.

Exceptions در Kotlin چطور کار می‌کنن؟

کاتلین مفهوم Exception رو از جاوا به ارث برده، چون باید بتونه با کتابخونه‌های JVM کار کنه، ولی checked exceptions رو حذف کرده چون یاد گرفته که اون روش دیگه کارایی نداره.

توی Kotlin همچنان چیزی به اسم “استثنا” داریم، اما نحوه‌ی استفاده‌اش فرق داره.

مثلاً وقتی با APIهای جاوا کار می‌کنی که از checked exception برای بازگردوندن نتیجه استفاده می‌کنن، باید بدونی چجوری باهاش کنار بیای و اصلاً استثناها توی Kotlin باید برای چی استفاده بشن !

استثنا ها در کالتین Exception in kotlin

1 – برای خطاهای منطقی در برنامه

اصلی‌ترین کاربرد exceptions در Kotlin اینه که خطاهای منطقی توی کدت رو بگیره.
استثناها برای بررسی شرط‌هایی استفاده می‌شن که سیستم تایپ (type system) نمی‌تونه موقع کامپایل چک کنه.

مثلاً اگه تابعی داری که مقدار سفارش رو آپدیت می‌کنه و باید همیشه مثبت باشه، می‌نویسی:

fun updateOrderQuantity(orderId: OrderId, quantity: Int) {
    require(quantity > 0) { "Quantity must be positive" }
    // ادامه کد
}

اگه مقدار منفی یا صفر بدی، Exception پرتاب می‌شه و یعنی یه جا توی کدت اشتباه منطقی داری.

در کل، نباید توی کد معمولی Kotlin استثناها رو با `try/catch` بگیری.
اگه این کار رو کردی، نشونه‌ی یه بوی بد در طراحی کدته (code smell).
استثناها باید توسط بخشی در سطح بالای برنامه کنترل شن تا خطاها گزارش بشن و برنامه ری‌استارت بشه.
همین — این هدف اصلی Exceptions در Kotlin هست.

2 – APIهایی با استفاده دوگانه

بعضی APIها شرایط مشخصی ندارن که بشه گفت آیا خطای منطقی هست یا نه؛ بستگی به موقعیت داره.

مثلاً تابع `”123″.toInt()` رو در نظر بگیر:

val number = "123".toInt()

اگه مقدار درست نباشه، NumberFormatException می‌ندازه.
ولی فرض کن رشته از ورودی کاربر اومده و می‌خوای اگه درست نبود، یه مقدار پیش‌فرض بذاری.
اون‌وقت نباید try/catch بنویسی مثل جاوا، چون توی Kotlin این کار اشتباهه.
تابع مخصوصش هست:

val number = string.toIntOrNull() ?: defaultValue

اگه خطا بخوره، مقدارش  null می‌شه و می‌تونی با  ?:  مقدار دیگه‌ای بذاری.

کتابخونه‌ی استاندارد Kotlin پر از این جور تابع‌هاست:
مثلاً  getOrNull()  برای لیست‌ها، که جایگرین گرفتن index اشتباهه بدون پرتاب Exception.

3 – طراحی API در Kotlin

وقتی خودت API می‌نویسی، همین قوانین رو رعایت کن:
– برای خطاهای منطقی از Exception استفاده کن.
– برای بقیه‌ی موارد، از نتیجه‌ی تایپ‌دار استفاده کن (مثل null یا sealed class).

اگه به APIی برخوردی که از Exception برای شرایطی استفاده کرده که توی منطق تو خطا محسوب نمی‌شه، بهتره یه wrapper function بنویسی که Exception رو به نتیجه‌ی مناسب تبدیل کنه و از اون تابع جدید توی کدت استفاده کنی.

برای خطاهای ساده می‌تونی null استفاده کنی، ولی برای چند نوع خطا، کلاس‌های sealed تعریف کن. مثلاً:

sealed class ParsedDate {
    data class Success(val date: Date) : ParsedDate()
    data class Failure(val errorOffset: Int) : ParsedDate()
}

fun DateFormat.tryParse(text: String): ParsedDate =
    try {
        ParsedDate.Success(parse(text))
    } catch (e: ParseException) {
        ParsedDate.Failure(e.errorOffset)
    }

هرجا try/catch  دیدی، با احتیاط نگاهش کن. چون ممکنه داره یه باگ جدی رو پنهان می‌کنه.

4 – ورودی/خروجی (I/O)

بیایم سراغ بزرگ‌ترین چالش — خطاهای ورودی/خروجی.

این خطاها معمولاً تقصیر کد نیستن، بلکه شرایط خارجی‌ان (مثلاً قطع شبکه)، ولی از اون طرف هم نباید برای هر درخواست شبکه کد خطا بنویسیم مثل زبان C.

راه‌حل Kotlin اینه: از Exception برای I/O استفاده کن تا کدت تمیز بمونه، مثل:

fun updateOrderQuantity(orderId: OrderId, quantity: Int) {
    require(quantity > 0) { "Quantity must be positive" }
    val order = loadOrder(orderId)
    order.quantity = quantity
    storeOrder(order)
}

کنترل خطاهای شبکه یا ورودی باید توی یه جای مرکزی کد انجام بشه — مثلاً جایی که داده‌ها به کاربر نشون داده می‌شن یا درخواست به سرور می‌ره.
نه اینکه توی هر تابع جداگانه.

چون استثناها در Kotlin checked نیستن، اگه کد بالا رو ننویسی و خطای شبکه رو هندل نکنی، به‌عنوان خطای منطقی گزارش می‌شه که معمولاً بهترین حالت ممکنه (چون باعث می‌شه باگ‌ها در مراحل توسعه شناسایی شن).

5- Exceptions، async و coroutines

مختصر بگم:

استفاده از استثناها در کدهای غیرهم‌زمان و coroutineها فرقی با حالت معمول نداره.
ولی از اونجا که Kotlin از **Structured Concurrency** استفاده می‌کنه، یه اصل داره:
هیچ استثنایی نباید گم بشه؛ همه باید به سطح بالا برسن و یه جا کنترل شن.

 قانون طلایی
– از exceptions برای بازگردوندن مقدار استفاده نکن.
– try/catch زیاد ننویس.
– خطاها رو به صورت مرکزی هندل کن.
– خطاهای I/O رو به‌صورت یکنواخت مدیریت کن.

امیدوارم این مطلب برای شما مفید باشه 🌸❤️

شاد باشید 😁

دیدگاه‌ها ۰
ارسال دیدگاه جدید