Try-Catch: الجوهر، بنية التقاط الاستثناءات وكيف يعمل في تطوير التطبيقات المحمولة

المؤلف: IT Sectr نُشر: 2026-05-25 وقت القراءة: 9 دق

Try-Catch هي بنية التقاط استثناءات تسمح بتنفيذ كود قد يكون خطراً في كتلة محمية ومعالجة الأخطاء بشكل صحيح دون إنهاء البرنامج بشكل غير متوقع. تحتوي كتلة try على كود قد يلقي استثناءً، وتلتقطه catch وتنفذ منطق الاستعادة. وفقاً لـ Apple Swift Documentation (2026)، يتم تنفيذ كتلة finally بغض النظر عما إذا تم إلقاء استثناء أم لا، مما يضمن تحرير الموارد.

أهم النقاط

  • Try-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 تعبيراً، في Dart finally إلزامي لموارد التدفق. على الرغم من الاختلافات، فإن المبدأ الأساسي واحد: يتم معالجة الخطأ في أقرب مكان ممكن من مكان حدوثه، وليس بشكل عام.

استخدام Try-Catch مهم بشكل خاص في تطوير التطبيقات المحمولة، حيث العوامل الخارجية — فقدان الشبكة، استجابة خادم غير صحيحة، نقص الذاكرة — تحدث باستمرار. المعالجة الصحيحة للاستثناءات تمنع تعطل التطبيق وتضمن تجربة مستخدم صحيحة: يرى المستخدم رسالة خطأ بدلاً من إغلاق التطبيق المفاجئ. وفقاً لـ 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، يصعد الاستثناء إلى أعلى في المكدس ويمكن معالجته على مستوى أعلى — وصولاً إلى معالج عام يعرض للمستخدم في التطبيق المحمول حوار خطأ. إذا لم تتم معالجة الاستثناء في أي مكان، يتعطل التطبيق. لهذا السبب فإن المعالجة الصحيحة لجميع أنواع الاستثناءات الممكنة أمر بالغ الأهمية لاستقرار التطبيق.

بناء الجملة الأساسي

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 وتحليل الاستجابة. إذا حدث IOException (مشكلة شبكة)، يتم تسجيل الاستثناء وإعادة إلقائه كـ AppException. إذا حدث JsonParseException — يتم إرجاع مستخدم افتراضي. تضمن كتلة finally إخفاء مؤشر التحميل، مما يمنع تسرب مكونات واجهة المستخدم على الشاشة.

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: تتحقق أولاً من الاتصال، ثم تنفذ الطلب والتحليل. في كتلة 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 لعرض حالات واجهة مستخدم مختلفة (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 استخدم .use { } لموارد Closeable، في Swift — defer { }، في Dart — await using من حزمة async. تضمن كتلة finally التحرير حتى عند إلقاء استثناء داخل catch.

الاستثناءات في الخيوط و coroutines

في الكود غير المتزامن، لا يلتقط try-catch الاستثناءات من الخيوط الأخرى. في Kotlin Coroutines استخدم CoroutineExceptionHandler أو SupervisorJob. في Swift async/await — do-catch داخل Task. في Flutter — runZonedGuarded للالتقاط العام. تجاهل هذه القاعدة هو سبب أعطال يصعب إعادة إنتاجها في الإنتاج.

حظر واجهة المستخدم عند الاستثناء

لا يجب أن تحجب معالجة الاستثناء واجهة المستخدم إلى أجل غير مسمى. اعرض للمستخدم رسالة محددة وأعطه فرصة لإعادة المحاولة. Snackbar مع زر Retry في Kotlin/Compose، UIAlertController مع إجراء في Swift، SnackBar مع إجراء في Flutter — تجربة مستخدم كافية دنياً لأخطاء الشبكة أو الخادم. تجنب الحوارات العامة “حدث خطأ” دون خيارات استعادة.

الأسئلة الشائعة

ما الفرق بين try-catch و Result Type؟

يستخدم try-catch الاستثناءات وفك المكدس لمعالجة الأخطاء، مما قد يكون مكلفاً من حيث الأداء عند وجود العديد من الأخطاء. Result Type هو نوع حاوية (Success أو Failure) يتم معالجته عبر مطابقة الأنماط دون فك مكدس، مما يجعله أكثر كفاءة للأخطاء المتوقعة.

هل أحتاج لاستخدام finally في كل try-catch؟

Finally إلزامي إذا كانت كتلة try تفتح موارد (ملفات، مقابس، مؤشرات) تحتاج للإغلاق. إذا لم تفتح موارد، لا حاجة لـ finally. في اللغات الحديثة استخدم AutoCloseable/use/defer للإغلاق التلقائي للموارد دون finally. كتلة use في Kotlin و Swift تحل محل finally لكائنات Closeable.

هل يمكن أن يكون try-catch بطيئاً؟

في التدفق الطبيعي (بدون استثناءات)، لا يؤثر try-catch عملياً على الأداء — JVM ومترجم Swift يحسنان هذه الحالة. ولكن عند إلقاء استثناء، يحدث فك المكدس، مما قد يستغرق 10–100 μs حسب عمق المكدس. لا تستخدم الاستثناءات للتحكم في التدفق — هذا نمط معاكس.

كيف نعالج الأخطاء في Kotlin Coroutines؟

في coroutines، استخدم try-catch داخل coroutineScope أو CoroutineExceptionHandler للالتقاط العام. يمنع SupervisorJob إلغاء coroutine الأب عند فشل coroutine ابن. لـ launch استخدم CoroutineExceptionHandler، لـ async — try-catch حول await().

ما الأفضل: catch متعددة أم catch واحدة مع if-else؟

catch المتعددة أفضل: الكود يُقرأ خطياً، كل كتلة تعالج نوع استثناء واحد. catch واحدة مع if-else أصعب في الصيانة، ومن السهل تفويت نوع استثناء جديد. في Swift، catch المتعددة إلزامية للمعالجة الشاملة لـ enum Error، في Kotlin لا توجد قيود، لكن أفضل ممارسة هي catch منفصل لكل نوع.

الخلاصة

  • Try-Catch هي بنية التقاط استثناءات مع كتل try و catch و finally اختياري لتحرير مضمون للموارد
  • آلية العمل — فك المكدس عند إلقاء استثناء مع البحث عن catch مناسب حسب نوع الخطأ
  • في Swift يُستخدم do-catch مع enum Error و try? لنتيجة اختيارية و try! لنجاح مضمون
  • في Kotlin try-catch هو تعبير يمكن تعيين نتيجته لمتغير، مناسب مع sealed class Result
  • في Dart يدعم شرط on للتصفية حسب النوع دون متغير و finally لإغلاق HttpClient
  • الأخطاء الشائعة: catch فارغ، catch واسع جداً، تجاهل finally وعدم معالجة في coroutines
  • استخدم try-catch للأخطاء غير المتوقعة، Result Type — للسيناريوهات المتوقعة مع احتمال الفشل

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا