Exception vs Result: hansını, niyə
Exception-larla işləmək tək qatlı kodda problem deyil. Problem qatlar arasında başlayır və rəsmi Flutter sənədi onu üç bəndlə sayır:
- Exception-lar sənədləşdirilmir. Fərqli qatlar və komponentlər sənədləşdirilməmiş exception ata bilir; çağıran tərəf nəyi tutmalı olduğunu bilmir.
- Developer tutmağı unudur. Kompilyator xəbərdarlıq etmir; nəticədə tutulmamış exception istifadəçiyə crash kimi çatır.
- `try/catch` bloklarının içi-içinə düşür. Bir neçə ardıcıl əməliyyat olduqda nəzarət axını oxunmaz olur.
Result sinfi eyni sənəddə bu üç problemin cavabı kimi təqdim olunur: çağıran tərəfi xətanı yoxlamağa məcbur edir, ona görə tutulmamış exception-lardan doğan baqların sayı azalır.
Məntiq sadədir: exception gizli çıxış yoludur (imzada görünmür), Result isə açıq — o, funksiyanın qaytarma tipindədir və kompilyator onu nəzərə almağa məcbur edir.
| Meyar | Exception | `Result<T>` |
|---|---|---|
| İmzada görünür? | Xeyr — `Future<Order>` xəta barədə heç nə demir | Bəli — `Future<Result<Order>>` |
| Kompilyator məcbur edir? | Xeyr | Bəli — `sealed` tipdə `switch` tam olmalıdır |
| Ardıcıl 3 əməliyyat | İçi-içinə `try/catch` | Ardıcıl yoxlama, düz axın |
| Kod həcmi | Az — "uğurlu yol" təmiz qalır | Çox — hər çağırışda yoxlama |
| Proqramçı səhvləri (`Error`) | Uyğundur — proqram dayanmalıdır | Uyğun deyil — belə səhvi bükmək baqı gizlədir |
| Qat sərhədləri | Sızmağa meyllidir | Sərhəddə dayanır |
Vacib bir ayırma var: Dart-da `Error` və `Exception` fərqli məqsədlər üçündür.
Error sinfinin rəsmi sənədi birbaşadır: Error obyektləri proqramçının qaçınmalı olduğu proqram nasazlığını təmsil edir; bunlar çağıranın gözləməli ya da tutmalı olduğu xətalar deyil, və baş verirsə proqramın dayandırılması ən təhlükəsiz cavab ola bilər. Nümunələr: RangeError, StateError, TypeError, AssertionError.
Bundan praktik qayda çıxır:
- `Result` yalnız gözlənilən uğursuzluqlar üçündür: şəbəkə yoxdur, 404, token bitib, validasiya alınmadı, ödəniş rədd edildi.
- `Error` bükülmür.
RangeError-uResult.error()içinə salmaq baqı gizlədir: siyahının indeksi səhvdirsə, düzəliş kodda olmalıdır, ekranda "xəta baş verdi" mesajında deyil. - Rəsmi
ResultimplementasiyasınınErrorvariantı da məhzExceptiontipini daşıyır —Objectdeyil. Bu, təsadüfi deyil.
Bir nüans: catch (e) hər şeyi tutur, o cümlədən Error-ları. Ona görə repository-də on SocketException, on UnauthorizedException kimi tipli tutma yazmaq daha düzgündür; catch (e) yalnız ən yuxarı səviyyədə (loqlama üçün) qalır.
// ══════ EXCEPTION ILƏ ══════
abstract interface class OrderRepository {
// İmza: xəta barədə heç nə demir.
// Hansı exception-lar atılır? Sənəddə yazılmasa — bilinmir.
Future<Order> place(Cart cart);
}
// Notifier-də: nə tutmalı olduğunu təxmin edirsən.
Future<void> submit(Cart cart) async {
state = const AsyncValue.loading();
try {
final order = await _repository.place(cart);
state = AsyncValue.data(order);
} on OutOfStockException catch (e) { // bunu bilirdin
state = AsyncValue.error(e, StackTrace.current);
} on PaymentDeclinedException catch (e) { // bunu da
state = AsyncValue.error(e, StackTrace.current);
}
// Bəs `SocketException`? Bəs `UnauthorizedException`?
// Tutulmadı → tətbiq crash olur, ya da state loading-də qalır.
}
// ══════ RESULT ILƏ ══════
abstract interface class OrderRepository {
// İmza: uğursuzluğun mümkün olduğunu AÇIQ deyir.
Future<Result<Order>> place(Cart cart);
}
// Notifier-də: kompilyator hər iki halı nəzərə almağa məcbur edir.
Future<void> submit(Cart cart) async {
state = const AsyncValue.loading();
final result = await _repository.place(cart);
// `sealed` tip: `switch` tam olmalıdır, yoxsa kompilyasiya xətası.
state = switch (result) {
Ok(:final value) => AsyncValue.data(value),
Error(:final error) => AsyncValue.error(error, StackTrace.current),
};
}
// ══════ ARDICIL ƏMƏLİYYATLAR: ən böyük fərq ══════
// Exception ilə: içi-içinə try/catch
Future<void> checkoutWithExceptions(Cart cart) async {
try {
final reserved = await _inventory.reserve(cart);
try {
final payment = await _payments.charge(reserved.total);
try {
await _orders.confirm(reserved, payment);
} catch (e) {
await _payments.refund(payment); // kompensasiya
rethrow;
}
} catch (e) {
await _inventory.release(reserved); // kompensasiya
rethrow;
}
} catch (e) {
// Burada hansı addım sındı? Bilinmir.
}
}
// Result ilə: düz axın, hər addım aydın
Future<Result<Order>> checkoutWithResult(Cart cart) async {
// Tam `switch` ifadəsi dəyəri çıxarır və uğursuzluqda dərhal qaytarır.
// (`reserved` yalnız bir budaqda təyin olunur — Dart bunu qəbul edir,
// çünki digər budaq `return` edir.)
final Reservation reserved;
switch (await _inventory.reserve(cart)) {
case Ok(:final value):
reserved = value;
case Error(:final error):
return Result.error(error);
}
final Payment payment;
switch (await _payments.charge(reserved.total)) {
case Ok(:final value):
payment = value;
case Error(:final error):
await _inventory.release(reserved); // kompensasiya
return Result.error(error);
}
switch (await _orders.confirm(reserved, payment)) {
case Ok(:final value):
return Result.ok(value);
case Error(:final error):
await _payments.refund(payment); // kompensasiya
await _inventory.release(reserved);
return Result.error(error);
}
}Eyni ssenari iki cür: exception ilə (nə tutulacağı görünmür) və `Result` ilə (imza hər şeyi deyir).
Result hər yerdə lazım deyil. Praktik bölgü belədir:
- Service qatı — exception atır (
UnauthorizedException,ServerException). BuradaResultəlavə etmək faydasızdır: service nazikdir və onun çağıranı yalnız repository-dir. - Repository qatı — exception-ları tutur və
Result(ya daFailure) qaytarır. Bu, sərhəddir. - Domain (use-case) —
Resultötürür ya da birləşdirir. - Presentation —
Result-uAsyncValue-a ya da state-ə çevirir.
Praktika. Layihəndə bir repository metodunu Result qaytarmağa keçir: exception-ları repository-nin içində tut, Result.ok/Result.error qaytar, notifier-də switch yaz. Sonra bir test əlavə et: şəbəkə xətası halında Result.error gəlir və exception atılmır.
Hazır sayılır: notifier-də try/catch qalmayıb, test yaşıldır.
📚 Mənbələr və sənədlər
- Result obyektləri ilə xəta idarəsirəsmidocs.flutter.dev
Bu mövzunun əsas mənbəyi: exception-ların üç problemi və `Result`-un onları necə həll etməsi.
- Dart: `Error` sinfirəsmiapi.dart.dev
"Proqramçının qaçınmalı olduğu nasazlıq" — `Error`-un tutulmamalı olduğunun rəsmi izahı.
- Dart: xəta idarəsirəsmidart.dev
`on` və `catch` fərqi, `rethrow` — tipli tutmanın sintaksisi.