Try-Catch: ماهیت، ساختار catch استثنا و نحوه کار در توسعه موبایل

نویسنده: IT Sectr منتشر شده: 2026-05-25 زمان مطالعه: 9 دقیقه

Try-Catch — ساختاری برای catch استثنا است که امکان اجرای کد بالقوه خطرناک در یک بلوک محافظت‌شده و مدیریت صحیح خطاها بدون پایان غیرمنتظره برنامه را فراهم می‌کند. بلوک try حاوی کدی است که ممکن است استثنا پرتاب کند، catch آن را گرفته و منطق بازیابی را اجرا می‌کند. طبق Apple Swift Documentation (2026)، بلوک finally صرف‌نظر از اینکه استثنا پرتاب شده باشد یا خیر اجرا می‌شود و آزادسازی منابع را تضمین می‌کند.

نکات اصلی

  • Try-Catch — ساختاری برای catch استثنا که از پایان غیرمنتظره برنامه هنگام بروز خطا جلوگیری می‌کند
  • بلوک try حاوی کدی است که ممکن است استثنا پرتاب کند — اجرا در اولین خطا متوقف می‌شود
  • بلوک catch استثنا از نوع مشخص شده را گرفته و منطق مدیریت یا بازیابی را اجرا می‌کند
  • بلوک finally پس از try/catch برای آزادسازی منابع، مثلاً بستن فایل‌ها، به طور تضمینی اجرا می‌شود
  • try-catch تودرتو امکان مدیریت خطاها در سطوح مختلف انتزاع در داخل یک تابع را فراهم می‌کند

Try-Catch چیست؟

Try-Catch — ساختار بنیادین مدیریت ساختاریافته استثنا است که در اکثر زبان‌های برنامه‌نویسی مدرن وجود دارد. از سه بلوک تشکیل شده است: try (تلاش برای اجرای کد خطرناک)، catch (گرفتن و مدیریت استثنا) و finally اختیاری (پایانی). ایده ساختار جداسازی منطق عملیات تجاری از منطق مدیریت خطا است که کد را خواناتر و قابل پیش‌بینی‌تر می‌کند.

این مفهوم ابتدا در زبان C++ به صورت try/catch پیاده‌سازی شد، سپس توسط Java، C#، Swift، Kotlin، Dart، Python، JavaScript و سایر زبان‌ها به عاریت گرفته شد. هر زبان ویژگی‌های خود را اضافه می‌کند: در Swift بلوک catch باید جامع باشد، در Kotlin try-catch می‌تواند عبارت (expression) باشد، در Dart finally برای منابع جریانی اجباری است. با وجود تفاوت‌ها، اصل اساسی یکی است: خطا در نزدیک‌ترین نقطه به محل وقوع مدیریت می‌شود، نه به صورت سراسری.

استفاده از Try-Catch به ویژه در توسعه موبایل اهمیت دارد، جایی که عوامل خارجی — از دست دادن شبکه، پاسخ نادرست سرور، کمبود حافظه — دائماً رخ می‌دهند. مدیریت صحیح استثناها از کرش برنامه جلوگیری می‌کند و UX مناسب را تضمین می‌کند: کاربر به جای بسته شدن ناگهانی برنامه، پیام خطا دریافت می‌کند. طبق Google Android Kotlin Style Guide (2026)، هر تابعی که می‌تواند استثنا پرتاب کند باید یا آن را از طریق try-catch مدیریت کند یا throws را در امضای خود اعلام کند.

Try-Catch چگونه کار می‌کند

مکانیزم اجرای try-catch بر اساس باز شدن پشته (stack unwinding) است. هنگامی که در داخل بلوک try یک استثنا توسط عملگر throw (یا در نتیجه خطای سیستمی) پرتاب می‌شود، جریان عادی اجرا بلافاصله قطع می‌شود. اجرا به سطح بالاتری در پشته فراخوانی برای یافتن بلوک catch مناسب حرکت می‌کند. زبان‌های مدرن به دنبال catch با نوع منطبق با نوع استثنای پرتاب شده می‌گردند و از مکانیزم تطبیق نوع (type matching) استفاده می‌کنند.

