Result Type — ένας τύπος-περιέκτης που αντιπροσωπεύει το αποτέλεσμα μιας λειτουργίας που μπορεί να τελειώσει με επιτυχία (Success) ή σφάλμα (Failure). Σε αντίθεση με τις εξαιρέσεις, το Result μεταδίδει το σφάλμα ως μια συνηθισμένη τιμή χωρίς να ξετυλίγει τη στοίβα, καθιστώντας τον χειρισμό αναμενόμενων σφαλμάτων ασφαλέστερο και πιο συνθέσιμο. Σύμφωνα με το Apple Swift Documentation (2026), το Result<Success, Failure> στη Swift επιτρέπει το συνδυασμό αλυσίδων λειτουργιών με αυτόματο χειρισμό σφαλμάτων μέσω map και flatMap, χωρίς να διακόπτει την εκτέλεση του προγράμματος.
Κύρια σημεία
Result Type είναι ένας γενικός τύπος που ενσωματώνει το αποτέλεσμα εκτέλεσης μιας λειτουργίας που μπορεί να τελειώσει τόσο με επιτυχία όσο και με σφάλμα. Σε αντίθεση με τις εξαιρέσεις, όπου το σφάλμα διακόπτει την κανονική ροή εκτέλεσης και απαιτεί ξετύλιγμα της στοίβας για να βρεθεί το catch, το Result μεταδίδει το σφάλμα ως μια συνηθισμένη τιμή — η καλούσα πλευρά λαμβάνει πάντα ένα αντικείμενο και αποφασίζει η ίδια πώς να το χειριστεί. Αυτό είναι ιδιαίτερα χρήσιμο για αναμενόμενα σφάλματα: μη έγκυρη είσοδος, επιχειρηματικοί κανόνες, άρνηση διακομιστή, όπου μια εξαίρεση θα ήταν πολύ βαρύς μηχανισμός.
Η έννοια του Result προέρχεται από τον συναρτησιακό προγραμματισμό, όπου παρόμοιοι τύποι ονομάζονται Either (ή Left/Right). Στη Swift, το Result έγινε τυπικός τύπος στην Swift 5.0, στην Kotlin το Result<T> εμφανίστηκε στην τυπική βιβλιοθήκη, στη Dart 3.0 — ενσωματωμένο Result<T>. Κάθε υλοποίηση παρέχει μεθόδους για εργασία με τον περιέκτη: map (μετασχηματισμός της επιτυχούς τιμής), flatMap (αλυσίδα συναρτήσεων Result), mapError (μετασχηματισμός σφάλματος), fold (χειρισμός και των δύο περιπτώσεων). Result Type — ο ακρογωνιαίος λίθος του συναρτησιακού χειρισμού σφαλμάτων στην ανάπτυξη κινητών, μια εναλλακτική του try-catch για αναμενόμενα σενάρια.
Το βασικό πλεονέκτημα του Result είναι η συνθεσιμότητα. Μπορείτε να συνδυάσετε πολλαπλές λειτουργίες, καθεμία από τις οποίες μπορεί να επιστρέψει σφάλμα, σε μια ενιαία αλυσίδα χωρίς ένθετα try-catch. Εάν οποιαδήποτε λειτουργία στην αλυσίδα τελειώσει με Failure, ολόκληρη η αλυσίδα διακόπτεται και επιστρέφει Failure — χωρίς ούτε μία συνθήκη if ή μπλοκ catch. Αυτό κάνει τον κώδικα γραμμικό και ευανάγνωστο, ειδικά σε σενάρια με πολλαπλά διαδοχικά αιτήματα API ή ελέγχους επιχειρηματικών κανόνων.
Στη Swift το Result<Success, Failure> είναι ένα enum με δύο περιπτώσεις: .success(Success) και .failure(Failure), όπου το Failure περιορίζεται από το πρωτόκολλο Error. Το Swift Result είναι ένας πλήρης συναρτησιακός τύπος με μεθόδους map, flatMap, mapError και get. Η get είναι μια ειδική μέθοδος: επιστρέφει την τιμή Success αν το αποτέλεσμα είναι επιτυχές και πετάει σφάλμα αν είναι Failure. Αυτό επιτρέπει τη χρήση του Result ως γέφυρας μεταξύ συναρτησιακού και βασισμένου σε εξαιρέσεις στυλ: χειριστείτε μέσω map/flatMap στην αλυσίδα, και στο τέλος — get μέσω do-catch για ενσωμάτωση με κώδικα throws.
Το Swift Result είναι ένα enum με γενικές παραμέτρους, που επιτρέπει στον μεταγλωττιστή να ελέγχει τον εξαντλητικό χειρισμό μέσω switch ή do-catch. Εάν προσθέσετε μια νέα περίπτωση στο enum NetworkError, ο μεταγλωττιστής θα δώσει σφάλμα σε όλες τις εκφράσεις switch όπου αυτή η περίπτωση δεν αντιμετωπίζεται. Εξαντλητικός έλεγχος (exhaustive checking) — το κύριο πλεονέκτημα του Result έναντι των εξαιρέσεων: ο μεταγλωττιστής εγγυάται ότι όλα τα πιθανά σφάλματα λαμβάνονται υπόψη στο στάδιο της μεταγλώττισης. Σε αντίθεση, οι εξαιρέσεις δεν ελέγχονται από τον μεταγλωττιστή στη Swift (μόνο το throws δηλώνεται, αλλά όχι ο τύπος σφάλματος).
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) μπορεί εύκολα να διαφύγει σε μια αναθεώρηση.
Στην Kotlin το Result<T> είναι ένας ενσωματωμένος τύπος από την τυπική βιβλιοθήκη, που αντιπροσωπεύει επιτυχία (T) ή σφάλμα (Throwable). Σε αντίθεση με τη Swift, στην Kotlin το Result δεν επιτρέπει τον καθορισμό συγκεκριμένου τύπου σφάλματος — μόνο Throwable. Αυτό έγινε για απλότητα, αλλά απαιτεί πρόσθετο έλεγχο του τύπου σφάλματος μέσω της έκφρασης when. Το Kotlin Result υποστηρίζει συναρτήσεις fold (χειρισμός και των δύο περιπτώσεων), getOrNull (επιτυχία ή null), getOrDefault (επιτυχία ή προεπιλεγμένη τιμή), map, recover, andThen. Ιδιαιτερότητα της Kotlin: το Result δεν προορίζεται για άμεση διάδοση μέσω ορίων συναρτήσεων — δεν μπορεί να χρησιμοποιηθεί ως τύπος επιστροφής για μεθόδους Android SDK ή Kotlin Coroutines API χωρίς πρόσθετη προσαρμογή.
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. Σημαντικό: το Kotlin Result δεν προορίζεται για αποθήκευση σε πεδία data class ή άμεση μετάδοση μέσω ορίων συναρτήσεων suspend — χρησιμοποιήστε τις δικές σας sealed class (Success/Error/Loading) για την αναπαράσταση καταστάσεων UI στο Jetpack Compose ή MVVM.
Από τη Dart 3.0 στην τυπική βιβλιοθήκη εμφανίστηκε το ενσωματωμένο Result<T> — μια sealed class με δύο κατασκευαστές: T.ok() (επιτυχία) και Error.error() (σφάλμα με Object και StackTrace). Πριν από τη Dart 3.0, οι προγραμματιστές Flutter χρησιμοποιούσαν Either<L, R> από το πακέτο dartz ή προσαρμοσμένες sealed classes. Το ενσωματωμένο Result στη Dart είναι μινιμαλιστικό: δεν παρέχει άμεσα map/flatMap — αυτές οι συναρτήσεις πρέπει να υλοποιηθούν μέσω when ή με χρήση extension methods. Για σοβαρό συναρτησιακό χειρισμό, το Either από το fpdart παραμένει μια πιο ισχυρή λύση με υποστήριξη για map, flatMap, mapLeft, fold, andThen και τελεστές bind.
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 είναι το de facto πρότυπο.
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 — sealed class με συγκεκριμένους τύπους σφαλμάτων (serverError, networkError). fold χειρίζεται και τις δύο περιπτώσεις: το πρώτο callback για Left (σφάλμα), το δεύτερο για Right (επιτυχία). Στη Dart 3.0 το ενσωματωμένο Result υποστηρίζει επίσης fold, αλλά δεν παρέχει map/flatMap. Για αλυσίδες, το Either από το fpdart παρέχει map, flatMap (bind), mapLeft, andThen — ένα πλήρες σύνολο συναρτησιακών συνδυαστών για σύνθεση σφαλμάτων.
Το κύριο πλεονέκτημα του 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, οι επόμενες λειτουργίες δεν εκτελούνται — η αλυσίδα διακόπτεται.
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 και οι εξαιρέσεις — δεν είναι αμοιβαία αποκλειόμενες προσεγγίσεις. Κάθε μία έχει τον τομέα εφαρμογής της, και σε μια καλά σχεδιασμένη εφαρμογή κινητού χρησιμοποιούνται και οι δύο. Η επιλογή εξαρτάται από το αν το σφάλμα είναι αναμενόμενο (expected) ή απροσδόκητο (unexpected). Result — για αναμενόμενα σφάλματα που αποτελούν μέρος της επιχειρηματικής λογικής: μη έγκυρο email, ανεπαρκή υπόλοιπα λογαριασμού, υπέρβαση ορίου αιτημάτων. Εξαιρέσεις — για απροσδόκητα σφάλματα συστήματος: απώλεια δικτύου, OutOfMemoryError, NullPointerException (που δεν θα έπρεπε να συμβαίνουν, αλλά συμβαίνουν).
| Κριτήριο | Result Type | Εξαιρέσεις (Exception/Error) |
|---|---|---|
| Τύπος σφάλματος | Αναμενόμενο (επιχειρηματική λογική) | Απροσδόκητο (σύστημα) |
| Απόδοση | Χαμηλό κόστος (χωρίς ξετύλιγμα στοίβας) | Υψηλό κόστος (ξετύλιγμα στοίβας, καταγραφή StackTrace) |
| Σύνθεση | Μέσω map/flatMap — γραμμικές αλυσίδες | Ένθετα try-catch — δύσκολο να διαβαστούν |
| Μεταγλωττιστής | Εξαντλητικός έλεγχος (switch/when) | Μόνο checked exceptions στη Java |
| Ροή εκτέλεσης | Δεν διακόπτεται — σφάλμα ως τιμή | Διακόπτεται μέχρι το πλησιέστερο catch |
| Δοκιμές | Εύκολο: έλεγχος αποτελέσματος, assert isSuccess/isError | Απαιτεί assertThrows και αντικείμενα mock |
| Πότε να χρησιμοποιείτε | Επιχειρηματική επικύρωση, αλυσίδες αιτημάτων, φόρμες | Απώλεια δικτύου, σφάλματα I/O, καταρρεύσεις συστήματος |
Πρακτικός κανόνας: εάν το σφάλμα είναι μέρος της κανονικής ροής εργασίας της εφαρμογής (ο χρήστης εισήγαγε μη έγκυρο email, δεν έχει επαρκή δικαιώματα πρόσβασης) — χρησιμοποιήστε Result. Εάν το σφάλμα είναι μια εξαιρετική κατάσταση (ο διακομιστής δεν ανταποκρίνεται, τελείωσε η μνήμη) — χρησιμοποιήστε εξαιρέσεις. Στην ανάπτυξη κινητών, Result στα όρια επιπέδων (UseCase -> ViewModel) και εξαιρέσεις εντός επιπέδων (API -> Repository) — ένα κοινό μοτίβο που συνδυάζει τα πλεονεκτήματα και των δύο προσεγγίσεων.
Η μετάβαση του υπάρχοντος κώδικα που βασίζεται σε εξαιρέσεις σε Result πρέπει να είναι σταδιακή. Ξεκινήστε από τα όρια επιπέδων: τυλίξτε κλήσεις συναρτήσεων throws σε Result { try ... } (Swift) ή runCatching { ... } (Kotlin). Στη συνέχεια, αντικαταστήστε τον τύπο επιστροφής των μεθόδων Repository και UseCase με Result/Either, αφήνοντας την εσωτερική υλοποίηση στις εξαιρέσεις. Στο τελευταίο στάδιο, μεταφέρετε το ViewModel: αντί για UiState με εξαιρέσεις, χρησιμοποιήστε sealed class UiState<T> (Loading, Success, Error), όπου το Error αποθηκεύει το σφάλμα τομέα, όχι το Throwable. Η σταδιακή μετάβαση επιτρέπει τη δοκιμή κάθε επιπέδου ξεχωριστά χωρίς καθολική αναδιάρθρωση.
Συχνές ερωτήσεις
Optional (T?) αντιπροσωπεύει την παρουσία ή απουσία μιας τιμής — το nil σημαίνει “δεν υπάρχουν δεδομένα”, αλλά δεν λέει γιατί. Result (Success/Failure) περιέχει όχι μόνο την επιτυχία, αλλά και την αιτία του σφάλματος με συγκεκριμένο τύπο. Χρησιμοποιήστε Optional όταν η απουσία τιμής είναι φυσιολογική (π.χ. προαιρετικό πεδίο προφίλ), Result — όταν χρειάζεστε πληροφορίες για το σφάλμα.
Στη Swift χρησιμοποιήστε Result { try throwingFunc() } — ο κατασκευαστής Result δέχεται ένα throws κλείσιμο. Στην Kotlin — runCatching { throwingFunc() }, που επιστρέφει Result<T>. Στη Dart — Result<T>.tryCatch(() => throwingFunc()). Αυτό επιτρέπει την εύκολη ενσωμάτωση κώδικα βασισμένου σε εξαιρέσεις σε αλυσίδες Result.
Χρησιμοποιήστε mapError (Swift) ή mapLeft (Either σε Dart/Kotlin) για μετασχηματισμό του τύπου σφάλματος χωρίς αλλαγή της επιτυχούς τιμής. Εάν πρέπει να χειριστείτε και τις δύο περιπτώσεις και να επιστρέψετε μια ενιαία τιμή — χρησιμοποιήστε fold. Για καταγραφή χωρίς διακοπή της αλυσίδας, χρησιμοποιήστε onFailure (Kotlin) ή ένα πρόθεμα inspection point.
Ναι, αλλά προσεκτικά. Το Kotlin Result<T> δεν συνιστάται ως άμεσος τύπος επιστροφής συναρτήσεων suspend λόγω χαρακτηριστικών του μεταγλωττιστή K2 και αντανάκλασης. Χρησιμοποιήστε τη δική σας sealed class NetworkResult<T> (Success, Error, Loading) για αναπαράσταση καταστάσεων σε κορουτίνες. Για αναμενόμενα σφάλματα στην επιχειρηματική λογική, το Either από το Arrow είναι μια πιο ισχυρή εναλλακτική.
fold — μια μέθοδος που δέχεται δύο callbacks: onSuccess (για την επιτυχή περίπτωση) και onFailure (για την περίπτωση σφάλματος), και επιστρέφει μια ενιαία τιμή οποιουδήποτε τύπου. Είναι το ισοδύναμο μιας έκφρασης switch, αλλά με τη μορφή συνάρτησης ανώτερης τάξης. Το fold — το κύριο σημείο εξόδου από αλυσίδες Result, όπου μετατρέπετε Success/Failure σε UiState, συμβολοσειρά για τον χρήστη ή άλλο Result.
Περίληψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης