Result Type: ما هو، نوع الحاوية Result وكيف يعمل في تطوير التطبيقات المحمولة

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

Result Type — نوع حاوية يمثل نتيجة عملية يمكن أن تكتمل بنجاح (Success) أو بخطأ (Failure). على عكس الاستثناءات، ينقل Result الخطأ كقيمة عادية دون فك تكدس الاستدعاءات، مما يجعل معالجة الأخطاء المتوقعة أكثر أماناً وقابلية للتركيب. وفقاً لـ Apple Swift Documentation (2026)، يسمح Result<Success, Failure> في Swift بربط العمليات مع معالجة تلقائية للأخطاء عبر map و flatMap دون مقاطعة تنفيذ البرنامج.

النقاط الرئيسية

  • Result Type — حاوية عامة بحالتين: Success (بيانات) و Failure (خطأ)، بدون فك تكدس الاستدعاءات
  • في Swift Result<Success, Failure> — نوع مدمج مع دوال map و flatMap و mapError و get
  • في Kotlin Result<T> يمثل نجاحاً أو Throwable، مع دوال getOrNull و getOrDefault و fold
  • في Dart Result<T> القياسي متاح منذ Dart 3.0، بالإضافة إلى Either من حزمة fpdart
  • التركيب عبر map و flatMap يسمح بدمج عمليات متعددة تعيد Result دون تحققات متداخلة

ما هو Result Type؟

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

يعود مفهوم Result إلى البرمجة الوظيفية، حيث تسمى الأنواع المشابهة Either (أو Left/Right). في Swift، أصبح Result نوعاً قياسياً في Swift 5.0؛ في Kotlin، ظهر Result<T> في المكتبة القياسية؛ في Dart 3.0، تم تقديم Result<T> المدمج. توفر كل implementación دوال للعمل مع الحاوية: map (تحويل قيمة النجاح)، flatMap (ربط دوال Result)، mapError (تحويل الخطأ)، fold (معالجة كلتا الحالتين). Result Type هو حجر الزاوية في معالجة الأخطاء الوظيفية في تطوير التطبيقات المحمولة، بديل لـ try-catch للسيناريوهات المتوقعة.

الميزة الرئيسية لـ Result هي قابلية التركيب. يمكنك دمج عمليات متعددة، كل منها قد تفشل، في سلسلة واحدة دون كتل try-catch متداخلة. إذا فشلت أي عملية في السلسلة بـ Failure، تنقطع السلسلة بأكملها وتعيد Failure — بدون شرط if واحد أو كتلة catch. هذا يجعل الكود خطياً وقابلاً للقراءة، خاصة في السيناريوهات ذات الطلبات المتعددة المتسلسلة لواجهة API أو تحققات قواعد العمل.

Result في Swift

في Swift، Result<Success, Failure> هو enum بحالتين: .success(Success) و .failure(Failure)، حيث Failure مقيدة ببروتوكول Error. Result في Swift هو نوع وظيفي كامل مع دوال map و flatMap و mapError و get. get هي دالة خاصة: تعيد قيمة Success إذا كانت النتيجة ناجحة، وتلقي الخطأ إذا كانت Failure. هذا يسمح لـ Result بالعمل كجسر بين الأنماط الوظيفية والقائمة على الاستثناءات: المعالجة عبر map/flatMap في سلسلة، وفي النهاية استخدام get مع do-catch للتكامل مع كود throws.

Enum Result و switch الشامل

Result في Swift هو enum مع معاملات عامة، مما يسمح للمترجم بالتحقق من المعالجة الشاملة عبر switch أو do-catch. إذا أضفت حالة جديدة إلى enum NetworkError، سينتج المترجم خطأ في جميع تعبيرات switch حيث لم تتم معالجة تلك الحالة. التحقق الشامل هو الميزة الرئيسية لـ Result على الاستثناءات: يضمن المترجم أن جميع الأخطاء الممكنة قد تم أخذها في الاعتبار في وقت البناء. على النقيض من ذلك، لا يتم التحقق من الاستثناءات بواسطة المترجم في Swift (يتم تعريف throws فقط، ولكن ليس نوع الخطأ).

swift
enum NetworkError: Error {
    case badURL
    case requestFailed(String)
    case decodingFailed
}

func fetchUser(id: Int) -> Result<User, NetworkError> {
    guard let url = URL(string: "https://api.example.com/users/\(id)") else {
        return .failure(.badURL)
    }
    let result = performRequest(url: url)
    switch result {
    case let .success(data):
        if let user = try? JSONDecoder().decode(User.self, from: data) {
            return .success(user)
        }
        return .failure(.decodingFailed)
    case let .failure(error):
        return .failure(.requestFailed(error.localizedDescription))
    }
}

// الاستخدام مع switch
let result = fetchUser(id: 42)
switch result {
case .success(let user):
    showUser(user)
case .failure(.badURL):
    logError("Invalid URL")
case .failure(.requestFailed(let msg)):
    showAlert(msg)
case .failure(.decodingFailed):
    logError("Decoding error")
}

Result<Success, Failure> يسمح بكتابة الخطأ على مستوى النوع: دالة fetchUser تعيد Result<User, NetworkError>، حيث NetworkError هو enum ملموس بثلاث حالات. يعالج المتلقي كل حالة عبر switch مع تغطية شاملة — يتحقق المترجم من أن جميع المتغيرات قد تمت معالجتها. switch الشامل هو ميزة رئيسية لـ Result على الاستثناءات: يضمن المترجم ألا تنسى معالجة .badURL أو .requestFailed أو .decodingFailed. مع الاستثناءات، لا يطلب المترجم المعالجة، ويمكن بسهولة تفويت catch(.badURL) المفقود في مراجعة الكود.

Result في Kotlin

في Kotlin، Result<T> هو نوع مدمج من المكتبة القياسية يمثل نجاحاً (T) أو خطأ (Throwable). على عكس Swift، لا يسمح Result في Kotlin بتحديد نوع خطأ ملموس — فقط Throwable. يتم ذلك للبساطة ولكنه يتطلب تحققاً إضافياً من نوع الخطأ عبر تعبير when. يدعم Result في Kotlin دوال fold (معالجة كلتا الحالتين)، getOrNull (نجاح أو null)، getOrDefault (نجاح أو قيمة افتراضية)، map، recover، andThen. خصوصية Kotlin: Result غير مصمم للانتشار المباشر عبر حدود الدوال — لا يمكن استخدامه كنوع إرجاع لدوال Android SDK أو واجهة برمجة Kotlin Coroutines دون تكييف إضافي.

kotlin
fun parseJson(input: String): Result<JsonObject> {
    return runCatching {
        JsonParser.parseString(input).asJsonObject()
    }
}

fun validateEmail(email: String): Result<String> {
    return if (email.contains("@")) {
        Result.success(email.trim())
    } else {
        Result.failure(IllegalArgumentException("Invalid email"))
    }
}

data class SignupData(val name: String, val email: String)

fun processSignup(name: String, email: String): SignupResult {
    return validateEmail(email).fold(
        onSuccess = { validateName(name) },
        onFailure = { SignupResult.Error("Invalid email") }
    )
}

runCatching هو غلاف مناسب في Kotlin يلتقط أي استثناء ويعيد Result.failure. validateEmail تعيد Result.success أو Result.failure حسب التحقق. fold يعالج كلتا الحالتين بشكل مضغوط: عند Success، يتم استدعاء دالة التحقق التالية؛ عند Failure، يتم إرجاع SignupResult.Error. مهم: Result في Kotlin غير مصمم للتخزين في حقول data class أو النقل المباشر عبر حدود دوال suspend — استخدم classes المختومة المخصصة (Success/Error/Loading) لتمثيل حالات واجهة المستخدم في Jetpack Compose أو MVVM.

Result في Dart و Flutter

منذ Dart 3.0، تتضمن المكتبة القياسية Result<T> مدمج — class مختوم بمنشئين: T.ok() (نجاح) و Error.error() (خطأ مع Object و StackTrace). قبل Dart 3.0، كان مطورو Flutter يستخدمون Either<L, R> من حزمة dartz أو classes المختومة المخصصة. Result المدمج في Dart بسيط: لا يوفر map/flatMap مباشرة — يجب تنفيذ هذه الدوال عبر when أو باستخدام دوال التمديد. للمعالجة الوظيفية الجادة، يظل Either من fpdart حلاً أكثر قوة مع دعم لـ map و flatMap و mapLeft و fold و andThen وعوامل bind.

Either من fpdart

Either<L, R> هو نوع منحرف لليسار من حزمة fpdart، حيث Left هو الخطأ و Right هو النجاح. على عكس Result<T> المدمج، يكتب Either الخطأ على مستوى معامل النوع (L)، مما يسمح بالتمييز بين أنواع الأخطاء في وقت الترجمة. توفر حزمة fpdart مجموعة كاملة من الدوال التجميعية الوظيفية: map (Right -> Right)، mapLeft (Left -> Left)، flatMap (bind — Either المتداخل)، andThen (ربط دون تحويل)، fold (الخروج من Either)، getOrElse (قيمة افتراضية). لتطبيقات Flutter ذات النهج الوظيفي، Either هو المعيار الفعلي.

dart
import 'dart:convert';
import 'package:fpdart/fpdart.dart';

class UserService {
    Either<AppError, User> fetchUser(String id) {
        try {
            final response = await http.get(
                Uri.parse('https://api.example.com/users/$id')
            );
            if (response.statusCode == 200) {
                final user = User.fromJson(
                    json.decode(response.body)
                );
                return Either.of(user);
            }
            return Either.left(
                AppError.serverError(response.statusCode)
            );
        } on SocketException catch (e) {
            return Either.left(AppError.networkError(e.message));
        }
    }
}

// الاستخدام مع fold
final result = await service.fetchUser('42');
result.fold(
    (left)  => showError(left.message),
    (right) => showUser(right),
);

Either<L, R> من fpdart هو نوع منحرف لليسار: Left — خطأ، Right — نجاح. fetchUser تعيد Either<AppError, User>، حيث AppError هو class مختوم بأنواع خطأ ملموسة (serverError، networkError). fold يعالج كلتا الحالتين: الاستدعاء الأول لـ Left (خطأ)، والثاني لـ Right (نجاح). في Dart 3.0، يدعم Result المدمج أيضاً fold، لكنه لا يوفر map/flatMap. للسلاسل، يوفر Either من fpdart map و flatMap (bind) و mapLeft و andThen — مجموعة كاملة من الدوال التجميعية الوظيفية لتأليف الأخطاء.

تركيب Result عبر map و flatMap

الميزة الرئيسية لـ Result على الاستثناءات هي التركيب. إذا كان لديك عمليات متعددة، كل منها قد تفشل، يمكنك ربطها باستخدام map و flatMap دون if متداخلة واحدة أو try-catch. map يحول قيمة النجاح: Result.success(x) -> Result.success(f(x)). flatMap (يسمى أيضاً bind أو andThen) هو للحالات التي يعيد فيها التحويل نفسه Result: Result.success(x) -> f(x) -> Result<Y>. إذا فشلت أي خطوة بـ Failure، لا يتم تنفيذ العمليات اللاحقة — تنقطع السلسلة.

kotlin
data class UserRequest(val userId: String, val token: String)

sealed class AuthError {
    data object InvalidToken : AuthError()
    data class UserNotFound(val id: String) : AuthError()
}

typealias Outcome<T> = Either<AuthError, T>

fun validateToken(token: String): Outcome<String> =
    if (token.isNotBlank()) Either.right(token)
    else Either.left(AuthError.InvalidToken)

fun fetchProfile(userId: String): Outcome<Profile> =
    if (userId == "42") Either.right(Profile("Alice"))
    else Either.left(AuthError.UserNotFound(userId))

// التركيب عبر flatMap (andThen في fpdart)
val result = validateToken("abc123")
    .flatMap { fetchProfile("42") }
    .map { it.name }
    .getOrElse { "Guest" }

println(result) // "Alice"

سلسلة: validateToken -> fetchProfile -> map name -> getOrElse «ضيف». إذا أعاد validateToken Left (InvalidToken)، تنقطع السلسلة وتعيد «ضيف». إذا أعاد fetchProfile Left (UserNotFound) — أيضاً «ضيف». إذا نجحت كلتا العمليتين — اسم الملف الشخصي. flatMap يسمح بدمج دوال Either، كل منها قد تفشل، في سلسلة خطية واحدة. في النمط التقليدي للاستثناءات، سيتطلب نفس الكود اثنين try-catch متداخلين أو تحققات null. getOrElse في النهاية هو نقطة الخروج من التركيب، التي توفر قيمة افتراضية لحالة Failure.

Result مقابل الاستثناءات: متى تختار ماذا

Result والاستثناءات ليسا نهجين متنافيين. لكل منهما مجاله الخاص، وفي تطبيق محمول جيد التصميم، يتم استخدام كليهما. يعتمد الاختيار على ما إذا كان الخطأ متوقعاً أم غير متوقع. Result للأخطاء المتوقعة التي هي جزء من منطق العمل: بريد إلكتروني غير صالح، أموال غير كافية، تجاوز حد الطلبات. الاستثناءات للأخطاء النظامية غير المتوقعة: فقدان الشبكة، OutOfMemoryError، NullPointerException (التي لا ينبغي أن تحدث ولكنها تحدث).

المعيارResult Typeالاستثناءات (Exception/Error)
نوع الخطأمتوقعة (منطق العمل)غير متوقعة (نظامية)
الأداءتكلفة منخفضة (بدون فك تكدس)تكلفة عالية (فك تكدس، التقاط StackTrace)
التركيبعبر map/flatMap — سلاسل خطيةtry-catch متداخلة — صعبة القراءة
المترجمتحقق شامل (switch/when)فقط الاستثناءات المفحوصة في Java
تدفق التنفيذلا ينقطع — الخطأ كقيمةينقطع حتى أقرب catch
الاختبارسهل: التحقق من النتيجة، تأكيد isSuccess/isErrorيتطلب assertThrows وكائنات mock
متى تستخدمالتحقق من العمل، سلاسل الطلبات، النماذجفقدان الشبكة، أخطاء الإدخال/الإخراج، أعطال النظام

قاعدة عملية: إذا كان الخطأ جزءاً من سير العمل الطبيعي للتطبيق (أدخل المستخدم بريداً إلكترونياً غير صالح، صلاحيات غير كافية) — استخدم Result. إذا كان الخطأ حالة استثنائية (الخادم لا يستجيب، نفاد الذاكرة) — استخدم الاستثناءات. في تطوير التطبيقات المحمولة، Result على حدود الطبقات (UseCase -> ViewModel) والاستثناءات داخل الطبقات (API -> Repository) هو نمط شائع يجمع مزايا كلا النهجين.

استراتيجية الترحيل من الاستثناءات إلى Result