اگر catch مناسب پیدا شود، بدنه آن اجرا می‌شود و سپس اجرا پس از کل ساختار try-catch-finally ادامه می‌یابد. اگر catch پیدا نشود، استثنا در پشته بالاتر می‌رود و ممکن است در سطح بالاتری مدیریت شود — تا handler سراسری که در برنامه موبایل یک دیالوگ خطا به کاربر نشان می‌دهد. اگر استثنا در هیچ کجا مدیریت نشود، برنامه به طور غیرمنتظره پایان می‌یابد. به همین دلیل مدیریت صحیح همه انواع ممکن استثنا برای پایداری برنامه حیاتی است.

سینتکس ساختار پایه

kotlin
fun readUserData(): User {
    return try {
        val response = api.fetchUser()
        parseUser(response)
    } catch (e: IOException) {
        logError("خطای شبکه", e)
        throw AppException("بارگیری داده‌ها ممکن نشد")
    } catch (e: JsonParseException) {
        logError("خطای تجزیه", e)
        return User.default()
    } finally {
        closeLoadingIndicator()
    }
}

کد ابتدا سعی می‌کند درخواست API را اجرا کرده و پاسخ را parse کند. اگر IOException (مشکل شبکه) رخ دهد، استثنا لاگ شده و به عنوان AppException به بیرون پرتاب می‌شود. اگر JsonParseException باشد — کاربر پیش‌فرض بازگردانده می‌شود. بلوک finally به طور تضمینی نشانگر بارگذاری را مخفی می‌کند و از نشت کامپوننت‌های UI روی صفحه جلوگیری می‌کند.

Try-Catch در Swift

در Swift مدیریت خطا از طریق پروتکل Error (قبلاً ErrorType) پیاده‌سازی شده است. هر نوع منطبق با Error می‌تواند توسط عملگر throw پرتاب شود. تابعی که می‌تواند خطا پرتاب کند با کلمه کلیدی throws در امضا مشخص می‌شود. فراخوانی چنین تابعی به پیشوند try (برای try-catch صریح)، try? (نتیجه اختیاری) یا try! (اجرای اجباری بدون مدیریت خطا) نیاز دارد.

swift
enum NetworkError: Error {
    case noConnection
    case serverError(code: Int)
    case timeout
}

func fetchUser(id: Int) throws -> User {
    guard isConnected() else {
        throw NetworkError.noConnection
    }
    let data = try performRequest(path: "/users/\(id)")
    return try decodeUser(from: data)
}

do {
    let user = try fetchUser(id: 42)
    updateUI(user)
} catch NetworkError.noConnection {
    showOfflineAlert()
} catch let error as NetworkError {
    showError("شبکه " + error.localizedDescription)
} catch {
    showGenericError()
}

Enum NetworkError پروتکل Error را پیاده‌سازی می‌کند و سه حالت را تعریف می‌کند: noConnection، serverError با کد و timeout. تابع fetchUser با throws مشخص شده است: ابتدا اتصال را بررسی می‌کند، سپس درخواست و parse را انجام می‌دهد. در بلوک do-catch سه catch سناریوهای مختلف را مدیریت می‌کنند: حالت خاص noConnection، نوع عمومی NetworkError و تمام خطاهای دیگر. این امکان را فراهم می‌کند که بسته به نوع مشکل، پیام‌های متفاوتی به کاربر نشان داده شود.

Try-Catch در Kotlin

Kotlin try-catch-finally را از Java به ارث برده است، اما یک تفاوت مهم اضافه کرده: در Kotlin try-catch یک عبارت (expression) است، نه یک دستور (statement). این بدان معناست که نتیجه بلوک try یا بلوک catch را می‌توان به یک متغیر نسبت داد. آخرین عبارت در بلوک try در صورت موفقیت نتیجه می‌شود، آخرین عبارت در catch — در صورت خطا. اگر خطا توسط هیچ catch مدیریت نشود، استثنا در پشته بالاتر پرتاب می‌شود.

kotlin
sealed class Result<out T> {
    data class Success<out T>(val data: T) : Result<T>()
    data class Error(val exception: Throwable) : Result<Nothing>()
}

fun loadData(): Result<List<Item>> {
    return try {
        val response = api.getItems()
        Result.Success(response.toList())
    } catch (e: HttpException) {
        Log.e("HTTP ", e)
        Result.Error(e)
    } catch (e: IOException) {
        Log.e("شبکه ", e)
        Result.Error(e)
    }
}

