اگر آپ نے فلٹر کے ساتھ کسی بھی معنی خیز وقت کے لیے کام کیا ہے، تو آپ نے شاید کچھ اس طرح لکھا ہوگا:
try {
final user = await repo.getUser();
print(user.name);
} catch (e) {
print('Something went wrong: $e');
}
یہ مرتب کرتا ہے۔ اسے پہنچا دیا جائے گا۔ اور چھ ماہ بعد، ایک خالی اسکرین کو گھورنے والے صارف کی طرف سے ایک بگ رپورٹ آئی۔ catch (e) میں نے حقیقی ناکامی کو نگل لیا۔
یہ ٹکڑا بے ضرر لگ سکتا ہے، لیکن اس میں تین مسائل ہیں جو صرف دباؤ میں ظاہر ہوتے ہیں۔
سب سے پہلے، ناکامی دستخط میں نظر نہیں آتی ہے۔ جو بھی repo.getUser() واپسی کی قیمت آپ کو یہ نہیں بتاتی ہے کہ اگر نیٹ ورک نیچے چلا جاتا ہے، ٹوکن ختم ہو جاتا ہے، یا جواب غلط ہوتا ہے تو کیا ہوتا ہے۔ آپ صرف نفاذ کو پڑھ کر یا پروڈکشن میں کیڑے ڈھونڈ کر ہی جان سکتے ہیں۔
دوسرا، مرتب کرنے والا آپ کی مدد نہیں کر سکتا۔ اگر ٹیم کا کوئی رکن بھول جائے۔ try/catch کوڈ بیس میں کہیں اور، ایپ ٹھیک مرتب کرتی ہے۔ یہ کچھ بھی انتباہ نہیں کرتا ہے۔ کریش اصل صارف کے سامنے رن ٹائم پر ہوتے ہیں، تعمیر وقت پر نہیں۔
تیسرا catch (e) وہ اندھا دھند ہر چیز پر قبضہ کر لیتے ہیں۔ ٹائپوز، null dereferences، اصلی نیٹ ورک کی خرابیاں، اور غلط JSON جوابات سب ایک ہی بلاک میں شامل ہیں۔ یہ خرابی کی تار کی جانچ کیے بغیر الگ الگ ہے اور نازک ہے کیونکہ یہ پیغام کے بدلتے ہی ٹوٹ جاتا ہے۔
ایک ساتھ لے کر، تمام ناکامی کے راستے فنکشن رائٹر اور کال کرنے والے کے درمیان ایک سماجی معاہدہ بن جاتے ہیں، بجائے اس کے کہ ٹائپ سسٹم کے ذریعے نافذ کیا جائے۔ سماجی معاہدہ دباؤ کے تحت ٹوٹ جاتا ہے، بڑی ٹیموں میں، اور 2 بجے جب کوئی حادثہ ہوتا ہے۔
کچھ ہفتے پہلے میں نے لکھا تھا ایڈوانسڈ ایرر ہینڈلنگ ان ڈارٹ: ریکارڈز، رزلٹ ٹائپس، مونڈز، اور فکسڈ ایکسپشنز یہ واضح کرنے کے لیے کہ ریکارڈ، سیل شدہ رزلٹ کی اقسام، اور مونڈ پیٹرنز کا استعمال کرتے ہوئے اس مسئلے کو کس طرح حل کیا جائے۔ dartzاور مقررہ مستثنیات جو غلطیوں کو قابل ٹائپ، مرئی، اور ناقابل توجہ بناتی ہیں۔
یہ مضمون اپنے طور پر لکھا گیا تھا، اس لیے اس سے پہلے کہ میں دوبارہ تھریڈ شروع کروں، میں اس کی ایک سرسری ریکیپ کے ساتھ شروع کروں گا کہ یہ کہاں تھا۔
ہم کیا احاطہ کریں گے:
-
خلاصہ: جہاں پچھلا مضمون چھوڑ دیا گیا تھا۔
-
پیٹرن کے بعد مسئلہ
-
DartExceptor کیسے کام کرتا ہے۔
-
بنیادی قسم
-
API: چار طریقے، ہر ایک میں ایک آپریشن ہوتا ہے۔
-
جہاں یہ کلین آرکیٹیکچر میں فٹ بیٹھتا ہے۔
-
کیوں نہ صرف ڈارٹز استعمال کریں؟
-
اسے آزمائیں
خلاصہ: جہاں پچھلا مضمون چھوڑ دیا گیا تھا۔
مضمون کئی پرتوں سے گزرا، ہر ایک پچھلی پرت کی حدود کو تبدیل کرتا ہے۔
ہم نے سب سے آسان ترمیم کے ساتھ Dart Records کے ساتھ آغاز کیا: کامیابی اور ناکامی کے لیے nullable فیلڈز کے ساتھ ایک ٹائپ شدہ ٹیپل۔
typedef Result = ({E? e, T? data});
یہ پہلے ہی ایک سادہ استثناء سے بہتر ہے کیونکہ واپسی کی قسم اب تسلیم کرتی ہے کہ فنکشن ناکام ہو سکتا ہے۔
تاہم، ریکارڈز کی عملی حدود ہیں۔ یہ یقینی بنانے کا کوئی طریقہ نہیں ہے کہ آپ یہ چیک کرنا نہ بھولیں کہ کون سے فیلڈز آباد ہیں، اور نتائج کو پہلے دستی طور پر کھولے بغیر تبدیل کرنے کا کوئی طریقہ نہیں ہے۔
فرق کا نتیجہ ایک مناسب مہر بند نتیجہ کی قسم میں ہوتا ہے۔ AppResultnullable فیلڈ ریکارڈز کو دو ساختی طور پر الگ ذیلی کلاسوں سے بدل دیں۔ AppSuccess اور AppFailureاور بھی ہے۔ when() دونوں صورتوں کو مجبور کرنے کا طریقہ:
sealed class AppResult {
const AppResult();
R when({
required R Function(T value) success,
required R Function(AppFailure failure) failure,
});
}
class AppSuccess extends AppResult {
const AppSuccess(this.value);
final T value;
@override
R when({
required R Function(T value) success,
required R Function(AppFailure failure) failure,
}) => success(value);
}
class AppFailure extends AppResult {
const AppFailure(this.error);
final AppError error;
@override
R when({
required R Function(T value) success,
required R Function(AppFailure failure) failure,
}) => failure(this);
}
کیونکہ AppResult ہے sealedکمپائلر مکمل کو نافذ کرتا ہے۔ آپ اپنے ریکارڈ یا اعمال کے ذریعے ناکامی کے نکات کو نہیں بھول سکتے۔ try/catch.
مضمون وہاں سے بڑھا۔ AppResult شامل کرکے مناسب مونڈ میں map اور flatMapنتائج کو ریپر کو چھوڑے بغیر تبدیل اور مربوط اور بازیافت کیا جاسکتا ہے۔ dartzایس Either یہ ان ٹیموں کے لیے زیادہ عام فنکشنل پروگرامنگ ہے جو وہ الفاظ چاہتے ہیں۔ چونکہ ہم نے فریزڈ بیس قسم کی رعایت کے ساتھ ختم کیا ہے، اس لیے ایرر سائیڈ نے بھی بنیادی سٹرنگ کے بجائے سٹرکچرڈ، پیٹرن سے ملنے والا ڈیٹا پاس کیا۔
آخر میں پیٹرن پورے اسٹیک میں اس طرح نظر آیا: یعنی سیل شدہ نتائج کی اقسام، ساختی استثناء، اور map/flatMap تبدیلی کے لیے ریپوزٹری، ڈومین، اور پریزنٹیشن لیئرز کے ذریعے مستقل کنکشن بنائے جاتے ہیں۔
مکمل اخذ، اگر آپ جاننا چاہتے ہیں کہ ہر پرت کیوں شامل کی گئی۔ dartz انضمام اور مقررہ استثناء کی ترتیبات کا اس مضمون میں تفصیل سے احاطہ کیا گیا ہے۔ یہاں جو کچھ آتا ہے وہ صرف اوپر کی شکل اختیار کرتا ہے اور یہ اوپر کی شکل کی طرف سفر نہیں ہے۔
پیٹرن کے بعد مسئلہ
اس مضمون کو شائع کرنے کے بعد کیا ہوا:
جب بھی میں نے کوئی نیا پروجیکٹ شروع کیا تو میں نے اپنے آپ کو یہی کام کرتے پایا۔ Result کلاس، دوبارہ لکھنا Ok اور Errدوبارہ نفاذ map, flatMapاور باقی۔ میں اسی تقریباً 150 لائنوں کو پروجیکٹ سے پروجیکٹ میں کاپی کرتا ہوں، چھوٹی چھوٹی چیزوں کو ایڈجسٹ کرتا ہوں، اور بعض اوقات میں پروجیکٹس کے درمیان تضادات کا شکار ہوجاتا ہوں کیونکہ میں بھول جاتا ہوں کہ انہیں پچھلی بار کیا کہا گیا تھا۔
پیٹرن درست تھا۔ تکرار ایسی نہیں تھی۔
ایک نمونہ جسے ہر بار دوبارہ لکھنا پڑتا ہے وہ نمونہ نہیں ہے، بلکہ ایک کام ہے۔ تو میں نے اسے پیک کیا۔
DartExceptor کیسے کام کرتا ہے۔
ڈارٹ ایکسپٹر ہلکا پھلکا، انحصار سے پاک ڈارٹ 3 پیکیج جو پچھلے مضمون کے عین مطابق پیٹرن کو نافذ کرتا ہے۔ Trace, Ok, Errاور یہ دوبارہ قابل استعمال پیکیج میں جان بوجھ کر موناڈیک آپریشنز کا ایک چھوٹا سیٹ فراہم کرتا ہے۔
نہیں dartzکوئی منجمد نہیں اور کوئی build_runner نہیں ہے۔ صرف Traceدو نفاذ اور چار طریقے۔
dependencies:
dart_exceptor: ^1.1.2
import 'package:dart_exceptor/dart_exceptor.dart';
یہ مکمل سیٹ اپ ہے۔
بنیادی قسم
DartExceptor میں تمام آپریشنز ہیں۔ Trace:
-
Tیہ کامیابی کی قسم ہے۔ -
Eخرابی کی قسم
Trace بالکل دو نفاذ ہیں:
return Ok(user); // success
return Err(AppException(code: 404, e: 'Not found')); // failure
آپ کبھی تعمیر نہیں کرتے Trace براہ راست تم واپس آ گئے Ok یا Errاور اس کے برعکس پروگرام Trace کہیں اور۔ اب فنکشن دستخط ہمیں سچ بتاتا ہے کہ کیا ہو سکتا ہے۔
Future> getUser(String id);
جو بھی اس دستخط کو پڑھے گا وہ فوراً جان لے گا کہ یہ کامیابی ہو سکتی ہے۔ Userیا یہ اس طرح ناکام ہوجاتا ہے: AppException. چھ مہینے کے بعد، یہ کوئی تعجب نہیں تھا.
API: چار طریقے، ہر ایک میں ایک آپریشن ہوتا ہے۔
پچھلے مضمون کے لیے Result ایک قسم تھی map, flatMapاور when() پیٹرن میچنگ کے لیے، DartExceptor ایک ہی شکل اختیار کرتا ہے اور اسے چار مرکوز طریقوں میں توڑ دیتا ہے:
splitاختتامی نقطہ
split تم وہیں سے چلے جاؤ Trace دنیا دونوں ہینڈلرز کی ضرورت ہے، لہذا آپ غلطی سے ناکامی کے راستے کو اوور رائیڈ نہیں کر سکتے۔
result.split(
data: (user) => print(user.name),
e: (e) => print(e.message),
);
mapنکالنے اور تبدیلی کی کامیابی
map سے قدر کو حل کریں۔ Ok آپ اسے خود تبدیل کر سکتے ہیں۔
final activeUsers = result.map(
data: (users) => users.where((u) => u.isActive).toList(),
);
mapErrorنکالنے اور تبدیلی کی ناکامی۔
یہ ایک آئینہ ہے mapغلطی کی طرف کے لیے۔ یہ اس وقت مفید ہے جب آپ ایک آرکیٹیکچرل باؤنڈری کو عبور کرتے ہیں جہاں ڈیٹا لیئر میں استثناء کی قسم ڈومین لیئر میں استثناء کی قسم سے مختلف ہوتی ہے۔
final domainError = result.mapError(
e: (e) => AppException(code: e.statusCode, e: e.toString()),
);
bindسلسلہ آپریشن جو واپس آتا ہے۔ Trace
یہ وہی ہے جو اصل کام کرتا ہے۔ bind آپ ایک ایسا آپریشن کر سکتے ہیں جو خود واپس آجائے۔ Traceہر قدم پر کامیابی کی قسم کو تبدیل کریں۔ اگر کوئی مرحلہ ناکام ہو جاتا ہے تو تمام نیچے والے مراحل خود بخود چھوڑ دیے جاتے ہیں۔
result
.bind(
n: (users) {
try {
return Ok(users.firstWhere((u) => u.id == id));
} catch (e) {
return Err(AppException(code: 404, e: 'User not found'));
}
},
)
.bind(n: (user) => Ok(user.firstName))
.split(
data: (name) => print('User: $name'),
e: (e) => print('Error: ${e.e}'),
);
List بن جاتا ہے۔ User بن جاتا ہے۔ String. ہر ایک bind جب آپ کسی قسم کو تبدیل کرتے ہیں تو، مرتب کرنے والا ہر قدم اور شارٹ سرکٹ کو فوراً چیک کرتا ہے اگر سلسلہ میں کہیں بھی کوئی خرابی واقع ہو جاتی ہے۔ e ہینڈلر split. یہ ایک پرانے مضمون سے ہے۔ flatMap بات چیت کے ذریعے منطقی انجام تک پہنچایا گیا۔
جہاں یہ کلین آرکیٹیکچر میں فٹ بیٹھتا ہے۔
اصل مضمون میں پیٹرن ہمیشہ صرف نحو سے زیادہ پر مشتمل ہوتے ہیں۔ مقصد ناکامی کو متعدد پرتوں میں ظاہر کرنا تھا۔ DartExceptor کو بغیر کسی ترمیم کے درست ڈھانچے میں داخل کیا جاتا ہے۔
// Data layer
abstract class DataSource {
Future, AppException>> getAllUsers();
}
// Repository layer
abstract class IUserRepository {
Future, AppException>> getAllUsers();
}
// Use case layer
class UserUseCase {
Future, AppException>> getAllUsers() => repository.getAllUsers();
}
// Presentation layer
void loadUsers() async {
final result = await useCase.getAllUsers();
result.split(
data: (users) => print('Loaded ${users.length} users'),
e: (e) => print('Failed: ${e.e}'),
);
}
آپ ہر بار فاؤنڈیشن کو دوبارہ لکھے بغیر ایک ہی درجہ بندی، وہی علیحدگی، اور ایک ہی قسم کے خرابی کے راستے استعمال کر سکتے ہیں۔
آپ کو صرف اس کا استعمال کیوں نہیں کرنا چاہئے۔ dartz?
ہم نے پچھلے مضمون میں اس کا احاطہ کیا تھا۔ dartzایس Either اگر آپ کی ٹیم API کی سطح کے ساتھ آرام دہ ہے اور انحصار کی جگہ کوئی مسئلہ نہیں ہے، تو یہ واقعی ایک ٹھوس انتخاب ہے۔
DartExceptor استعمال کیا جاتا ہے جب آپ ہاسکل طرز کے فنکشنل پروگرامنگ کنونشنز پر بنی لائبریری کو درآمد کیے بغیر نتیجہ کی قسم کا نمونہ چاہتے ہیں۔ نہیں Left/Rightنہیں foldکوئی عبوری انحصار نہیں ہے۔ صرف Trace, Ok, Errیہاں براہ راست نقشہ بنانے کے چار طریقے ہیں کہ پچھلے مضمون کے نمونوں کو عملی طور پر کیسے استعمال کیا جاتا ہے:
| ڈارٹ ایکسپٹر | ڈارٹس | |
|---|---|---|
| انحصار | صفر | اکثریت |
| ڈارٹس 3 مقامی | ہاں | نہیں |
| API سطح | 4 طریقے | سائز میں بڑا |
| ہاسکل تصورات درکار ہیں۔ | نہیں | ہاں |
سیف چین ٹائپ کریں (bind) |
ہاں | ہاں (flatMap) |
اسے آزمائیں
DartExceptor pub.dev پر شائع ہوا ہے:
dependencies:
dart_exceptor: ^1.1.2
پیکیج: pub.dev/packages/dart_justor ماخذ: GitHub
اگر آپ نے میرے پچھلے مضامین پڑھے ہیں اور خود ہی کچھ ایسا بنایا ہے، تو میں واقعی یہ سننا چاہوں گا کہ آپ کے ورژن کا موازنہ کیسے ہوتا ہے۔ اور DartExceptor کے ساتھ، آپ اس پیٹرن کو ایک بار پھر سے لکھے بغیر GitHub پر اسٹار بن سکتے ہیں۔