يجب أن يكون ترحيل الكود الحالي القائم على الاستثناءات إلى Result تدريجياً. ابدأ بـ حدود الطبقات: غلف استدعاءات دوال throws في Result { try ... } (Swift) أو runCatching { ... } (Kotlin). ثم استبدل نوع الإرجاع لدوال Repository و UseCase بـ Result/Either، مع إبقاء التنفيذ الداخلي على الاستثناءات. في الخطوة الأخيرة، قم بترحيل ViewModel: بدلاً من UiState مع الاستثناءات، استخدم class مختوم UiState<T> (Loading، Success، Error)، حيث يخزن Error خطأ المجال، وليس Throwable. الترحيل التدريجي يسمح باختبار كل طبقة على حدة دون إعادة هيكلة شاملة.

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

كيف يختلف Result Type عن Optional/Option؟

Optional (T?) يمثل وجود أو غياب قيمة — nil يعني «لا توجد بيانات» لكنه لا يشرح لماذا. Result (Success/Failure) يحتوي ليس فقط على النجاح ولكن أيضاً على سبب الخطأ بنوع ملموس. استخدم Optional عندما يكون الغياب طبيعياً (مثلاً، حقل ملف شخصي اختياري)، و Result عندما تحتاج إلى معلومات الخطأ.

كيف تحول دالة throws إلى Result؟

في Swift، استخدم Result { try throwingFunc() } — منشئ Result يأخذ إغلاقاً يرمي. في Kotlin، استخدم runCatching { throwingFunc() }، الذي يعيد Result<T>. في Dart، استخدم Result<T>.tryCatch(() => throwingFunc()). هذا يسمح بدمج سهل للكود القائم على الاستثناءات في سلاسل Result.

كيف تتعامل مع خطأ في Result دون فقدان المعلومات؟

استخدم mapError (Swift) أو mapLeft (Either في Dart/Kotlin) لتحويل نوع الخطأ دون تغيير قيمة النجاح. إذا كنت بحاجة لمعالجة كلتا الحالتين وإرجاع قيمة واحدة، استخدم fold. للتسجيل دون مقاطعة السلسلة، استخدم onFailure (Kotlin) أو نقطة فحص بادئة.

هل يمكن استخدام Result في Kotlin مع coroutines؟

نعم، ولكن بحذر. لا يُوصى باستخدام Result<T> من Kotlin كنوع إرجاع لدوال suspend مباشرة بسبب خصائص مترجم K2 والانعكاس. استخدم class المختوم الخاص بك NetworkResult<T> (Success، Error، Loading) لتمثيل الحالات في coroutines. للأخطاء المتوقعة في منطق العمل، Either من Arrow هو بديل أكثر قوة.

ما هو fold في سياق Result؟

fold هو دالة تأخذ استدعاءين: onSuccess (لحالة النجاح) و onFailure (لحالة الخطأ)، وتعيد قيمة واحدة من أي نوع. إنه مكافئ لتعبير switch لكن كدالة ذات ترتيب أعلى. fold هو نقطة الخروج الرئيسية من سلاسل Result، حيث تحول Success/Failure إلى UiState أو سلسلة للمستخدم أو Result آخر.

الملخص

  • Result Type — حاوية عامة (Success/Failure) لمعالجة آمنة للأخطاء المتوقعة بدون فك تكدس الاستدعاءات
  • في Swift Result<Success, Failure> مع Failure: Error يوفر تحققاً شاملاً عبر switch مع أخطاء type-safe
  • في Kotlin Result<T> يغلف نجاحاً أو Throwable، runCatching هو منشئ مناسب من كود throws
  • في Dart Result<T> المدمج (Dart 3.0) و Either<L, R> من fpdart للتركيب المتقدم مع map/flatMap
  • التركيب عبر map (تحويل النجاح) و flatMap (ربط دوال Result) يحل محل try-catch المتداخلة
  • Result مقابل الاستثناءات: Result للأخطاء التجارية المتوقعة، الاستثناءات للأعطال النظامية غير المتوقعة
  • استخدم Result على حدود الطبقات لمعالجة أخطاء صريحة وقابلة للاختبار دون مقاطعة تدفق التنفيذ

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

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

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

اقرأ أيضًا