Try-Catch — ساختاری برای catch استثنا است که امکان اجرای کد بالقوه خطرناک در یک بلوک محافظتشده و مدیریت صحیح خطاها بدون پایان غیرمنتظره برنامه را فراهم میکند. بلوک try حاوی کدی است که ممکن است استثنا پرتاب کند، catch آن را گرفته و منطق بازیابی را اجرا میکند. طبق Apple Swift Documentation (2026)، بلوک finally صرفنظر از اینکه استثنا پرتاب شده باشد یا خیر اجرا میشود و آزادسازی منابع را تضمین میکند.
نکات اصلی
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 بر اساس باز شدن پشته (stack unwinding) است. هنگامی که در داخل بلوک try یک استثنا توسط عملگر throw (یا در نتیجه خطای سیستمی) پرتاب میشود، جریان عادی اجرا بلافاصله قطع میشود. اجرا به سطح بالاتری در پشته فراخوانی برای یافتن بلوک catch مناسب حرکت میکند. زبانهای مدرن به دنبال catch با نوع منطبق با نوع استثنای پرتاب شده میگردند و از مکانیزم تطبیق نوع (type matching) استفاده میکنند.
اگر catch مناسب پیدا شود، بدنه آن اجرا میشود و سپس اجرا پس از کل ساختار try-catch-finally ادامه مییابد. اگر catch پیدا نشود، استثنا در پشته بالاتر میرود و ممکن است در سطح بالاتری مدیریت شود — تا handler سراسری که در برنامه موبایل یک دیالوگ خطا به کاربر نشان میدهد. اگر استثنا در هیچ کجا مدیریت نشود، برنامه به طور غیرمنتظره پایان مییابد. به همین دلیل مدیریت صحیح همه انواع ممکن استثنا برای پایداری برنامه حیاتی است.
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 روی صفحه جلوگیری میکند.
در Swift مدیریت خطا از طریق پروتکل Error (قبلاً ErrorType) پیادهسازی شده است. هر نوع منطبق با Error میتواند توسط عملگر throw پرتاب شود. تابعی که میتواند خطا پرتاب کند با کلمه کلیدی throws در امضا مشخص میشود. فراخوانی چنین تابعی به پیشوند try (برای try-catch صریح)، try? (نتیجه اختیاری) یا try! (اجرای اجباری بدون مدیریت خطا) نیاز دارد.
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 و تمام خطاهای دیگر. این امکان را فراهم میکند که بسته به نوع مشکل، پیامهای متفاوتی به کاربر نشان داده شود.
Kotlin try-catch-finally را از Java به ارث برده است، اما یک تفاوت مهم اضافه کرده: در Kotlin try-catch یک عبارت (expression) است، نه یک دستور (statement). این بدان معناست که نتیجه بلوک try یا بلوک catch را میتوان به یک متغیر نسبت داد. آخرین عبارت در بلوک try در صورت موفقیت نتیجه میشود، آخرین عبارت در catch — در صورت خطا. اگر خطا توسط هیچ catch مدیریت نشود، استثنا در پشته بالاتر پرتاب میشود.
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 راحت است.
Dart از try-catch-finally با سینتکسی شبیه به Java پشتیبانی میکند، اما با اضافه شدن بند on برای فیلتر کردن بر اساس نوع استثنا بدون مشخص کردن متغیر. این زمانی راحت است که خود استثنا مورد نیاز نیست — فقط واقعیت نوع آن مهم است. Dart همچنین از بلوک catch با دو پارامتر پشتیبانی میکند: شیء استثنا و StackTrace که برای لاگ کردن زنجیره کامل فراخوانیها مفید است.
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 مرتکب اشتباهاتی میشوند که منجر به نشت حافظه، باگهای پنهان یا رفتار نامناسب برنامه میشود. پنج مشکل رایج در توسعه موبایل را بررسی میکنیم.
catch خالی — یکی از بدترین روشها. استثنا بلعیده میشود، برنامه در حالت نادرست به کار ادامه میدهد و توسعهدهنده از مشکل مطلع نمیشود. همیشه حداقل استثنا را لاگ کنید. در Kotlin از catch(e: Exception) { Log.e(...) }، در Swift — از catch { print($0) } استفاده کنید. در Dart حداقل catch قابل قبول باید debugPrint را فراخوانی کند یا در Crashlytics بنویسد.
گرفتن همه استثناها با catch (Exception e) بدون تفکیک بر اساس نوع، خطاهای غیرمنتظره — NullPointerException، OutOfMemoryError، StackOverflowError — را پنهان میکند. فقط آن دسته از انواعی را بگیرید که انتظار دارید و میتوانید مدیریت کنید. برای بقیه اجازه انتشار به بالا را بدهید. در توسعه موبایل catch اختصاصی برای IOException، TimeoutException، AuthException پیامهای معنادارتری به کاربر میدهد.
منابع — فایلها، سوکتها، کورسورهای پایگاه داده، انیمیشنها — باید در 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 است.
مدیریت استثنا نباید رابط کاربری را برای مدت نامحدودی مسدود کند. به کاربر پیام مشخصی نشان دهید و امکان تکرار عملیات را بدهید. Snackbar با دکمه Retry در Kotlin/Compose، UIAlertController با action در Swift، SnackBar با action در Flutter — حداقل UX کافی برای خطاهای شبکه یا سرور. از دیالوگهای عمومی «خطایی رخ داد» بدون امکان بازیابی خودداری کنید.
سوالات متداول
Try-catch برای مدیریت خطاها از استثناها و باز شدن پشته استفاده میکند که میتواند با تعداد زیادی خطا از نظر عملکرد پرهزینه باشد. Result Type یک نوع کانتینری (Success یا Failure) است که از طریق pattern matching بدون باز شدن پشته مدیریت میشود و برای خطاهای مورد انتظار کارآمدتر است.
Finally در صورتی اجباری است که بلوک try منابعی (فایلها، سوکتها، کورسورها) را باز کند که باید بسته شوند. اگر منابع باز نمیشوند، finally لازم نیست. در زبانهای مدرن از AutoCloseable/use/defer برای بستن خودکار منابع بدون finally استفاده کنید. بلوک use در Kotlin و Swift finally را برای اشیاء Closeable جایگزین میکند.
در جریان عادی (بدون استثنا) try-catch عملاً بر عملکرد تأثیر نمیگذارد — JVM و کامپایلر Swift این حالت را بهینهسازی میکنند. اما هنگام پرتاب استثنا، باز شدن پشته رخ میدهد که بسته به عمق پشته ممکن است 10–100 میکروثانیه طول بکشد. از استثناها برای کنترل جریان اجرا استفاده نکنید — این یک ضدالگو است.
در کوروتینها از try-catch در داخل coroutineScope یا CoroutineExceptionHandler برای گرفتن سراسری استفاده کنید. SupervisorJob از لغو کوروتین والد در صورت خطا در کوروتین فرزند جلوگیری میکند. برای launch از CoroutineExceptionHandler، برای async — try-catch در اطراف await() استفاده کنید.
catch متعدد ترجیح داده میشود: کد به صورت خطی خوانده میشود، هر بلوک یک نوع استثنا را مدیریت میکند. یک catch با if-else نگهداری سختتر است، به راحتی میتوان نوع جدید استثنا را از دست داد. در Swift برای مدیریت جامع enum Error، catch متعدد اجباری است، در Kotlin محدودیتی وجود ندارد، اما بهترین روش catch جداگانه به ازای هر نوع است.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید