Throw/Throws — ما هو، عوامل رمي الاستثناءات وكيف تعمل في تطوير التطبيقات المحمولة

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

Throw و Throws هما آليتان لرمي وإعلان الاستثناءات في لغات البرمجة. عامل throw يقطع التنفيذ الطبيعي للدالة ويمرر كائن الخطأ إلى أعلى مكدس الاستدعاءات. الكلمة المفتاحية throws في توقيع الدالة تحذر الطرف المستدعي من احتمال حدوث خطأ، مما يجعل الكود قابلاً للتنبؤ. وفقاً لوثائق Kotlin (2026)، throw في Kotlin هو تعبير (expression) وليس تعليمة، مما يسمح باستخدامه داخل كتل when وعامل Elvis.

الخلاصة

  • Throw — عامل يقطع تنفيذ الدالة ويمرر كائن الخطأ إلى أعلى مكدس الاستدعاءات
  • Throws — كلمة مفتاحية في توقيع الدالة تعلن أن الدالة قد ترمي استثناءً
  • في Swift تُعلَّم الدالة بـ throws، والاستدعاء يتطلب try/try?/try!، وthrow يقبل أي نوع يطبق بروتوكول Error
  • في Kotlin throw هو تعبير من نوع Nothing، throws غير موجود (جميع الاستثناءات غير محققة)
  • الأخطاء المخصصة تُنشأ عبر enum (Swift) أو sealed class (Kotlin) لسهولة المعالجة في catch

ما هو Throw و Throws؟

Throw هو عامل يولد استثناءً في نقطة تنفيذ البرنامج. عند مواجهة throw، يتوقف تيار التنفيذ الحالي فوراً ويُنقل التحكم إلى أقرب معالج catch في مكدس الاستدعاءات. إذا لم يتم العثور على معالج، يتعطل التطبيق. يستخدم throw في تطوير التطبيقات المحمولة للإشارة إلى الأخطاء التي لا يمكن معالجتها في المستوى الحالي من التجريد — على سبيل المثال، استجابة خادم غير صالحة، انقطاع الشبكة أو وسائط غير صحيحة.

Throws — معدِّل في توقيع الدالة (بشكل أساسي في Swift) يعلن أن الدالة قد ترمي خطأً. هذا جزء من آلية الأخطاء المحققة (checked errors) في Swift: الطرف المستدعي ملزم بمعالجة الخطأ عبر do-catch أو try? أو try! أو تعليم دالته بـ throws لنقلها إلى الأعلى. في Kotlin و Java يوجد throws أيضاً، لكن Kotlin يعتبره زائداً — جميع الاستثناءات في Kotlin غير محققة، مما يعني أنها قد لا تُعالج دون إلزام نحوي. وفقاً لوثائق Apple Swift (2026)، throws في Swift هو الطريقة الوحيدة للإعلان صراحة عن احتمال حدوث خطأ في نوع الدالة، مما يجعل عقود API شفافة للمطور.

الفرق بين throw و throws جوهري: throw هو إجراء (رمي استثناء في وقت التشغيل)، throws هو إعلان (عقد في وقت الترجمة). لا يمكن لدالة بدون throws استخدام throw — سينتج مترجم Swift خطأ. يمكن لدالة مع throws ألا تستخدم throw — هذا مسموح لكنه غير مجدٍ. هذا الفصل يجعل throw/throws أداة قوية لتصميم API، حيث يكون عقد الخطأ مرئياً في توقيع الدالة قبل استدعائها.

Throw في Swift

في Swift، عامل throw يقبل أي نوع يطبق بروتوكول Error. الأكثر شيوعاً هو enum مع حالات لأنواع مختلفة من الأخطاء. لا يدعم Swift الاستثناءات المحققة على غرار Java — بدلاً من ذلك، يُعلَّم نوع الدالة بـ throws، وتُفوض المعالجة إلى الطرف المستدعي. هذا يجعل throw في Swift أكثر مرونة ولكنه يضع مسؤولية أكبر على المطور.

بروتوكول Error وأنواع الأخطاء

أي نوع يتوافق مع بروتوكول Error يمكن رميه عبر throw. غالباً ما يستخدم المطورون enum مع حالات بدون قيم مرتبطة (للأخطاء البسيطة) أو بقيم مرتبطة (لنقل السياق). لا يتطلب Swift أن يكون الخطأ من نوع enum — يمكن استخدام struct أو class يطبق Error، لكن enum هو الأفضل بفضل الفحص الشامل (exhaustive switch) في جانب المعالج. يتحقق المترجم من معالجة جميع الحالات في do-catch.