در مثال sealed class Result پاسخ موفق یا خطا را می‌پوشاند. تابع loadData از try-catch به عنوان عبارت استفاده می‌کند: در صورت موفقیت Result.Success را برمی‌گرداند، در صورت استثنای HttpException یا IOException — Result.Error با لاگ کردن. این رویکرد به طرف فراخواننده اجازه می‌دهد خطاها را بدون استثنا مدیریت کند — از طریق when-عبارت بر اساس نوع Result. این به ویژه در Jetpack Compose برای نمایش حالت‌های مختلف UI (Loading, Success, Error) از طریق StateFlow و collectAsState راحت است.

Try-Catch در Dart و Flutter

Dart از try-catch-finally با سینتکسی شبیه به Java پشتیبانی می‌کند، اما با اضافه شدن بند on برای فیلتر کردن بر اساس نوع استثنا بدون مشخص کردن متغیر. این زمانی راحت است که خود استثنا مورد نیاز نیست — فقط واقعیت نوع آن مهم است. Dart همچنین از بلوک catch با دو پارامتر پشتیبانی می‌کند: شیء استثنا و StackTrace که برای لاگ کردن زنجیره کامل فراخوانی‌ها مفید است.

dart
import 'dart:io';
import 'dart:convert';

class UserRepository {
    Future<User> fetchUser(String id) async {
        try {
            final client = HttpClient();
            final request = await client.getUrl(
                Uri.parse('https://api.example.com/users/$id')
            );
            final response = await request.close();
            final body = await response.transform(utf8.decoder).join();
            return User.fromJson(json.decode(body));
        } on SocketException catch (e, stackTrace) {
            log("No internet", e, stackTrace);
            throw AppException("Connection failed");
        } on FormatException {
            throw AppException("Invalid response format");
        } finally {
            client.close();
        }
    }
}

SocketException با گرفتن شیء و StackTrace برای لاگ کردن جزئیات گرفته می‌شود و سپس به عنوان AppException پرتاب می‌شود. FormatException بدون متغیر گرفته می‌شود — کافی است بدانیم فرمت پاسخ نادرست است. بلوک finally به طور تضمینی HttpClient را می‌بندد و از نشت سوکت جلوگیری می‌کند. در Flutter این رویکرد به ویژه برای تست Widget مهم است، جایی که استثناهای مدیریت نشده در State.initState منجر به سقوط کل جلسه تست می‌شوند.

اشتباهات رایج در استفاده از Try-Catch

حتی توسعه‌دهندگان با تجربه در کار با try-catch مرتکب اشتباهاتی می‌شوند که منجر به نشت حافظه، باگ‌های پنهان یا رفتار نامناسب برنامه می‌شود. پنج مشکل رایج در توسعه موبایل را بررسی می‌کنیم.

بلوک catch خالی

catch خالی — یکی از بدترین روش‌ها. استثنا بلعیده می‌شود، برنامه در حالت نادرست به کار ادامه می‌دهد و توسعه‌دهنده از مشکل مطلع نمی‌شود. همیشه حداقل استثنا را لاگ کنید. در Kotlin از catch(e: Exception) { Log.e(...) }، در Swift — از catch { print($0) } استفاده کنید. در Dart حداقل catch قابل قبول باید debugPrint را فراخوانی کند یا در Crashlytics بنویسد.

catch بیش از حد گسترده

گرفتن همه استثناها با catch (Exception e) بدون تفکیک بر اساس نوع، خطاهای غیرمنتظره — NullPointerException، OutOfMemoryError، StackOverflowError — را پنهان می‌کند. فقط آن دسته از انواعی را بگیرید که انتظار دارید و می‌توانید مدیریت کنید. برای بقیه اجازه انتشار به بالا را بدهید. در توسعه موبایل catch اختصاصی برای IOException، TimeoutException، AuthException پیام‌های معنادارتری به کاربر می‌دهد.

نادیده گرفتن finally

منابع — فایل‌ها، سوکت‌ها، کورسورهای پایگاه داده، انیمیشن‌ها — باید در finally یا در بلوک use (AutoCloseable) آزاد شوند. توسعه‌دهندگان اغلب در هنگام استثنا بستن منابع را فراموش می‌کنند که منجر به نشت می‌شود. در Kotlin برای منابع Closeable از .use { }، در Swift — از defer { }، در Dart — از await using در بسته async استفاده کنید. بلوک finally حتی در صورت پرتاب استثنا در داخل catch نیز آزادسازی را تضمین می‌کند.

استثناها در رشته‌ها و کوروتین‌ها