swift
enum AuthError: Error {
    case invalidCredentials
    case tokenExpired
    case accountLocked(remainingMinutes: Int)
}

func login(username: String, password: String) throws -> Session {
    guard isValid(username) else {
        throw AuthError.invalidCredentials
    }
    let response = try api.authenticate(username, password)
    if response.isLocked {
        throw AuthError.accountLocked(
            remainingMinutes: response.lockDuration
        )
    }
    return Session(token: response.token)
}

AuthError يحدد ثلاث سيناريوهات: بيانات اعتماد غير صالحة، رمز منتهي الصلاحية، وحساب مقفل بقيمة مرتبطة remainingMinutes. الدالة login مُعلَنة كـ throws — المترجم يتطلب استدعاءها عبر try. داخل الدالة، يُستخدم throw في مكانين: لاسم مستخدم غير صالح ولحساب مقفل. القيمة المرتبطة accountLocked تسمح بتمرير بيانات محددة للمستخدم — كم دقيقة للانتظار قبل فك القفل. هذا النهج يلغي الحاجة إلى نقاط نهاية API منفصلة للتحقق من حالة القفل.

Throw في Kotlin

في Kotlin throw هو تعبير (expression) من نوع Nothing، وليس تعليمة. هذا يعني أنه يمكن استخدام throw في الجانب الأيمن من الإسناد، داخل تعبيرات when وعامل Elvis ?:. نوع Nothing هو نوع فرعي خاص لجميع الأنواع في Kotlin، مما يسمح باستخدام throw في الأماكن التي تتطلب قيمة من أي نوع. يدرك المترجم أن التنفيذ لا يستمر بعد throw ولا يتطلب فرعاً لهذه الحالة.

نوع Nothing ودوره في التركيب

Nothing هو نوع فريد في Kotlin وهو نوع فرعي لجميع الأنواع الممكنة. الدالة التي تعيد Nothing (مثل TODO()) لا تكتمل بشكل طبيعي أبداً — إما أن ترمي استثناءً دائماً أو تدخل في حلقة لا نهائية. هذا يجعل throw مرشحاً طبيعياً للاستخدام في الأماكن التي تتطلب قيمة: عامل Elvis، when بدون else، تهيئة المتغيرات. إذا كان throw في فرع when، يفهم المترجم أن الفرع يؤدي إلى Nothing ولا يتطلب return أو else لذلك الفرع.

kotlin
data class Config(val apiUrl: String, val timeoutSec: Int)

class ConfigParser {
    fun parse(json: String): Config {
        val obj = JSONObject(json)
        val url = obj.optString("apiUrl")
            ?: throw IllegalArgumentException("apiUrl is required")
        val timeout = obj.optInt("timeoutSec", 30)
        return Config(url, timeout)
    }

    fun getErrorMessage(code: Int): String {
        return when (code) {
            404 -> "Not found"
            500 -> "Server error"
            else -> throw IllegalArgumentException("Unknown code: $code")
        }
    }
}

في المثال الأول، يُستخدم throw في عامل Elvis ?:: إذا كان حقل apiUrl مفقوداً من JSON، فإن تعبير throw يقطع التنفيذ فوراً ويرمي IllegalArgumentException. نوع Nothing يسمح للمترجم باستنتاج نوع الجانب الأيمن كـ String (Elvis يتوقع String، throw له نوع Nothing، Nothing هو نوع فرعي من String). في المثال الثاني، throw داخل تعبير when: إذا كان الكود لا يتطابق مع أي كود معروف، يُرمى استثناء. يدرك المترجم أن الكود بعد throw غير قابل للوصول، لذلك نوع الإرجاع للدالة String لا يُنتهك.

Throws في توقيع الدالة و rethrows

في Swift throws يُحدد بعد قائمة المعاملات وقبل سهم نوع الإرجاع. دالة مع throws يمكنها فقط استدعاء دوال throws أخرى داخل do-catch أو مع try?. إذا كانت دالة throws لا تعالج الخطأ، فإنها تمرره إلى الطرف المستدعي. يدعم Swift أيضاً rethrows — معدِّل للدوال عالية المستوى التي تقبل إغلاق (closure) من نوع throws وتنشر خطأه. rethrows تعني أن الدالة ترمي خطأ فقط إذا رمى الإغلاق الممرر خطأ — الدالة نفسها لا تولد خطأ.

swift
func mapValues<T>(
    _ array: [T],
    transform: (T) throws -> U
) rethrows -> [U] {
    var result = [U]()
    for element in array {
        result.append(try transform(element))
    }
    return result
}

// الاستخدام مع إغلاق throws
let parsed = try mapValues(jsonStrings) { str in
    let data = Data(str.utf8)
    return try JSONDecoder().decode(Item.self, from: data)
}

rethrows يسمح للدالة mapValues بأن تكون مرنة: تقبل كل من الإغلاقات throws والعادية. إذا تم تمرير إغلاق throws، استدعاء mapValues يتطلب try؛ إذا كان عادياً، try غير مطلوب. هذا يجعل rethrows مثالياً للدوال عالية المستوى مثل map و filter و reduce في المكتبة القياسية لـ Swift. توصية Apple: استخدم rethrows لواجهات API التي تقبل إغلاقات throws والمصدر الوحيد للخطأ هو ذلك الإغلاق. إذا كانت الدالة يمكنها رمي خطأ خاص بها، استخدم throws.

الاستثناءات المحققة مقابل غير المحققة

throws في Swift أقرب إلى الاستثناءات المحققة (كما في Java) — الأخطاء المرمية تُعلن في التوقيع. Kotlin و Dart يستخدمان الاستثناءات غير المحققة — throws في التوقيع غير مطلوب. الفرق جوهري: المحققة تجبر المطور على معالجة الخطأ (أكثر أماناً لكن أكثر إسهاباً)، غير المحققة تعطي حرية لكنها تزيد من خطر نسيان معالجة الخطأ. اختار Swift المحققة لـ throws، واختار Kotlin غير المحققة لجميع الاستثناءات. كلا النهجين لهما مزايا: Swift أكثر موثوقية على مستوى اللغة، Kotlin أكثر إحكاماً وملاءمة في سلاسل التحويلات الوظيفية.

أنواع الأخطاء المخصصة

لمعالجة منظمة للأخطاء في التطبيقات المحمولة، يُوصى بإنشاء أنواع أخطاء مخصصة بدلاً من استخدام Exception أو Error الأساسية. في Swift يُستخدم enum مع بروتوكول Error؛ في Kotlin sealed class يرث من Throwable (أو من Exception)؛ في Dart class يرث من Exception. الأنواع المخصصة تسمح بتجميع الأخطاء حسب الفئات وتمرير البيانات المرتبطة.

اللغةنوع الخطأالميزة
Swiftenum: Error { ... }قيم مرتبطة، فحص شامل (exhaustive switch) في catch
Kotlinsealed class : Throwable()Data class للأخطاء مع حقول، تعبير when
Dartclass implements Exceptionحقل Message، عبارة on في catch
Javaclass extends Exceptionمحققة مقابل غير محققة، throws إلزامي في التوقيع

عند تصميم الأخطاء المخصصة، اتبع القاعدة: خطأ واحد — سيناريو واحد. لا تجمع أسباباً مختلفة في نوع واحد مع علامة String message — أنشئ حالات/فئات فرعية منفصلة لكل سيناريو. هذا سيسمح للطرف المستدعي بمعالجة كل حالة عبر مطابقة الأنماط (pattern matching) (when/switch) بدلاً من مقارنة السلاسل. في Swift هذا يعطي فحصاً شاملاً — المترجم سيحذر إذا لم تتم معالجة أي حالة من حالات enum NetworkError.

مثال على خطأ مخصص في Kotlin

kotlin
sealed class NetworkError(val message: String) : Throwable(message) {
    data class Timeout(val durationMs: Long) :
        NetworkError("Request timed out after ${durationMs}ms")
    data class HttpError(val code: Int, val body: String?) :
        NetworkError("HTTP $code")
    data class NoConnection(val cause: IOException) :
        NetworkError("No internet connection")
}

sealed class NetworkError يرث من Throwable (النوع القياسي للاستثناءات في Kotlin). كل فئة فرعية هي data class مع حقولها الخاصة: Timeout يحتوي على مدة المهلة بالمللي ثانية، HttpError يحتوي على الكود ونص الاستجابة، NoConnection يحتوي على IOException الأصلي. هذا التصميم يسمح بمعالجة كل خطأ عبر when مع مطابقة شاملة (عند إضافة فئة فرعية جديدة، سيُجبر المترجم على تحديث جميع تعبيرات when).

Try و try? و try! في Swift

يقدم Swift ثلاث طرق لاستدعاء دوال throws، لكل منها عقد الأمان الخاص به. try هو الطريقة القياسية: يتطلب do-catch أو أن يكون داخل دالة throws. try? يحوِّل الخطأ إلى nil — تصبح النتيجة اختيارية، عند الخطأ تُعيد nil، ويتغير النوع من T إلى T?. try! — تنفيذ إجباري بدون معالجة: إذا تم رمي خطأ، يتعطل التطبيق. استخدم try! فقط عندما تكون متأكداً تماماً من استحالة حدوث خطأ (مثلاً، بيانات صالحة بشكل مؤكد).

swift
let configPath = Bundle.main.path(forResource: "config", ofType: "json")!

// try? — نتيجة اختيارية
let data = try? Data(contentsOf: URL(fileURLWithPath: configPath))
let json = try? JSONSerialization.jsonObject(with: data ?? Data())

// try! — نجاح مضمون (فقط عندما تكون متأكداً)
let decoder = JSONDecoder()
let defaultConfig = try! decoder.decode(
    Config.self,
    from: Config.defaultJSON
)

// try — معالجة قياسية
do {
    let user = try fetchUser()
    showUser(user)
} catch let error as NetworkError {
    showRetryAlert(error.message)
}

في المثال، try! يُستخدم لـ JSON صالح بشكل مؤكد والمضمن في حزمة التطبيق — خطأ فك الترميز مستحيل مع إصدار صحيح. try? يُستخدم لقراءة ملف إعدادات — إذا كان الملف مفقوداً أو تالفاً، يستخدم التطبيق القيم الافتراضية بدلاً من التعطل. try في do-catch يُستخدم لطلبات الشبكة حيث يكون الخطأ متوقعاً ويتطلب استجابة المستخدم. توصية: تجنب try! في كود الإنتاج — استخدمه فقط للبيانات الثابتة التي تم التحقق منها في وقت البناء.

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

ما الفرق بين throw و throws في Swift؟

Throw هو عامل يرمي خطأ أثناء تنفيذ البرنامج، مقطعاً التدفق. Throws هو معدِّل توقيع الدالة يعلن أن الدالة قد ترمي خطأ. لا يمكن لدالة بدون throws استخدام throw. Throws هو عقد في وقت الترجمة، throw هو إجراء في وقت التشغيل.

لماذا لا يوجد throws في Kotlin؟

Kotlin يتبع فلسفة الاستثناءات غير المحققة: جميع الاستثناءات يمكن ألا تُعالج دون إلزام نحوي. يعتقد مطورو Kotlin أن throws في Java يؤدي إلى كتل try-catch مفرطة وتجاهل الاستثناءات المحققة عبر catch فارغة. نوع Nothing في Kotlin يسمح باستخدام throw كتعبير، مستبدلاً throws بنهج أكثر مرونة.

متى نستخدم try! في Swift؟

try! مقبول فقط عندما تكون متأكداً تماماً من استحالة حدوث خطأ: JSON صالح بشكل مؤكد من الحزمة، بيانات ثابتة، مخططات URL صحيحة. في كود الإنتاج، try! استثناء وليس قاعدة. try? أفضل للسيناريوهات الاختيارية مع قيمة احتياطية، try مع do-catch للمعالجة الإلزامية للأخطاء.

ما هو rethrows في Swift؟

Rethrows هو معدِّل للدوال التي تقبل إغلاقات throws. دالة مع rethrows ترمي خطأ فقط إذا رمى الإغلاق الممرر خطأ. هذا يسمح للدوال عالية المستوى (map, filter) بالعمل مع كل من الإغلاقات throws والعادية دون فرض try على الطرف المستدعي.

هل يمكن رمي خطأ داخل كتلة catch؟

نعم، داخل catch يمكن استخدام throw لنشر الخطأ إلى أعلى المكدس، ملفوفاً في نوع مختلف أو مع إضافة سياق. هذا يسمى error chaining أو إعادة الرمي. في Swift، throw آخر داخل catch كافٍ؛ في Kotlin، throw داخل كتلة catch. كتلة finally تُنفَّذ قبل تمرير التحكم إلى الأعلى.

الملخص

  • Throw — عامل رمي استثناء يقطع التدفق الحالي ويمرر كائن الخطأ إلى أعلى المكدس
  • Throws — إعلان في وقت الترجمة في توقيع الدالة عن احتمال حدوث خطأ، إلزامي في Swift، غير موجود في Kotlin
  • في Swift throw يقبل enum: Error، الدالة تُعلَّم بـ throws، تُستدعى عبر try / try? / try!
  • في Kotlin throw هو تعبير من نوع Nothing، يسمح باستخدامه في when وعامل Elvis والإسنادات
  • Rethrows في Swift يسمح للدوال عالية المستوى بنشر الأخطاء فقط من الإغلاقات الممررة
  • الأخطاء المخصصة تُنشأ عبر enum (Swift) أو sealed class (Kotlin) مع قيم مرتبطة لمعلومات مفصلة
  • صمم أنواع الأخطاء على مبدأ "حالة واحدة — سيناريو واحد" لسهولة المعالجة عبر مطابقة الأنماط

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

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

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

اقرأ أيضًا