در کد ناهمگام، try-catch استثناهای رشته‌های دیگر را نمی‌گیرد. در Kotlin Coroutines از CoroutineExceptionHandler یا SupervisorJob استفاده کنید. در Swift async/await — do-catch در داخل Task. در Flutter — runZonedGuarded برای گرفتن سراسری. نادیده گرفتن این قانون علت کرش‌های به سختی قابل بازتولید در production است.

مسدود کردن UI در هنگام استثنا

مدیریت استثنا نباید رابط کاربری را برای مدت نامحدودی مسدود کند. به کاربر پیام مشخصی نشان دهید و امکان تکرار عملیات را بدهید. Snackbar با دکمه Retry در Kotlin/Compose، UIAlertController با action در Swift، SnackBar با action در Flutter — حداقل UX کافی برای خطاهای شبکه یا سرور. از دیالوگ‌های عمومی «خطایی رخ داد» بدون امکان بازیابی خودداری کنید.

سوالات متداول

تفاوت try-catch با Result Type چیست؟

Try-catch برای مدیریت خطاها از استثناها و باز شدن پشته استفاده می‌کند که می‌تواند با تعداد زیادی خطا از نظر عملکرد پرهزینه باشد. Result Type یک نوع کانتینری (Success یا Failure) است که از طریق pattern matching بدون باز شدن پشته مدیریت می‌شود و برای خطاهای مورد انتظار کارآمدتر است.

آیا باید در هر try-catch از finally استفاده کرد؟

Finally در صورتی اجباری است که بلوک try منابعی (فایل‌ها، سوکت‌ها، کورسورها) را باز کند که باید بسته شوند. اگر منابع باز نمی‌شوند، finally لازم نیست. در زبان‌های مدرن از AutoCloseable/use/defer برای بستن خودکار منابع بدون finally استفاده کنید. بلوک use در Kotlin و Swift finally را برای اشیاء Closeable جایگزین می‌کند.

آیا try-catch می‌تواند کند باشد؟

در جریان عادی (بدون استثنا) try-catch عملاً بر عملکرد تأثیر نمی‌گذارد — JVM و کامپایلر Swift این حالت را بهینه‌سازی می‌کنند. اما هنگام پرتاب استثنا، باز شدن پشته رخ می‌دهد که بسته به عمق پشته ممکن است 10–100 میکروثانیه طول بکشد. از استثناها برای کنترل جریان اجرا استفاده نکنید — این یک ضدالگو است.

چگونه خطاها را در Kotlin Coroutines مدیریت کنیم؟

در کوروتین‌ها از try-catch در داخل coroutineScope یا CoroutineExceptionHandler برای گرفتن سراسری استفاده کنید. SupervisorJob از لغو کوروتین والد در صورت خطا در کوروتین فرزند جلوگیری می‌کند. برای launch از CoroutineExceptionHandler، برای async — try-catch در اطراف await() استفاده کنید.

کدام بهتر است: catch متعدد یا یک catch با if-else؟

catch متعدد ترجیح داده می‌شود: کد به صورت خطی خوانده می‌شود، هر بلوک یک نوع استثنا را مدیریت می‌کند. یک catch با if-else نگهداری سخت‌تر است، به راحتی می‌توان نوع جدید استثنا را از دست داد. در Swift برای مدیریت جامع enum Error، catch متعدد اجباری است، در Kotlin محدودیتی وجود ندارد، اما بهترین روش catch جداگانه به ازای هر نوع است.

خلاصه

  • Try-Catch — ساختار catch استثنا با بلوک‌های try، catch و finally اختیاری برای آزادسازی تضمینی منابع
  • مکانیزم کار — باز شدن پشته هنگام پرتاب استثنا با جستجوی catch مناسب بر اساس نوع خطا
  • در Swift از do-catch با enum Error، try? برای نتیجه اختیاری و try! برای موفقیت تضمینی استفاده می‌شود
  • در Kotlin try-catch یک عبارت است که نتیجه آن را می‌توان به متغیر نسبت داد، همراه با sealed class Result راحت است
  • در Dart از بند on برای فیلتر کردن بر اساس نوع بدون متغیر و finally برای بستن HttpClient پشتیبانی می‌کند
  • اشتباهات رایج: catch خالی، گرفتن بیش از حد گسترده، نادیده گرفتن finally و عدم مدیریت در کوروتین‌ها
  • از try-catch برای خطاهای غیرمنتظره و Result Type برای سناریوهای مورد انتظار با شکست احتمالی استفاده کنید

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید